diff --git a/.gitignore b/.gitignore index ccff05290a9cc7e85b3be8c4c258c5ea22766c90..c9b6393da3de23c4bb5cf6b35e66630c98612a49 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ **/.idea **/*.iml **/*.ipr + +# VS Code project +**/.vscode +**/*.code-workspace diff --git a/Cronet/OWNERS b/Cronet/OWNERS index 62c5737a2e8e5a7da8dcff019eb36d3c55b94ce4..c24680e9b06a616db26148e4d2ce2e833626daf9 100644 --- a/Cronet/OWNERS +++ b/Cronet/OWNERS @@ -1,2 +1,2 @@ set noparent -file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking +file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking diff --git a/Cronet/tests/OWNERS b/Cronet/tests/OWNERS index acb6ee638aafcab8e295c7af6442d85ac6a94a60..a35a789bdb179677827e1bb5d88b604125ea3ce0 100644 --- a/Cronet/tests/OWNERS +++ b/Cronet/tests/OWNERS @@ -1,7 +1,7 @@ # Bug component: 31808 set noparent -file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts +file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking_xts # TODO: Temp ownership to develop cronet CTS colibie@google.com #{LAST_RESORT_SUGGESTION} diff --git a/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt b/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt index 0760e6853681bc9150d5702d126fffd98e84c0a4..464862d8a6f3d67a888685360e047385a744ace9 100644 --- a/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt +++ b/Cronet/tests/cts/src/android/net/http/cts/BidirectionalStreamTest.kt @@ -27,11 +27,14 @@ import android.os.Build import androidx.test.core.app.ApplicationProvider import com.android.testutils.DevSdkIgnoreRule import com.android.testutils.DevSdkIgnoreRunner +import com.android.testutils.SkipPresubmit +import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlin.test.assertEquals import org.hamcrest.MatcherAssert import org.hamcrest.Matchers import org.junit.After +import org.junit.AssumptionViolatedException import org.junit.Before import org.junit.runner.RunWith @@ -57,11 +60,6 @@ class BidirectionalStreamTest { @After @Throws(Exception::class) fun tearDown() { - // cancel active requests to enable engine shutdown. - stream?.let { - it.cancel() - callback.blockForDone() - } httpEngine.shutdown() } @@ -71,14 +69,133 @@ class BidirectionalStreamTest { @Test @Throws(Exception::class) + @SkipPresubmit(reason = "b/293141085 Confirm non-flaky and move to presubmit after SLO") fun testBidirectionalStream_GetStream_CompletesSuccessfully() { stream = createBidirectionalStreamBuilder(URL).setHttpMethod("GET").build() stream!!.start() - callback.assumeCallback(ResponseStep.ON_SUCCEEDED) + // We call to a real server and hence the server may not be reachable, cancel this stream + // and rethrow the exception before tearDown, + // otherwise shutdown would fail with active request error. + try { + callback.assumeCallback(ResponseStep.ON_SUCCEEDED) + } catch (e: AssumptionViolatedException) { + stream!!.cancel() + callback.blockForDone() + throw e + } + val info = callback.mResponseInfo assumeOKStatusCode(info) MatcherAssert.assertThat( "Received byte count must be > 0", info.receivedByteCount, Matchers.greaterThan(0L)) assertEquals("h2", info.negotiatedProtocol) } + + @Test + @Throws(Exception::class) + fun testBidirectionalStream_getHttpMethod() { + val builder = createBidirectionalStreamBuilder(URL) + val method = "GET" + + builder.setHttpMethod(method) + stream = builder.build() + assertThat(stream!!.getHttpMethod()).isEqualTo(method) + } + + @Test + @Throws(Exception::class) + fun testBidirectionalStream_hasTrafficStatsTag() { + val builder = createBidirectionalStreamBuilder(URL) + + builder.setTrafficStatsTag(10) + stream = builder.build() + assertThat(stream!!.hasTrafficStatsTag()).isTrue() + } + + @Test + @Throws(Exception::class) + fun testBidirectionalStream_getTrafficStatsTag() { + val builder = createBidirectionalStreamBuilder(URL) + val trafficStatsTag = 10 + + builder.setTrafficStatsTag(trafficStatsTag) + stream = builder.build() + assertThat(stream!!.getTrafficStatsTag()).isEqualTo(trafficStatsTag) + } + + @Test + @Throws(Exception::class) + fun testBidirectionalStream_hasTrafficStatsUid() { + val builder = createBidirectionalStreamBuilder(URL) + + builder.setTrafficStatsUid(10) + stream = builder.build() + assertThat(stream!!.hasTrafficStatsUid()).isTrue() + } + + @Test + @Throws(Exception::class) + fun testBidirectionalStream_getTrafficStatsUid() { + val builder = createBidirectionalStreamBuilder(URL) + val trafficStatsUid = 10 + + builder.setTrafficStatsUid(trafficStatsUid) + stream = builder.build() + assertThat(stream!!.getTrafficStatsUid()).isEqualTo(trafficStatsUid) + } + + @Test + @Throws(Exception::class) + fun testBidirectionalStream_getHeaders_asList() { + val builder = createBidirectionalStreamBuilder(URL) + val expectedHeaders = mapOf( + "Authorization" to "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", + "Max-Forwards" to "10", + "X-Client-Data" to "random custom header content").entries.toList() + + for (header in expectedHeaders) { + builder.addHeader(header.key, header.value) + } + + stream = builder.build() + assertThat(stream!!.getHeaders().getAsList()).containsAtLeastElementsIn(expectedHeaders) + } + + @Test + @Throws(Exception::class) + fun testBidirectionalStream_getHeaders_asMap() { + val builder = createBidirectionalStreamBuilder(URL) + val expectedHeaders = mapOf( + "Authorization" to listOf("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="), + "Max-Forwards" to listOf("10"), + "X-Client-Data" to listOf("random custom header content")) + + for (header in expectedHeaders) { + builder.addHeader(header.key, header.value.get(0)) + } + + stream = builder.build() + assertThat(stream!!.getHeaders().getAsMap()).containsAtLeastEntriesIn(expectedHeaders) + } + + @Test + @Throws(Exception::class) + fun testBidirectionalStream_getPriority() { + val builder = createBidirectionalStreamBuilder(URL) + val priority = BidirectionalStream.STREAM_PRIORITY_LOW + + builder.setPriority(priority) + stream = builder.build() + assertThat(stream!!.getPriority()).isEqualTo(priority) + } + + @Test + @Throws(Exception::class) + fun testBidirectionalStream_isDelayRequestHeadersUntilFirstFlushEnabled() { + val builder = createBidirectionalStreamBuilder(URL) + + builder.setDelayRequestHeadersUntilFirstFlushEnabled(true) + stream = builder.build() + assertThat(stream!!.isDelayRequestHeadersUntilFirstFlushEnabled()).isTrue() + } } diff --git a/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java index 07e7d45fe76e85ba7d428841cf1491054d173c5d..3c4d134da090ddab5edc3db97079e280d3a905a6 100644 --- a/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java +++ b/Cronet/tests/cts/src/android/net/http/cts/UrlRequestTest.java @@ -363,6 +363,116 @@ public class UrlRequestTest { .containsAtLeastElementsIn(expectedHeaders); } + @Test + public void testUrlRequest_getHttpMethod() throws Exception { + UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl()); + final String method = "POST"; + + builder.setHttpMethod(method); + UrlRequest request = builder.build(); + assertThat(request.getHttpMethod()).isEqualTo(method); + } + + @Test + public void testUrlRequest_getHeaders_asList() throws Exception { + UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl()); + final List> expectedHeaders = Arrays.asList( + Map.entry("Authorization", "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="), + Map.entry("Max-Forwards", "10"), + Map.entry("X-Client-Data", "random custom header content")); + + for (Map.Entry header : expectedHeaders) { + builder.addHeader(header.getKey(), header.getValue()); + } + + UrlRequest request = builder.build(); + assertThat(request.getHeaders().getAsList()).containsAtLeastElementsIn(expectedHeaders); + } + + @Test + public void testUrlRequest_getHeaders_asMap() throws Exception { + UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl()); + final Map> expectedHeaders = Map.of( + "Authorization", Arrays.asList("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="), + "Max-Forwards", Arrays.asList("10"), + "X-Client-Data", Arrays.asList("random custom header content")); + + for (Map.Entry> header : expectedHeaders.entrySet()) { + builder.addHeader(header.getKey(), header.getValue().get(0)); + } + + UrlRequest request = builder.build(); + assertThat(request.getHeaders().getAsMap()).containsAtLeastEntriesIn(expectedHeaders); + } + + @Test + public void testUrlRequest_isCacheDisabled() throws Exception { + UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl()); + final boolean isCacheDisabled = true; + + builder.setCacheDisabled(isCacheDisabled); + UrlRequest request = builder.build(); + assertThat(request.isCacheDisabled()).isEqualTo(isCacheDisabled); + } + + @Test + public void testUrlRequest_isDirectExecutorAllowed() throws Exception { + UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl()); + final boolean isDirectExecutorAllowed = true; + + builder.setDirectExecutorAllowed(isDirectExecutorAllowed); + UrlRequest request = builder.build(); + assertThat(request.isDirectExecutorAllowed()).isEqualTo(isDirectExecutorAllowed); + } + + @Test + public void testUrlRequest_getPriority() throws Exception { + UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl()); + final int priority = UrlRequest.REQUEST_PRIORITY_LOW; + + builder.setPriority(priority); + UrlRequest request = builder.build(); + assertThat(request.getPriority()).isEqualTo(priority); + } + + @Test + public void testUrlRequest_hasTrafficStatsTag() throws Exception { + UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl()); + + builder.setTrafficStatsTag(10); + UrlRequest request = builder.build(); + assertThat(request.hasTrafficStatsTag()).isEqualTo(true); + } + + @Test + public void testUrlRequest_getTrafficStatsTag() throws Exception { + UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl()); + final int trafficStatsTag = 10; + + builder.setTrafficStatsTag(trafficStatsTag); + UrlRequest request = builder.build(); + assertThat(request.getTrafficStatsTag()).isEqualTo(trafficStatsTag); + } + + @Test + public void testUrlRequest_hasTrafficStatsUid() throws Exception { + UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl()); + + builder.setTrafficStatsUid(10); + UrlRequest request = builder.build(); + assertThat(request.hasTrafficStatsUid()).isEqualTo(true); + } + + @Test + public void testUrlRequest_getTrafficStatsUid() throws Exception { + UrlRequest.Builder builder = createUrlRequestBuilder(mTestServer.getSuccessUrl()); + final int trafficStatsUid = 10; + + builder.setTrafficStatsUid(trafficStatsUid); + UrlRequest request = builder.build(); + assertThat(request.getTrafficStatsUid()).isEqualTo(trafficStatsUid); + } + private static List> extractEchoedHeaders(HeaderBlock headers) { return headers.getAsList() .stream() diff --git a/Cronet/tests/mts/Android.bp b/Cronet/tests/mts/Android.bp index 4e4251cfea8f7883f26e7dce15bfd6637a9077ce..63905c83b1e0a47de8e5b10b113247873986610a 100644 --- a/Cronet/tests/mts/Android.bp +++ b/Cronet/tests/mts/Android.bp @@ -38,7 +38,11 @@ java_genrule { // tests need to inherit the NetHttpTests manifest. android_library { name: "NetHttpTestsLibPreJarJar", - static_libs: ["cronet_java_tests"], + static_libs: [ + "cronet_aml_api_java", + "cronet_aml_java__testing", + "cronet_java_tests", + ], sdk_version: "module_current", min_sdk_version: "30", } @@ -51,7 +55,8 @@ android_test { static_libs: ["NetHttpTestsLibPreJarJar"], jarjar_rules: ":net-http-test-jarjar-rules", jni_libs: [ - "cronet_aml_components_cronet_android_cronet_tests__testing" + "cronet_aml_components_cronet_android_cronet__testing", + "cronet_aml_components_cronet_android_cronet_tests__testing", ], test_suites: [ "general-tests", diff --git a/Cronet/tests/mts/jarjar_excludes.txt b/Cronet/tests/mts/jarjar_excludes.txt index a3e86de3b71508f1e99610044cd082a751479ed2..a0ce5c29177292eb4888ec670a0abfbaedb536bb 100644 --- a/Cronet/tests/mts/jarjar_excludes.txt +++ b/Cronet/tests/mts/jarjar_excludes.txt @@ -1,5 +1,10 @@ -# It's prohibited to jarjar androidx packages +# Exclude some test prefixes, as they can't be found after being jarjared. +com\.android\.testutils\..+ +# jarjar-gen can't handle some kotlin object expression, exclude packages that include them androidx\..+ +kotlin\.test\..+ +kotlin\.reflect\..+ +org\.mockito\..+ # Do not jarjar the api classes android\.net\..+ # cronet_tests.so is not jarjared and uses base classes. We can remove this when there's a diff --git a/Cronet/tools/import/copy.bara.sky b/Cronet/tools/import/copy.bara.sky index 5372a4df2670b78779c36a29a6d4bf696ebd8798..61e3ba4ab12e9764e147ce7d74af3458e0ec5800 100644 --- a/Cronet/tools/import/copy.bara.sky +++ b/Cronet/tools/import/copy.bara.sky @@ -71,6 +71,8 @@ cronet_origin_files = glob( "base/third_party/nspr/**", "base/third_party/superfasthash/**", "base/third_party/valgrind/**", + # Those are temporarily needed until Chromium finish the migration + # of libc++[abi] "buildtools/third_party/libc++/**", "buildtools/third_party/libc++abi/**", # Note: Only used for tests. @@ -84,9 +86,15 @@ cronet_origin_files = glob( "third_party/brotli/**", # Note: Only used for tests. "third_party/ced/**", + "third_party/cpu_features/**", + # Note: Only used for tests. + "third_party/google_benchmark/**", # Note: Only used for tests. "third_party/googletest/**", "third_party/icu/**", + "third_party/jni_zero/**", + "third_party/libc++/**", + "third_party/libc++abi/**", "third_party/libevent/**", # Note: Only used for tests. "third_party/libxml/**", diff --git a/DnsResolver/Android.bp b/DnsResolver/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..d1330343f30f410ea20f5a83e1a51bd21524646f --- /dev/null +++ b/DnsResolver/Android.bp @@ -0,0 +1,83 @@ +// +// Copyright (C) 2023 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: ["Android-Apache-2.0"], +} + +cc_library { + name: "libcom.android.tethering.dns_helper", + version_script: "libcom.android.tethering.dns_helper.map.txt", + stubs: { + versions: [ + "1", + ], + symbol_file: "libcom.android.tethering.dns_helper.map.txt", + }, + defaults: ["netd_defaults"], + header_libs: [ + "bpf_connectivity_headers", + "libcutils_headers", + ], + srcs: [ + "DnsBpfHelper.cpp", + "DnsHelper.cpp", + ], + static_libs: [ + "libmodules-utils-build", + ], + shared_libs: [ + "libbase", + ], + export_include_dirs: ["include"], + header_abi_checker: { + enabled: true, + symbol_file: "libcom.android.tethering.dns_helper.map.txt", + }, + sanitize: { + cfi: true, + }, + apex_available: ["com.android.tethering"], + min_sdk_version: "30", +} + +cc_test { + name: "dns_helper_unit_test", + defaults: ["netd_defaults"], + test_suites: ["general-tests", "mts-tethering"], + test_config_template: ":net_native_test_config_template", + header_libs: [ + "bpf_connectivity_headers", + ], + srcs: [ + "DnsBpfHelperTest.cpp", + ], + static_libs: [ + "libcom.android.tethering.dns_helper", + ], + shared_libs: [ + "libbase", + "libcutils", + ], + compile_multilib: "both", + multilib: { + lib32: { + suffix: "32", + }, + lib64: { + suffix: "64", + }, + }, +} diff --git a/DnsResolver/DnsBpfHelper.cpp b/DnsResolver/DnsBpfHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..de8bef58602e601822af6dda057711e00dcb8976 --- /dev/null +++ b/DnsResolver/DnsBpfHelper.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 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. + */ + +#define LOG_TAG "DnsBpfHelper" + +#include "DnsBpfHelper.h" + +#include +#include + +namespace android { +namespace net { + +#define RETURN_IF_RESULT_NOT_OK(result) \ + do { \ + if (!result.ok()) { \ + LOG(ERROR) << "L" << __LINE__ << " " << __func__ << ": " << strerror(result.error().code()); \ + return result.error(); \ + } \ + } while (0) + +base::Result DnsBpfHelper::init() { + if (!android::modules::sdklevel::IsAtLeastT()) { + LOG(ERROR) << __func__ << ": Unsupported before Android T."; + return base::Error(EOPNOTSUPP); + } + + RETURN_IF_RESULT_NOT_OK(mConfigurationMap.init(CONFIGURATION_MAP_PATH)); + RETURN_IF_RESULT_NOT_OK(mUidOwnerMap.init(UID_OWNER_MAP_PATH)); + RETURN_IF_RESULT_NOT_OK(mDataSaverEnabledMap.init(DATA_SAVER_ENABLED_MAP_PATH)); + return {}; +} + +base::Result DnsBpfHelper::isUidNetworkingBlocked(uid_t uid, bool metered) { + if (is_system_uid(uid)) return false; + if (!mConfigurationMap.isValid() || !mUidOwnerMap.isValid()) { + LOG(ERROR) << __func__ + << ": BPF maps are not ready. Forgot to call ADnsHelper_init?"; + return base::Error(EUNATCH); + } + + auto enabledRules = mConfigurationMap.readValue(UID_RULES_CONFIGURATION_KEY); + RETURN_IF_RESULT_NOT_OK(enabledRules); + + auto value = mUidOwnerMap.readValue(uid); + uint32_t uidRules = value.ok() ? value.value().rule : 0; + + // For doze mode, battery saver, low power standby. + if (isBlockedByUidRules(enabledRules.value(), uidRules)) return true; + + // For data saver. + // DataSaverEnabled map on V+ platforms is the only reliable source of information about the + // current data saver status. While ConnectivityService offers two ways to update this map for U + // and V+, the U- platform implementation can have delays, potentially leading to inaccurate + // results. Conversely, the V+ platform implementation is synchronized with the actual data saver + // state, making it a trustworthy source. Since this library primarily serves DNS resolvers, + // relying solely on V+ data prevents erroneous blocking of DNS queries. + if (android::modules::sdklevel::IsAtLeastV() && metered) { + // The background data setting (PENALTY_BOX_MATCH) and unrestricted data usage setting + // (HAPPY_BOX_MATCH) for individual apps override the system wide Data Saver setting. + if (uidRules & PENALTY_BOX_MATCH) return true; + if (uidRules & HAPPY_BOX_MATCH) return false; + + auto dataSaverSetting = mDataSaverEnabledMap.readValue(DATA_SAVER_ENABLED_KEY); + RETURN_IF_RESULT_NOT_OK(dataSaverSetting); + return dataSaverSetting.value(); + } + + return false; +} + +} // namespace net +} // namespace android diff --git a/DnsResolver/DnsBpfHelper.h b/DnsResolver/DnsBpfHelper.h new file mode 100644 index 0000000000000000000000000000000000000000..f1c3992812ccc98eeb6668a53a9fc8347049a049 --- /dev/null +++ b/DnsResolver/DnsBpfHelper.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 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. + */ + +#pragma once + +#include + +#include "bpf/BpfMap.h" +#include "netd.h" + +namespace android { +namespace net { + +class DnsBpfHelper { + public: + DnsBpfHelper() = default; + DnsBpfHelper(const DnsBpfHelper&) = delete; + DnsBpfHelper& operator=(const DnsBpfHelper&) = delete; + + base::Result init(); + base::Result isUidNetworkingBlocked(uid_t uid, bool metered); + + private: + android::bpf::BpfMapRO mConfigurationMap; + android::bpf::BpfMapRO mUidOwnerMap; + android::bpf::BpfMapRO mDataSaverEnabledMap; + + // For testing + friend class DnsBpfHelperTest; +}; + +} // namespace net +} // namespace android diff --git a/DnsResolver/DnsBpfHelperTest.cpp b/DnsResolver/DnsBpfHelperTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..67b5b956c58b3abce8caa4123a6dc5339e5b91fd --- /dev/null +++ b/DnsResolver/DnsBpfHelperTest.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2023 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. + */ + +#include +#include + +#define BPF_MAP_MAKE_VISIBLE_FOR_TESTING +#include "DnsBpfHelper.h" + +using namespace android::bpf; // NOLINT(google-build-using-namespace): exempted + +namespace android { +namespace net { + +constexpr int TEST_MAP_SIZE = 2; + +#define ASSERT_VALID(x) ASSERT_TRUE((x).isValid()) + +class DnsBpfHelperTest : public ::testing::Test { + protected: + DnsBpfHelper mDnsBpfHelper; + BpfMap mFakeConfigurationMap; + BpfMap mFakeUidOwnerMap; + BpfMap mFakeDataSaverEnabledMap; + + void SetUp() { + mFakeConfigurationMap.resetMap(BPF_MAP_TYPE_ARRAY, CONFIGURATION_MAP_SIZE); + ASSERT_VALID(mFakeConfigurationMap); + + mFakeUidOwnerMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE); + ASSERT_VALID(mFakeUidOwnerMap); + + mFakeDataSaverEnabledMap.resetMap(BPF_MAP_TYPE_ARRAY, DATA_SAVER_ENABLED_MAP_SIZE); + ASSERT_VALID(mFakeDataSaverEnabledMap); + + mDnsBpfHelper.mConfigurationMap = mFakeConfigurationMap; + ASSERT_VALID(mDnsBpfHelper.mConfigurationMap); + mDnsBpfHelper.mUidOwnerMap = mFakeUidOwnerMap; + ASSERT_VALID(mDnsBpfHelper.mUidOwnerMap); + mDnsBpfHelper.mDataSaverEnabledMap = mFakeDataSaverEnabledMap; + ASSERT_VALID(mDnsBpfHelper.mDataSaverEnabledMap); + } + + void ResetAllMaps() { + mDnsBpfHelper.mConfigurationMap.reset(); + mDnsBpfHelper.mUidOwnerMap.reset(); + mDnsBpfHelper.mDataSaverEnabledMap.reset(); + } +}; + +TEST_F(DnsBpfHelperTest, IsUidNetworkingBlocked) { + struct TestConfig { + const uid_t uid; + const uint32_t enabledRules; + const uint32_t uidRules; + const int expectedResult; + std::string toString() const { + return fmt::format( + "uid: {}, enabledRules: {}, uidRules: {}, expectedResult: {}", + uid, enabledRules, uidRules, expectedResult); + } + } testConfigs[] = { + // clang-format off + // No rule enabled: + // uid, enabledRules, uidRules, expectedResult + {AID_APP_START, NO_MATCH, NO_MATCH, false}, + + // An allowlist rule: + {AID_APP_START, NO_MATCH, DOZABLE_MATCH, false}, + {AID_APP_START, DOZABLE_MATCH, NO_MATCH, true}, + {AID_APP_START, DOZABLE_MATCH, DOZABLE_MATCH, false}, + // A denylist rule + {AID_APP_START, NO_MATCH, STANDBY_MATCH, false}, + {AID_APP_START, STANDBY_MATCH, NO_MATCH, false}, + {AID_APP_START, STANDBY_MATCH, STANDBY_MATCH, true}, + + // Multiple rules enabled: + // Match only part of the enabled allowlist rules. + {AID_APP_START, DOZABLE_MATCH|POWERSAVE_MATCH, DOZABLE_MATCH, true}, + {AID_APP_START, DOZABLE_MATCH|POWERSAVE_MATCH, POWERSAVE_MATCH, true}, + // Match all of the enabled allowlist rules. + {AID_APP_START, DOZABLE_MATCH|POWERSAVE_MATCH, DOZABLE_MATCH|POWERSAVE_MATCH, false}, + // Match allowlist. + {AID_APP_START, DOZABLE_MATCH|STANDBY_MATCH, DOZABLE_MATCH, false}, + // Match no rule. + {AID_APP_START, DOZABLE_MATCH|STANDBY_MATCH, NO_MATCH, true}, + {AID_APP_START, DOZABLE_MATCH|POWERSAVE_MATCH, NO_MATCH, true}, + + // System UID: always unblocked. + {AID_SYSTEM, NO_MATCH, NO_MATCH, false}, + {AID_SYSTEM, NO_MATCH, DOZABLE_MATCH, false}, + {AID_SYSTEM, DOZABLE_MATCH, NO_MATCH, false}, + {AID_SYSTEM, DOZABLE_MATCH, DOZABLE_MATCH, false}, + {AID_SYSTEM, NO_MATCH, STANDBY_MATCH, false}, + {AID_SYSTEM, STANDBY_MATCH, NO_MATCH, false}, + {AID_SYSTEM, STANDBY_MATCH, STANDBY_MATCH, false}, + {AID_SYSTEM, DOZABLE_MATCH|POWERSAVE_MATCH, DOZABLE_MATCH, false}, + {AID_SYSTEM, DOZABLE_MATCH|POWERSAVE_MATCH, POWERSAVE_MATCH, false}, + {AID_SYSTEM, DOZABLE_MATCH|POWERSAVE_MATCH, DOZABLE_MATCH|POWERSAVE_MATCH, false}, + {AID_SYSTEM, DOZABLE_MATCH|STANDBY_MATCH, DOZABLE_MATCH, false}, + {AID_SYSTEM, DOZABLE_MATCH|STANDBY_MATCH, NO_MATCH, false}, + {AID_SYSTEM, DOZABLE_MATCH|POWERSAVE_MATCH, NO_MATCH, false}, + // clang-format on + }; + + for (const auto& config : testConfigs) { + SCOPED_TRACE(config.toString()); + + // Setup maps. + EXPECT_RESULT_OK(mFakeConfigurationMap.writeValue(UID_RULES_CONFIGURATION_KEY, + config.enabledRules, BPF_EXIST)); + EXPECT_RESULT_OK(mFakeUidOwnerMap.writeValue(config.uid, {.iif = 0, .rule = config.uidRules}, + BPF_ANY)); + + // Verify the function. + auto result = mDnsBpfHelper.isUidNetworkingBlocked(config.uid, /*metered=*/false); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(config.expectedResult, result.value()); + } +} + +TEST_F(DnsBpfHelperTest, IsUidNetworkingBlocked_uninitialized) { + ResetAllMaps(); + + auto result = mDnsBpfHelper.isUidNetworkingBlocked(AID_APP_START, /*metered=*/false); + EXPECT_FALSE(result.ok()); + EXPECT_EQ(EUNATCH, result.error().code()); + + result = mDnsBpfHelper.isUidNetworkingBlocked(AID_SYSTEM, /*metered=*/false); + EXPECT_TRUE(result.ok()); + EXPECT_FALSE(result.value()); +} + +// Verify DataSaver on metered network. +TEST_F(DnsBpfHelperTest, IsUidNetworkingBlocked_metered) { + struct TestConfig { + const uint32_t enabledRules; // Settings in configuration map. + const bool dataSaverEnabled; // Settings in data saver enabled map. + const uint32_t uidRules; // Settings in uid owner map. + const int blocked; // Whether the UID is expected to be networking blocked or not. + std::string toString() const { + return fmt::format( + ", enabledRules: {}, dataSaverEnabled: {}, uidRules: {}, expect blocked: {}", + enabledRules, dataSaverEnabled, uidRules, blocked); + } + } testConfigs[]{ + // clang-format off + // enabledRules, dataSaverEnabled, uidRules, blocked + {NO_MATCH, false, NO_MATCH, false}, + {NO_MATCH, false, PENALTY_BOX_MATCH, true}, + {NO_MATCH, false, HAPPY_BOX_MATCH, false}, + {NO_MATCH, false, PENALTY_BOX_MATCH|HAPPY_BOX_MATCH, true}, + {NO_MATCH, true, NO_MATCH, true}, + {NO_MATCH, true, PENALTY_BOX_MATCH, true}, + {NO_MATCH, true, HAPPY_BOX_MATCH, false}, + {NO_MATCH, true, PENALTY_BOX_MATCH|HAPPY_BOX_MATCH, true}, + {STANDBY_MATCH, false, STANDBY_MATCH, true}, + {STANDBY_MATCH, false, STANDBY_MATCH|PENALTY_BOX_MATCH, true}, + {STANDBY_MATCH, false, STANDBY_MATCH|HAPPY_BOX_MATCH, true}, + {STANDBY_MATCH, false, STANDBY_MATCH|PENALTY_BOX_MATCH|HAPPY_BOX_MATCH, true}, + {STANDBY_MATCH, true, STANDBY_MATCH, true}, + {STANDBY_MATCH, true, STANDBY_MATCH|PENALTY_BOX_MATCH, true}, + {STANDBY_MATCH, true, STANDBY_MATCH|HAPPY_BOX_MATCH, true}, + {STANDBY_MATCH, true, STANDBY_MATCH|PENALTY_BOX_MATCH|HAPPY_BOX_MATCH, true}, + // clang-format on + }; + + for (const auto& config : testConfigs) { + SCOPED_TRACE(config.toString()); + + // Setup maps. + EXPECT_RESULT_OK(mFakeConfigurationMap.writeValue(UID_RULES_CONFIGURATION_KEY, + config.enabledRules, BPF_EXIST)); + EXPECT_RESULT_OK(mFakeDataSaverEnabledMap.writeValue(DATA_SAVER_ENABLED_KEY, + config.dataSaverEnabled, BPF_EXIST)); + EXPECT_RESULT_OK(mFakeUidOwnerMap.writeValue(AID_APP_START, {.iif = 0, .rule = config.uidRules}, + BPF_ANY)); + + // Verify the function. + auto result = mDnsBpfHelper.isUidNetworkingBlocked(AID_APP_START, /*metered=*/true); + EXPECT_RESULT_OK(result); + EXPECT_EQ(config.blocked, result.value()); + } +} + +} // namespace net +} // namespace android diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothManager.java b/DnsResolver/DnsHelper.cpp similarity index 51% rename from nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothManager.java rename to DnsResolver/DnsHelper.cpp index 6bb220946d7a6e9f4b4ac7ebe2f39e7e46f440e8..3372908add60988949c0f2c2df6b038ccead9a4f 100644 --- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothManager.java +++ b/DnsResolver/DnsHelper.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Android Open Source Project + * Copyright (C) 2023 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. @@ -14,25 +14,24 @@ * limitations under the License. */ -/** - * Intentionally in package android.bluetooth to fake existing interface in Android. - */ -package android.bluetooth; - -/** - * Fake interface for IBluetoothManager. - */ -public interface IBluetoothManager { +#include - boolean enable(); +#include "DnsBpfHelper.h" +#include "DnsHelperPublic.h" - boolean disable(boolean persist); +static android::net::DnsBpfHelper sDnsBpfHelper; - String getAddress(); +int ADnsHelper_init() { + auto result = sDnsBpfHelper.init(); + if (!result.ok()) return -result.error().code(); - String getName(); + return 0; +} - IBluetooth registerAdapter(IBluetoothManagerCallback callback); +int ADnsHelper_isUidNetworkingBlocked(uid_t uid, bool metered) { + auto result = sDnsBpfHelper.isUidNetworkingBlocked(uid, metered); + if (!result.ok()) return -result.error().code(); - IBluetoothGatt getBluetoothGatt(); + // bool -> int conversion. + return result.value(); } diff --git a/DnsResolver/include/DnsHelperPublic.h b/DnsResolver/include/DnsHelperPublic.h new file mode 100644 index 0000000000000000000000000000000000000000..7c9fc9e58ecfd519f27d56f05923af81c6eeb612 --- /dev/null +++ b/DnsResolver/include/DnsHelperPublic.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 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. + */ + +#pragma once + +#include +#include + +__BEGIN_DECLS + +/* + * Perform any required initialization - including opening any required BPF maps. This function + * needs to be called before using other functions of this library. + * + * Returns 0 on success, a negative POSIX error code (see errno.h) on other failures. + */ +int ADnsHelper_init(); + +/* + * The function reads bpf maps and returns whether the given uid has blocked networking or not. The + * function is supported starting from Android T. + * + * |uid| is a Linux/Android UID to be queried. It is a combination of UserID and AppID. + * |metered| indicates whether the uid is currently using a billing network. + * + * Returns 0(false)/1(true) on success, a negative POSIX error code (see errno.h) on other failures. + */ +int ADnsHelper_isUidNetworkingBlocked(uid_t uid, bool metered); + +__END_DECLS diff --git a/nearby/tests/robotests/config/robolectric.properties b/DnsResolver/libcom.android.tethering.dns_helper.map.txt similarity index 52% rename from nearby/tests/robotests/config/robolectric.properties rename to DnsResolver/libcom.android.tethering.dns_helper.map.txt index 932de7d61067f4e8e97b21c03d217233fc834237..3c965a212c5327875a35877dc3a322cd508ceee8 100644 --- a/nearby/tests/robotests/config/robolectric.properties +++ b/DnsResolver/libcom.android.tethering.dns_helper.map.txt @@ -1,5 +1,5 @@ # -# Copyright (C) 2021 Google Inc. +# Copyright (C) 2023 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. @@ -7,10 +7,21 @@ # # http://www.apache.org/licenses/LICENSE-2.0 # -# Unless required by applicable law or agreed to in writing, software +# 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. # -sdk=NEWEST_SDK \ No newline at end of file + +# This lists the entry points visible to applications that use the +# libcom.android.tethering.dns_helper library. Other entry points present in +# the library won't be usable. + +LIBCOM_ANDROID_TETHERING_DNS_HELPER { + global: + ADnsHelper_init; # apex + ADnsHelper_isUidNetworkingBlocked; # apex + local: + *; +}; diff --git a/OWNERS b/OWNERS index 07a775ee71bcd157538ed458a9713ae55f7df378..b2176cc945697506d9c8617c3dd6839e081d0014 100644 --- a/OWNERS +++ b/OWNERS @@ -1,4 +1,5 @@ +# Bug component: 31808 set noparent -file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking +file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking -per-file **IpSec* = file:platform/frameworks/base:master:/services/core/java/com/android/server/vcn/OWNERS \ No newline at end of file +per-file **IpSec* = file:platform/frameworks/base:main:/services/core/java/com/android/server/vcn/OWNERS diff --git a/OWNERS_core_networking_xts b/OWNERS_core_networking_xts index 1844334eee134b2b1c1071ca1083eee92e414b9d..7612210556014d453f08e4bdb4c5216c36f5ecb8 100644 --- a/OWNERS_core_networking_xts +++ b/OWNERS_core_networking_xts @@ -4,4 +4,6 @@ satk@google.com #{LAST_RESORT_SUGGESTION} # For cherry-picks of CLs that are already merged in aosp/master, or flaky test fixes. jchalard@google.com #{LAST_RESORT_SUGGESTION} maze@google.com #{LAST_RESORT_SUGGESTION} +# In addition to cherry-picks and flaky test fixes, also for incremental changes on NsdManager tests +# to increase coverage for existing behavior, and testing of bug fixes in NsdManager reminv@google.com #{LAST_RESORT_SUGGESTION} diff --git a/TEST_MAPPING b/TEST_MAPPING index d2f6d6a6d632ebfbd8f4c27b30a07db484ae0bfc..ab3ed66bfcaaa4bb730580bb5f6737b352827c7b 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -1,11 +1,24 @@ { "presubmit": [ { - "name": "ConnectivityCoverageTests" + "name": "ConnectivityCoverageTests", + "options": [ + { + "exclude-annotation": "com.android.testutils.NetworkStackModuleTest" + }, + { + "exclude-annotation": "com.android.testutils.SkipPresubmit" + } + ] }, { // In addition to ConnectivityCoverageTests, runs non-connectivity-module tests - "name": "FrameworksNetTests" + "name": "FrameworksNetTests", + "options": [ + { + "exclude-annotation": "com.android.testutils.SkipPresubmit" + } + ] }, // Run in addition to mainline-presubmit as mainline-presubmit is not // supported in every branch. @@ -86,9 +99,6 @@ { "name": "TetheringIntegrationTests" }, - { - "name": "traffic_controller_unit_test" - }, { "name": "libnetworkstats_test" }, @@ -99,6 +109,9 @@ { "name": "NetHttpCoverageTests", "options": [ + { + "exclude-annotation": "com.android.testutils.SkipPresubmit" + }, { // These sometimes take longer than 1 min which is the presubmit timeout "exclude-annotation": "androidx.test.filters.LargeTest" @@ -115,11 +128,25 @@ "keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"] }, { - "name": "traffic_controller_unit_test", - "keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"] + "name": "dns_helper_unit_test" }, { "name": "FrameworksNetDeflakeTest" + }, + // Postsubmit on virtual devices to monitor flakiness of @SkipPresubmit methods + { + "name": "CtsNetTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.RequiresDevice" + } + ] + }, + { + "name": "FrameworksNetTests" + }, + { + "name": "NetHttpCoverageTests" } ], "mainline-presubmit": [ @@ -129,6 +156,9 @@ { "exclude-annotation": "com.android.testutils.SkipPresubmit" }, + { + "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit" + }, { "exclude-annotation": "androidx.test.filters.RequiresDevice" } @@ -140,6 +170,9 @@ { "exclude-annotation": "com.android.testutils.SkipPresubmit" }, + { + "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit" + }, { "exclude-annotation": "androidx.test.filters.RequiresDevice" } @@ -151,6 +184,9 @@ { "exclude-annotation": "com.android.testutils.SkipPresubmit" }, + { + "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit" + }, { "exclude-annotation": "androidx.test.filters.RequiresDevice" } @@ -162,6 +198,9 @@ { "exclude-annotation": "com.android.testutils.SkipPresubmit" }, + { + "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit" + }, { "exclude-annotation": "androidx.test.filters.RequiresDevice" } @@ -175,11 +214,17 @@ { "exclude-annotation": "com.android.testutils.SkipPresubmit" }, + { + "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit" + }, { "exclude-annotation": "androidx.test.filters.RequiresDevice" }, { "exclude-annotation": "com.android.testutils.ConnectivityModuleTest" + }, + { + "exclude-annotation": "com.android.testutils.DnsResolverModuleTest" } ] }, @@ -193,8 +238,14 @@ { "exclude-annotation": "com.android.testutils.SkipPresubmit" }, + { + "exclude-annotation": "com.android.testutils.SkipMainlinePresubmit" + }, { "exclude-annotation": "androidx.test.filters.RequiresDevice" + }, + { + "exclude-annotation": "com.android.testutils.DnsResolverModuleTest" } ] }, @@ -208,10 +259,12 @@ "name": "netd_updatable_unit_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]" }, { - "name": "ConnectivityCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]" - }, - { - "name": "traffic_controller_unit_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]" + "name": "ConnectivityCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]", + "options": [ + { + "exclude-annotation": "com.android.testutils.SkipPresubmit" + } + ] }, { "name": "libnetworkstats_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]" @@ -219,11 +272,22 @@ { "name": "NetHttpCoverageTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]", "options": [ + { + "exclude-annotation": "com.android.testutils.SkipPresubmit" + }, { // These sometimes take longer than 1 min which is the presubmit timeout "exclude-annotation": "androidx.test.filters.LargeTest" } ] + }, + { + "name": "CtsTetheringTestLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]", + "options": [ + { + "exclude-annotation": "com.android.testutils.NetworkStackModuleTest" + } + ] } ], "mainline-postsubmit": [ @@ -231,6 +295,24 @@ { "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]", "keywords": ["sim"] + }, + { + "name": "CtsTetheringTestLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]", + "keywords": ["sim"], + "options": [ + { + "exclude-annotation": "com.android.testutils.NetworkStackModuleTest" + } + ] + }, + // Postsubmit on virtual devices to monitor flakiness of @SkipMainlinePresubmit methods + { + "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]", + "options": [ + { + "exclude-annotation": "androidx.test.filters.RequiresDevice" + } + ] } ], "imports": [ diff --git a/Tethering/Android.bp b/Tethering/Android.bp index 1511a22f445dae003449cb26dd319380b90aba22..2735c161741f27f40d454cbe6747c4d61d72ab8b 100644 --- a/Tethering/Android.bp +++ b/Tethering/Android.bp @@ -82,7 +82,10 @@ java_defaults { "framework-tethering.impl", ], manifest: "AndroidManifestBase.xml", - lint: { strict_updatability_linting: true }, + lint: { + strict_updatability_linting: true, + error_checks: ["NewApi"], + }, } // build tethering static library, used to compile both variants of the tethering. @@ -96,6 +99,7 @@ android_library { ], static_libs: [ "NetworkStackApiCurrentShims", + "net-utils-device-common-struct", ], apex_available: ["com.android.tethering"], lint: { strict_updatability_linting: true }, @@ -110,6 +114,7 @@ android_library { ], static_libs: [ "NetworkStackApiStableShims", + "net-utils-device-common-struct", ], apex_available: ["com.android.tethering"], lint: { strict_updatability_linting: true }, @@ -196,11 +201,7 @@ android_app { certificate: "networkstack", manifest: "AndroidManifest.xml", use_embedded_native_libs: true, - // The network stack *must* be included to ensure security of the device - required: [ - "NetworkStack", - "privapp_allowlist_com.android.tethering", - ], + privapp_allowlist: ":privapp_allowlist_com.android.tethering", apex_available: ["com.android.tethering"], lint: { strict_updatability_linting: true }, } @@ -216,13 +217,12 @@ android_app { certificate: "networkstack", manifest: "AndroidManifest.xml", use_embedded_native_libs: true, - // The network stack *must* be included to ensure security of the device - required: [ - "NetworkStackNext", - "privapp_allowlist_com.android.tethering", - ], + privapp_allowlist: ":privapp_allowlist_com.android.tethering", apex_available: ["com.android.tethering"], - lint: { strict_updatability_linting: true }, + lint: { + strict_updatability_linting: true, + error_checks: ["NewApi"], + }, } sdk { @@ -233,6 +233,7 @@ sdk { "com.android.tethering", ], native_shared_libs: [ + "libcom.android.tethering.dns_helper", "libnetd_updatable", ], } diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp index 253fb0067a5319bb4936f57d40da44c66cab7dfc..de9017ae576831f3ea3796c9d17709efdfdbd7f8 100644 --- a/Tethering/apex/Android.bp +++ b/Tethering/apex/Android.bp @@ -18,13 +18,6 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } -prebuilt_etc { - name: "TetheringOutOfProcessFlag", - src: "out-of-process", - filename_from_src: true, - sub_dir: "flag", -} - // Defaults to enable/disable java targets which uses development APIs. "enabled" may have a // different value depending on the branch. java_defaults { @@ -87,9 +80,11 @@ apex { first: { jni_libs: [ "libservice-connectivity", + "libservice-thread-jni", "libandroid_net_connectivity_com_android_net_module_util_jni", ], native_shared_libs: [ + "libcom.android.tethering.dns_helper", "libcom.android.tethering.connectivity_native", "libnetd_updatable", ], @@ -103,6 +98,9 @@ apex { }, binaries: [ "clatd", + "ethtool", + "netbpfload", + "ot-daemon", ], canned_fs_config: "canned_fs_config", bpfs: [ @@ -117,12 +115,10 @@ apex { ], apps: [ "ServiceConnectivityResources", - "HalfSheetUX", ], prebuilts: [ + "ot-daemon.init.34rc", "current_sdkinfo", - "privapp_allowlist_com.android.tethering", - "TetheringOutOfProcessFlag", ], manifest: "manifest.json", key: "com.android.tethering.key", @@ -214,11 +210,14 @@ bootclasspath_fragment { // result in a build failure due to inconsistent flags. package_prefixes: [ "android.nearby.aidl", + "android.remoteauth.aidl", + "android.remoteauth", "android.net.apf", "android.net.connectivity", "android.net.http.apihelpers", "android.net.netstats.provider", "android.net.nsd", + "android.net.thread", "android.net.wear", ], }, diff --git a/Tethering/apex/canned_fs_config b/Tethering/apex/canned_fs_config index 5a033471d5a9cb7118509bbbf44183d641290ff9..1f5fcfaef5906c96382e2ef5b5a34a6d51ebb90a 100644 --- a/Tethering/apex/canned_fs_config +++ b/Tethering/apex/canned_fs_config @@ -1,2 +1,3 @@ /bin/for-system 0 1000 0750 /bin/for-system/clatd 1029 1029 06755 +/bin/netbpfload 0 0 0750 diff --git a/Tethering/apex/permissions/Android.bp b/Tethering/apex/permissions/Android.bp index ac9ec65dd547369e0c466cd825b01b195647f0e7..69c1aa2d4c383bfa55e107c2ff8a0af728bb3a34 100644 --- a/Tethering/apex/permissions/Android.bp +++ b/Tethering/apex/permissions/Android.bp @@ -19,10 +19,7 @@ package { default_visibility: ["//packages/modules/Connectivity/Tethering:__subpackages__"], } -prebuilt_etc { +filegroup { name: "privapp_allowlist_com.android.tethering", - sub_dir: "permissions", - filename: "permissions.xml", - src: "permissions.xml", - installable: false, + srcs: ["permissions.xml"], } \ No newline at end of file diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java index 898b124e078df3e852583e08de255a330b72022a..0df904786f34fad1a00e0586060610be8f53d661 100644 --- a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java +++ b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java @@ -17,7 +17,6 @@ package com.android.networkstack.tethering.apishim.api30; import android.net.INetd; -import android.net.MacAddress; import android.net.TetherStatsParcel; import android.os.RemoteException; import android.os.ServiceSpecificException; @@ -32,7 +31,8 @@ import com.android.net.module.util.bpf.Tether4Key; import com.android.net.module.util.bpf.Tether4Value; import com.android.net.module.util.bpf.TetherStatsValue; import com.android.networkstack.tethering.BpfCoordinator.Dependencies; -import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6UpstreamRule; /** * Bpf coordinator class for API shims. @@ -57,7 +57,17 @@ public class BpfCoordinatorShimImpl }; @Override - public boolean tetherOffloadRuleAdd(@NonNull final Ipv6ForwardingRule rule) { + public boolean addIpv6UpstreamRule(@NonNull final Ipv6UpstreamRule rule) { + return true; + }; + + @Override + public boolean removeIpv6UpstreamRule(@NonNull final Ipv6UpstreamRule rule) { + return true; + } + + @Override + public boolean addIpv6DownstreamRule(@NonNull final Ipv6DownstreamRule rule) { try { mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel()); } catch (RemoteException | ServiceSpecificException e) { @@ -69,7 +79,7 @@ public class BpfCoordinatorShimImpl }; @Override - public boolean tetherOffloadRuleRemove(@NonNull final Ipv6ForwardingRule rule) { + public boolean removeIpv6DownstreamRule(@NonNull final Ipv6DownstreamRule rule) { try { mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel()); } catch (RemoteException | ServiceSpecificException e) { @@ -79,19 +89,6 @@ public class BpfCoordinatorShimImpl return true; } - @Override - public boolean startUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex, - @NonNull MacAddress inDstMac, @NonNull MacAddress outSrcMac, - @NonNull MacAddress outDstMac, int mtu) { - return true; - } - - @Override - public boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, - int upstreamIfindex, @NonNull MacAddress inDstMac) { - return true; - } - @Override @Nullable public SparseArray tetherOffloadGetStats() { diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java index 3cad1c665a23729eae46d6cb3933948a698d00ac..4d1e7ef482ee8be655ea1f647e0f3db59e2b66df 100644 --- a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java +++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java @@ -18,7 +18,8 @@ package com.android.networkstack.tethering.apishim.api31; import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; -import android.net.MacAddress; +import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; + import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; @@ -36,7 +37,8 @@ import com.android.net.module.util.bpf.Tether4Value; import com.android.net.module.util.bpf.TetherStatsKey; import com.android.net.module.util.bpf.TetherStatsValue; import com.android.networkstack.tethering.BpfCoordinator.Dependencies; -import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6UpstreamRule; import com.android.networkstack.tethering.BpfUtils; import com.android.networkstack.tethering.Tether6Value; import com.android.networkstack.tethering.TetherDevKey; @@ -164,67 +166,61 @@ public class BpfCoordinatorShimImpl } @Override - public boolean tetherOffloadRuleAdd(@NonNull final Ipv6ForwardingRule rule) { - if (!isInitialized()) return false; + public boolean addIpv6UpstreamRule(@NonNull final Ipv6UpstreamRule rule) { + // RFC7421_PREFIX_LENGTH = 64 which is the most commonly used IPv6 subnet prefix length. + if (rule.sourcePrefix.getPrefixLength() != RFC7421_PREFIX_LENGTH) return false; - final TetherDownstream6Key key = rule.makeTetherDownstream6Key(); + final TetherUpstream6Key key = rule.makeTetherUpstream6Key(); final Tether6Value value = rule.makeTether6Value(); try { - mBpfDownstream6Map.updateEntry(key, value); - } catch (ErrnoException e) { - mLog.e("Could not update entry: ", e); + mBpfUpstream6Map.insertEntry(key, value); + } catch (ErrnoException | IllegalStateException e) { + mLog.e("Could not insert upstream IPv6 entry: " + e); return false; } - return true; } @Override - public boolean tetherOffloadRuleRemove(@NonNull final Ipv6ForwardingRule rule) { - if (!isInitialized()) return false; + public boolean removeIpv6UpstreamRule(@NonNull final Ipv6UpstreamRule rule) { + // RFC7421_PREFIX_LENGTH = 64 which is the most commonly used IPv6 subnet prefix length. + if (rule.sourcePrefix.getPrefixLength() != RFC7421_PREFIX_LENGTH) return false; try { - mBpfDownstream6Map.deleteEntry(rule.makeTetherDownstream6Key()); + mBpfUpstream6Map.deleteEntry(rule.makeTetherUpstream6Key()); } catch (ErrnoException e) { - // Silent if the rule did not exist. - if (e.errno != OsConstants.ENOENT) { - mLog.e("Could not update entry: ", e); - return false; - } + mLog.e("Could not delete upstream IPv6 entry: " + e); + return false; } return true; } @Override - public boolean startUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex, - @NonNull MacAddress inDstMac, @NonNull MacAddress outSrcMac, - @NonNull MacAddress outDstMac, int mtu) { - if (!isInitialized()) return false; - - final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfindex, inDstMac); - final Tether6Value value = new Tether6Value(upstreamIfindex, outSrcMac, - outDstMac, OsConstants.ETH_P_IPV6, mtu); + public boolean addIpv6DownstreamRule(@NonNull final Ipv6DownstreamRule rule) { + final TetherDownstream6Key key = rule.makeTetherDownstream6Key(); + final Tether6Value value = rule.makeTether6Value(); + try { - mBpfUpstream6Map.insertEntry(key, value); - } catch (ErrnoException | IllegalStateException e) { - mLog.e("Could not insert upstream6 entry: " + e); + mBpfDownstream6Map.updateEntry(key, value); + } catch (ErrnoException e) { + mLog.e("Could not update entry: ", e); return false; } + return true; } @Override - public boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex, - @NonNull MacAddress inDstMac) { - if (!isInitialized()) return false; - - final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfindex, inDstMac); + public boolean removeIpv6DownstreamRule(@NonNull final Ipv6DownstreamRule rule) { try { - mBpfUpstream6Map.deleteEntry(key); + mBpfDownstream6Map.deleteEntry(rule.makeTetherDownstream6Key()); } catch (ErrnoException e) { - mLog.e("Could not delete upstream IPv6 entry: " + e); - return false; + // Silent if the rule did not exist. + if (e.errno != OsConstants.ENOENT) { + mLog.e("Could not update entry: ", e); + return false; + } } return true; } @@ -232,8 +228,6 @@ public class BpfCoordinatorShimImpl @Override @Nullable public SparseArray tetherOffloadGetStats() { - if (!isInitialized()) return null; - final SparseArray tetherStatsList = new SparseArray(); try { // The reported tether stats are total data usage for all currently-active upstream @@ -248,8 +242,6 @@ public class BpfCoordinatorShimImpl @Override public boolean tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes) { - if (!isInitialized()) return false; - // The common case is an update, where the stats already exist, // hence we read first, even though writing with BPF_NOEXIST // first would make the code simpler. @@ -305,8 +297,6 @@ public class BpfCoordinatorShimImpl @Override @Nullable public TetherStatsValue tetherOffloadGetAndClearStats(int ifIndex) { - if (!isInitialized()) return null; - // getAndClearTetherOffloadStats is called after all offload rules have already been // deleted for the given upstream interface. Before starting to do cleanup stuff in this // function, use synchronizeKernelRCU to make sure that all the current running eBPF @@ -352,8 +342,6 @@ public class BpfCoordinatorShimImpl @Override public boolean tetherOffloadRuleAdd(boolean downstream, @NonNull Tether4Key key, @NonNull Tether4Value value) { - if (!isInitialized()) return false; - try { if (downstream) { mBpfDownstream4Map.insertEntry(key, value); @@ -377,8 +365,6 @@ public class BpfCoordinatorShimImpl @Override public boolean tetherOffloadRuleRemove(boolean downstream, @NonNull Tether4Key key) { - if (!isInitialized()) return false; - try { if (downstream) { if (!mBpfDownstream4Map.deleteEntry(key)) return false; // Rule did not exist @@ -411,8 +397,6 @@ public class BpfCoordinatorShimImpl @Override public void tetherOffloadRuleForEach(boolean downstream, @NonNull ThrowingBiConsumer action) { - if (!isInitialized()) return; - try { if (downstream) { mBpfDownstream4Map.forEach(action); @@ -426,8 +410,6 @@ public class BpfCoordinatorShimImpl @Override public boolean attachProgram(String iface, boolean downstream, boolean ipv4) { - if (!isInitialized()) return false; - try { BpfUtils.attachProgram(iface, downstream, ipv4); } catch (IOException e) { @@ -439,8 +421,6 @@ public class BpfCoordinatorShimImpl @Override public boolean detachProgram(String iface, boolean ipv4) { - if (!isInitialized()) return false; - try { BpfUtils.detachProgram(iface, ipv4); } catch (IOException e) { @@ -458,8 +438,6 @@ public class BpfCoordinatorShimImpl @Override public boolean addDevMap(int ifIndex) { - if (!isInitialized()) return false; - try { mBpfDevMap.updateEntry(new TetherDevKey(ifIndex), new TetherDevValue(ifIndex)); } catch (ErrnoException e) { @@ -471,8 +449,6 @@ public class BpfCoordinatorShimImpl @Override public boolean removeDevMap(int ifIndex) { - if (!isInitialized()) return false; - try { mBpfDevMap.deleteEntry(new TetherDevKey(ifIndex)); } catch (ErrnoException e) { diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java index 51cecfe889ff002cc0bbf0facea2678ca425dc02..d28a397cb42b6b255d92c2f1aa1d300a9ce1827d 100644 --- a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java +++ b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java @@ -16,7 +16,6 @@ package com.android.networkstack.tethering.apishim.common; -import android.net.MacAddress; import android.util.SparseArray; import androidx.annotation.NonNull; @@ -27,7 +26,8 @@ import com.android.net.module.util.bpf.Tether4Key; import com.android.net.module.util.bpf.Tether4Value; import com.android.net.module.util.bpf.TetherStatsValue; import com.android.networkstack.tethering.BpfCoordinator.Dependencies; -import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6UpstreamRule; /** * Bpf coordinator class for API shims. @@ -53,51 +53,51 @@ public abstract class BpfCoordinatorShim { public abstract boolean isInitialized(); /** - * Adds a tethering offload rule to BPF map, or updates it if it already exists. + * Adds a tethering offload upstream rule to BPF map, or updates it if it already exists. * - * Currently, only downstream /128 IPv6 entries are supported. An existing rule will be updated - * if the input interface and destination prefix match. Otherwise, a new rule will be created. - * Note that this can be only called on handler thread. + * An existing rule will be updated if the input interface, destination MAC and source prefix + * match. Otherwise, a new rule will be created. Note that this can be only called on handler + * thread. * * @param rule The rule to add or update. + * @return true if operation succeeded or was a no-op, false otherwise. */ - public abstract boolean tetherOffloadRuleAdd(@NonNull Ipv6ForwardingRule rule); + public abstract boolean addIpv6UpstreamRule(@NonNull Ipv6UpstreamRule rule); /** - * Deletes a tethering offload rule from the BPF map. + * Deletes a tethering offload upstream rule from the BPF map. * - * Currently, only downstream /128 IPv6 entries are supported. An existing rule will be deleted - * if the destination IP address and the source interface match. It is not an error if there is - * no matching rule to delete. + * An existing rule will be deleted if the input interface, destination MAC and source prefix + * match. It is not an error if there is no matching rule to delete. * * @param rule The rule to delete. + * @return true if operation succeeded or was a no-op, false otherwise. */ - public abstract boolean tetherOffloadRuleRemove(@NonNull Ipv6ForwardingRule rule); + public abstract boolean removeIpv6UpstreamRule(@NonNull Ipv6UpstreamRule rule); /** - * Starts IPv6 forwarding between the specified interfaces. - - * @param downstreamIfindex the downstream interface index - * @param upstreamIfindex the upstream interface index - * @param inDstMac the destination MAC address to use for XDP - * @param outSrcMac the source MAC address to use for packets - * @param outDstMac the destination MAC address to use for packets - * @return true if operation succeeded or was a no-op, false otherwise + * Adds a tethering offload downstream rule to BPF map, or updates it if it already exists. + * + * Currently, only downstream /128 IPv6 entries are supported. An existing rule will be updated + * if the input interface and destination prefix match. Otherwise, a new rule will be created. + * Note that this can be only called on handler thread. + * + * @param rule The rule to add or update. + * @return true if operation succeeded or was a no-op, false otherwise. */ - public abstract boolean startUpstreamIpv6Forwarding(int downstreamIfindex, int upstreamIfindex, - @NonNull MacAddress inDstMac, @NonNull MacAddress outSrcMac, - @NonNull MacAddress outDstMac, int mtu); + public abstract boolean addIpv6DownstreamRule(@NonNull Ipv6DownstreamRule rule); /** - * Stops IPv6 forwarding between the specified interfaces. - - * @param downstreamIfindex the downstream interface index - * @param upstreamIfindex the upstream interface index - * @param inDstMac the destination MAC address to use for XDP - * @return true if operation succeeded or was a no-op, false otherwise + * Deletes a tethering offload downstream rule from the BPF map. + * + * Currently, only downstream /128 IPv6 entries are supported. An existing rule will be deleted + * if the destination IP address and the source interface match. It is not an error if there is + * no matching rule to delete. + * + * @param rule The rule to delete. + * @return true if operation succeeded or was a no-op, false otherwise. */ - public abstract boolean stopUpstreamIpv6Forwarding(int downstreamIfindex, - int upstreamIfindex, @NonNull MacAddress inDstMac); + public abstract boolean removeIpv6DownstreamRule(@NonNull Ipv6DownstreamRule rule); /** * Return BPF tethering offload statistics. diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp index a957e23bc28bcb1e9973c13f8e0e91f1e7103f9f..6e8d0c9ca2a325ea0475749e27768e6d2e061a78 100644 --- a/Tethering/common/TetheringLib/Android.bp +++ b/Tethering/common/TetheringLib/Android.bp @@ -37,10 +37,10 @@ java_sdk_library { "//frameworks/base/core/tests/utillib", "//frameworks/base/packages/Connectivity/tests:__subpackages__", "//frameworks/base/tests/vcn", - "//frameworks/libs/net/common/testutils", - "//frameworks/libs/net/common/tests:__subpackages__", "//frameworks/opt/telephony/tests/telephonytests", "//packages/modules/CaptivePortalLogin/tests", + "//packages/modules/Connectivity/staticlibs/testutils", + "//packages/modules/Connectivity/staticlibs/tests:__subpackages__", "//packages/modules/Connectivity/Tethering/tests:__subpackages__", "//packages/modules/Connectivity/tests:__subpackages__", "//packages/modules/IPsec/tests/iketests", @@ -57,25 +57,6 @@ java_sdk_library { lint: { strict_updatability_linting: true }, } -java_defaults { - name: "CronetJavaDefaults", - srcs: [":cronet_aml_api_sources"], - libs: [ - "androidx.annotation_annotation", - ], - impl_only_static_libs: [ - "cronet_aml_java", - ], -} - -java_defaults { - name: "CronetJavaPrejarjarDefaults", - static_libs: [ - "cronet_aml_api_java", - "cronet_aml_java" - ], -} - java_library { name: "framework-tethering-pre-jarjar", defaults: [ diff --git a/Tethering/apex/in-process b/Tethering/common/TetheringLib/api/lint-baseline.txt similarity index 100% rename from Tethering/apex/in-process rename to Tethering/common/TetheringLib/api/lint-baseline.txt diff --git a/Tethering/common/TetheringLib/api/module-lib-lint-baseline.txt b/Tethering/common/TetheringLib/api/module-lib-lint-baseline.txt new file mode 100644 index 0000000000000000000000000000000000000000..1d095989c459e2600575f4eecd0962f45ca827d5 --- /dev/null +++ b/Tethering/common/TetheringLib/api/module-lib-lint-baseline.txt @@ -0,0 +1,23 @@ +// Baseline format: 1.0 +BroadcastBehavior: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED: + Field 'ACTION_TETHER_STATE_CHANGED' is missing @BroadcastBehavior + + +RequiresPermission: android.net.TetheringManager#requestLatestTetheringEntitlementResult(int, boolean, java.util.concurrent.Executor, android.net.TetheringManager.OnTetheringEntitlementResultListener): + Method 'requestLatestTetheringEntitlementResult' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.TetheringManager#startTethering(android.net.TetheringManager.TetheringRequest, java.util.concurrent.Executor, android.net.TetheringManager.StartTetheringCallback): + Method 'startTethering' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.TetheringManager#startTethering(int, java.util.concurrent.Executor, android.net.TetheringManager.StartTetheringCallback): + Method 'startTethering' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.TetheringManager#stopAllTethering(): + Method 'stopAllTethering' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.TetheringManager#stopTethering(int): + Method 'stopTethering' documentation mentions permissions already declared by @RequiresPermission + + +SdkConstant: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED: + Field 'ACTION_TETHER_STATE_CHANGED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + + +Todo: android.net.TetheringConstants: + Documentation mentions 'TODO' diff --git a/Tethering/common/TetheringLib/api/system-lint-baseline.txt b/Tethering/common/TetheringLib/api/system-lint-baseline.txt new file mode 100644 index 0000000000000000000000000000000000000000..e678ce184e52d5a43a26bd2ff670df3ae3048948 --- /dev/null +++ b/Tethering/common/TetheringLib/api/system-lint-baseline.txt @@ -0,0 +1,17 @@ +// Baseline format: 1.0 +BroadcastBehavior: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED: + Field 'ACTION_TETHER_STATE_CHANGED' is missing @BroadcastBehavior + + +RequiresPermission: android.net.TetheringManager#requestLatestTetheringEntitlementResult(int, boolean, java.util.concurrent.Executor, android.net.TetheringManager.OnTetheringEntitlementResultListener): + Method 'requestLatestTetheringEntitlementResult' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.TetheringManager#startTethering(android.net.TetheringManager.TetheringRequest, java.util.concurrent.Executor, android.net.TetheringManager.StartTetheringCallback): + Method 'startTethering' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.TetheringManager#stopAllTethering(): + Method 'stopAllTethering' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.TetheringManager#stopTethering(int): + Method 'stopTethering' documentation mentions permissions already declared by @RequiresPermission + + +SdkConstant: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED: + Field 'ACTION_TETHER_STATE_CHANGED' is missing @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) diff --git a/Tethering/jni/onload.cpp b/Tethering/jni/onload.cpp index ed8012845822cf87e5b4cfc1c0a1e5ed8188196d..fd40d411ab86cddf806779529d950c2f0a5fa923 100644 --- a/Tethering/jni/onload.cpp +++ b/Tethering/jni/onload.cpp @@ -25,7 +25,6 @@ namespace android { int register_com_android_net_module_util_BpfMap(JNIEnv* env, char const* class_name); int register_com_android_net_module_util_TcUtils(JNIEnv* env, char const* class_name); int register_com_android_networkstack_tethering_BpfCoordinator(JNIEnv* env); -int register_com_android_networkstack_tethering_BpfUtils(JNIEnv* env); int register_com_android_networkstack_tethering_util_TetheringUtils(JNIEnv* env); extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { diff --git a/Tethering/proguard.flags b/Tethering/proguard.flags index 109bbda584ee314c04c9bb662408e43ce3e07836..47e284866b3b3118962a131427e1b4e4f1cb04e7 100644 --- a/Tethering/proguard.flags +++ b/Tethering/proguard.flags @@ -15,6 +15,10 @@ native ; } +-keep class com.android.networkstack.tethering.util.TetheringUtils { + native ; +} + # Ensure runtime-visible field annotations are kept when using R8 full mode. -keepattributes RuntimeVisibleAnnotations,AnnotationDefault -keep interface com.android.networkstack.tethering.util.Struct$Field { diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java index 6affb62a6fc921d740e9c216c12464cb16df1321..a8c840803b50e30257a6472b7971b7a70204b83e 100644 --- a/Tethering/src/android/net/ip/IpServer.java +++ b/Tethering/src/android/net/ip/IpServer.java @@ -28,11 +28,11 @@ import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR; import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR; import static android.net.TetheringManager.TetheringRequest.checkStaticAddressConfiguration; import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; -import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; import static android.net.util.NetworkConstants.asByte; import static android.system.OsConstants.RT_SCOPE_UNIVERSE; import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH; +import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; import static com.android.networkstack.tethering.UpstreamNetworkState.isVcnInterface; import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix; import static com.android.networkstack.tethering.util.TetheringMessageBase.BASE_IPSERVER; @@ -44,6 +44,7 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MacAddress; import android.net.RouteInfo; +import android.net.RoutingCoordinatorManager; import android.net.TetheredClient; import android.net.TetheringManager; import android.net.TetheringRequestParcel; @@ -55,7 +56,6 @@ import android.net.dhcp.IDhcpEventCallbacks; import android.net.dhcp.IDhcpServer; import android.net.ip.RouterAdvertisementDaemon.RaParams; import android.os.Handler; -import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceSpecificException; @@ -65,30 +65,34 @@ import android.util.SparseArray; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.MessageUtils; import com.android.internal.util.State; -import com.android.internal.util.StateMachine; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.NetdUtils; +import com.android.net.module.util.SdkUtil.LateSdk; import com.android.net.module.util.SharedLog; import com.android.net.module.util.ip.InterfaceController; import com.android.net.module.util.ip.IpNeighborMonitor; import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEvent; import com.android.networkstack.tethering.BpfCoordinator; import com.android.networkstack.tethering.BpfCoordinator.ClientInfo; -import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule; import com.android.networkstack.tethering.PrivateAddressCoordinator; import com.android.networkstack.tethering.TetheringConfiguration; import com.android.networkstack.tethering.metrics.TetheringMetrics; import com.android.networkstack.tethering.util.InterfaceSet; import com.android.networkstack.tethering.util.PrefixUtils; +import com.android.networkstack.tethering.util.StateMachineShim; +import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -102,7 +106,7 @@ import java.util.Set; * * @hide */ -public class IpServer extends StateMachine { +public class IpServer extends StateMachineShim { public static final int STATE_UNAVAILABLE = 0; public static final int STATE_AVAILABLE = 1; public static final int STATE_TETHERED = 2; @@ -127,6 +131,7 @@ public class IpServer extends StateMachine { // TODO: have this configurable private static final int DHCP_LEASE_TIME_SECS = 3600; + private static final int NO_UPSTREAM = 0; private static final MacAddress NULL_MAC_ADDRESS = MacAddress.fromString("00:00:00:00:00:00"); private static final String TAG = "IpServer"; @@ -233,6 +238,7 @@ public class IpServer extends StateMachine { public static final int CMD_NEW_PREFIX_REQUEST = BASE_IPSERVER + 12; // request from PrivateAddressCoordinator to restart tethering. public static final int CMD_NOTIFY_PREFIX_CONFLICT = BASE_IPSERVER + 13; + public static final int CMD_SERVICE_FAILED_TO_START = BASE_IPSERVER + 14; private final State mInitialState; private final State mLocalHotspotState; @@ -244,6 +250,11 @@ public class IpServer extends StateMachine { private final INetd mNetd; @NonNull private final BpfCoordinator mBpfCoordinator; + // Contains null if the connectivity module is unsupported, as the routing coordinator is not + // available. Must use LateSdk because MessageUtils enumerates fields in this class, so it + // must be able to find all classes at runtime. + @NonNull + private final LateSdk mRoutingCoordinator; private final Callback mCallback; private final InterfaceController mInterfaceCtrl; private final PrivateAddressCoordinator mPrivateAddressCoordinator; @@ -252,7 +263,6 @@ public class IpServer extends StateMachine { private final int mInterfaceType; private final LinkProperties mLinkProperties; private final boolean mUsingLegacyDhcp; - private final boolean mUsingBpfOffload; private final int mP2pLeasesSubnetPrefixLength; private final Dependencies mDeps; @@ -260,6 +270,12 @@ public class IpServer extends StateMachine { private int mLastError; private int mServingMode; private InterfaceSet mUpstreamIfaceSet; // may change over time + // mInterfaceParams can't be final now because IpServer will be created when receives + // WIFI_AP_STATE_CHANGED broadcasts or when it detects that the wifi interface has come up. + // In the latter case, the interface is not fully initialized and the MAC address might not + // be correct (it will be set with a randomized MAC address later). + // TODO: Consider create the IpServer only when tethering want to enable it, then we can + // make mInterfaceParams final. private InterfaceParams mInterfaceParams; // TODO: De-duplicate this with mLinkProperties above. Currently, these link // properties are those selected by the IPv6TetheringCoordinator and relayed @@ -283,6 +299,9 @@ public class IpServer extends StateMachine { private List mDhcpLeases = Collections.emptyList(); private int mLastIPv6UpstreamIfindex = 0; + private boolean mUpstreamSupportsBpf = false; + @NonNull + private Set mLastIPv6UpstreamPrefixes = Collections.emptySet(); private class MyNeighborEventConsumer implements IpNeighborMonitor.NeighborEventConsumer { public void accept(NeighborEvent e) { @@ -295,26 +314,31 @@ public class IpServer extends StateMachine { private LinkAddress mIpv4Address; private final TetheringMetrics mTetheringMetrics; + private final Handler mHandler; + private final boolean mIsSyncSM; // TODO: Add a dependency object to pass the data members or variables from the tethering // object. It helps to reduce the arguments of the constructor. public IpServer( - String ifaceName, Looper looper, int interfaceType, SharedLog log, - INetd netd, @NonNull BpfCoordinator coordinator, Callback callback, + String ifaceName, Handler handler, int interfaceType, SharedLog log, + INetd netd, @NonNull BpfCoordinator bpfCoordinator, + @Nullable LateSdk routingCoordinator, Callback callback, TetheringConfiguration config, PrivateAddressCoordinator addressCoordinator, TetheringMetrics tetheringMetrics, Dependencies deps) { - super(ifaceName, looper); + super(ifaceName, config.isSyncSM() ? null : handler.getLooper()); + mHandler = handler; mLog = log.forSubComponent(ifaceName); mNetd = netd; - mBpfCoordinator = coordinator; + mBpfCoordinator = bpfCoordinator; + mRoutingCoordinator = routingCoordinator; mCallback = callback; mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog); mIfaceName = ifaceName; mInterfaceType = interfaceType; mLinkProperties = new LinkProperties(); mUsingLegacyDhcp = config.useLegacyDhcpServer(); - mUsingBpfOffload = config.isBpfOffloadEnabled(); mP2pLeasesSubnetPrefixLength = config.getP2pLeasesSubnetPrefixLength(); + mIsSyncSM = config.isSyncSM(); mPrivateAddressCoordinator = addressCoordinator; mDeps = deps; mTetheringMetrics = tetheringMetrics; @@ -325,11 +349,9 @@ public class IpServer extends StateMachine { mIpNeighborMonitor = mDeps.getIpNeighborMonitor(getHandler(), mLog, new MyNeighborEventConsumer()); - // IP neighbor monitor monitors the neighbor events for adding/removing offload - // forwarding rules per client. If BPF offload is not supported, don't start listening - // for neighbor events. See updateIpv6ForwardingRules, addIpv6ForwardingRule, - // removeIpv6ForwardingRule. - if (mUsingBpfOffload && !mIpNeighborMonitor.start()) { + // IP neighbor monitor monitors the neighbor events for adding/removing IPv6 downstream rule + // per client. If BPF offload is not supported, don't start listening for neighbor events. + if (mBpfCoordinator.isUsingBpfOffload() && !mIpNeighborMonitor.start()) { mLog.e("Failed to create IpNeighborMonitor on " + mIfaceName); } @@ -338,13 +360,22 @@ public class IpServer extends StateMachine { mTetheredState = new TetheredState(); mUnavailableState = new UnavailableState(); mWaitingForRestartState = new WaitingForRestartState(); - addState(mInitialState); - addState(mLocalHotspotState); - addState(mTetheredState); - addState(mWaitingForRestartState, mTetheredState); - addState(mUnavailableState); + final ArrayList allStates = new ArrayList(); + allStates.add(new StateInfo(mInitialState, null)); + allStates.add(new StateInfo(mLocalHotspotState, null)); + allStates.add(new StateInfo(mTetheredState, null)); + allStates.add(new StateInfo(mWaitingForRestartState, mTetheredState)); + allStates.add(new StateInfo(mUnavailableState, null)); + addAllStates(allStates); + } + + private Handler getHandler() { + return mHandler; + } - setInitialState(mInitialState); + /** Start IpServer state machine. */ + public void start() { + start(mInitialState); } /** Interface name which IpServer served.*/ @@ -485,7 +516,12 @@ public class IpServer extends StateMachine { private void handleError() { mLastError = TETHER_ERROR_DHCPSERVER_ERROR; - transitionTo(mInitialState); + if (mIsSyncSM) { + sendMessage(CMD_SERVICE_FAILED_TO_START, TETHER_ERROR_DHCPSERVER_ERROR); + } else { + sendMessageAtFrontOfQueueToAsyncSM(CMD_SERVICE_FAILED_TO_START, + TETHER_ERROR_DHCPSERVER_ERROR); + } } } @@ -743,7 +779,7 @@ public class IpServer extends StateMachine { RaParams params = null; String upstreamIface = null; InterfaceParams upstreamIfaceParams = null; - int upstreamIfIndex = 0; + int upstreamIfIndex = NO_UPSTREAM; if (v6only != null) { upstreamIface = v6only.getInterfaceName(); @@ -757,13 +793,8 @@ public class IpServer extends StateMachine { if (params.hasDefaultRoute) params.hopLimit = getHopLimit(upstreamIface, ttlAdjustment); - for (LinkAddress linkAddr : v6only.getLinkAddresses()) { - if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue; - - final IpPrefix prefix = new IpPrefix( - linkAddr.getAddress(), linkAddr.getPrefixLength()); - params.prefixes.add(prefix); - + params.prefixes = getTetherableIpv6Prefixes(v6only); + for (IpPrefix prefix : params.prefixes) { final Inet6Address dnsServer = getLocalDnsIpFor(prefix); if (dnsServer != null) { params.dnses.add(dnsServer); @@ -775,19 +806,21 @@ public class IpServer extends StateMachine { // CMD_TETHER_CONNECTION_CHANGED. Adding the mapping update here to the avoid potential // timing issue. It prevents that the IPv6 capability is updated later than // CMD_TETHER_CONNECTION_CHANGED. - mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfIndex, upstreamIface); + mBpfCoordinator.maybeAddUpstreamToLookupTable(upstreamIfIndex, upstreamIface); // If v6only is null, we pass in null to setRaParams(), which handles // deprecation of any existing RA data. - setRaParams(params); - // Be aware that updateIpv6ForwardingRules use mLastIPv6LinkProperties, so this line should - // be eariler than updateIpv6ForwardingRules. - // TODO: avoid this dependencies and move this logic into BpfCoordinator. - mLastIPv6LinkProperties = v6only; - updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, upstreamIfIndex, null); + // Not support BPF on virtual upstream interface + final boolean upstreamSupportsBpf = upstreamIface != null && !isVcnInterface(upstreamIface); + final Set upstreamPrefixes = params != null ? params.prefixes : Set.of(); + updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamPrefixes, + upstreamIfIndex, upstreamPrefixes, upstreamSupportsBpf); + mLastIPv6LinkProperties = v6only; mLastIPv6UpstreamIfindex = upstreamIfIndex; + mLastIPv6UpstreamPrefixes = upstreamPrefixes; + mUpstreamSupportsBpf = upstreamSupportsBpf; if (mDadProxy != null) { mDadProxy.setUpstreamIface(upstreamIfaceParams); } @@ -804,21 +837,62 @@ public class IpServer extends StateMachine { for (RouteInfo route : toBeRemoved) mLinkProperties.removeRoute(route); } - private void addRoutesToLocalNetwork(@NonNull final List toBeAdded) { + private void addInterfaceToNetwork(final int netId, @NonNull final String ifaceName) { try { - // It's safe to call networkAddInterface() even if - // the interface is already in the local_network. - mNetd.networkAddInterface(INetd.LOCAL_NET_ID, mIfaceName); - try { - // Add routes from local network. Note that adding routes that - // already exist does not cause an error (EEXIST is silently ignored). - NetdUtils.addRoutesToLocalNetwork(mNetd, mIfaceName, toBeAdded); - } catch (IllegalStateException e) { - mLog.e("Failed to add IPv4/v6 routes to local table: " + e); - return; + if (SdkLevel.isAtLeastS() && null != mRoutingCoordinator.value) { + // TODO : remove this call in favor of using the LocalNetworkConfiguration + // correctly, which will let ConnectivityService do it automatically. + mRoutingCoordinator.value.addInterfaceToNetwork(netId, ifaceName); + } else { + mNetd.networkAddInterface(netId, ifaceName); } } catch (ServiceSpecificException | RemoteException e) { mLog.e("Failed to add " + mIfaceName + " to local table: ", e); + } + } + + private void addInterfaceForward(@NonNull final String fromIface, + @NonNull final String toIface) throws ServiceSpecificException, RemoteException { + if (SdkLevel.isAtLeastS() && null != mRoutingCoordinator.value) { + mRoutingCoordinator.value.addInterfaceForward(fromIface, toIface); + } else { + mNetd.tetherAddForward(fromIface, toIface); + mNetd.ipfwdAddInterfaceForward(fromIface, toIface); + } + } + + private void removeInterfaceForward(@NonNull final String fromIface, + @NonNull final String toIface) { + if (SdkLevel.isAtLeastS() && null != mRoutingCoordinator.value) { + try { + mRoutingCoordinator.value.removeInterfaceForward(fromIface, toIface); + } catch (ServiceSpecificException e) { + mLog.e("Exception in removeInterfaceForward", e); + } + } else { + try { + mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface); + } catch (RemoteException | ServiceSpecificException e) { + mLog.e("Exception in ipfwdRemoveInterfaceForward", e); + } + try { + mNetd.tetherRemoveForward(fromIface, toIface); + } catch (RemoteException | ServiceSpecificException e) { + mLog.e("Exception in disableNat", e); + } + } + } + + private void addRoutesToLocalNetwork(@NonNull final List toBeAdded) { + // It's safe to call addInterfaceToNetwork() even if + // the interface is already in the local_network. + addInterfaceToNetwork(INetd.LOCAL_NET_ID, mIfaceName); + try { + // Add routes from local network. Note that adding routes that + // already exist does not cause an error (EEXIST is silently ignored). + NetdUtils.addRoutesToLocalNetwork(mNetd, mIfaceName, toBeAdded); + } catch (IllegalStateException e) { + mLog.e("Failed to add IPv4/v6 routes to local table: " + e); return; } @@ -890,62 +964,26 @@ public class IpServer extends StateMachine { } } - private void addIpv6ForwardingRule(Ipv6ForwardingRule rule) { - // Theoretically, we don't need this check because IP neighbor monitor doesn't start if BPF - // offload is disabled. Add this check just in case. - // TODO: Perhaps remove this protection check. - if (!mUsingBpfOffload) return; - - mBpfCoordinator.tetherOffloadRuleAdd(this, rule); - } - - private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule) { - // TODO: Perhaps remove this protection check. - // See the related comment in #addIpv6ForwardingRule. - if (!mUsingBpfOffload) return; - - mBpfCoordinator.tetherOffloadRuleRemove(this, rule); + private int getInterfaceIndexForRule(int ifindex, boolean supportsBpf) { + return supportsBpf ? ifindex : NO_UPSTREAM; } - private void clearIpv6ForwardingRules() { - if (!mUsingBpfOffload) return; - - mBpfCoordinator.tetherOffloadRuleClear(this); - } - - private void updateIpv6ForwardingRule(int newIfindex) { - // TODO: Perhaps remove this protection check. - // See the related comment in #addIpv6ForwardingRule. - if (!mUsingBpfOffload) return; - - mBpfCoordinator.tetherOffloadRuleUpdate(this, newIfindex); - } - - private boolean isIpv6VcnNetworkInterface() { - if (mLastIPv6LinkProperties == null) return false; - - return isVcnInterface(mLastIPv6LinkProperties.getInterfaceName()); - } - - // Handles all updates to IPv6 forwarding rules. These can currently change only if the upstream - // changes or if a neighbor event is received. - private void updateIpv6ForwardingRules(int prevUpstreamIfindex, int upstreamIfindex, - NeighborEvent e) { - // If no longer have an upstream or it is virtual network, clear forwarding rules and do - // nothing else. - // TODO: Rather than always clear rules, ensure whether ipv6 ever enable first. - if (upstreamIfindex == 0 || isIpv6VcnNetworkInterface()) { - clearIpv6ForwardingRules(); - return; - } - + // Handles updates to IPv6 forwarding rules if the upstream or its prefixes change. + private void updateIpv6ForwardingRules(int prevUpstreamIfindex, + @NonNull Set prevUpstreamPrefixes, int upstreamIfindex, + @NonNull Set upstreamPrefixes, boolean upstreamSupportsBpf) { // If the upstream interface has changed, remove all rules and re-add them with the new - // upstream interface. - if (prevUpstreamIfindex != upstreamIfindex) { - updateIpv6ForwardingRule(upstreamIfindex); + // upstream interface. If upstream is a virtual network, treated as no upstream. + if (prevUpstreamIfindex != upstreamIfindex + || !prevUpstreamPrefixes.equals(upstreamPrefixes)) { + mBpfCoordinator.updateAllIpv6Rules(this, this.mInterfaceParams, + getInterfaceIndexForRule(upstreamIfindex, upstreamSupportsBpf), + upstreamPrefixes); } + } - // If we're here to process a NeighborEvent, do so now. + // Handles updates to IPv6 downstream rules if a neighbor event is received. + private void addOrRemoveIpv6Downstream(NeighborEvent e) { // mInterfaceParams must be non-null or the event would not have arrived. if (e == null) return; if (!(e.ip instanceof Inet6Address) || e.ip.isMulticastAddress() @@ -954,24 +992,21 @@ public class IpServer extends StateMachine { } // When deleting rules, we still need to pass a non-null MAC, even though it's ignored. - // Do this here instead of in the Ipv6ForwardingRule constructor to ensure that we never - // add rules with a null MAC, only delete them. + // Do this here instead of in the Ipv6DownstreamRule constructor to ensure that we + // never add rules with a null MAC, only delete them. MacAddress dstMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS; - Ipv6ForwardingRule rule = new Ipv6ForwardingRule(upstreamIfindex, + Ipv6DownstreamRule rule = new Ipv6DownstreamRule( + getInterfaceIndexForRule(mLastIPv6UpstreamIfindex, mUpstreamSupportsBpf), mInterfaceParams.index, (Inet6Address) e.ip, mInterfaceParams.macAddr, dstMac); if (e.isValid()) { - addIpv6ForwardingRule(rule); + mBpfCoordinator.addIpv6DownstreamRule(this, rule); } else { - removeIpv6ForwardingRule(rule); + mBpfCoordinator.removeIpv6DownstreamRule(this, rule); } } // TODO: consider moving into BpfCoordinator. private void updateClientInfoIpv4(NeighborEvent e) { - // TODO: Perhaps remove this protection check. - // See the related comment in #addIpv6ForwardingRule. - if (!mUsingBpfOffload) return; - if (e == null) return; if (!(e.ip instanceof Inet4Address) || e.ip.isMulticastAddress() || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) { @@ -995,7 +1030,7 @@ public class IpServer extends StateMachine { if (mInterfaceParams != null && mInterfaceParams.index == e.ifindex && mInterfaceParams.hasMacAddress) { - updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamIfindex, e); + addOrRemoveIpv6Downstream(e); updateClientInfoIpv4(e); } } @@ -1128,7 +1163,19 @@ public class IpServer extends StateMachine { startServingInterface(); if (mLastError != TETHER_ERROR_NO_ERROR) { - transitionTo(mInitialState); + // This will transition to InitialState right away, regardless of whether any + // message is already waiting in the StateMachine queue (including maybe some + // message to go to InitialState). InitialState will then process any pending + // message (and generally ignores them). It is difficult to know for sure whether + // this is correct in all cases, but this is equivalent to what IpServer was doing + // in previous versions of the mainline module. + // TODO : remove sendMessageAtFrontOfQueueToAsyncSM after migrating to the Sync + // StateMachine. + if (mIsSyncSM) { + sendSelfMessageToSyncSM(CMD_SERVICE_FAILED_TO_START, mLastError); + } else { + sendMessageAtFrontOfQueueToAsyncSM(CMD_SERVICE_FAILED_TO_START, mLastError); + } } if (DBG) Log.d(TAG, getStateString(mDesiredInterfaceState) + " serve " + mIfaceName); @@ -1218,6 +1265,9 @@ public class IpServer extends StateMachine { mCallback.requestEnableTethering(mInterfaceType, false /* enabled */); transitionTo(mWaitingForRestartState); break; + case CMD_SERVICE_FAILED_TO_START: + mLog.e("start serving fail, error: " + message.arg1); + transitionTo(mInitialState); default: return false; } @@ -1328,6 +1378,7 @@ public class IpServer extends StateMachine { @Override public void exit() { cleanupUpstream(); + mBpfCoordinator.clearAllIpv6Rules(IpServer.this); super.exit(); } @@ -1346,7 +1397,8 @@ public class IpServer extends StateMachine { for (String ifname : mUpstreamIfaceSet.ifnames) cleanupUpstreamInterface(ifname); mUpstreamIfaceSet = null; - clearIpv6ForwardingRules(); + mBpfCoordinator.updateAllIpv6Rules( + IpServer.this, IpServer.this.mInterfaceParams, NO_UPSTREAM, Set.of()); } private void cleanupUpstreamInterface(String upstreamIface) { @@ -1355,16 +1407,7 @@ public class IpServer extends StateMachine { // to remove their rules, which generates errors. // Just do the best we can. mBpfCoordinator.maybeDetachProgram(mIfaceName, upstreamIface); - try { - mNetd.ipfwdRemoveInterfaceForward(mIfaceName, upstreamIface); - } catch (RemoteException | ServiceSpecificException e) { - mLog.e("Exception in ipfwdRemoveInterfaceForward: " + e.toString()); - } - try { - mNetd.tetherRemoveForward(mIfaceName, upstreamIface); - } catch (RemoteException | ServiceSpecificException e) { - mLog.e("Exception in disableNat: " + e.toString()); - } + removeInterfaceForward(mIfaceName, upstreamIface); } @Override @@ -1413,17 +1456,16 @@ public class IpServer extends StateMachine { final InterfaceParams upstreamIfaceParams = mDeps.getInterfaceParams(ifname); if (upstreamIfaceParams != null) { - mBpfCoordinator.addUpstreamNameToLookupTable( + mBpfCoordinator.maybeAddUpstreamToLookupTable( upstreamIfaceParams.index, ifname); } } mBpfCoordinator.maybeAttachProgram(mIfaceName, ifname); try { - mNetd.tetherAddForward(mIfaceName, ifname); - mNetd.ipfwdAddInterfaceForward(mIfaceName, ifname); + addInterfaceForward(mIfaceName, ifname); } catch (RemoteException | ServiceSpecificException e) { - mLog.e("Exception enabling NAT: " + e.toString()); + mLog.e("Exception enabling iface forward", e); cleanupUpstream(); mLastError = TETHER_ERROR_ENABLE_FORWARDING_ERROR; transitionTo(mInitialState); @@ -1473,6 +1515,8 @@ public class IpServer extends StateMachine { class UnavailableState extends State { @Override public void enter() { + // TODO: move mIpNeighborMonitor.stop() to TetheredState#exit, and trigger a neighbours + // dump after starting mIpNeighborMonitor. mIpNeighborMonitor.stop(); mLastError = TETHER_ERROR_NO_ERROR; sendInterfaceState(STATE_UNAVAILABLE); @@ -1531,4 +1575,21 @@ public class IpServer extends StateMachine { } return random; } + + /** Get IPv6 prefixes from LinkProperties */ + @NonNull + @VisibleForTesting + static HashSet getTetherableIpv6Prefixes(@NonNull Collection addrs) { + final HashSet prefixes = new HashSet<>(); + for (LinkAddress linkAddr : addrs) { + if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue; + prefixes.add(new IpPrefix(linkAddr.getAddress(), RFC7421_PREFIX_LENGTH)); + } + return prefixes; + } + + @NonNull + private HashSet getTetherableIpv6Prefixes(@NonNull LinkProperties lp) { + return getTetherableIpv6Prefixes(lp.getLinkAddresses()); + } } diff --git a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java index 18c2171592faf2cf15f4e49719c33ec54ff59362..5e9bbcb5d1e026845430faaadcd5e634deab86bf 100644 --- a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java +++ b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java @@ -16,9 +16,9 @@ package android.net.ip; -import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; import static android.system.OsConstants.AF_INET6; import static android.system.OsConstants.IPPROTO_ICMPV6; +import static android.system.OsConstants.SOCK_NONBLOCK; import static android.system.OsConstants.SOCK_RAW; import static android.system.OsConstants.SOL_SOCKET; import static android.system.OsConstants.SO_SNDTIMEO; @@ -30,6 +30,7 @@ import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SO import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS; import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK; +import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; import static com.android.net.module.util.NetworkStackConstants.TAG_SYSTEM_NEIGHBOR; import static com.android.networkstack.tethering.util.TetheringUtils.getAllNodesForScopeId; @@ -38,12 +39,21 @@ import android.net.LinkAddress; import android.net.MacAddress; import android.net.TrafficStats; import android.net.util.SocketUtils; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; import android.system.ErrnoException; import android.system.Os; import android.system.StructTimeval; import android.util.Log; +import android.util.Pair; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.annotations.GuardedBy; +import com.android.net.module.util.FdEventsReader; import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.structs.Icmpv6Header; import com.android.net.module.util.structs.LlaOption; @@ -103,6 +113,11 @@ public class RouterAdvertisementDaemon { private static final int DAY_IN_SECONDS = 86_400; + // Commands for IpServer to control RouterAdvertisementDaemon + private static final int CMD_START = 1; + private static final int CMD_STOP = 2; + private static final int CMD_BUILD_NEW_RA = 3; + private final InterfaceParams mInterface; private final InetSocketAddress mAllNodes; @@ -120,9 +135,13 @@ public class RouterAdvertisementDaemon { @GuardedBy("mLock") private RaParams mRaParams; + // To be accessed only from RaMessageHandler + private RsPacketListener mRsPacketListener; + private volatile FileDescriptor mSocket; private volatile MulticastTransmitter mMulticastTransmitter; - private volatile UnicastResponder mUnicastResponder; + private volatile RaMessageHandler mRaMessageHandler; + private volatile HandlerThread mRaHandlerThread; /** Encapsulate the RA parameters for RouterAdvertisementDaemon.*/ public static class RaParams { @@ -244,6 +263,94 @@ public class RouterAdvertisementDaemon { } } + private class RaMessageHandler extends Handler { + RaMessageHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case CMD_START: + mRsPacketListener = new RsPacketListener(this); + mRsPacketListener.start(); + break; + case CMD_STOP: + if (mRsPacketListener != null) { + mRsPacketListener.stop(); + mRsPacketListener = null; + } + break; + case CMD_BUILD_NEW_RA: + synchronized (mLock) { + // raInfo.first is deprecatedParams and raInfo.second is newParams. + final Pair raInfo = (Pair) msg.obj; + if (raInfo.first != null) { + mDeprecatedInfoTracker.putPrefixes(raInfo.first.prefixes); + mDeprecatedInfoTracker.putDnses(raInfo.first.dnses); + } + + if (raInfo.second != null) { + // Process information that is no longer deprecated. + mDeprecatedInfoTracker.removePrefixes(raInfo.second.prefixes); + mDeprecatedInfoTracker.removeDnses(raInfo.second.dnses); + } + mRaParams = raInfo.second; + assembleRaLocked(); + } + + maybeNotifyMulticastTransmitter(); + break; + default: + Log.e(TAG, "Unknown message, cmd = " + String.valueOf(msg.what)); + break; + } + } + } + + private class RsPacketListener extends FdEventsReader { + private static final class RecvBuffer { + // The recycled buffer for receiving Router Solicitations from clients. + // If the RS is larger than IPV6_MIN_MTU the packets are truncated. + // This is fine since currently only byte 0 is examined anyway. + final byte[] mBytes = new byte[IPV6_MIN_MTU]; + final InetSocketAddress mSrcAddr = new InetSocketAddress(0); + } + + RsPacketListener(@NonNull Handler handler) { + super(handler, new RecvBuffer()); + } + + @Override + protected int recvBufSize(@NonNull RecvBuffer buffer) { + return buffer.mBytes.length; + } + + @Override + protected FileDescriptor createFd() { + return mSocket; + } + + @Override + protected int readPacket(@NonNull FileDescriptor fd, @NonNull RecvBuffer buffer) + throws Exception { + return Os.recvfrom( + fd, buffer.mBytes, 0, buffer.mBytes.length, 0 /* flags */, buffer.mSrcAddr); + } + + @Override + protected final void handlePacket(@NonNull RecvBuffer buffer, int length) { + // Do the least possible amount of validations. + if (buffer.mSrcAddr == null + || length <= 0 + || buffer.mBytes[0] != asByte(ICMPV6_ROUTER_SOLICITATION)) { + return; + } + + maybeSendRA(buffer.mSrcAddr); + } + } + public RouterAdvertisementDaemon(InterfaceParams ifParams) { mInterface = ifParams; mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mInterface.index), 0); @@ -252,48 +359,43 @@ public class RouterAdvertisementDaemon { /** Build new RA.*/ public void buildNewRa(RaParams deprecatedParams, RaParams newParams) { - synchronized (mLock) { - if (deprecatedParams != null) { - mDeprecatedInfoTracker.putPrefixes(deprecatedParams.prefixes); - mDeprecatedInfoTracker.putDnses(deprecatedParams.dnses); - } - - if (newParams != null) { - // Process information that is no longer deprecated. - mDeprecatedInfoTracker.removePrefixes(newParams.prefixes); - mDeprecatedInfoTracker.removeDnses(newParams.dnses); - } - - mRaParams = newParams; - assembleRaLocked(); - } - - maybeNotifyMulticastTransmitter(); + final Pair raInfo = new Pair<>(deprecatedParams, newParams); + sendMessage(CMD_BUILD_NEW_RA, raInfo); } /** Start router advertisement daemon. */ public boolean start() { if (!createSocket()) { + Log.e(TAG, "Failed to start RouterAdvertisementDaemon."); return false; } mMulticastTransmitter = new MulticastTransmitter(); mMulticastTransmitter.start(); - mUnicastResponder = new UnicastResponder(); - mUnicastResponder.start(); + mRaHandlerThread = new HandlerThread(TAG); + mRaHandlerThread.start(); + mRaMessageHandler = new RaMessageHandler(mRaHandlerThread.getLooper()); - return true; + return sendMessage(CMD_START); } /** Stop router advertisement daemon. */ public void stop() { + if (!sendMessage(CMD_STOP)) { + Log.e(TAG, "RouterAdvertisementDaemon has been stopped or was never started."); + return; + } + + mRaHandlerThread.quitSafely(); + mRaHandlerThread = null; + mRaMessageHandler = null; + closeSocket(); // Wake up mMulticastTransmitter thread to interrupt a potential 1 day sleep before // the thread's termination. maybeNotifyMulticastTransmitter(); mMulticastTransmitter = null; - mUnicastResponder = null; } @GuardedBy("mLock") @@ -503,7 +605,7 @@ public class RouterAdvertisementDaemon { final int oldTag = TrafficStats.getAndSetThreadStatsTag(TAG_SYSTEM_NEIGHBOR); try { - mSocket = Os.socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); + mSocket = Os.socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_ICMPV6); // Setting SNDTIMEO is purely for defensive purposes. Os.setsockoptTimeval( mSocket, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(send_timout_ms)); @@ -565,34 +667,17 @@ public class RouterAdvertisementDaemon { } } - private final class UnicastResponder extends Thread { - private final InetSocketAddress mSolicitor = new InetSocketAddress(0); - // The recycled buffer for receiving Router Solicitations from clients. - // If the RS is larger than IPV6_MIN_MTU the packets are truncated. - // This is fine since currently only byte 0 is examined anyway. - private final byte[] mSolicitation = new byte[IPV6_MIN_MTU]; - - @Override - public void run() { - while (isSocketValid()) { - try { - // Blocking receive. - final int rval = Os.recvfrom( - mSocket, mSolicitation, 0, mSolicitation.length, 0, mSolicitor); - // Do the least possible amount of validation. - if (rval < 1 || mSolicitation[0] != asByte(ICMPV6_ROUTER_SOLICITATION)) { - continue; - } - } catch (ErrnoException | SocketException e) { - if (isSocketValid()) { - Log.e(TAG, "recvfrom error: " + e); - } - continue; - } + private boolean sendMessage(int cmd) { + return sendMessage(cmd, null); + } - maybeSendRA(mSolicitor); - } + private boolean sendMessage(int cmd, @Nullable Object obj) { + if (mRaMessageHandler == null) { + return false; } + + return mRaMessageHandler.sendMessage( + Message.obtain(mRaMessageHandler, cmd, obj)); } // TODO: Consider moving this to run on a provided Looper as a Handler, diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java index 976f5df1431afd08be5238427f46e77ad07085cd..9f542f4aef60c7e70fe79b72f1c7b1c11e30f83f 100644 --- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java +++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java @@ -28,6 +28,7 @@ import static android.system.OsConstants.ETH_P_IP; import static android.system.OsConstants.ETH_P_IPV6; import static com.android.net.module.util.NetworkStackConstants.IPV4_MIN_MTU; +import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN; import static com.android.net.module.util.ip.ConntrackMonitor.ConntrackEvent; import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM; import static com.android.networkstack.tethering.BpfUtils.UPSTREAM; @@ -37,6 +38,7 @@ import static com.android.networkstack.tethering.util.TetheringUtils.getTetherin import android.app.usage.NetworkStatsManager; import android.net.INetd; +import android.net.IpPrefix; import android.net.LinkProperties; import android.net.MacAddress; import android.net.NetworkStats; @@ -49,6 +51,7 @@ import android.os.SystemClock; import android.system.ErrnoException; import android.system.OsConstants; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; @@ -149,6 +152,7 @@ public class BpfCoordinator { static final int NF_CONNTRACK_UDP_TIMEOUT_STREAM = 180; @VisibleForTesting static final int INVALID_MTU = 0; + static final int NO_UPSTREAM = 0; // List of TCP port numbers which aren't offloaded because the packets require the netfilter // conntrack helper. See also TetherController::setForwardRules in netd. @@ -217,6 +221,23 @@ public class BpfCoordinator { // TODO: Remove the unused interface name. private final SparseArray mInterfaceNames = new SparseArray<>(); + // How IPv6 upstream rules and downstream rules are managed in BpfCoordinator: + // 1. Each upstream rule represents a downstream interface to an upstream interface forwarding. + // No upstream rule will be exist if there is no upstream interface. + // Note that there is at most one upstream interface for a given downstream interface. + // 2. Each downstream rule represents an IPv6 neighbor, regardless of the existence of the + // upstream interface. If the upstream is not present, the downstream rules have an upstream + // interface index of NO_UPSTREAM, only exist in BpfCoordinator and won't be written to the + // BPF map. When the upstream comes back, those downstream rules will be updated by calling + // Ipv6DownstreamRule#onNewUpstream and written to the BPF map again. We don't remove the + // downstream rules when upstream is lost is because the upstream may come back with the + // same prefix and we won't receive any neighbor update event in this case. + // TODO: Remove downstream rules when upstream is lost and dump neighbors table when upstream + // interface comes back in order to reconstruct the downstream rules. + // 3. It is the same thing for BpfCoordinator if there is no upstream interface or the upstream + // interface is a virtual interface (which currently not supports BPF). In this case, + // IpServer will update its upstream ifindex to NO_UPSTREAM to the BpfCoordinator. + // Map of downstream rule maps. Each of these maps represents the IPv6 forwarding rules for a // given downstream. Each map: // - Is owned by the IpServer that is responsible for that downstream. @@ -233,8 +254,18 @@ public class BpfCoordinator { // rules function without a valid IPv6 downstream interface index even if it may have one // before. IpServer would need to call getInterfaceParams() in the constructor instead of when // startIpv6() is called, and make mInterfaceParams final. - private final HashMap> - mIpv6ForwardingRules = new LinkedHashMap<>(); + private final HashMap> + mIpv6DownstreamRules = new LinkedHashMap<>(); + + // Map of IPv6 upstream rules maps. Each of these maps represents the IPv6 upstream rules for a + // given downstream. Each map: + // - Is owned by the IpServer that is responsible for that downstream. + // - Must only be modified by that IpServer. + // - Is created when the IpServer adds its first upstream rule, and deleted when the IpServer + // deletes its last upstream rule (or clears its upstream rules) + // - Each upstream rule in the ArraySet is corresponding to an upstream interface. + private final ArrayMap> + mIpv6UpstreamRules = new ArrayMap<>(); // Map of downstream client maps. Each of these maps represents the IPv4 clients for a given // downstream. Needed to build IPv4 forwarding rules when conntrack events are received. @@ -497,8 +528,8 @@ public class BpfCoordinator { /** * Stop BPF tethering offload stats polling. * The data limit cleanup and the tether stats maps cleanup are not implemented here. - * These cleanups rely on all IpServers calling #tetherOffloadRuleRemove. After the - * last rule is removed from the upstream, #tetherOffloadRuleRemove does the cleanup + * These cleanups rely on all IpServers calling #removeIpv6DownstreamRule. After the + * last rule is removed from the upstream, #removeIpv6DownstreamRule does the cleanup * functionality. * Note that this can be only called on handler thread. */ @@ -519,6 +550,18 @@ public class BpfCoordinator { mLog.i("Polling stopped"); } + /** + * Return whether BPF offload is supported + */ + public boolean isUsingBpfOffload() { + return isUsingBpf(); + } + + // This is identical to isUsingBpfOffload above but is only used internally. + // The reason for having two separate methods is that the code calls isUsingBpf + // very often. But the tests call verifyNoMoreInteractions, which will check all + // calls to public methods. If isUsingBpf were public, the test would need to + // verify all calls to it, which would clutter the test. private boolean isUsingBpf() { return mIsBpfEnabled && mBpfCoordinatorShim.isInitialized(); } @@ -587,133 +630,177 @@ public class BpfCoordinator { } /** - * Add forwarding rule. After adding the first rule on a given upstream, must add the data - * limit on the given upstream. - * Note that this can be only called on handler thread. + * Add IPv6 upstream rule. After adding the first rule on a given upstream, must add the + * data limit on the given upstream. */ - public void tetherOffloadRuleAdd( - @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) { + private void addIpv6UpstreamRule( + @NonNull final IpServer ipServer, @NonNull final Ipv6UpstreamRule rule) { if (!isUsingBpf()) return; - // TODO: Perhaps avoid to add a duplicate rule. - if (!mBpfCoordinatorShim.tetherOffloadRuleAdd(rule)) return; - - if (!mIpv6ForwardingRules.containsKey(ipServer)) { - mIpv6ForwardingRules.put(ipServer, new LinkedHashMap()); - } - LinkedHashMap rules = mIpv6ForwardingRules.get(ipServer); - // Add upstream and downstream interface index to dev map. maybeAddDevMap(rule.upstreamIfindex, rule.downstreamIfindex); // When the first rule is added to an upstream, setup upstream forwarding and data limit. maybeSetLimit(rule.upstreamIfindex); - if (!isAnyRuleFromDownstreamToUpstream(rule.downstreamIfindex, rule.upstreamIfindex)) { - final int downstream = rule.downstreamIfindex; - final int upstream = rule.upstreamIfindex; - // TODO: support upstream forwarding on non-point-to-point interfaces. - // TODO: get the MTU from LinkProperties and update the rules when it changes. - if (!mBpfCoordinatorShim.startUpstreamIpv6Forwarding(downstream, upstream, rule.srcMac, - NULL_MAC_ADDRESS, NULL_MAC_ADDRESS, NetworkStackConstants.ETHER_MTU)) { - mLog.e("Failed to enable upstream IPv6 forwarding from " - + getIfName(downstream) + " to " + getIfName(upstream)); + // TODO: support upstream forwarding on non-point-to-point interfaces. + // TODO: get the MTU from LinkProperties and update the rules when it changes. + if (!mBpfCoordinatorShim.addIpv6UpstreamRule(rule)) { + return; + } + + ArraySet rules = mIpv6UpstreamRules.computeIfAbsent( + ipServer, k -> new ArraySet()); + rules.add(rule); + } + + /** + * Clear all IPv6 upstream rules for a given downstream. After removing the last rule on a given + * upstream, must clear data limit, update the last tether stats and remove the tether stats in + * the BPF maps. + */ + private void clearIpv6UpstreamRules(@NonNull final IpServer ipServer) { + if (!isUsingBpf()) return; + + final ArraySet upstreamRules = mIpv6UpstreamRules.remove(ipServer); + if (upstreamRules == null) return; + + int upstreamIfindex = 0; + for (Ipv6UpstreamRule rule: upstreamRules) { + if (upstreamIfindex != 0 && rule.upstreamIfindex != upstreamIfindex) { + Log.wtf(TAG, "BUG: upstream rules point to more than one interface"); } + upstreamIfindex = rule.upstreamIfindex; + mBpfCoordinatorShim.removeIpv6UpstreamRule(rule); + } + // Clear the limit if there are no more rules on the given upstream. + // Using upstreamIfindex outside the loop is fine because all the rules for a given IpServer + // will always have the same upstream index (since they are always added all together by + // updateAllIpv6Rules). + // The upstreamIfindex can't be 0 because we won't add an Ipv6UpstreamRule with + // upstreamIfindex == 0 and if there is no Ipv6UpstreamRule for an IpServer, it will be + // removed from mIpv6UpstreamRules. + if (upstreamIfindex == 0) { + Log.wtf(TAG, "BUG: upstream rules have empty Set or rule.upstreamIfindex == 0"); + return; } + maybeClearLimit(upstreamIfindex); + } - // Must update the adding rule after calling #isAnyRuleOnUpstream because it needs to - // check if it is about adding a first rule for a given upstream. + /** + * Add IPv6 downstream rule. + * Note that this can be only called on handler thread. + */ + public void addIpv6DownstreamRule( + @NonNull final IpServer ipServer, @NonNull final Ipv6DownstreamRule rule) { + if (!isUsingBpf()) return; + + // TODO: Perhaps avoid to add a duplicate rule. + if (rule.upstreamIfindex != NO_UPSTREAM + && !mBpfCoordinatorShim.addIpv6DownstreamRule(rule)) return; + + LinkedHashMap rules = + mIpv6DownstreamRules.computeIfAbsent(ipServer, + k -> new LinkedHashMap()); rules.put(rule.address, rule); } /** - * Remove forwarding rule. After removing the last rule on a given upstream, must clear - * data limit, update the last tether stats and remove the tether stats in the BPF maps. + * Remove IPv6 downstream rule. * Note that this can be only called on handler thread. */ - public void tetherOffloadRuleRemove( - @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) { + public void removeIpv6DownstreamRule( + @NonNull final IpServer ipServer, @NonNull final Ipv6DownstreamRule rule) { if (!isUsingBpf()) return; - if (!mBpfCoordinatorShim.tetherOffloadRuleRemove(rule)) return; + if (rule.upstreamIfindex != NO_UPSTREAM + && !mBpfCoordinatorShim.removeIpv6DownstreamRule(rule)) return; - LinkedHashMap rules = mIpv6ForwardingRules.get(ipServer); + LinkedHashMap rules = mIpv6DownstreamRules.get(ipServer); if (rules == null) return; - // Must remove rules before calling #isAnyRuleOnUpstream because it needs to check if - // the last rule is removed for a given upstream. If no rule is removed, return early. - // Avoid unnecessary work on a non-existent rule which may have never been added or - // removed already. + // If no rule is removed, return early. Avoid unnecessary work on a non-existent rule which + // may have never been added or removed already. if (rules.remove(rule.address) == null) return; // Remove the downstream entry if it has no more rule. if (rules.isEmpty()) { - mIpv6ForwardingRules.remove(ipServer); + mIpv6DownstreamRules.remove(ipServer); } + } - // If no more rules between this upstream and downstream, stop upstream forwarding. - if (!isAnyRuleFromDownstreamToUpstream(rule.downstreamIfindex, rule.upstreamIfindex)) { - final int downstream = rule.downstreamIfindex; - final int upstream = rule.upstreamIfindex; - if (!mBpfCoordinatorShim.stopUpstreamIpv6Forwarding(downstream, upstream, - rule.srcMac)) { - mLog.e("Failed to disable upstream IPv6 forwarding from " - + getIfName(downstream) + " to " + getIfName(upstream)); - } - } + /** + * Clear all downstream rules for a given IpServer and return a copy of all removed rules. + */ + @Nullable + private Collection clearIpv6DownstreamRules( + @NonNull final IpServer ipServer) { + final LinkedHashMap downstreamRules = + mIpv6DownstreamRules.remove(ipServer); + if (downstreamRules == null) return null; - // Do cleanup functionality if there is no more rule on the given upstream. - maybeClearLimit(rule.upstreamIfindex); + final Collection removedRules = downstreamRules.values(); + for (final Ipv6DownstreamRule rule : removedRules) { + if (rule.upstreamIfindex == NO_UPSTREAM) continue; + mBpfCoordinatorShim.removeIpv6DownstreamRule(rule); + } + return removedRules; } /** * Clear all forwarding rules for a given downstream. * Note that this can be only called on handler thread. - * TODO: rename to tetherOffloadRuleClear6 because of IPv6 only. */ - public void tetherOffloadRuleClear(@NonNull final IpServer ipServer) { + public void clearAllIpv6Rules(@NonNull final IpServer ipServer) { if (!isUsingBpf()) return; - final LinkedHashMap rules = mIpv6ForwardingRules.get( - ipServer); - if (rules == null) return; - - // Need to build a rule list because the rule map may be changed in the iteration. - for (final Ipv6ForwardingRule rule : new ArrayList(rules.values())) { - tetherOffloadRuleRemove(ipServer, rule); - } + // Clear downstream rules first, because clearing upstream rules fetches the stats, and + // fetching the stats requires that no rules be forwarding traffic to or from the upstream. + clearIpv6DownstreamRules(ipServer); + clearIpv6UpstreamRules(ipServer); } /** - * Update existing forwarding rules to new upstream for a given downstream. + * Delete all upstream and downstream rules for the passed-in IpServer, and if the new upstream + * is nonzero, reapply them to the new upstream. * Note that this can be only called on handler thread. */ - public void tetherOffloadRuleUpdate(@NonNull final IpServer ipServer, int newUpstreamIfindex) { + public void updateAllIpv6Rules(@NonNull final IpServer ipServer, + final InterfaceParams interfaceParams, int newUpstreamIfindex, + @NonNull final Set newUpstreamPrefixes) { if (!isUsingBpf()) return; - final LinkedHashMap rules = mIpv6ForwardingRules.get( - ipServer); - if (rules == null) return; - - // Need to build a rule list because the rule map may be changed in the iteration. - // First remove all the old rules, then add all the new rules. This is because the upstream - // forwarding code in tetherOffloadRuleAdd cannot support rules on two upstreams at the - // same time. Deleting the rules first ensures that upstream forwarding is disabled on the - // old upstream when the last rule is removed from it, and re-enabled on the new upstream - // when the first rule is added to it. - // TODO: Once the IPv6 client processing code has moved from IpServer to BpfCoordinator, do - // something smarter. - final ArrayList rulesCopy = new ArrayList<>(rules.values()); - for (final Ipv6ForwardingRule rule : rulesCopy) { - // Remove the old rule before adding the new one because the map uses the same key for - // both rules. Reversing the processing order causes that the new rule is removed as - // unexpected. - // TODO: Add new rule first to reduce the latency which has no rule. - tetherOffloadRuleRemove(ipServer, rule); + // Remove IPv6 downstream rules. Remove the old ones before adding the new rules, otherwise + // we need to keep a copy of the old rules. + // We still need to keep the downstream rules even when the upstream goes away because it + // may come back with the same prefixes (unlikely, but possible). Neighbor entries won't be + // deleted and we're not expected to receive new Neighbor events in this case. + // TODO: Add new rule first to reduce the latency which has no rule. But this is okay + // because if this is a new upstream, it will probably have different prefixes than + // the one these downstream rules are in. If so, they will never see any downstream + // traffic before new neighbor entries are created. + final Collection deletedDownstreamRules = + clearIpv6DownstreamRules(ipServer); + + // Remove IPv6 upstream rules. Downstream rules must be removed first because + // BpfCoordinatorShimImpl#tetherOffloadGetAndClearStats will be called after the removal of + // the last upstream rule and it requires that no rules be forwarding traffic to or from + // that upstream. + clearIpv6UpstreamRules(ipServer); + + // Add new upstream rules. + if (newUpstreamIfindex != 0 && interfaceParams != null && interfaceParams.macAddr != null) { + for (final IpPrefix ipPrefix : newUpstreamPrefixes) { + addIpv6UpstreamRule(ipServer, new Ipv6UpstreamRule( + newUpstreamIfindex, interfaceParams.index, ipPrefix, + interfaceParams.macAddr, NULL_MAC_ADDRESS, NULL_MAC_ADDRESS)); + } } - for (final Ipv6ForwardingRule rule : rulesCopy) { - tetherOffloadRuleAdd(ipServer, rule.onNewUpstream(newUpstreamIfindex)); + + // Add updated downstream rules. + if (deletedDownstreamRules == null) return; + for (final Ipv6DownstreamRule rule : deletedDownstreamRules) { + addIpv6DownstreamRule(ipServer, rule.onNewUpstream(newUpstreamIfindex)); } } @@ -723,7 +810,7 @@ public class BpfCoordinator { * expects the interface name in NetworkStats object. * Note that this can be only called on handler thread. */ - public void addUpstreamNameToLookupTable(int upstreamIfindex, @NonNull String upstreamIface) { + public void maybeAddUpstreamToLookupTable(int upstreamIfindex, @Nullable String upstreamIface) { if (!isUsingBpf()) return; if (upstreamIfindex == 0 || TextUtils.isEmpty(upstreamIface)) return; @@ -993,7 +1080,7 @@ public class BpfCoordinator { * TODO: consider error handling if the attach program failed. */ public void maybeAttachProgram(@NonNull String intIface, @NonNull String extIface) { - if (isVcnInterface(extIface)) return; + if (!isUsingBpf() || isVcnInterface(extIface)) return; if (forwardingPairExists(intIface, extIface)) return; @@ -1017,6 +1104,8 @@ public class BpfCoordinator { * Detach BPF program */ public void maybeDetachProgram(@NonNull String intIface, @NonNull String extIface) { + if (!isUsingBpf()) return; + forwardingPairRemove(intIface, extIface); // Detaching program may fail because the interface has been removed already. @@ -1139,14 +1228,14 @@ public class BpfCoordinator { private void dumpIpv6ForwardingRulesByDownstream(@NonNull IndentingPrintWriter pw) { pw.println("IPv6 Forwarding rules by downstream interface:"); pw.increaseIndent(); - if (mIpv6ForwardingRules.size() == 0) { - pw.println("No IPv6 rules"); + if (mIpv6DownstreamRules.size() == 0) { + pw.println("No downstream IPv6 rules"); pw.decreaseIndent(); return; } - for (Map.Entry> entry : - mIpv6ForwardingRules.entrySet()) { + for (Map.Entry> entry : + mIpv6DownstreamRules.entrySet()) { IpServer ipServer = entry.getKey(); // The rule downstream interface index is paired with the interface name from // IpServer#interfaceName. See #startIPv6, #updateIpv6ForwardingRules in IpServer. @@ -1155,8 +1244,8 @@ public class BpfCoordinator { + "[srcmac] [dstmac]"); pw.increaseIndent(); - LinkedHashMap rules = entry.getValue(); - for (Ipv6ForwardingRule rule : rules.values()) { + LinkedHashMap rules = entry.getValue(); + for (Ipv6DownstreamRule rule : rules.values()) { final int upstreamIfindex = rule.upstreamIfindex; pw.println(String.format("%d(%s) %d(%s) %s [%s] [%s]", upstreamIfindex, getIfName(upstreamIfindex), rule.downstreamIfindex, @@ -1168,10 +1257,30 @@ public class BpfCoordinator { pw.decreaseIndent(); } + /** + * Returns a /64 IpPrefix corresponding to the passed in byte array + * + * @param ip64 byte array to convert format + * @return the converted IpPrefix + */ + @VisibleForTesting + public static IpPrefix bytesToPrefix(final byte[] ip64) { + IpPrefix sourcePrefix; + byte[] prefixBytes = Arrays.copyOf(ip64, IPV6_ADDR_LEN); + try { + sourcePrefix = new IpPrefix(InetAddress.getByAddress(prefixBytes), 64); + } catch (UnknownHostException e) { + // Cannot happen. InetAddress.getByAddress can only throw an exception if the byte array + // is the wrong length, but we allocate it with fixed length IPV6_ADDR_LEN. + throw new IllegalArgumentException("Invalid IPv6 address"); + } + return sourcePrefix; + } + private String ipv6UpstreamRuleToString(TetherUpstream6Key key, Tether6Value value) { - return String.format("%d(%s) [%s] -> %d(%s) %04x [%s] [%s]", - key.iif, getIfName(key.iif), key.dstMac, value.oif, getIfName(value.oif), - value.ethProto, value.ethSrcMac, value.ethDstMac); + return String.format("%d(%s) [%s] [%s] -> %d(%s) %04x [%s] [%s]", + key.iif, getIfName(key.iif), key.dstMac, bytesToPrefix(key.src64), value.oif, + getIfName(value.oif), value.ethProto, value.ethSrcMac, value.ethDstMac); } private void dumpIpv6UpstreamRules(IndentingPrintWriter pw) { @@ -1221,8 +1330,8 @@ public class BpfCoordinator { // TODO: use dump utils with headerline and lambda which prints key and value to reduce // duplicate bpf map dump code. private void dumpBpfForwardingRulesIpv6(IndentingPrintWriter pw) { - pw.println("IPv6 Upstream: iif(iface) [inDstMac] -> oif(iface) etherType [outSrcMac] " - + "[outDstMac]"); + pw.println("IPv6 Upstream: iif(iface) [inDstMac] [sourcePrefix] -> oif(iface) etherType " + + "[outSrcMac] [outDstMac]"); pw.increaseIndent(); dumpIpv6UpstreamRules(pw); pw.decreaseIndent(); @@ -1403,13 +1512,13 @@ public class BpfCoordinator { pw.decreaseIndent(); } - /** IPv6 forwarding rule class. */ - public static class Ipv6ForwardingRule { - // The upstream6 and downstream6 rules are built as the following tables. Only raw ip - // upstream interface is supported. + /** IPv6 upstream forwarding rule class. */ + public static class Ipv6UpstreamRule { + // The upstream6 rules are built as the following tables. Only raw ip upstream interface is + // supported. // TODO: support ether ip upstream interface. // - // NAT network topology: + // Tethering network topology: // // public network (rawip) private network // | UE | @@ -1419,15 +1528,15 @@ public class BpfCoordinator { // // upstream6 key and value: // - // +------+-------------+ - // | TetherUpstream6Key | - // +------+------+------+ - // |field |iif |dstMac| - // | | | | - // +------+------+------+ - // |value |downst|downst| - // | |ream |ream | - // +------+------+------+ + // +------+-------------------+ + // | TetherUpstream6Key | + // +------+------+------+-----+ + // |field |iif |dstMac|src64| + // | | | | | + // +------+------+------+-----+ + // |value |downst|downst|upstr| + // | |ream |ream |eam | + // +------+------+------+-----+ // // +------+----------------------------------+ // | |Tether6Value | @@ -1439,6 +1548,89 @@ public class BpfCoordinator { // | |am | | |IP | | // +------+------+------+------+------+------+ // + public final int upstreamIfindex; + public final int downstreamIfindex; + @NonNull + public final IpPrefix sourcePrefix; + @NonNull + public final MacAddress inDstMac; + @NonNull + public final MacAddress outSrcMac; + @NonNull + public final MacAddress outDstMac; + + public Ipv6UpstreamRule(int upstreamIfindex, int downstreamIfindex, + @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac, + @NonNull MacAddress outSrcMac, @NonNull MacAddress outDstMac) { + this.upstreamIfindex = upstreamIfindex; + this.downstreamIfindex = downstreamIfindex; + this.sourcePrefix = sourcePrefix; + this.inDstMac = inDstMac; + this.outSrcMac = outSrcMac; + this.outDstMac = outDstMac; + } + + /** + * Return a TetherUpstream6Key object built from the rule. + */ + @NonNull + public TetherUpstream6Key makeTetherUpstream6Key() { + final byte[] prefix64 = Arrays.copyOf(sourcePrefix.getRawAddress(), 8); + return new TetherUpstream6Key(downstreamIfindex, inDstMac, prefix64); + } + + /** + * Return a Tether6Value object built from the rule. + */ + @NonNull + public Tether6Value makeTether6Value() { + return new Tether6Value(upstreamIfindex, outDstMac, outSrcMac, ETH_P_IPV6, + NetworkStackConstants.ETHER_MTU); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Ipv6UpstreamRule)) return false; + Ipv6UpstreamRule that = (Ipv6UpstreamRule) o; + return this.upstreamIfindex == that.upstreamIfindex + && this.downstreamIfindex == that.downstreamIfindex + && Objects.equals(this.sourcePrefix, that.sourcePrefix) + && Objects.equals(this.inDstMac, that.inDstMac) + && Objects.equals(this.outSrcMac, that.outSrcMac) + && Objects.equals(this.outDstMac, that.outDstMac); + } + + @Override + public int hashCode() { + return 13 * upstreamIfindex + 41 * downstreamIfindex + + Objects.hash(sourcePrefix, inDstMac, outSrcMac, outDstMac); + } + + @Override + public String toString() { + return "upstreamIfindex: " + upstreamIfindex + + ", downstreamIfindex: " + downstreamIfindex + + ", sourcePrefix: " + sourcePrefix + + ", inDstMac: " + inDstMac + + ", outSrcMac: " + outSrcMac + + ", outDstMac: " + outDstMac; + } + } + + /** IPv6 downstream forwarding rule class. */ + public static class Ipv6DownstreamRule { + // The downstream6 rules are built as the following tables. Only raw ip upstream interface + // is supported. + // TODO: support ether ip upstream interface. + // + // Tethering network topology: + // + // public network (rawip) private network + // | UE | + // +------------+ V +------------+------------+ V +------------+ + // | Sever +---------+ Upstream | Downstream +---------+ Client | + // +------------+ +------------+------------+ +------------+ + // // downstream6 key and value: // // +------+--------------------+ @@ -1472,11 +1664,11 @@ public class BpfCoordinator { @NonNull public final MacAddress dstMac; - public Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, + public Ipv6DownstreamRule(int upstreamIfindex, int downstreamIfindex, @NonNull Inet6Address address, @NonNull MacAddress srcMac, @NonNull MacAddress dstMac) { this.upstreamIfindex = upstreamIfindex; - this.downstreamIfindex = downstreamIfIndex; + this.downstreamIfindex = downstreamIfindex; this.address = address; this.srcMac = srcMac; this.dstMac = dstMac; @@ -1484,8 +1676,8 @@ public class BpfCoordinator { /** Return a new rule object which updates with new upstream index. */ @NonNull - public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) { - return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac, + public Ipv6DownstreamRule onNewUpstream(int newUpstreamIfindex) { + return new Ipv6DownstreamRule(newUpstreamIfindex, downstreamIfindex, address, srcMac, dstMac); } @@ -1525,8 +1717,8 @@ public class BpfCoordinator { @Override public boolean equals(Object o) { - if (!(o instanceof Ipv6ForwardingRule)) return false; - Ipv6ForwardingRule that = (Ipv6ForwardingRule) o; + if (!(o instanceof Ipv6DownstreamRule)) return false; + Ipv6DownstreamRule that = (Ipv6DownstreamRule) o; return this.upstreamIfindex == that.upstreamIfindex && this.downstreamIfindex == that.downstreamIfindex && Objects.equals(this.address, that.address) @@ -1536,9 +1728,8 @@ public class BpfCoordinator { @Override public int hashCode() { - // TODO: if this is ever used in production code, don't pass ifindices - // to Objects.hash() to avoid autoboxing overhead. - return Objects.hash(upstreamIfindex, downstreamIfindex, address, srcMac, dstMac); + return 13 * upstreamIfindex + 41 * downstreamIfindex + + Objects.hash(address, srcMac, dstMac); } @Override @@ -1867,9 +2058,8 @@ public class BpfCoordinator { } private int getInterfaceIndexFromRules(@NonNull String ifName) { - for (LinkedHashMap rules : mIpv6ForwardingRules - .values()) { - for (Ipv6ForwardingRule rule : rules.values()) { + for (ArraySet rules : mIpv6UpstreamRules.values()) { + for (Ipv6UpstreamRule rule : rules) { final int upstreamIfindex = rule.upstreamIfindex; if (TextUtils.equals(ifName, mInterfaceNames.get(upstreamIfindex))) { return upstreamIfindex; @@ -1887,6 +2077,7 @@ public class BpfCoordinator { } private boolean sendDataLimitToBpfMap(int ifIndex, long quotaBytes) { + if (!isUsingBpf()) return false; if (ifIndex == 0) { Log.wtf(TAG, "Invalid interface index."); return false; @@ -1960,28 +2151,14 @@ public class BpfCoordinator { // TODO: Rename to isAnyIpv6RuleOnUpstream and define an isAnyRuleOnUpstream method that called // both isAnyIpv6RuleOnUpstream and mBpfCoordinatorShim.isAnyIpv4RuleOnUpstream. private boolean isAnyRuleOnUpstream(int upstreamIfindex) { - for (LinkedHashMap rules : mIpv6ForwardingRules - .values()) { - for (Ipv6ForwardingRule rule : rules.values()) { + for (ArraySet rules : mIpv6UpstreamRules.values()) { + for (Ipv6UpstreamRule rule : rules) { if (upstreamIfindex == rule.upstreamIfindex) return true; } } return false; } - private boolean isAnyRuleFromDownstreamToUpstream(int downstreamIfindex, int upstreamIfindex) { - for (LinkedHashMap rules : mIpv6ForwardingRules - .values()) { - for (Ipv6ForwardingRule rule : rules.values()) { - if (downstreamIfindex == rule.downstreamIfindex - && upstreamIfindex == rule.upstreamIfindex) { - return true; - } - } - } - return false; - } - // TODO: remove the index from map while the interface has been removed because the map size // is 64 entries. See packages\modules\Connectivity\Tethering\bpf_progs\offload.c. private void maybeAddDevMap(int upstreamIfindex, int downstreamIfindex) { @@ -2223,13 +2400,13 @@ public class BpfCoordinator { CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS); } - // Return forwarding rule map. This is used for testing only. + // Return IPv6 downstream forwarding rule map. This is used for testing only. // Note that this can be only called on handler thread. @NonNull @VisibleForTesting - final HashMap> - getForwardingRulesForTesting() { - return mIpv6ForwardingRules; + final HashMap> + getIpv6DownstreamRulesForTesting() { + return mIpv6DownstreamRules; } // Return upstream interface name map. This is used for testing only. diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHalAidlImpl.java b/Tethering/src/com/android/networkstack/tethering/OffloadHalAidlImpl.java index e7dc7575af3ee02be7f1b4df4ca74702f081ae48..9ef0f45513d0034e884e39bfcb6e20446e243287 100644 --- a/Tethering/src/com/android/networkstack/tethering/OffloadHalAidlImpl.java +++ b/Tethering/src/com/android/networkstack/tethering/OffloadHalAidlImpl.java @@ -19,18 +19,21 @@ package com.android.networkstack.tethering; import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_AIDL; import android.annotation.NonNull; +import android.annotation.TargetApi; import android.hardware.tetheroffload.ForwardedStats; import android.hardware.tetheroffload.IOffload; import android.hardware.tetheroffload.ITetheringOffloadCallback; import android.hardware.tetheroffload.NatTimeoutUpdate; import android.hardware.tetheroffload.NetworkProtocol; import android.hardware.tetheroffload.OffloadCallbackEvent; +import android.os.Build; import android.os.Handler; import android.os.NativeHandle; import android.os.ParcelFileDescriptor; import android.os.ServiceManager; import android.system.OsConstants; +import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.SharedLog; import com.android.networkstack.tethering.OffloadHardwareInterface.OffloadHalCallback; @@ -40,6 +43,7 @@ import java.util.ArrayList; /** * The implementation of IOffloadHal which based on Stable AIDL interface */ +@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public class OffloadHalAidlImpl implements IOffloadHal { private static final String TAG = OffloadHalAidlImpl.class.getSimpleName(); private static final String HAL_INSTANCE_NAME = IOffload.DESCRIPTOR + "/default"; @@ -52,6 +56,7 @@ public class OffloadHalAidlImpl implements IOffloadHal { private TetheringOffloadCallback mTetheringOffloadCallback; + @VisibleForTesting public OffloadHalAidlImpl(int version, @NonNull IOffload offload, @NonNull Handler handler, @NonNull SharedLog log) { mOffloadVersion = version; diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHalHidlImpl.java b/Tethering/src/com/android/networkstack/tethering/OffloadHalHidlImpl.java index e0a9878bbdf68b95dcd408f73cf6a9a95f561e1b..71922f9ce4495aee6f8b830563054246e66dcfc2 100644 --- a/Tethering/src/com/android/networkstack/tethering/OffloadHalHidlImpl.java +++ b/Tethering/src/com/android/networkstack/tethering/OffloadHalHidlImpl.java @@ -74,10 +74,7 @@ public class OffloadHalHidlImpl implements IOffloadHal { */ public boolean initOffload(@NonNull NativeHandle handle1, @NonNull NativeHandle handle2, @NonNull OffloadHalCallback callback) { - final String logmsg = String.format("initOffload(%d, %d, %s)", - handle1.getFileDescriptor().getInt$(), handle2.getFileDescriptor().getInt$(), - (callback == null) ? "null" - : "0x" + Integer.toHexString(System.identityHashCode(callback))); + final String logmsg = "initOffload()"; mOffloadHalCallback = callback; mTetheringOffloadCallback = new TetheringOffloadCallback( diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java index de15c5b1224e9f5beb36daadc487bc2a1c1a83ca..53c80ae1234ca1c5ebc897d1b013c415ff79cd5f 100644 --- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java +++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java @@ -283,13 +283,16 @@ public class OffloadHardwareInterface { private int initWithHandles(NativeHandle h1, NativeHandle h2) { if (h1 == null || h2 == null) { + // Set mIOffload to null has two purposes: + // 1. NativeHandles can be closed after initWithHandles() fails + // 2. Prevent mIOffload.stopOffload() to be called in stopOffload() + mIOffload = null; mLog.e("Failed to create socket."); return OFFLOAD_HAL_VERSION_NONE; } requestSocketDump(h1); if (!mIOffload.initOffload(h1, h2, mOffloadHalCallback)) { - mIOffload.stopOffload(); mLog.e("Failed to initialize offload."); return OFFLOAD_HAL_VERSION_NONE; } @@ -329,9 +332,9 @@ public class OffloadHardwareInterface { mOffloadHalCallback = offloadCb; final int version = initWithHandles(h1, h2); - // Explicitly close FDs for HIDL. AIDL will pass the original FDs to the service, - // they shouldn't be closed here. - if (version < OFFLOAD_HAL_VERSION_AIDL) { + // Explicitly close FDs for HIDL or when mIOffload is null (cleared in initWithHandles). + // AIDL will pass the original FDs to the service, they shouldn't be closed here. + if (mIOffload == null || mIOffload.getVersion() < OFFLOAD_HAL_VERSION_AIDL) { maybeCloseFdInNativeHandles(h1, h2); } return version; diff --git a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java index 6c0ca822ac4f754ad585c75f0100b679c4836b53..528991f437ef862ae50a7d893bb04ae1b3afff7f 100644 --- a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java +++ b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java @@ -79,6 +79,7 @@ public class PrivateAddressCoordinator { private final TetheringConfiguration mConfig; // keyed by downstream type(TetheringManager.TETHERING_*). private final ArrayMap mCachedAddresses; + private final Random mRandom; public PrivateAddressCoordinator(Context context, TetheringConfiguration config) { mDownstreams = new ArraySet<>(); @@ -95,6 +96,7 @@ public class PrivateAddressCoordinator { mTetheringPrefixes = new ArrayList<>(Arrays.asList(new IpPrefix("192.168.0.0/16"), new IpPrefix("172.16.0.0/12"), new IpPrefix("10.0.0.0/8"))); + mRandom = new Random(); } /** @@ -187,7 +189,10 @@ public class PrivateAddressCoordinator { return cachedAddress; } - for (IpPrefix prefixRange : mTetheringPrefixes) { + final int prefixIndex = getStartedPrefixIndex(); + for (int i = 0; i < mTetheringPrefixes.size(); i++) { + final IpPrefix prefixRange = mTetheringPrefixes.get( + (prefixIndex + i) % mTetheringPrefixes.size()); final LinkAddress newAddress = chooseDownstreamAddress(prefixRange); if (newAddress != null) { mDownstreams.add(ipServer); @@ -200,6 +205,28 @@ public class PrivateAddressCoordinator { return null; } + private int getStartedPrefixIndex() { + if (!mConfig.isRandomPrefixBaseEnabled()) return 0; + + final int random = getRandomInt() & 0xffffff; + // This is to select the starting prefix range (/8, /12, or /16) instead of the actual + // LinkAddress. To avoid complex operations in the selection logic and make the selected + // rate approximate consistency with that /8 is around 2^4 times of /12 and /12 is around + // 2^4 times of /16, we simply define a map between the value and the prefix value like + // this: + // + // Value 0 ~ 0xffff (65536/16777216 = 0.39%) -> 192.168.0.0/16 + // Value 0x10000 ~ 0xfffff (983040/16777216 = 5.86%) -> 172.16.0.0/12 + // Value 0x100000 ~ 0xffffff (15728640/16777216 = 93.7%) -> 10.0.0.0/8 + if (random > 0xfffff) { + return 2; + } else if (random > 0xffff) { + return 1; + } else { + return 0; + } + } + private int getPrefixBaseAddress(final IpPrefix prefix) { return inet4AddressToIntHTH((Inet4Address) prefix.getAddress()); } @@ -263,12 +290,13 @@ public class PrivateAddressCoordinator { // is less than 127.0.0.0 = 0x7f000000 = 2130706432. // // Additionally, it makes debug output easier to read by making the numbers smaller. - final int randomPrefixStart = getRandomInt() & ~prefixRangeMask & prefixMask; + final int randomInt = getRandomInt(); + final int randomPrefixStart = randomInt & ~prefixRangeMask & prefixMask; // A random offset within the prefix. Used to determine the local address once the prefix // is selected. It does not result in an IPv4 address ending in .0, .1, or .255 - // For a PREFIX_LENGTH of 255, this is a number between 2 and 254. - final int subAddress = getSanitizedSubAddr(~prefixMask); + // For a PREFIX_LENGTH of 24, this is a number between 2 and 254. + final int subAddress = getSanitizedSubAddr(randomInt, ~prefixMask); // Find a prefix length PREFIX_LENGTH between randomPrefixStart and the end of the block, // such that the prefix does not conflict with any upstream. @@ -310,12 +338,12 @@ public class PrivateAddressCoordinator { /** Get random int which could be used to generate random address. */ @VisibleForTesting public int getRandomInt() { - return (new Random()).nextInt(); + return mRandom.nextInt(); } /** Get random subAddress and avoid selecting x.x.x.0, x.x.x.1 and x.x.x.255 address. */ - private int getSanitizedSubAddr(final int subAddrMask) { - final int randomSubAddr = getRandomInt() & subAddrMask; + private int getSanitizedSubAddr(final int randomInt, final int subAddrMask) { + final int randomSubAddr = randomInt & subAddrMask; // If prefix length > 30, the selecting speace would be less than 4 which may be hard to // avoid 3 consecutive address. if (PREFIX_LENGTH > 30) return randomSubAddr; diff --git a/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java b/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java index 5893885a76a1e7454b3ef52fa0e342713d432b1b..0cc3dd983a353760df3999f3893561e5118800ae 100644 --- a/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java +++ b/Tethering/src/com/android/networkstack/tethering/TetherUpstream6Key.java @@ -29,13 +29,17 @@ public class TetherUpstream6Key extends Struct { @Field(order = 0, type = Type.S32) public final int iif; // The input interface index. - @Field(order = 1, type = Type.EUI48, padding = 2) + @Field(order = 1, type = Type.EUI48, padding = 6) public final MacAddress dstMac; // Destination ethernet mac address (zeroed iff rawip ingress). - public TetherUpstream6Key(int iif, @NonNull final MacAddress dstMac) { + @Field(order = 2, type = Type.ByteArray, arraysize = 8) + public final byte[] src64; // The top 64-bits of the source ip. + + public TetherUpstream6Key(int iif, @NonNull final MacAddress dstMac, final byte[] src64) { Objects.requireNonNull(dstMac); this.iif = iif; this.dstMac = dstMac; + this.src64 = src64; } } diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java index 0ac6d90b1f3ecdfb2acbe14200dab718c8ac8264..7cd710c50877efb414364e0654ae63591e610f14 100644 --- a/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -91,6 +91,7 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkInfo; +import android.net.RoutingCoordinatorManager; import android.net.TetherStatesParcel; import android.net.TetheredClient; import android.net.TetheringCallbackStartedParcel; @@ -137,6 +138,7 @@ import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.BaseNetdUnsolicitedEventListener; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.NetdUtils; +import com.android.net.module.util.SdkUtil.LateSdk; import com.android.net.module.util.SharedLog; import com.android.networkstack.apishim.common.BluetoothPanShim; import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim; @@ -160,6 +162,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @@ -252,6 +255,10 @@ public class Tethering { private final Handler mHandler; private final INetd mNetd; private final NetdCallback mNetdCallback; + // Contains null if the connectivity module is unsupported, as the routing coordinator is not + // available. Must use LateSdk because MessageUtils enumerates fields in this class, so it + // must be able to find all classes at runtime. + @NonNull private final LateSdk mRoutingCoordinator; private final UserRestrictionActionListener mTetheringRestriction; private final ActiveDataSubIdListener mActiveDataSubIdListener; private final ConnectedClientsTracker mConnectedClientsTracker; @@ -298,9 +305,10 @@ public class Tethering { mDeps = deps; mContext = mDeps.getContext(); mNetd = mDeps.getINetd(mContext); - mLooper = mDeps.getTetheringLooper(); - mNotificationUpdater = mDeps.getNotificationUpdater(mContext, mLooper); - mTetheringMetrics = mDeps.getTetheringMetrics(); + mRoutingCoordinator = mDeps.getRoutingCoordinator(mContext); + mLooper = mDeps.makeTetheringLooper(); + mNotificationUpdater = mDeps.makeNotificationUpdater(mContext, mLooper); + mTetheringMetrics = mDeps.makeTetheringMetrics(); // This is intended to ensrure that if something calls startTethering(bluetooth) just after // bluetooth is enabled. Before onServiceConnected is called, store the calls into this @@ -314,7 +322,7 @@ public class Tethering { mTetherMainSM.start(); mHandler = mTetherMainSM.getHandler(); - mOffloadController = mDeps.getOffloadController(mHandler, mLog, + mOffloadController = mDeps.makeOffloadController(mHandler, mLog, new OffloadController.Dependencies() { @Override @@ -322,15 +330,17 @@ public class Tethering { return mConfig; } }); - mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMainSM, mLog, - TetherMainSM.EVENT_UPSTREAM_CALLBACK); + mUpstreamNetworkMonitor = mDeps.makeUpstreamNetworkMonitor(mContext, mHandler, mLog, + (what, obj) -> { + mTetherMainSM.sendMessage(TetherMainSM.EVENT_UPSTREAM_CALLBACK, what, 0, obj); + }); mForwardedDownstreams = new HashSet<>(); IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); // EntitlementManager will send EVENT_UPSTREAM_PERMISSION_CHANGED when cellular upstream // permission is changed according to entitlement check result. - mEntitlementMgr = mDeps.getEntitlementManager(mContext, mHandler, mLog, + mEntitlementMgr = mDeps.makeEntitlementManager(mContext, mHandler, mLog, () -> mTetherMainSM.sendMessage( TetherMainSM.EVENT_UPSTREAM_PERMISSION_CHANGED)); mEntitlementMgr.setOnTetherProvisioningFailedListener((downstream, reason) -> { @@ -366,11 +376,11 @@ public class Tethering { // It is OK for the configuration to be passed to the PrivateAddressCoordinator at // construction time because the only part of the configuration it uses is // shouldEnableWifiP2pDedicatedIp(), and currently do not support changing that. - mPrivateAddressCoordinator = mDeps.getPrivateAddressCoordinator(mContext, mConfig); + mPrivateAddressCoordinator = mDeps.makePrivateAddressCoordinator(mContext, mConfig); // Must be initialized after tethering configuration is loaded because BpfCoordinator // constructor needs to use the configuration. - mBpfCoordinator = mDeps.getBpfCoordinator( + mBpfCoordinator = mDeps.makeBpfCoordinator( new BpfCoordinator.Dependencies() { @NonNull public Handler getHandler() { @@ -399,7 +409,7 @@ public class Tethering { }); if (SdkLevel.isAtLeastT() && mConfig.isWearTetheringEnabled()) { - mWearableConnectionManager = mDeps.getWearableConnectionManager(mContext); + mWearableConnectionManager = mDeps.makeWearableConnectionManager(mContext); } else { mWearableConnectionManager = null; } @@ -879,7 +889,7 @@ public class Tethering { private void changeBluetoothTetheringSettings(@NonNull final BluetoothPan bluetoothPan, final boolean enable) { - final BluetoothPanShim panShim = mDeps.getBluetoothPanShim(bluetoothPan); + final BluetoothPanShim panShim = mDeps.makeBluetoothPanShim(bluetoothPan); if (enable) { if (mBluetoothIfaceRequest != null) { Log.d(TAG, "Bluetooth tethering settings already enabled"); @@ -1693,6 +1703,8 @@ public class Tethering { static final int EVENT_IFACE_UPDATE_LINKPROPERTIES = BASE_MAIN_SM + 7; // Events from EntitlementManager to choose upstream again. static final int EVENT_UPSTREAM_PERMISSION_CHANGED = BASE_MAIN_SM + 8; + // Internal request from IpServer to enable or disable downstream. + static final int EVENT_REQUEST_CHANGE_DOWNSTREAM = BASE_MAIN_SM + 9; private final State mInitialState; private final State mTetherModeAliveState; @@ -1741,7 +1753,7 @@ public class Tethering { addState(mSetDnsForwardersErrorState); mNotifyList = new ArrayList<>(); - mIPv6TetheringCoordinator = deps.getIPv6TetheringCoordinator(mNotifyList, mLog); + mIPv6TetheringCoordinator = deps.makeIPv6TetheringCoordinator(mNotifyList, mLog); mOffload = new OffloadWrapper(); setInitialState(mInitialState); @@ -1859,11 +1871,12 @@ public class Tethering { setUpstreamNetwork(ns); final Network newUpstream = (ns != null) ? ns.network : null; - if (mTetherUpstream != newUpstream) { + if (!Objects.equals(mTetherUpstream, newUpstream)) { mTetherUpstream = newUpstream; reportUpstreamChanged(mTetherUpstream); - // Need to notify capabilities change after upstream network changed because new - // network's capabilities should be checked every time. + // Need to notify capabilities change after upstream network changed because + // upstream may switch to existing network which don't have + // UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES callback. mNotificationUpdater.onUpstreamCapabilitiesChanged( (ns != null) ? ns.networkCapabilities : null); } @@ -2191,6 +2204,12 @@ public class Tethering { } break; } + case EVENT_REQUEST_CHANGE_DOWNSTREAM: { + final int tetheringType = message.arg1; + final Boolean enabled = (Boolean) message.obj; + enableTetheringInternal(tetheringType, enabled, null); + break; + } default: retValue = false; break; @@ -2735,83 +2754,73 @@ public class Tethering { } } - private IpServer.Callback makeControlCallback() { - return new IpServer.Callback() { - @Override - public void updateInterfaceState(IpServer who, int state, int lastError) { - notifyInterfaceStateChange(who, state, lastError); + private class ControlCallback extends IpServer.Callback { + @Override + public void updateInterfaceState(IpServer who, int state, int lastError) { + final String iface = who.interfaceName(); + final TetherState tetherState = mTetherStates.get(iface); + if (tetherState != null && tetherState.ipServer.equals(who)) { + tetherState.lastState = state; + tetherState.lastError = lastError; + } else { + if (DBG) Log.d(TAG, "got notification from stale iface " + iface); } - @Override - public void updateLinkProperties(IpServer who, LinkProperties newLp) { - notifyLinkPropertiesChanged(who, newLp); - } + mLog.log(String.format("OBSERVED iface=%s state=%s error=%s", iface, state, lastError)); - @Override - public void dhcpLeasesChanged() { - maybeDhcpLeasesChanged(); + // If TetherMainSM is in ErrorState, TetherMainSM stays there. + // Thus we give a chance for TetherMainSM to recover to InitialState + // by sending CMD_CLEAR_ERROR + if (lastError == TETHER_ERROR_INTERNAL_ERROR) { + mTetherMainSM.sendMessage(TetherMainSM.CMD_CLEAR_ERROR, who); } - - @Override - public void requestEnableTethering(int tetheringType, boolean enabled) { - enableTetheringInternal(tetheringType, enabled, null); + int which; + switch (state) { + case IpServer.STATE_UNAVAILABLE: + case IpServer.STATE_AVAILABLE: + which = TetherMainSM.EVENT_IFACE_SERVING_STATE_INACTIVE; + break; + case IpServer.STATE_TETHERED: + case IpServer.STATE_LOCAL_ONLY: + which = TetherMainSM.EVENT_IFACE_SERVING_STATE_ACTIVE; + break; + default: + Log.wtf(TAG, "Unknown interface state: " + state); + return; } - }; - } - - // TODO: Move into TetherMainSM. - private void notifyInterfaceStateChange(IpServer who, int state, int error) { - final String iface = who.interfaceName(); - final TetherState tetherState = mTetherStates.get(iface); - if (tetherState != null && tetherState.ipServer.equals(who)) { - tetherState.lastState = state; - tetherState.lastError = error; - } else { - if (DBG) Log.d(TAG, "got notification from stale iface " + iface); + mTetherMainSM.sendMessage(which, state, 0, who); + sendTetherStateChangedBroadcast(); } - mLog.log(String.format("OBSERVED iface=%s state=%s error=%s", iface, state, error)); - - // If TetherMainSM is in ErrorState, TetherMainSM stays there. - // Thus we give a chance for TetherMainSM to recover to InitialState - // by sending CMD_CLEAR_ERROR - if (error == TETHER_ERROR_INTERNAL_ERROR) { - mTetherMainSM.sendMessage(TetherMainSM.CMD_CLEAR_ERROR, who); - } - int which; - switch (state) { - case IpServer.STATE_UNAVAILABLE: - case IpServer.STATE_AVAILABLE: - which = TetherMainSM.EVENT_IFACE_SERVING_STATE_INACTIVE; - break; - case IpServer.STATE_TETHERED: - case IpServer.STATE_LOCAL_ONLY: - which = TetherMainSM.EVENT_IFACE_SERVING_STATE_ACTIVE; - break; - default: - Log.wtf(TAG, "Unknown interface state: " + state); + @Override + public void updateLinkProperties(IpServer who, LinkProperties newLp) { + final String iface = who.interfaceName(); + final int state; + final TetherState tetherState = mTetherStates.get(iface); + if (tetherState != null && tetherState.ipServer.equals(who)) { + state = tetherState.lastState; + } else { + mLog.log("got notification from stale iface " + iface); return; + } + + mLog.log(String.format( + "OBSERVED LinkProperties update iface=%s state=%s lp=%s", + iface, IpServer.getStateString(state), newLp)); + final int which = TetherMainSM.EVENT_IFACE_UPDATE_LINKPROPERTIES; + mTetherMainSM.sendMessage(which, state, 0, newLp); } - mTetherMainSM.sendMessage(which, state, 0, who); - sendTetherStateChangedBroadcast(); - } - private void notifyLinkPropertiesChanged(IpServer who, LinkProperties newLp) { - final String iface = who.interfaceName(); - final int state; - final TetherState tetherState = mTetherStates.get(iface); - if (tetherState != null && tetherState.ipServer.equals(who)) { - state = tetherState.lastState; - } else { - mLog.log("got notification from stale iface " + iface); - return; + @Override + public void dhcpLeasesChanged() { + maybeDhcpLeasesChanged(); } - mLog.log(String.format( - "OBSERVED LinkProperties update iface=%s state=%s lp=%s", - iface, IpServer.getStateString(state), newLp)); - final int which = TetherMainSM.EVENT_IFACE_UPDATE_LINKPROPERTIES; - mTetherMainSM.sendMessage(which, state, 0, newLp); + @Override + public void requestEnableTethering(int tetheringType, boolean enabled) { + mTetherMainSM.sendMessage(TetherMainSM.EVENT_REQUEST_CHANGE_DOWNSTREAM, + tetheringType, 0, enabled ? Boolean.TRUE : Boolean.FALSE); + } } private boolean hasSystemFeature(final String feature) { @@ -2852,9 +2861,10 @@ public class Tethering { mLog.i("adding IpServer for: " + iface); final TetherState tetherState = new TetherState( - new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator, - makeControlCallback(), mConfig, mPrivateAddressCoordinator, - mTetheringMetrics, mDeps.getIpServerDependencies()), isNcm); + new IpServer(iface, mHandler, interfaceType, mLog, mNetd, mBpfCoordinator, + mRoutingCoordinator, new ControlCallback(), mConfig, + mPrivateAddressCoordinator, mTetheringMetrics, + mDeps.makeIpServerDependencies()), isNcm); mTetherStates.put(iface, tetherState); tetherState.ipServer.start(); } @@ -2880,4 +2890,9 @@ public class Tethering { } catch (RemoteException e) { } }); } + + @VisibleForTesting + public TetherMainSM getTetherMainSMForTesting() { + return mTetherMainSM; + } } diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java index b10a7bb8c48842c10bfdbebed1f49b4cf99c1563..a2e7912cb093321626d2ff44fc75fa28ee30e9a9 100644 --- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java +++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java @@ -21,9 +21,7 @@ import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_MOBILE_DUN; import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; -import static android.provider.DeviceConfig.NAMESPACE_TETHERING; -import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME; import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL; import android.content.ContentResolver; @@ -110,8 +108,8 @@ public class TetheringConfiguration { * config_tether_upstream_automatic when set to true. * * This flag is enabled if !=0 and less than the module APEX version: see - * {@link DeviceConfigUtils#isFeatureEnabled}. It is also ignored after R, as later devices - * should just set config_tether_upstream_automatic to true instead. + * {@link DeviceConfigUtils#isTetheringFeatureEnabled}. It is also ignored after R, as later + * devices should just set config_tether_upstream_automatic to true instead. */ public static final String TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION = "tether_force_upstream_automatic_version"; @@ -130,6 +128,11 @@ public class TetheringConfiguration { public static final String TETHER_ENABLE_WEAR_TETHERING = "tether_enable_wear_tethering"; + public static final String TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION = + "tether_force_random_prefix_base_selection"; + + public static final String TETHER_ENABLE_SYNC_SM = "tether_enable_sync_sm"; + /** * Default value that used to periodic polls tether offload stats from tethering offload HAL * to make the data warnings work. @@ -168,6 +171,8 @@ public class TetheringConfiguration { private final int mP2pLeasesSubnetPrefixLength; private final boolean mEnableWearTethering; + private final boolean mRandomPrefixBase; + private final boolean mEnableSyncSm; private final int mUsbTetheringFunction; protected final ContentResolver mContentResolver; @@ -177,16 +182,30 @@ public class TetheringConfiguration { */ @VisibleForTesting public static class Dependencies { - boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace, - @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) { - return DeviceConfigUtils.isFeatureEnabled(context, namespace, name, - moduleName, defaultEnabled); + boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) { + return DeviceConfigUtils.isTetheringFeatureEnabled(context, name); } boolean getDeviceConfigBoolean(@NonNull String namespace, @NonNull String name, boolean defaultValue) { return DeviceConfig.getBoolean(namespace, name, defaultValue); } + + /** + * TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION is used to force enable the feature on specific + * R devices. Just checking the flag value is enough since the flag has been pushed to + * enable the feature on the old version and any new binary will always have a version + * number newer than the flag. + * This flag is wrongly configured in the connectivity namespace so this method reads the + * flag value from the connectivity namespace. But the tethering module should use the + * tethering namespace. This method can be removed after R EOL. + */ + boolean isTetherForceUpstreamAutomaticFeatureEnabled() { + final int flagValue = DeviceConfigUtils.getDeviceConfigPropertyInt( + NAMESPACE_CONNECTIVITY, TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION, + 0 /* defaultValue */); + return flagValue > 0; + } } public TetheringConfiguration(@NonNull Context ctx, @NonNull SharedLog log, int id) { @@ -235,7 +254,7 @@ public class TetheringConfiguration { // - S, T: can be enabled/disabled by resource config_tether_upstream_automatic. // - U+ : automatic mode only. final boolean forceAutomaticUpstream = SdkLevel.isAtLeastU() || (!SdkLevel.isAtLeastS() - && isConnectivityFeatureEnabled(ctx, TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION)); + && mDeps.isTetherForceUpstreamAutomaticFeatureEnabled()); chooseUpstreamAutomatically = forceAutomaticUpstream || getResourceBoolean( res, R.bool.config_tether_upstream_automatic, false /** defaultValue */); preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired); @@ -271,6 +290,9 @@ public class TetheringConfiguration { mEnableWearTethering = shouldEnableWearTethering(ctx); + mRandomPrefixBase = mDeps.isFeatureEnabled(ctx, TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION); + mEnableSyncSm = mDeps.isFeatureEnabled(ctx, TETHER_ENABLE_SYNC_SM); + configLog.log(toString()); } @@ -359,6 +381,14 @@ public class TetheringConfiguration { return mEnableWearTethering; } + public boolean isRandomPrefixBaseEnabled() { + return mRandomPrefixBase; + } + + public boolean isSyncSM() { + return mEnableSyncSm; + } + /** Does the dumping.*/ public void dump(PrintWriter pw) { pw.print("activeDataSubId: "); @@ -409,6 +439,12 @@ public class TetheringConfiguration { pw.print("mUsbTetheringFunction: "); pw.println(isUsingNcm() ? "NCM" : "RNDIS"); + + pw.print("mRandomPrefixBase: "); + pw.println(mRandomPrefixBase); + + pw.print("mEnableSyncSm: "); + pw.println(mEnableSyncSm); } /** Returns the string representation of this object.*/ @@ -602,32 +638,13 @@ public class TetheringConfiguration { private boolean shouldEnableWearTethering(Context context) { return SdkLevel.isAtLeastT() - && isTetheringFeatureEnabled(context, TETHER_ENABLE_WEAR_TETHERING); + && mDeps.isFeatureEnabled(context, TETHER_ENABLE_WEAR_TETHERING); } private boolean getDeviceConfigBoolean(final String name, final boolean defaultValue) { return mDeps.getDeviceConfigBoolean(NAMESPACE_CONNECTIVITY, name, defaultValue); } - /** - * This is deprecated because connectivity namespace already be used for NetworkStack mainline - * module. Tethering should use its own namespace to roll out the feature flag. - * @deprecated new caller should use isTetheringFeatureEnabled instead. - */ - @Deprecated - private boolean isConnectivityFeatureEnabled(Context ctx, String featureVersionFlag) { - return isFeatureEnabled(ctx, NAMESPACE_CONNECTIVITY, featureVersionFlag); - } - - private boolean isTetheringFeatureEnabled(Context ctx, String featureVersionFlag) { - return isFeatureEnabled(ctx, NAMESPACE_TETHERING, featureVersionFlag); - } - - private boolean isFeatureEnabled(Context ctx, String namespace, String featureVersionFlag) { - return mDeps.isFeatureEnabled(ctx, namespace, featureVersionFlag, TETHERING_MODULE_NAME, - false /* defaultEnabled */); - } - private Resources getResources(Context ctx, int subId) { if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { return getResourcesForSubIdWrapper(ctx, subId); diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java index 741a5c519488759454b5fad2cedf2a7d186864d8..9dfd225deb5aa681c521c038e042684dd3d4ae6c 100644 --- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java +++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java @@ -16,11 +16,14 @@ package com.android.networkstack.tethering; +import android.annotation.Nullable; import android.app.usage.NetworkStatsManager; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothPan; import android.content.Context; import android.net.INetd; +import android.net.RoutingCoordinatorManager; +import android.net.connectivity.ConnectivityInternalApiUtil; import android.net.ip.IpServer; import android.os.Build; import android.os.Handler; @@ -32,7 +35,8 @@ import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; -import com.android.internal.util.StateMachine; +import com.android.modules.utils.build.SdkLevel; +import com.android.net.module.util.SdkUtil.LateSdk; import com.android.net.module.util.SharedLog; import com.android.networkstack.apishim.BluetoothPanShimImpl; import com.android.networkstack.apishim.common.BluetoothPanShim; @@ -49,58 +53,58 @@ import java.util.ArrayList; */ public abstract class TetheringDependencies { /** - * Get a reference to the BpfCoordinator to be used by tethering. + * Make the BpfCoordinator to be used by tethering. */ - public @NonNull BpfCoordinator getBpfCoordinator( + public @NonNull BpfCoordinator makeBpfCoordinator( @NonNull BpfCoordinator.Dependencies deps) { return new BpfCoordinator(deps); } /** - * Get a reference to the offload hardware interface to be used by tethering. + * Make the offload hardware interface to be used by tethering. */ - public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) { + public OffloadHardwareInterface makeOffloadHardwareInterface(Handler h, SharedLog log) { return new OffloadHardwareInterface(h, log); } /** - * Get a reference to the offload controller to be used by tethering. + * Make the offload controller to be used by tethering. */ @NonNull - public OffloadController getOffloadController(@NonNull Handler h, + public OffloadController makeOffloadController(@NonNull Handler h, @NonNull SharedLog log, @NonNull OffloadController.Dependencies deps) { final NetworkStatsManager statsManager = (NetworkStatsManager) getContext().getSystemService(Context.NETWORK_STATS_SERVICE); - return new OffloadController(h, getOffloadHardwareInterface(h, log), + return new OffloadController(h, makeOffloadHardwareInterface(h, log), getContext().getContentResolver(), statsManager, log, deps); } /** - * Get a reference to the UpstreamNetworkMonitor to be used by tethering. + * Make the UpstreamNetworkMonitor to be used by tethering. */ - public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target, - SharedLog log, int what) { - return new UpstreamNetworkMonitor(ctx, target, log, what); + public UpstreamNetworkMonitor makeUpstreamNetworkMonitor(Context ctx, Handler h, + SharedLog log, UpstreamNetworkMonitor.EventListener listener) { + return new UpstreamNetworkMonitor(ctx, h, log, listener); } /** - * Get a reference to the IPv6TetheringCoordinator to be used by tethering. + * Make the IPv6TetheringCoordinator to be used by tethering. */ - public IPv6TetheringCoordinator getIPv6TetheringCoordinator( + public IPv6TetheringCoordinator makeIPv6TetheringCoordinator( ArrayList notifyList, SharedLog log) { return new IPv6TetheringCoordinator(notifyList, log); } /** - * Get dependencies to be used by IpServer. + * Make dependencies to be used by IpServer. */ - public abstract IpServer.Dependencies getIpServerDependencies(); + public abstract IpServer.Dependencies makeIpServerDependencies(); /** - * Get a reference to the EntitlementManager to be used by tethering. + * Make the EntitlementManager to be used by tethering. */ - public EntitlementManager getEntitlementManager(Context ctx, Handler h, SharedLog log, + public EntitlementManager makeEntitlementManager(Context ctx, Handler h, SharedLog log, Runnable callback) { return new EntitlementManager(ctx, h, log, callback); } @@ -122,20 +126,30 @@ public abstract class TetheringDependencies { } /** - * Get a reference to the TetheringNotificationUpdater to be used by tethering. + * Get the routing coordinator, or null if below S. */ - public TetheringNotificationUpdater getNotificationUpdater(@NonNull final Context ctx, + @Nullable + public LateSdk getRoutingCoordinator(Context context) { + if (!SdkLevel.isAtLeastS()) return new LateSdk<>(null); + return new LateSdk<>( + ConnectivityInternalApiUtil.getRoutingCoordinatorManager(context)); + } + + /** + * Make the TetheringNotificationUpdater to be used by tethering. + */ + public TetheringNotificationUpdater makeNotificationUpdater(@NonNull final Context ctx, @NonNull final Looper looper) { return new TetheringNotificationUpdater(ctx, looper); } /** - * Get tethering thread looper. + * Make tethering thread looper. */ - public abstract Looper getTetheringLooper(); + public abstract Looper makeTetheringLooper(); /** - * Get Context of TetheringSerice. + * Get Context of TetheringService. */ public abstract Context getContext(); @@ -152,26 +166,26 @@ public abstract class TetheringDependencies { } /** - * Get a reference to PrivateAddressCoordinator to be used by Tethering. + * Make PrivateAddressCoordinator to be used by Tethering. */ - public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx, + public PrivateAddressCoordinator makePrivateAddressCoordinator(Context ctx, TetheringConfiguration cfg) { return new PrivateAddressCoordinator(ctx, cfg); } /** - * Get BluetoothPanShim object to enable/disable bluetooth tethering. + * Make BluetoothPanShim object to enable/disable bluetooth tethering. * * TODO: use BluetoothPan directly when mainline module is built with API 32. */ - public BluetoothPanShim getBluetoothPanShim(BluetoothPan pan) { + public BluetoothPanShim makeBluetoothPanShim(BluetoothPan pan) { return BluetoothPanShimImpl.newInstance(pan); } /** - * Get a reference to the TetheringMetrics to be used by tethering. + * Make the TetheringMetrics to be used by tethering. */ - public TetheringMetrics getTetheringMetrics() { + public TetheringMetrics makeTetheringMetrics() { return new TetheringMetrics(); } @@ -179,7 +193,7 @@ public abstract class TetheringDependencies { * Returns the implementation of WearableConnectionManager. */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) - public WearableConnectionManager getWearableConnectionManager(Context ctx) { + public WearableConnectionManager makeWearableConnectionManager(Context ctx) { return new WearableConnectionManager(ctx); } } diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java index 96ddfa01c46dc7ac1ee7c6696732ab856004d516..aa73819cc38f2e84ddb65a2f6a3147ab0ec33f61 100644 --- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java +++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java @@ -322,7 +322,7 @@ public class TetheringService extends Service { public TetheringDependencies makeTetheringDependencies() { return new TetheringDependencies() { @Override - public Looper getTetheringLooper() { + public Looper makeTetheringLooper() { final HandlerThread tetherThread = new HandlerThread("android.tethering"); tetherThread.start(); return tetherThread.getLooper(); @@ -334,7 +334,7 @@ public class TetheringService extends Service { } @Override - public IpServer.Dependencies getIpServerDependencies() { + public IpServer.Dependencies makeIpServerDependencies() { return new IpServer.Dependencies() { @Override public void makeDhcpServer(String ifName, DhcpServingParamsParcel params, diff --git a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java index c8fe31f5f04fa3ede459a6db5e34c02fbd9915ce..577a5e17aa2c024cc647e1805ebca866a46762ef 100644 --- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java +++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java @@ -47,7 +47,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.StateMachine; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.SharedLog; import com.android.networkstack.apishim.ConnectivityManagerShimImpl; @@ -121,9 +120,8 @@ public class UpstreamNetworkMonitor { private final Context mContext; private final SharedLog mLog; - private final StateMachine mTarget; private final Handler mHandler; - private final int mWhat; + private final EventListener mEventListener; private final HashMap mNetworkMap = new HashMap<>(); private HashSet mLocalPrefixes; private ConnectivityManager mCM; @@ -147,12 +145,11 @@ public class UpstreamNetworkMonitor { // Set if the Internet is considered reachable via the main user's VPN network private Network mTetheringUpstreamVpn; - public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what) { + public UpstreamNetworkMonitor(Context ctx, Handler h, SharedLog log, EventListener listener) { mContext = ctx; - mTarget = tgt; - mHandler = mTarget.getHandler(); + mHandler = h; mLog = log.forSubComponent(TAG); - mWhat = what; + mEventListener = listener; mLocalPrefixes = new HashSet<>(); mIsDefaultCellularUpstream = false; mCM = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); @@ -425,11 +422,12 @@ public class UpstreamNetworkMonitor { network, newNc)); } - mNetworkMap.put(network, new UpstreamNetworkState( - prev.linkProperties, newNc, network)); + final UpstreamNetworkState uns = + new UpstreamNetworkState(prev.linkProperties, newNc, network); + mNetworkMap.put(network, uns); // TODO: If sufficient information is available to select a more // preferable upstream, do so now and notify the target. - notifyTarget(EVENT_ON_CAPABILITIES, network); + mEventListener.onUpstreamEvent(EVENT_ON_CAPABILITIES, uns); } private @Nullable UpstreamNetworkState updateLinkProperties(@NonNull Network network, @@ -462,7 +460,7 @@ public class UpstreamNetworkMonitor { private void handleLinkProp(Network network, LinkProperties newLp) { final UpstreamNetworkState ns = updateLinkProperties(network, newLp); if (ns != null) { - notifyTarget(EVENT_ON_LINKPROPERTIES, ns); + mEventListener.onUpstreamEvent(EVENT_ON_LINKPROPERTIES, ns); } } @@ -493,7 +491,7 @@ public class UpstreamNetworkMonitor { // preferable upstream, do so now and notify the target. Likewise, // if the current upstream network is gone, notify the target of the // fact that we now have no upstream at all. - notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network)); + mEventListener.onUpstreamEvent(EVENT_ON_LOST, mNetworkMap.remove(network)); } private void maybeHandleNetworkSwitch(@NonNull Network network) { @@ -511,14 +509,14 @@ public class UpstreamNetworkMonitor { // Default network changed. Update local data and notify tethering. Log.d(TAG, "New default Internet network: " + network); mDefaultInternetNetwork = network; - notifyTarget(EVENT_DEFAULT_SWITCHED, ns); + mEventListener.onUpstreamEvent(EVENT_DEFAULT_SWITCHED, ns); } private void recomputeLocalPrefixes() { final HashSet localPrefixes = allLocalPrefixes(mNetworkMap.values()); if (!mLocalPrefixes.equals(localPrefixes)) { mLocalPrefixes = localPrefixes; - notifyTarget(NOTIFY_LOCAL_PREFIXES, localPrefixes.clone()); + mEventListener.onUpstreamEvent(NOTIFY_LOCAL_PREFIXES, localPrefixes.clone()); } } @@ -557,12 +555,13 @@ public class UpstreamNetworkMonitor { // onLinkPropertiesChanged right after this method and mDefaultInternetNetwork will // be updated then. // - // Technically, not updating here isn't necessary, because the notifications to - // Tethering sent by notifyTarget are messages sent to a state machine running on - // the same thread as this method, and so cannot arrive until after this method has - // returned. However, it is not a good idea to rely on that because fact that - // Tethering uses multiple state machines running on the same thread is a major - // source of race conditions and something that should be fixed. + // Technically, mDefaultInternetNetwork could be updated here, because the + // Callback#onChange implementation sends messages to the state machine running + // on the same thread as this method. If there is new default network change, + // the message cannot arrive until onLinkPropertiesChanged returns. + // However, it is not a good idea to rely on that because fact that Tethering uses + // multiple state machines running on the same thread is a major source of race + // conditions and something that should be fixed. // // TODO: is it correct that this code always updates EntitlementManager? // This code runs when the default network connects or changes capabilities, but the @@ -606,7 +605,7 @@ public class UpstreamNetworkMonitor { mIsDefaultCellularUpstream = false; mEntitlementMgr.notifyUpstream(false); Log.d(TAG, "Lost default Internet network: " + network); - notifyTarget(EVENT_DEFAULT_SWITCHED, null); + mEventListener.onUpstreamEvent(EVENT_DEFAULT_SWITCHED, null); return; } @@ -624,14 +623,6 @@ public class UpstreamNetworkMonitor { if (cb != null) cm().unregisterNetworkCallback(cb); } - private void notifyTarget(int which, Network network) { - notifyTarget(which, mNetworkMap.get(network)); - } - - private void notifyTarget(int which, Object obj) { - mTarget.sendMessage(mWhat, which, 0, obj); - } - private static class TypeStatePair { public int type = TYPE_NONE; public UpstreamNetworkState ns = null; @@ -769,4 +760,10 @@ public class UpstreamNetworkMonitor { public void setPreferTestNetworks(boolean prefer) { mPreferTestNetworks = prefer; } + + /** An interface to notify upstream network changes. */ + public interface EventListener { + /** Notify the client of some event */ + void onUpstreamEvent(int what, Object obj); + } } diff --git a/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java b/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java new file mode 100644 index 0000000000000000000000000000000000000000..078a35f0e2d59e3b9be6a7980fd2d70ae0e71e39 --- /dev/null +++ b/Tethering/src/com/android/networkstack/tethering/util/StateMachineShim.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2023 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.networkstack.tethering.util; + +import android.annotation.Nullable; +import android.os.Looper; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo; + +import java.util.List; + +/** A wrapper to decide whether use synchronous state machine for tethering. */ +public class StateMachineShim { + // Exactly one of mAsyncSM or mSyncSM is non-null. + private final AsyncStateMachine mAsyncSM; + private final SyncStateMachine mSyncSM; + + /** + * The Looper parameter is only needed for AsyncSM, so if looper is null, the shim will be + * created for SyncSM. + */ + public StateMachineShim(final String name, @Nullable final Looper looper) { + this(name, looper, new Dependencies()); + } + + @VisibleForTesting + public StateMachineShim(final String name, @Nullable final Looper looper, + final Dependencies deps) { + if (looper == null) { + mAsyncSM = null; + mSyncSM = deps.makeSyncStateMachine(name, Thread.currentThread()); + } else { + mAsyncSM = deps.makeAsyncStateMachine(name, looper); + mSyncSM = null; + } + } + + /** A dependencies class which used for testing injection. */ + @VisibleForTesting + public static class Dependencies { + /** Create SyncSM instance, for injection. */ + public SyncStateMachine makeSyncStateMachine(final String name, final Thread thread) { + return new SyncStateMachine(name, thread); + } + + /** Create AsyncSM instance, for injection. */ + public AsyncStateMachine makeAsyncStateMachine(final String name, final Looper looper) { + return new AsyncStateMachine(name, looper); + } + } + + /** Start the state machine */ + public void start(final State initialState) { + if (mSyncSM != null) { + mSyncSM.start(initialState); + } else { + mAsyncSM.setInitialState(initialState); + mAsyncSM.start(); + } + } + + /** Add states to state machine. */ + public void addAllStates(final List stateInfos) { + if (mSyncSM != null) { + mSyncSM.addAllStates(stateInfos); + } else { + for (final StateInfo info : stateInfos) { + mAsyncSM.addState(info.state, info.parent); + } + } + } + + /** + * Transition to given state. + * + * SyncSM doesn't allow this be called during state transition (#enter() or #exit() methods), + * or multiple times while processing a single message. + */ + public void transitionTo(final State state) { + if (mSyncSM != null) { + mSyncSM.transitionTo(state); + } else { + mAsyncSM.transitionTo(state); + } + } + + /** Send message to state machine. */ + public void sendMessage(int what) { + sendMessage(what, 0, 0, null); + } + + /** Send message to state machine. */ + public void sendMessage(int what, Object obj) { + sendMessage(what, 0, 0, obj); + } + + /** Send message to state machine. */ + public void sendMessage(int what, int arg1) { + sendMessage(what, arg1, 0, null); + } + + /** + * Send message to state machine. + * + * If using asynchronous state machine, putting the message into looper's message queue. + * Tethering runs on single looper thread that ipServers and mainSM all share with same message + * queue. The enqueued message will be processed by asynchronous state machine when all the + * messages before such enqueued message are processed. + * If using synchronous state machine, the message is processed right away without putting into + * looper's message queue. + */ + public void sendMessage(int what, int arg1, int arg2, Object obj) { + if (mSyncSM != null) { + mSyncSM.processMessage(what, arg1, arg2, obj); + } else { + mAsyncSM.sendMessage(what, arg1, arg2, obj); + } + } + + /** + * Send message after delayMillis millisecond. + * + * This can only be used with async state machine, so this will throw if using sync state + * machine. + */ + public void sendMessageDelayedToAsyncSM(final int what, final long delayMillis) { + if (mSyncSM != null) { + throw new IllegalStateException("sendMessageDelayed can only be used with async SM"); + } + + mAsyncSM.sendMessageDelayed(what, delayMillis); + } + + /** + * Enqueue a message to the front of the queue. + * Protected, may only be called by instances of async state machine. + * + * Message is ignored if state machine has quit. + */ + protected void sendMessageAtFrontOfQueueToAsyncSM(int what, int arg1) { + if (mSyncSM != null) { + throw new IllegalStateException("sendMessageAtFrontOfQueue can only be used with" + + " async SM"); + } + + mAsyncSM.sendMessageAtFrontOfQueueToAsyncSM(what, arg1); + } + + /** + * Send self message. + * This can only be used with sync state machine, so this will throw if using async state + * machine. + */ + public void sendSelfMessageToSyncSM(final int what, final Object obj) { + if (mSyncSM == null) { + throw new IllegalStateException("sendSelfMessage can only be used with sync SM"); + } + + mSyncSM.sendSelfMessage(what, 0, 0, obj); + } + + /** + * An alias StateMahchine class with public construtor. + * + * Since StateMachine.java only provides protected construtor, adding a child class so that this + * shim could create StateMachine instance. + */ + @VisibleForTesting + public static class AsyncStateMachine extends StateMachine { + public AsyncStateMachine(final String name, final Looper looper) { + super(name, looper); + } + + /** Enqueue a message to the front of the queue for this state machine. */ + public void sendMessageAtFrontOfQueueToAsyncSM(int what, int arg1) { + sendMessageAtFrontOfQueue(what, arg1); + } + } +} diff --git a/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java b/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java new file mode 100644 index 0000000000000000000000000000000000000000..a17eb269ab0c124188be2ef10e76cceb5002e740 --- /dev/null +++ b/Tethering/src/com/android/networkstack/tethering/util/SyncStateMachine.java @@ -0,0 +1,333 @@ +/** + * Copyright (C) 2023 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.networkstack.tethering.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Message; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; + +import com.android.internal.util.State; + +import java.util.ArrayDeque; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * An implementation of a state machine, meant to be called synchronously. + * + * This class implements a finite state automaton based on the same State + * class as StateMachine. + * All methods of this class must be called on only one thread. + */ +public class SyncStateMachine { + @NonNull private final String mName; + @NonNull private final Thread mMyThread; + private final boolean mDbg; + private final ArrayMap mStateInfo = new ArrayMap<>(); + + // mCurrentState is the current state. mDestState is the target state that mCurrentState will + // transition to. The value of mDestState can be changed when a state processes a message and + // calls #transitionTo, but it cannot be changed during the state transition. When the state + // transition is complete, mDestState will be set to mCurrentState. Both mCurrentState and + // mDestState only be null before state machine starts and must only be touched on mMyThread. + @Nullable private State mCurrentState; + @Nullable private State mDestState; + private final ArrayDeque mSelfMsgQueue = new ArrayDeque(); + + // MIN_VALUE means not currently processing any message. + private int mCurrentlyProcessing = Integer.MIN_VALUE; + // Indicates whether automaton can send self message. Self messages can only be sent by + // automaton from State#enter, State#exit, or State#processMessage. Calling from outside + // of State is not allowed. + private boolean mSelfMsgAllowed = false; + + /** + * A information class about a state and its parent. Used to maintain the state hierarchy. + */ + public static class StateInfo { + /** The state who owns this StateInfo. */ + public final State state; + /** The parent state. */ + public final State parent; + // True when the state has been entered and on the stack. + private boolean mActive; + + public StateInfo(@NonNull final State child, @Nullable final State parent) { + this.state = child; + this.parent = parent; + } + } + + /** + * The constructor. + * + * @param name of this machine. + * @param thread the running thread of this machine. It must either be the thread on which this + * constructor is called, or a thread that is not started yet. + */ + public SyncStateMachine(@NonNull final String name, @NonNull final Thread thread) { + this(name, thread, false /* debug */); + } + + /** + * The constructor. + * + * @param name of this machine. + * @param thread the running thread of this machine. It must either be the thread on which this + * constructor is called, or a thread that is not started yet. + * @param dbg whether to print debug logs. + */ + public SyncStateMachine(@NonNull final String name, @NonNull final Thread thread, + final boolean dbg) { + mMyThread = thread; + // Machine can either be setup from machine thread or before machine thread started. + ensureCorrectOrNotStartedThread(); + + mName = name; + mDbg = dbg; + } + + /** + * Add all of states to the state machine. Different StateInfos which have same state are not + * allowed. In other words, a state can not have multiple parent states. #addAllStates can + * only be called once either from mMyThread or before mMyThread started. + */ + public final void addAllStates(@NonNull final List stateInfos) { + ensureCorrectOrNotStartedThread(); + + if (mCurrentState != null) { + throw new IllegalStateException("State only can be added before started"); + } + + if (stateInfos.isEmpty()) throw new IllegalStateException("Empty state is not allowed"); + + if (!mStateInfo.isEmpty()) throw new IllegalStateException("States are already configured"); + + final Set usedClasses = new ArraySet<>(); + for (final StateInfo info : stateInfos) { + Objects.requireNonNull(info.state); + if (!usedClasses.add(info.state.getClass())) { + throw new IllegalStateException("Adding the same state multiple times in a state " + + "machine is forbidden because it tends to be confusing; it can be done " + + "with anonymous subclasses but consider carefully whether you want to " + + "use a single state or other alternatives instead."); + } + + mStateInfo.put(info.state, info); + } + + // Check whether all of parent states indicated from StateInfo are added. + for (final StateInfo info : stateInfos) { + if (info.parent != null) ensureExistingState(info.parent); + } + } + + /** + * Start the state machine. The initial state can't be child state. + * + * @param initialState the first state of this machine. The state must be exact state object + * setting up by {@link #addAllStates}, not a copy of it. + */ + public final void start(@NonNull final State initialState) { + ensureCorrectThread(); + ensureExistingState(initialState); + + mDestState = initialState; + mSelfMsgAllowed = true; + performTransitions(); + mSelfMsgAllowed = false; + // If sendSelfMessage was called inside initialState#enter(), mSelfMsgQueue must be + // processed. + maybeProcessSelfMessageQueue(); + } + + /** + * Process the message synchronously then perform state transition. This method is used + * externally to the automaton to request that the automaton process the given message. + * The message is processed sequentially, so calling this method recursively is not permitted. + * In other words, using this method inside State#enter, State#exit, or State#processMessage + * is incorrect and will result in an IllegalStateException. + */ + public final void processMessage(int what, int arg1, int arg2, @Nullable Object obj) { + ensureCorrectThread(); + + if (mCurrentlyProcessing != Integer.MIN_VALUE) { + throw new IllegalStateException("Message(" + mCurrentlyProcessing + + ") is still being processed"); + } + + // mCurrentlyProcessing tracks the external message request and it prevents this method to + // be called recursively. Once this message is processed and the transitions have been + // performed, the automaton will process the self message queue. The messages in the self + // message queue are added from within the automaton during processing external message. + // mCurrentlyProcessing is still the original external one and it will not prevent self + // messages from being processed. + mCurrentlyProcessing = what; + final Message msg = Message.obtain(null, what, arg1, arg2, obj); + currentStateProcessMessageThenPerformTransitions(msg); + msg.recycle(); + maybeProcessSelfMessageQueue(); + + mCurrentlyProcessing = Integer.MIN_VALUE; + } + + private void maybeProcessSelfMessageQueue() { + while (!mSelfMsgQueue.isEmpty()) { + currentStateProcessMessageThenPerformTransitions(mSelfMsgQueue.poll()); + } + } + + private void currentStateProcessMessageThenPerformTransitions(@NonNull final Message msg) { + mSelfMsgAllowed = true; + StateInfo consideredState = mStateInfo.get(mCurrentState); + while (null != consideredState) { + // Ideally this should compare with IState.HANDLED, but it is not public field so just + // checking whether the return value is true (IState.HANDLED = true). + if (consideredState.state.processMessage(msg)) { + if (mDbg) { + Log.d(mName, "State " + consideredState.state + + " processed message " + msg.what); + } + break; + } + consideredState = mStateInfo.get(consideredState.parent); + } + if (null == consideredState) { + Log.wtf(mName, "Message " + msg.what + " was not handled"); + } + + performTransitions(); + mSelfMsgAllowed = false; + } + + /** + * Send self message during state transition. + * + * Must only be used inside State processMessage, enter or exit. The typical use case is + * something wrong happens during state transition, sending an error message which would be + * handled after finishing current state transitions. + */ + public final void sendSelfMessage(int what, int arg1, int arg2, Object obj) { + if (!mSelfMsgAllowed) { + throw new IllegalStateException("sendSelfMessage can only be called inside " + + "State#enter, State#exit or State#processMessage"); + } + + mSelfMsgQueue.add(Message.obtain(null, what, arg1, arg2, obj)); + } + + /** + * Transition to destination state. Upon returning from processMessage the automaton will + * transition to the given destination state. + * + * This function can NOT be called inside the State enter and exit function. The transition + * target is always defined and can never be changed mid-way of state transition. + * + * @param destState will be the state to transition to. The state must be the same instance set + * up by {@link #addAllStates}, not a copy of it. + */ + public final void transitionTo(@NonNull final State destState) { + if (mDbg) Log.d(mName, "transitionTo " + destState); + ensureCorrectThread(); + ensureExistingState(destState); + + if (mDestState == mCurrentState) { + mDestState = destState; + } else { + throw new IllegalStateException("Destination already specified"); + } + } + + private void performTransitions() { + // 1. Determine the common ancestor state of current/destination states + // 2. Invoke state exit list from current state to common ancestor state. + // 3. Invoke state enter list from common ancestor state to destState by going + // through mEnterStateStack. + if (mDestState == mCurrentState) return; + + final StateInfo commonAncestor = getLastActiveAncestor(mStateInfo.get(mDestState)); + + executeExitMethods(commonAncestor, mStateInfo.get(mCurrentState)); + executeEnterMethods(commonAncestor, mStateInfo.get(mDestState)); + mCurrentState = mDestState; + } + + // Null is the root of all states. + private StateInfo getLastActiveAncestor(@Nullable final StateInfo start) { + if (null == start || start.mActive) return start; + + return getLastActiveAncestor(mStateInfo.get(start.parent)); + } + + // Call the exit method from current state to common ancestor state. + // Both the commonAncestor and exitingState StateInfo can be null because null is the ancestor + // of all states. + // For example: When transitioning from state1 to state2, the + // executeExitMethods(commonAncestor, exitingState) function will be called twice, once with + // null and state1 as the argument, and once with null and null as the argument. + // root + // | \ + // current <- state1 state2 -> destination + private void executeExitMethods(@Nullable StateInfo commonAncestor, + @Nullable StateInfo exitingState) { + if (commonAncestor == exitingState) return; + + if (mDbg) Log.d(mName, exitingState.state + " exit()"); + exitingState.state.exit(); + exitingState.mActive = false; + executeExitMethods(commonAncestor, mStateInfo.get(exitingState.parent)); + } + + // Call the enter method from common ancestor state to destination state. + // Both the commonAncestor and enteringState StateInfo can be null because null is the ancestor + // of all states. + // For example: When transitioning from state1 to state2, the + // executeEnterMethods(commonAncestor, enteringState) function will be called twice, once with + // null and state2 as the argument, and once with null and null as the argument. + // root + // | \ + // current <- state1 state2 -> destination + private void executeEnterMethods(@Nullable StateInfo commonAncestor, + @Nullable StateInfo enteringState) { + if (enteringState == commonAncestor) return; + + executeEnterMethods(commonAncestor, mStateInfo.get(enteringState.parent)); + if (mDbg) Log.d(mName, enteringState.state + " enter()"); + enteringState.state.enter(); + enteringState.mActive = true; + } + + private void ensureCorrectThread() { + if (!mMyThread.equals(Thread.currentThread())) { + throw new IllegalStateException("Called from wrong thread"); + } + } + + private void ensureCorrectOrNotStartedThread() { + if (!mMyThread.isAlive()) return; + + ensureCorrectThread(); + } + + private void ensureExistingState(@NonNull final State state) { + if (!mStateInfo.containsKey(state)) throw new IllegalStateException("Invalid state"); + } +} diff --git a/Tethering/src/com/android/networkstack/tethering/wear/CompanionDeviceManagerProxy.java b/Tethering/src/com/android/networkstack/tethering/wear/CompanionDeviceManagerProxy.java index e94febbcc7ad9932bc967b91b2ca790c812b2dc4..e845f3f849aa852fa334ac3ef017b6a2c1e22d5f 100644 --- a/Tethering/src/com/android/networkstack/tethering/wear/CompanionDeviceManagerProxy.java +++ b/Tethering/src/com/android/networkstack/tethering/wear/CompanionDeviceManagerProxy.java @@ -19,7 +19,7 @@ package com.android.networkstack.tethering.wear; import android.companion.AssociationInfo; import android.companion.CompanionDeviceManager; import android.content.Context; -import android.net.connectivity.TiramisuConnectivityInternalApiUtil; +import android.net.connectivity.ConnectivityInternalApiUtil; import android.net.wear.ICompanionDeviceManagerProxy; import android.os.Build; import android.os.RemoteException; @@ -39,7 +39,7 @@ public class CompanionDeviceManagerProxy { @RequiresApi(Build.VERSION_CODES.TIRAMISU) public CompanionDeviceManagerProxy(Context context) { mService = ICompanionDeviceManagerProxy.Stub.asInterface( - TiramisuConnectivityInternalApiUtil.getCompanionDeviceManagerProxyService(context)); + ConnectivityInternalApiUtil.getCompanionDeviceManagerProxyService(context)); } /** diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp index 5e08abac94e4c9dd1958bd68b0d91a07530ad09f..2594a5e13c5ee8932cf56c61936778c9e648ad42 100644 --- a/Tethering/tests/integration/Android.bp +++ b/Tethering/tests/integration/Android.bp @@ -25,11 +25,12 @@ java_defaults { ], min_sdk_version: "30", static_libs: [ - "NetworkStackApiStableLib", + "DhcpPacketLib", "androidx.test.rules", "cts-net-utils", - "mockito-target-extended-minus-junit4", + "mockito-target-minus-junit4", "net-tests-utils", + "net-utils-device-common", "net-utils-device-common-bpf", "testables", "connectivity-net-module-utils-bpf", @@ -39,11 +40,6 @@ java_defaults { "android.test.base", "android.test.mock", ], - jni_libs: [ - // For mockito extended - "libdexmakerjvmtiagent", - "libstaticjvmtiagent", - ], } android_library { @@ -53,6 +49,7 @@ android_library { defaults: ["TetheringIntegrationTestsDefaults"], visibility: [ "//packages/modules/Connectivity/Tethering/tests/mts", + "//packages/modules/Connectivity/tests/cts/net", ] } diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java index 007bf23a1c227d7f17bbc587efad59c6c8c1fc29..377da91ca4badc0b0d0aa4f02feadb344f7fe5f6 100644 --- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java +++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java @@ -25,23 +25,18 @@ import static android.net.InetAddresses.parseNumericAddress; import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL; import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL; import static android.net.TetheringManager.TETHERING_ETHERNET; +import static android.net.TetheringTester.buildTcpPacket; +import static android.net.TetheringTester.buildUdpPacket; +import static android.net.TetheringTester.isAddressIpv4; import static android.net.TetheringTester.isExpectedIcmpPacket; import static android.net.TetheringTester.isExpectedTcpPacket; import static android.net.TetheringTester.isExpectedUdpPacket; -import static android.system.OsConstants.IPPROTO_IP; -import static android.system.OsConstants.IPPROTO_IPV6; -import static android.system.OsConstants.IPPROTO_TCP; -import static android.system.OsConstants.IPPROTO_UDP; - import static com.android.net.module.util.HexDump.dumpHexString; -import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4; -import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK; import static com.android.net.module.util.NetworkStackConstants.TCPHDR_SYN; import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork; import static com.android.testutils.TestPermissionUtil.runAsShell; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -61,17 +56,17 @@ import android.net.TetheringManager.TetheringEventCallback; import android.net.TetheringManager.TetheringRequest; import android.net.TetheringTester.TetheredDevice; import android.net.cts.util.CtsNetUtils; +import android.net.cts.util.CtsTetheringUtils; +import android.net.cts.util.CtsTetheringUtils.TestTetheringEventCallback; import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; import android.util.Log; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.test.platform.app.InstrumentationRegistry; import com.android.modules.utils.build.SdkLevel; -import com.android.net.module.util.PacketBuilder; import com.android.net.module.util.Struct; import com.android.net.module.util.structs.Ipv6Header; import com.android.testutils.HandlerUtils; @@ -80,6 +75,7 @@ import com.android.testutils.TestNetworkTracker; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import java.io.FileDescriptor; import java.net.Inet4Address; @@ -124,29 +120,17 @@ public abstract class EthernetTetheringTestBase { (Inet4Address) parseNumericAddress("8.8.8.8"); protected static final Inet6Address REMOTE_IP6_ADDR = (Inet6Address) parseNumericAddress("2002:db8:1::515:ca"); + // The IPv6 network address translation of REMOTE_IP4_ADDR if pref64::/n is 64:ff9b::/96. + // For more information, see TetheringTester#PREF64_IPV4ONLY_ADDR, which assumes a prefix + // of 64:ff9b::/96. protected static final Inet6Address REMOTE_NAT64_ADDR = (Inet6Address) parseNumericAddress("64:ff9b::808:808"); - protected static final IpPrefix TEST_NAT64PREFIX = new IpPrefix("64:ff9b::/96"); - - // IPv4 header definition. - protected static final short ID = 27149; - protected static final short FLAGS_AND_FRAGMENT_OFFSET = (short) 0x4000; // flags=DF, offset=0 - protected static final byte TIME_TO_LIVE = (byte) 0x40; - protected static final byte TYPE_OF_SERVICE = 0; - - // IPv6 header definition. - private static final short HOP_LIMIT = 0x40; - // version=6, traffic class=0x0, flowlabel=0x0; - private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000; - // UDP and TCP header definition. // LOCAL_PORT is used by public port and private port. Assume port 9876 has not been used yet // before the testing that public port and private port are the same in the testing. Note that // NAT port forwarding could be different between private port and public port. protected static final short LOCAL_PORT = 9876; protected static final short REMOTE_PORT = 433; - private static final short WINDOW = (short) 0x2000; - private static final short URGENT_POINTER = 0; // Payload definition. protected static final ByteBuffer EMPTY_PAYLOAD = ByteBuffer.wrap(new byte[0]); @@ -162,8 +146,6 @@ public abstract class EthernetTetheringTestBase { private final TetheringManager mTm = mContext.getSystemService(TetheringManager.class); private final PackageManager mPackageManager = mContext.getPackageManager(); private final CtsNetUtils mCtsNetUtils = new CtsNetUtils(mContext); - private final UiAutomation mUiAutomation = - InstrumentationRegistry.getInstrumentation().getUiAutomation(); // Late initialization in setUp() private boolean mRunTests; @@ -178,19 +160,50 @@ public abstract class EthernetTetheringTestBase { private TapPacketReader mDownstreamReader; private MyTetheringEventCallback mTetheringEventCallback; + public Context getContext() { + return mContext; + } + + @BeforeClass + public static void setUpOnce() throws Exception { + // The first test case may experience tethering restart with IP conflict handling. + // Tethering would cache the last upstreams so that the next enabled tethering avoids + // picking up the address that is in conflict with the upstreams. To protect subsequent + // tests, turn tethering on and off before running them. + final Context ctx = InstrumentationRegistry.getInstrumentation().getContext(); + final CtsTetheringUtils utils = new CtsTetheringUtils(ctx); + final TestTetheringEventCallback callback = utils.registerTetheringEventCallback(); + try { + if (!callback.isWifiTetheringSupported(ctx)) return; + + callback.expectNoTetheringActive(); + + utils.startWifiTethering(callback); + callback.getCurrentValidUpstream(); + utils.stopWifiTethering(callback); + } finally { + utils.unregisterTetheringEventCallback(callback); + } + } + @Before public void setUp() throws Exception { mHandlerThread = new HandlerThread(getClass().getSimpleName()); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); - mRunTests = runAsShell(NETWORK_SETTINGS, TETHER_PRIVILEGED, () -> - mTm.isTetheringSupported()); + mRunTests = isEthernetTetheringSupported(); assumeTrue(mRunTests); mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm); } + private boolean isEthernetTetheringSupported() throws Exception { + if (mEm == null) return false; + + return runAsShell(NETWORK_SETTINGS, TETHER_PRIVILEGED, () -> mTm.isTetheringSupported()); + } + protected void maybeStopTapPacketReader(final TapPacketReader tapPacketReader) throws Exception { if (tapPacketReader != null) { @@ -261,7 +274,6 @@ public abstract class EthernetTetheringTestBase { } finally { mHandlerThread.quitSafely(); mHandlerThread.join(); - mUiAutomation.dropShellPermissionIdentity(); } } @@ -351,6 +363,11 @@ public abstract class EthernetTetheringTestBase { private volatile Collection mClients = null; private volatile Network mUpstream = null; + // The dnsmasq in R might block netd for 20 seconds, which can also block tethering + // enable/disable for 20 seconds. To fix this, changing the timeouts from 5 seconds to 30 + // seconds. See b/289881008. + private static final int EXPANDED_TIMEOUT_MS = 30000; + MyTetheringEventCallback(TetheringManager tm, String iface) { this(tm, iface, null); mAcceptAnyUpstream = true; @@ -409,13 +426,13 @@ public abstract class EthernetTetheringTestBase { } public void awaitInterfaceTethered() throws Exception { - assertTrue("Ethernet not tethered after " + TIMEOUT_MS + "ms", - mTetheringStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + assertTrue("Ethernet not tethered after " + EXPANDED_TIMEOUT_MS + "ms", + mTetheringStartedLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS)); } public void awaitInterfaceLocalOnly() throws Exception { - assertTrue("Ethernet not local-only after " + TIMEOUT_MS + "ms", - mLocalOnlyStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + assertTrue("Ethernet not local-only after " + EXPANDED_TIMEOUT_MS + "ms", + mLocalOnlyStartedLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS)); } // Used to check if the callback has registered. When the callback is registered, @@ -429,8 +446,9 @@ public abstract class EthernetTetheringTestBase { } public void awaitCallbackRegistered() throws Exception { - if (!mCallbackRegisteredLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) { - fail("Did not receive callback registered signal after " + TIMEOUT_MS + "ms"); + if (!mCallbackRegisteredLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { + fail("Did not receive callback registered signal after " + EXPANDED_TIMEOUT_MS + + "ms"); } } @@ -442,11 +460,11 @@ public abstract class EthernetTetheringTestBase { if (!mInterfaceWasTethered && !mInterfaceWasLocalOnly) return; if (mInterfaceWasTethered) { - assertTrue(mIface + " not untethered after " + TIMEOUT_MS + "ms", - mTetheringStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + assertTrue(mIface + " not untethered after " + EXPANDED_TIMEOUT_MS + "ms", + mTetheringStoppedLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS)); } else if (mInterfaceWasLocalOnly) { - assertTrue(mIface + " not untethered after " + TIMEOUT_MS + "ms", - mLocalOnlyStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + assertTrue(mIface + " not untethered after " + EXPANDED_TIMEOUT_MS + "ms", + mLocalOnlyStoppedLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS)); } else { fail(mIface + " cannot be both tethered and local-only. Update this test class."); } @@ -473,8 +491,9 @@ public abstract class EthernetTetheringTestBase { } public Collection awaitClientConnected() throws Exception { - assertTrue("Did not receive client connected callback after " + TIMEOUT_MS + "ms", - mClientConnectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + assertTrue("Did not receive client connected callback after " + + EXPANDED_TIMEOUT_MS + "ms", + mClientConnectedLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS)); return mClients; } @@ -491,10 +510,10 @@ public abstract class EthernetTetheringTestBase { } public Network awaitUpstreamChanged(boolean throwTimeoutException) throws Exception { - if (!mUpstreamLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) { + if (!mUpstreamLatch.await(EXPANDED_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { final String errorMessage = "Did not receive upstream " + (mAcceptAnyUpstream ? "any" : mExpectedUpstream) - + " callback after " + TIMEOUT_MS + "ms"; + + " callback after " + EXPANDED_TIMEOUT_MS + "ms"; if (throwTimeoutException) { throw new TimeoutException(errorMessage); @@ -649,77 +668,10 @@ public abstract class EthernetTetheringTestBase { final LinkProperties lp = new LinkProperties(); lp.setLinkAddresses(addresses); lp.setDnsServers(dnses); - lp.setNat64Prefix(TEST_NAT64PREFIX); return runAsShell(MANAGE_TEST_NETWORKS, () -> initTestNetwork(mContext, lp, TIMEOUT_MS)); } - private short getEthType(@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp) { - return isAddressIpv4(srcIp, dstIp) ? (short) ETHER_TYPE_IPV4 : (short) ETHER_TYPE_IPV6; - } - - private int getIpProto(@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp) { - return isAddressIpv4(srcIp, dstIp) ? IPPROTO_IP : IPPROTO_IPV6; - } - - @NonNull - protected ByteBuffer buildUdpPacket( - @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac, - @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp, - short srcPort, short dstPort, @Nullable final ByteBuffer payload) - throws Exception { - final int ipProto = getIpProto(srcIp, dstIp); - final boolean hasEther = (srcMac != null && dstMac != null); - final int payloadLen = (payload == null) ? 0 : payload.limit(); - final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_UDP, - payloadLen); - final PacketBuilder packetBuilder = new PacketBuilder(buffer); - - // [1] Ethernet header - if (hasEther) { - packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp)); - } - - // [2] IP header - if (ipProto == IPPROTO_IP) { - packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET, - TIME_TO_LIVE, (byte) IPPROTO_UDP, (Inet4Address) srcIp, (Inet4Address) dstIp); - } else { - packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_UDP, - HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp); - } - - // [3] UDP header - packetBuilder.writeUdpHeader(srcPort, dstPort); - - // [4] Payload - if (payload != null) { - buffer.put(payload); - // in case data might be reused by caller, restore the position and - // limit of bytebuffer. - payload.clear(); - } - - return packetBuilder.finalizePacket(); - } - - @NonNull - protected ByteBuffer buildUdpPacket(@NonNull final InetAddress srcIp, - @NonNull final InetAddress dstIp, short srcPort, short dstPort, - @Nullable final ByteBuffer payload) throws Exception { - return buildUdpPacket(null /* srcMac */, null /* dstMac */, srcIp, dstIp, srcPort, - dstPort, payload); - } - - private boolean isAddressIpv4(@NonNull final InetAddress srcIp, - @NonNull final InetAddress dstIp) { - if (srcIp instanceof Inet4Address && dstIp instanceof Inet4Address) return true; - if (srcIp instanceof Inet6Address && dstIp instanceof Inet6Address) return false; - - fail("Unsupported conditions: srcIp " + srcIp + ", dstIp " + dstIp); - return false; // unreachable - } - protected void sendDownloadPacketUdp(@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp, @NonNull final TetheringTester tester, boolean is6To4) throws Exception { @@ -761,45 +713,6 @@ public abstract class EthernetTetheringTestBase { }); } - - @NonNull - private ByteBuffer buildTcpPacket( - @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac, - @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp, - short srcPort, short dstPort, final short seq, final short ack, - final byte tcpFlags, @NonNull final ByteBuffer payload) throws Exception { - final int ipProto = getIpProto(srcIp, dstIp); - final boolean hasEther = (srcMac != null && dstMac != null); - final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_TCP, - payload.limit()); - final PacketBuilder packetBuilder = new PacketBuilder(buffer); - - // [1] Ethernet header - if (hasEther) { - packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp)); - } - - // [2] IP header - if (ipProto == IPPROTO_IP) { - packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET, - TIME_TO_LIVE, (byte) IPPROTO_TCP, (Inet4Address) srcIp, (Inet4Address) dstIp); - } else { - packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_TCP, - HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp); - } - - // [3] TCP header - packetBuilder.writeTcpHeader(srcPort, dstPort, seq, ack, tcpFlags, WINDOW, URGENT_POINTER); - - // [4] Payload - buffer.put(payload); - // in case data might be reused by caller, restore the position and - // limit of bytebuffer. - payload.clear(); - - return packetBuilder.finalizePacket(); - } - protected void sendDownloadPacketTcp(@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp, short seq, short ack, byte tcpFlags, @NonNull final ByteBuffer payload, @NonNull final TetheringTester tester, diff --git a/Tethering/tests/integration/base/android/net/TetheringTester.java b/Tethering/tests/integration/base/android/net/TetheringTester.java index ae39b246b65b5ea018772f9a3e7d9186af55ade6..ae4ae55098f6d4c4e88e7ceb111fd60a002b4239 100644 --- a/Tethering/tests/integration/base/android/net/TetheringTester.java +++ b/Tethering/tests/integration/base/android/net/TetheringTester.java @@ -16,17 +16,24 @@ package android.net; +import static android.net.DnsResolver.CLASS_IN; +import static android.net.DnsResolver.TYPE_AAAA; import static android.net.InetAddresses.parseNumericAddress; +import static android.system.OsConstants.ICMP_ECHO; +import static android.system.OsConstants.ICMP_ECHOREPLY; import static android.system.OsConstants.IPPROTO_ICMP; import static android.system.OsConstants.IPPROTO_ICMPV6; +import static android.system.OsConstants.IPPROTO_IP; +import static android.system.OsConstants.IPPROTO_IPV6; import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IPPROTO_UDP; - import static com.android.net.module.util.DnsPacket.ANSECTION; -import static com.android.net.module.util.DnsPacket.ARSECTION; -import static com.android.net.module.util.DnsPacket.NSSECTION; +import static com.android.net.module.util.DnsPacket.DnsHeader; +import static com.android.net.module.util.DnsPacket.DnsRecord; import static com.android.net.module.util.DnsPacket.QDSECTION; import static com.android.net.module.util.HexDump.dumpHexString; +import static com.android.net.module.util.IpUtils.icmpChecksum; +import static com.android.net.module.util.IpUtils.ipChecksum; import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY; import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST; import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN; @@ -38,11 +45,14 @@ import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; +import static com.android.net.module.util.NetworkStackConstants.ICMP_CHECKSUM_OFFSET; +import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET; +import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN; +import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET; import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST; import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE; import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED; import static com.android.net.module.util.NetworkStackConstants.TCPHDR_SYN; - import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; @@ -58,7 +68,9 @@ import androidx.annotation.Nullable; import com.android.net.module.util.DnsPacket; import com.android.net.module.util.Ipv6Utils; +import com.android.net.module.util.PacketBuilder; import com.android.net.module.util.Struct; +import com.android.net.module.util.arp.ArpPacket; import com.android.net.module.util.structs.EthernetHeader; import com.android.net.module.util.structs.Icmpv4Header; import com.android.net.module.util.structs.Icmpv6Header; @@ -70,7 +82,6 @@ import com.android.net.module.util.structs.PrefixInformationOption; import com.android.net.module.util.structs.RaHeader; import com.android.net.module.util.structs.TcpHeader; import com.android.net.module.util.structs.UdpHeader; -import com.android.networkstack.arp.ArpPacket; import com.android.testutils.TapPacketReader; import java.net.Inet4Address; @@ -101,6 +112,44 @@ public final class TetheringTester { DhcpPacket.DHCP_LEASE_TIME, }; private static final InetAddress LINK_LOCAL = parseNumericAddress("fe80::1"); + // IPv4 header definition. + protected static final short ID = 27149; + protected static final short FLAGS_AND_FRAGMENT_OFFSET = (short) 0x4000; // flags=DF, offset=0 + protected static final byte TIME_TO_LIVE = (byte) 0x40; + protected static final byte TYPE_OF_SERVICE = 0; + + // IPv6 header definition. + private static final short HOP_LIMIT = 0x40; + // version=6, traffic class=0x0, flowlabel=0x0; + private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000; + + // UDP and TCP header definition. + private static final short WINDOW = (short) 0x2000; + private static final short URGENT_POINTER = 0; + + // ICMP definition. + private static final short ICMPECHO_CODE = 0x0; + + // Prefix64 discovery definition. See RFC 7050 section 8. + // Note that the AAAA response Pref64::WKAs consisting of Pref64::/n and WKA. + // Use 64:ff9b::/96 as Pref64::/n and WKA 192.0.0.17{0|1} here. + // + // Host DNS64 server + // | | + // | "AAAA" query for "ipv4only.arpa." | + // |----------------------------------------------->| + // | | + // | "AAAA" response with: | + // | "64:ff9b::192.0.0.170" | + // |<-----------------------------------------------| + // + private static final String PREF64_IPV4ONLY_HOSTNAME = "ipv4only.arpa"; + private static final InetAddress PREF64_IPV4ONLY_ADDR = parseNumericAddress( + "64:ff9b::192.0.0.170"); + + // DNS header definition. + private static final short FLAG = (short) 0x8100; // qr, ra + private static final short TTL = (short) 0; public static final String DHCP_HOSTNAME = "testhostname"; @@ -462,6 +511,11 @@ public final class TetheringTester { super(data); } + TestDnsPacket(@NonNull DnsHeader header, @Nullable ArrayList qd, + @Nullable ArrayList an) { + super(header, qd, an); + } + @Nullable public static TestDnsPacket getTestDnsPacket(final ByteBuffer buf) { try { @@ -628,7 +682,191 @@ public final class TetheringTester { return false; } - private void sendUploadPacket(ByteBuffer packet) throws Exception { + @NonNull + public static ByteBuffer buildUdpPacket( + @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac, + @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp, + short srcPort, short dstPort, @Nullable final ByteBuffer payload) + throws Exception { + final int ipProto = getIpProto(srcIp, dstIp); + final boolean hasEther = (srcMac != null && dstMac != null); + final int payloadLen = (payload == null) ? 0 : payload.limit(); + final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_UDP, + payloadLen); + final PacketBuilder packetBuilder = new PacketBuilder(buffer); + + // [1] Ethernet header + if (hasEther) { + packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp)); + } + + // [2] IP header + if (ipProto == IPPROTO_IP) { + packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET, + TIME_TO_LIVE, (byte) IPPROTO_UDP, (Inet4Address) srcIp, (Inet4Address) dstIp); + } else { + packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_UDP, + HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp); + } + + // [3] UDP header + packetBuilder.writeUdpHeader(srcPort, dstPort); + + // [4] Payload + if (payload != null) { + buffer.put(payload); + // in case data might be reused by caller, restore the position and + // limit of bytebuffer. + payload.clear(); + } + + return packetBuilder.finalizePacket(); + } + + @NonNull + public static ByteBuffer buildUdpPacket(@NonNull final InetAddress srcIp, + @NonNull final InetAddress dstIp, short srcPort, short dstPort, + @Nullable final ByteBuffer payload) throws Exception { + return buildUdpPacket(null /* srcMac */, null /* dstMac */, srcIp, dstIp, srcPort, + dstPort, payload); + } + + @NonNull + public static ByteBuffer buildTcpPacket( + @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac, + @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp, + short srcPort, short dstPort, final short seq, final short ack, + final byte tcpFlags, @NonNull final ByteBuffer payload) throws Exception { + final int ipProto = getIpProto(srcIp, dstIp); + final boolean hasEther = (srcMac != null && dstMac != null); + final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_TCP, + payload.limit()); + final PacketBuilder packetBuilder = new PacketBuilder(buffer); + + // [1] Ethernet header + if (hasEther) { + packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp)); + } + + // [2] IP header + if (ipProto == IPPROTO_IP) { + packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET, + TIME_TO_LIVE, (byte) IPPROTO_TCP, (Inet4Address) srcIp, (Inet4Address) dstIp); + } else { + packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_TCP, + HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp); + } + + // [3] TCP header + packetBuilder.writeTcpHeader(srcPort, dstPort, seq, ack, tcpFlags, WINDOW, URGENT_POINTER); + + // [4] Payload + buffer.put(payload); + // in case data might be reused by caller, restore the position and + // limit of bytebuffer. + payload.clear(); + + return packetBuilder.finalizePacket(); + } + + // PacketBuilder doesn't support IPv4 ICMP packet. It may need to refactor PacketBuilder first + // because ICMP is a specific layer 3 protocol for PacketBuilder which expects packets always + // have layer 3 (IP) and layer 4 (TCP, UDP) for now. Since we don't use IPv4 ICMP packet too + // much in this test, we just write a ICMP packet builder here. + @NonNull + public static ByteBuffer buildIcmpEchoPacketV4( + @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac, + @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp, + int type, short id, short seq) throws Exception { + if (type != ICMP_ECHO && type != ICMP_ECHOREPLY) { + fail("Unsupported ICMP type: " + type); + } + + // Build ICMP echo id and seq fields as payload. Ignore the data field. + final ByteBuffer payload = ByteBuffer.allocate(4); + payload.putShort(id); + payload.putShort(seq); + payload.rewind(); + + final boolean hasEther = (srcMac != null && dstMac != null); + final int etherHeaderLen = hasEther ? Struct.getSize(EthernetHeader.class) : 0; + final int ipv4HeaderLen = Struct.getSize(Ipv4Header.class); + final int Icmpv4HeaderLen = Struct.getSize(Icmpv4Header.class); + final int payloadLen = payload.limit(); + final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv4HeaderLen + + Icmpv4HeaderLen + payloadLen); + + // [1] Ethernet header + if (hasEther) { + final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, ETHER_TYPE_IPV4); + ethHeader.writeToByteBuffer(packet); + } + + // [2] IP header + final Ipv4Header ipv4Header = new Ipv4Header(TYPE_OF_SERVICE, + (short) 0 /* totalLength, calculate later */, ID, + FLAGS_AND_FRAGMENT_OFFSET, TIME_TO_LIVE, (byte) IPPROTO_ICMP, + (short) 0 /* checksum, calculate later */, srcIp, dstIp); + ipv4Header.writeToByteBuffer(packet); + + // [3] ICMP header + final Icmpv4Header icmpv4Header = new Icmpv4Header((byte) type, ICMPECHO_CODE, + (short) 0 /* checksum, calculate later */); + icmpv4Header.writeToByteBuffer(packet); + + // [4] Payload + packet.put(payload); + packet.flip(); + + // [5] Finalize packet + // Used for updating IP header fields. If there is Ehternet header, IPv4 header offset + // in buffer equals ethernet header length because IPv4 header is located next to ethernet + // header. Otherwise, IPv4 header offset is 0. + final int ipv4HeaderOffset = hasEther ? etherHeaderLen : 0; + + // Populate the IPv4 totalLength field. + packet.putShort(ipv4HeaderOffset + IPV4_LENGTH_OFFSET, + (short) (ipv4HeaderLen + Icmpv4HeaderLen + payloadLen)); + + // Populate the IPv4 header checksum field. + packet.putShort(ipv4HeaderOffset + IPV4_CHECKSUM_OFFSET, + ipChecksum(packet, ipv4HeaderOffset /* headerOffset */)); + + // Populate the ICMP checksum field. + packet.putShort(ipv4HeaderOffset + IPV4_HEADER_MIN_LEN + ICMP_CHECKSUM_OFFSET, + icmpChecksum(packet, ipv4HeaderOffset + IPV4_HEADER_MIN_LEN, + Icmpv4HeaderLen + payloadLen)); + return packet; + } + + @NonNull + public static ByteBuffer buildIcmpEchoPacketV4(@NonNull final Inet4Address srcIp, + @NonNull final Inet4Address dstIp, int type, short id, short seq) + throws Exception { + return buildIcmpEchoPacketV4(null /* srcMac */, null /* dstMac */, srcIp, dstIp, + type, id, seq); + } + + private static short getEthType(@NonNull final InetAddress srcIp, + @NonNull final InetAddress dstIp) { + return isAddressIpv4(srcIp, dstIp) ? (short) ETHER_TYPE_IPV4 : (short) ETHER_TYPE_IPV6; + } + + private static int getIpProto(@NonNull final InetAddress srcIp, + @NonNull final InetAddress dstIp) { + return isAddressIpv4(srcIp, dstIp) ? IPPROTO_IP : IPPROTO_IPV6; + } + + public static boolean isAddressIpv4(@NonNull final InetAddress srcIp, + @NonNull final InetAddress dstIp) { + if (srcIp instanceof Inet4Address && dstIp instanceof Inet4Address) return true; + if (srcIp instanceof Inet6Address && dstIp instanceof Inet6Address) return false; + + fail("Unsupported conditions: srcIp " + srcIp + ", dstIp " + dstIp); + return false; // unreachable + } + + public void sendUploadPacket(ByteBuffer packet) throws Exception { mDownstreamReader.sendResponse(packet); } @@ -650,10 +888,85 @@ public final class TetheringTester { return null; } + @NonNull + private ByteBuffer buildUdpDnsPrefix64ReplyPacket(int dnsId, @NonNull final Inet6Address srcIp, + @NonNull final Inet6Address dstIp, short srcPort, short dstPort) throws Exception { + // [1] Build prefix64 DNS message. + final ArrayList qlist = new ArrayList<>(); + // Fill QD section. + qlist.add(DnsRecord.makeQuestion(PREF64_IPV4ONLY_HOSTNAME, TYPE_AAAA, CLASS_IN)); + final ArrayList alist = new ArrayList<>(); + // Fill AN sections. + alist.add(DnsRecord.makeAOrAAAARecord(ANSECTION, PREF64_IPV4ONLY_HOSTNAME, CLASS_IN, TTL, + PREF64_IPV4ONLY_ADDR)); + final TestDnsPacket dns = new TestDnsPacket( + new DnsHeader(dnsId, FLAG, qlist.size(), alist.size()), qlist, alist); + + // [2] Build IPv6 UDP DNS packet. + return buildUdpPacket(srcIp, dstIp, srcPort, dstPort, ByteBuffer.wrap(dns.getBytes())); + } + + private void maybeReplyUdpDnsPrefix64Discovery(@NonNull byte[] packet) { + final ByteBuffer buf = ByteBuffer.wrap(packet); + + // [1] Parse the prefix64 discovery DNS query for hostname ipv4only.arpa. + // Parse IPv6 and UDP header. + Ipv6Header ipv6Header = null; + try { + ipv6Header = Struct.parse(Ipv6Header.class, buf); + if (ipv6Header == null || ipv6Header.nextHeader != IPPROTO_UDP) return; + } catch (Exception e) { + // Parsing packet fail means it is not IPv6 UDP packet. + return; + } + final UdpHeader udpHeader = Struct.parse(UdpHeader.class, buf); + + // Parse DNS message. + final TestDnsPacket pref64Query = TestDnsPacket.getTestDnsPacket(buf); + if (pref64Query == null) return; + if (pref64Query.getHeader().isResponse()) return; + if (pref64Query.getQDCount() != 1) return; + if (pref64Query.getANCount() != 0) return; + if (pref64Query.getNSCount() != 0) return; + if (pref64Query.getARCount() != 0) return; + + final List qdRecordList = pref64Query.getRecordList(QDSECTION); + if (qdRecordList.size() != 1) return; + if (!qdRecordList.get(0).dName.equals(PREF64_IPV4ONLY_HOSTNAME)) return; + + // [2] Build prefix64 DNS discovery reply from received query. + // DNS response transaction id must be copied from DNS query. Used by the requester + // to match up replies to outstanding queries. See RFC 1035 section 4.1.1. Also reverse + // the source/destination address/port of query packet for building reply packet. + final ByteBuffer replyPacket; + try { + replyPacket = buildUdpDnsPrefix64ReplyPacket(pref64Query.getHeader().getId(), + ipv6Header.dstIp /* srcIp */, ipv6Header.srcIp /* dstIp */, + (short) udpHeader.dstPort /* srcPort */, + (short) udpHeader.srcPort /* dstPort */); + } catch (Exception e) { + fail("Failed to build prefix64 discovery reply for " + ipv6Header.srcIp + ": " + e); + return; + } + + Log.d(TAG, "Sending prefix64 discovery reply"); + try { + sendDownloadPacket(replyPacket); + } catch (Exception e) { + fail("Failed to reply prefix64 discovery for " + ipv6Header.srcIp + ": " + e); + } + } + private byte[] getUploadPacket(Predicate filter) { assertNotNull("Can't deal with upstream interface in local only mode", mUpstreamReader); - return mUpstreamReader.poll(PACKET_READ_TIMEOUT_MS, filter); + byte[] packet; + while ((packet = mUpstreamReader.poll(PACKET_READ_TIMEOUT_MS)) != null) { + if (filter.test(packet)) return packet; + + maybeReplyUdpDnsPrefix64Discovery(packet); + } + return null; } private @NonNull byte[] verifyPacketNotNull(String message, @Nullable byte[] packet) { @@ -680,4 +993,12 @@ public final class TetheringTester { return verifyPacketNotNull("Download fail", getDownloadPacket(filter)); } + + // Send DHCPDISCOVER to DHCP server to see if DHCP server is still alive to handle + // the upcoming DHCP packets. This method should be only used when we know the DHCP + // server has been created successfully before. + public boolean testDhcpServerAlive(final MacAddress mac) throws Exception { + sendDhcpDiscover(mac.toByteArray()); + return getNextDhcpPacket() != null; + } } diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java index 55854e273f3cb5e47a40e030910a40b13ea8e7b8..4949eaa9e2ae317f6f75f747ba723e14c1d92144 100644 --- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java +++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java @@ -20,23 +20,17 @@ import static android.net.InetAddresses.parseNumericAddress; import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL; import static android.net.TetheringManager.TETHERING_ETHERNET; import static android.net.TetheringTester.TestDnsPacket; +import static android.net.TetheringTester.buildIcmpEchoPacketV4; +import static android.net.TetheringTester.buildUdpPacket; import static android.net.TetheringTester.isExpectedIcmpPacket; import static android.net.TetheringTester.isExpectedUdpDnsPacket; import static android.system.OsConstants.ICMP_ECHO; import static android.system.OsConstants.ICMP_ECHOREPLY; -import static android.system.OsConstants.IPPROTO_ICMP; import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA; import static com.android.net.module.util.HexDump.dumpHexString; -import static com.android.net.module.util.IpUtils.icmpChecksum; -import static com.android.net.module.util.IpUtils.ipChecksum; -import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; -import static com.android.net.module.util.NetworkStackConstants.ICMP_CHECKSUM_OFFSET; -import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET; -import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN; -import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -53,18 +47,16 @@ import android.os.SystemProperties; import android.util.Log; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.test.filters.MediumTest; +import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; import com.android.net.module.util.Ipv6Utils; import com.android.net.module.util.Struct; -import com.android.net.module.util.structs.EthernetHeader; -import com.android.net.module.util.structs.Icmpv4Header; import com.android.net.module.util.structs.Ipv4Header; import com.android.net.module.util.structs.UdpHeader; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; +import com.android.testutils.NetworkStackModuleTest; import com.android.testutils.TapPacketReader; import org.junit.Rule; @@ -87,7 +79,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @RunWith(AndroidJUnit4.class) -@MediumTest +@LargeTest public class EthernetTetheringTest extends EthernetTetheringTestBase { @Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); @@ -95,7 +87,6 @@ public class EthernetTetheringTest extends EthernetTetheringTestBase { private static final String TAG = EthernetTetheringTest.class.getSimpleName(); private static final short DNS_PORT = 53; - private static final short ICMPECHO_CODE = 0x0; private static final short ICMPECHO_ID = 0x0; private static final short ICMPECHO_SEQ = 0x0; @@ -335,6 +326,14 @@ public class EthernetTetheringTest extends EthernetTetheringTestBase { waitForRouterAdvertisement(downstreamReader, iface, WAIT_RA_TIMEOUT_MS); expectLocalOnlyAddresses(iface); + + // After testing the IPv6 local address, the DHCP server may still be in the process + // of being created. If the downstream interface is killed by the test while the + // DHCP server is starting, a DHCP server error may occur. To ensure that the DHCP + // server has started completely before finishing the test, also test the dhcp server + // by calling runDhcp. + final TetheringTester tester = new TetheringTester(downstreamReader); + tester.runDhcp(MacAddress.fromString("1:2:3:4:5:6").toByteArray()); } finally { maybeStopTapPacketReader(downstreamReader); maybeCloseTestInterface(downstreamIface); @@ -563,85 +562,6 @@ public class EthernetTetheringTest extends EthernetTetheringTestBase { runClatUdpTest(); } - // PacketBuilder doesn't support IPv4 ICMP packet. It may need to refactor PacketBuilder first - // because ICMP is a specific layer 3 protocol for PacketBuilder which expects packets always - // have layer 3 (IP) and layer 4 (TCP, UDP) for now. Since we don't use IPv4 ICMP packet too - // much in this test, we just write a ICMP packet builder here. - // TODO: move ICMPv4 packet build function to common utilis. - @NonNull - private ByteBuffer buildIcmpEchoPacketV4( - @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac, - @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp, - int type, short id, short seq) throws Exception { - if (type != ICMP_ECHO && type != ICMP_ECHOREPLY) { - fail("Unsupported ICMP type: " + type); - } - - // Build ICMP echo id and seq fields as payload. Ignore the data field. - final ByteBuffer payload = ByteBuffer.allocate(4); - payload.putShort(id); - payload.putShort(seq); - payload.rewind(); - - final boolean hasEther = (srcMac != null && dstMac != null); - final int etherHeaderLen = hasEther ? Struct.getSize(EthernetHeader.class) : 0; - final int ipv4HeaderLen = Struct.getSize(Ipv4Header.class); - final int Icmpv4HeaderLen = Struct.getSize(Icmpv4Header.class); - final int payloadLen = payload.limit(); - final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv4HeaderLen - + Icmpv4HeaderLen + payloadLen); - - // [1] Ethernet header - if (hasEther) { - final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, ETHER_TYPE_IPV4); - ethHeader.writeToByteBuffer(packet); - } - - // [2] IP header - final Ipv4Header ipv4Header = new Ipv4Header(TYPE_OF_SERVICE, - (short) 0 /* totalLength, calculate later */, ID, - FLAGS_AND_FRAGMENT_OFFSET, TIME_TO_LIVE, (byte) IPPROTO_ICMP, - (short) 0 /* checksum, calculate later */, srcIp, dstIp); - ipv4Header.writeToByteBuffer(packet); - - // [3] ICMP header - final Icmpv4Header icmpv4Header = new Icmpv4Header((byte) type, ICMPECHO_CODE, - (short) 0 /* checksum, calculate later */); - icmpv4Header.writeToByteBuffer(packet); - - // [4] Payload - packet.put(payload); - packet.flip(); - - // [5] Finalize packet - // Used for updating IP header fields. If there is Ehternet header, IPv4 header offset - // in buffer equals ethernet header length because IPv4 header is located next to ethernet - // header. Otherwise, IPv4 header offset is 0. - final int ipv4HeaderOffset = hasEther ? etherHeaderLen : 0; - - // Populate the IPv4 totalLength field. - packet.putShort(ipv4HeaderOffset + IPV4_LENGTH_OFFSET, - (short) (ipv4HeaderLen + Icmpv4HeaderLen + payloadLen)); - - // Populate the IPv4 header checksum field. - packet.putShort(ipv4HeaderOffset + IPV4_CHECKSUM_OFFSET, - ipChecksum(packet, ipv4HeaderOffset /* headerOffset */)); - - // Populate the ICMP checksum field. - packet.putShort(ipv4HeaderOffset + IPV4_HEADER_MIN_LEN + ICMP_CHECKSUM_OFFSET, - icmpChecksum(packet, ipv4HeaderOffset + IPV4_HEADER_MIN_LEN, - Icmpv4HeaderLen + payloadLen)); - return packet; - } - - @NonNull - private ByteBuffer buildIcmpEchoPacketV4(@NonNull final Inet4Address srcIp, - @NonNull final Inet4Address dstIp, int type, short id, short seq) - throws Exception { - return buildIcmpEchoPacketV4(null /* srcMac */, null /* dstMac */, srcIp, dstIp, - type, id, seq); - } - @Test public void testIcmpv4Echo() throws Exception { final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR), @@ -839,4 +759,43 @@ public class EthernetTetheringTest extends EthernetTetheringTestBase { REMOTE_NAT64_ADDR /* downloadSrcIp */, clatIp6 /* downloadDstIp */, tester, true /* isClat */); } + + private static final byte[] ZeroLengthDhcpPacket = new byte[] { + // scapy.Ether( + // dst="ff:ff:ff:ff:ff:ff") + // scapy.IP( + // dst="255.255.255.255") + // scapy.UDP(sport=68, dport=67) + /* Ethernet Header */ + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xe0, (byte) 0x4f, (byte) 0x43, (byte) 0xe6, (byte) 0xfb, (byte) 0xd2, + (byte) 0x08, (byte) 0x00, + /* Ip header */ + (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x1c, (byte) 0x00, (byte) 0x01, + (byte) 0x00, (byte) 0x00, (byte) 0x40, (byte) 0x11, (byte) 0xb6, (byte) 0x58, + (byte) 0x64, (byte) 0x4f, (byte) 0x60, (byte) 0x29, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, + /* UDP header */ + (byte) 0x00, (byte) 0x44, (byte) 0x00, (byte) 0x43, + (byte) 0x00, (byte) 0x08, (byte) 0x3a, (byte) 0xdf + }; + + // This test requires the update in NetworkStackModule(See b/269692093). + @NetworkStackModuleTest + @Test + public void testTetherZeroLengthDhcpPacket() throws Exception { + final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR), + toList(TEST_IP4_DNS)); + tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */); + + // Send a zero-length DHCP packet to upstream DHCP server. + final ByteBuffer packet = ByteBuffer.wrap(ZeroLengthDhcpPacket); + tester.sendUploadPacket(packet); + + // Send DHCPDISCOVER packet from another downstream tethered device to verify that + // upstream DHCP server doesn't close the listening socket and stop reading, then we + // can still receive the next DHCP packet from server. + final MacAddress macAddress = MacAddress.fromString("11:22:33:44:55:66"); + assertTrue(tester.testDhcpServerAlive(macAddress)); + } } diff --git a/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java b/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java index 328e3fb61c3530f3f22cf25b484bda7995215474..dac5b63fe254a759e2f2b6c6ca70aaf2620bd494 100644 --- a/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java +++ b/Tethering/tests/privileged/src/android/net/ip/RouterAdvertisementDaemonTest.java @@ -16,8 +16,6 @@ package android.net.ip; -import static android.net.RouteInfo.RTN_UNICAST; - import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN; import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU; @@ -42,12 +40,13 @@ import android.content.Context; import android.net.INetd; import android.net.IpPrefix; import android.net.MacAddress; -import android.net.RouteInfo; import android.net.ip.RouterAdvertisementDaemon.RaParams; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceSpecificException; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -55,7 +54,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.Ipv6Utils; -import com.android.net.module.util.NetdUtils; import com.android.net.module.util.Struct; import com.android.net.module.util.structs.EthernetHeader; import com.android.net.module.util.structs.Icmpv6Header; @@ -80,7 +78,6 @@ import java.net.Inet6Address; import java.net.InetAddress; import java.nio.ByteBuffer; import java.util.HashSet; -import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest @@ -332,10 +329,12 @@ public final class RouterAdvertisementDaemonTest { // Add a default route "fe80::/64 -> ::" to local network, otherwise, device will fail to // send the unicast RA out due to the ENETUNREACH error(No route to the peer's link-local // address is present). - final String iface = mTetheredParams.name; - final RouteInfo linkLocalRoute = - new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RTN_UNICAST); - NetdUtils.addRoutesToLocalNetwork(sNetd, iface, List.of(linkLocalRoute)); + try { + sNetd.networkAddRoute(INetd.LOCAL_NET_ID, mTetheredParams.name, + "fe80::/64", INetd.NEXTHOP_NONE); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); + } final ByteBuffer rs = createRsPacket("fe80::1122:3344:5566:7788"); mTetheredPacketReader.sendResponse(rs); diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java index b3fb3e45f67371db366c8fd2cda9767c56ca8007..60f2d17b09b495c457c5186e79e54c604fa1bfa9 100644 --- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java +++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java @@ -44,6 +44,7 @@ import com.android.net.module.util.netlink.NetlinkMessage; import com.android.net.module.util.netlink.NetlinkUtils; import com.android.net.module.util.netlink.StructNlMsgHdr; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -84,6 +85,14 @@ public class ConntrackSocketTest { mOffloadHw = new OffloadHardwareInterface(mHandler, mLog, mDeps); } + @After + public void tearDown() throws Exception { + if (mHandlerThread != null) { + mHandlerThread.quitSafely(); + mHandlerThread.join(); + } + } + void findConnectionOrThrow(FileDescriptor fd, InetSocketAddress local, InetSocketAddress remote) throws Exception { Log.d(TAG, "Looking for socket " + local + " -> " + remote); @@ -106,6 +115,7 @@ public class ConntrackSocketTest { ConntrackMessage.Tuple tuple = ctmsg.tupleOrig; if (nlmsghdr.nlmsg_type == (NFNL_SUBSYS_CTNETLINK << 8 | IPCTNL_MSG_CT_NEW) + && tuple != null && tuple.protoNum == IPPROTO_TCP && tuple.srcIp.equals(local.getAddress()) && tuple.dstIp.equals(remote.getAddress()) diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index 46e50ef835713c2e2ade4365b23b931d2c878098..a7064e8fed076371dc042fbf00346da5f854b86d 100644 --- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -34,15 +34,10 @@ import static android.net.ip.IpServer.STATE_AVAILABLE; import static android.net.ip.IpServer.STATE_LOCAL_ONLY; import static android.net.ip.IpServer.STATE_TETHERED; import static android.net.ip.IpServer.STATE_UNAVAILABLE; -import static android.system.OsConstants.ETH_P_IPV6; +import static android.net.ip.IpServer.getTetherableIpv6Prefixes; import static com.android.modules.utils.build.SdkLevel.isAtLeastT; import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH; -import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH; -import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH; -import static com.android.net.module.util.netlink.StructNdMsg.NUD_FAILED; -import static com.android.net.module.util.netlink.StructNdMsg.NUD_REACHABLE; -import static com.android.net.module.util.netlink.StructNdMsg.NUD_STALE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -56,15 +51,14 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -80,8 +74,7 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MacAddress; import android.net.RouteInfo; -import android.net.TetherOffloadRuleParcel; -import android.net.TetherStatsParcel; +import android.net.RoutingCoordinatorManager; import android.net.dhcp.DhcpServerCallbacks; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpEventCallbacks; @@ -94,35 +87,16 @@ import android.os.RemoteException; import android.os.test.TestLooper; import android.text.TextUtils; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.net.module.util.BpfMap; +import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.InterfaceParams; -import com.android.net.module.util.NetworkStackConstants; +import com.android.net.module.util.SdkUtil.LateSdk; import com.android.net.module.util.SharedLog; -import com.android.net.module.util.Struct.S32; -import com.android.net.module.util.bpf.Tether4Key; -import com.android.net.module.util.bpf.Tether4Value; -import com.android.net.module.util.bpf.TetherStatsKey; -import com.android.net.module.util.bpf.TetherStatsValue; -import com.android.net.module.util.ip.ConntrackMonitor; import com.android.net.module.util.ip.IpNeighborMonitor; -import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEvent; -import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEventConsumer; import com.android.networkstack.tethering.BpfCoordinator; -import com.android.networkstack.tethering.BpfCoordinator.ClientInfo; -import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; import com.android.networkstack.tethering.PrivateAddressCoordinator; -import com.android.networkstack.tethering.Tether6Value; -import com.android.networkstack.tethering.TetherDevKey; -import com.android.networkstack.tethering.TetherDevValue; -import com.android.networkstack.tethering.TetherDownstream6Key; -import com.android.networkstack.tethering.TetherLimitKey; -import com.android.networkstack.tethering.TetherLimitValue; -import com.android.networkstack.tethering.TetherUpstream6Key; import com.android.networkstack.tethering.TetheringConfiguration; import com.android.networkstack.tethering.metrics.TetheringMetrics; import com.android.networkstack.tethering.util.InterfaceSet; @@ -136,17 +110,15 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatcher; import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.net.Inet4Address; -import java.net.Inet6Address; import java.net.InetAddress; -import java.util.Arrays; import java.util.List; +import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest @@ -158,6 +130,7 @@ public class IpServerTest { private static final String UPSTREAM_IFACE = "upstream0"; private static final String UPSTREAM_IFACE2 = "upstream1"; private static final String IPSEC_IFACE = "ipsec0"; + private static final int NO_UPSTREAM = 0; private static final int UPSTREAM_IFINDEX = 101; private static final int UPSTREAM_IFINDEX2 = 102; private static final int IPSEC_IFINDEX = 103; @@ -183,6 +156,18 @@ public class IpServerTest { private final LinkAddress mTestAddress = new LinkAddress("192.168.42.5/24"); private final IpPrefix mBluetoothPrefix = new IpPrefix("192.168.44.0/24"); + private static final Set NO_ADDRESSES = Set.of(); + private static final Set NO_PREFIXES = Set.of(); + private static final Set UPSTREAM_ADDRESSES = + Set.of(new LinkAddress("2001:db8:0:1234::168/64")); + private static final Set UPSTREAM_PREFIXES = + Set.of(new IpPrefix("2001:db8:0:1234::/64")); + private static final Set UPSTREAM_ADDRESSES2 = Set.of( + new LinkAddress("2001:db8:0:1234::168/64"), + new LinkAddress("2001:db8:0:abcd::168/64")); + private static final Set UPSTREAM_PREFIXES2 = Set.of( + new IpPrefix("2001:db8:0:1234::/64"), new IpPrefix("2001:db8:0:abcd::/64")); + @Mock private INetd mNetd; @Mock private IpServer.Callback mCallback; @Mock private SharedLog mSharedLog; @@ -192,29 +177,21 @@ public class IpServerTest { @Mock private IpNeighborMonitor mIpNeighborMonitor; @Mock private IpServer.Dependencies mDependencies; @Mock private PrivateAddressCoordinator mAddressCoordinator; + private final LateSdk mRoutingCoordinatorManager = + new LateSdk<>(SdkLevel.isAtLeastS() ? mock(RoutingCoordinatorManager.class) : null); @Mock private NetworkStatsManager mStatsManager; @Mock private TetheringConfiguration mTetherConfig; - @Mock private ConntrackMonitor mConntrackMonitor; @Mock private TetheringMetrics mTetheringMetrics; - @Mock private BpfMap mBpfDownstream4Map; - @Mock private BpfMap mBpfUpstream4Map; - @Mock private BpfMap mBpfDownstream6Map; - @Mock private BpfMap mBpfUpstream6Map; - @Mock private BpfMap mBpfStatsMap; - @Mock private BpfMap mBpfLimitMap; - @Mock private BpfMap mBpfDevMap; - @Mock private BpfMap mBpfErrorMap; + @Mock private BpfCoordinator mBpfCoordinator; @Captor private ArgumentCaptor mDhcpParamsCaptor; - private final TestLooper mLooper = new TestLooper(); + private TestLooper mLooper; + private Handler mHandler; private final ArgumentCaptor mLinkPropertiesCaptor = ArgumentCaptor.forClass(LinkProperties.class); private IpServer mIpServer; private InterfaceConfigurationParcel mInterfaceConfiguration; - private NeighborEventConsumer mNeighborEventConsumer; - private BpfCoordinator mBpfCoordinator; - private BpfCoordinator.Dependencies mBpfDeps; private void initStateMachine(int interfaceType) throws Exception { initStateMachine(interfaceType, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD); @@ -236,44 +213,47 @@ public class IpServerTest { mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH; } - ArgumentCaptor neighborCaptor = - ArgumentCaptor.forClass(NeighborEventConsumer.class); - doReturn(mIpNeighborMonitor).when(mDependencies).getIpNeighborMonitor(any(), any(), - neighborCaptor.capture()); + doReturn(mIpNeighborMonitor).when(mDependencies).getIpNeighborMonitor(any(), any(), any()); when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(usingBpfOffload); when(mTetherConfig.useLegacyDhcpServer()).thenReturn(usingLegacyDhcp); when(mTetherConfig.getP2pLeasesSubnetPrefixLength()).thenReturn(P2P_SUBNET_PREFIX_LENGTH); - mIpServer = new IpServer( - IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator, - mCallback, mTetherConfig, mAddressCoordinator, mTetheringMetrics, mDependencies); + when(mBpfCoordinator.isUsingBpfOffload()).thenReturn(usingBpfOffload); + mIpServer = createIpServer(interfaceType); + verify(mIpNeighborMonitor).start(); mIpServer.start(); - mNeighborEventConsumer = neighborCaptor.getValue(); // Starting the state machine always puts us in a consistent state and notifies // the rest of the world that we've changed from an unknown to available state. mLooper.dispatchAll(); - reset(mNetd, mCallback); + reset(mNetd, mCallback, mIpNeighborMonitor); when(mRaDaemon.start()).thenReturn(true); } private void initTetheredStateMachine(int interfaceType, String upstreamIface) throws Exception { - initTetheredStateMachine(interfaceType, upstreamIface, false, + initTetheredStateMachine(interfaceType, upstreamIface, NO_ADDRESSES, false, DEFAULT_USING_BPF_OFFLOAD); } private void initTetheredStateMachine(int interfaceType, String upstreamIface, - boolean usingLegacyDhcp, boolean usingBpfOffload) throws Exception { + Set upstreamAddresses, boolean usingLegacyDhcp, boolean usingBpfOffload) + throws Exception { initStateMachine(interfaceType, usingLegacyDhcp, usingBpfOffload); dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); if (upstreamIface != null) { + InterfaceParams interfaceParams = mDependencies.getInterfaceParams(upstreamIface); + assertNotNull("missing upstream interface: " + upstreamIface, interfaceParams); LinkProperties lp = new LinkProperties(); lp.setInterfaceName(upstreamIface); + lp.setLinkAddresses(upstreamAddresses); dispatchTetherConnectionChanged(upstreamIface, lp, 0); + Set upstreamPrefixes = getTetherableIpv6Prefixes(lp.getLinkAddresses()); + verify(mBpfCoordinator).updateAllIpv6Rules( + mIpServer, TEST_IFACE_PARAMS, interfaceParams.index, upstreamPrefixes); } - reset(mNetd, mCallback, mAddressCoordinator, mBpfCoordinator); + reset(mNetd, mBpfCoordinator, mCallback, mAddressCoordinator); when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(), anyBoolean())).thenReturn(mTestAddress); } @@ -301,90 +281,42 @@ public class IpServerTest { when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(DEFAULT_USING_BPF_OFFLOAD); when(mTetherConfig.useLegacyDhcpServer()).thenReturn(false /* default value */); - mBpfDeps = new BpfCoordinator.Dependencies() { - @NonNull - public Handler getHandler() { - return new Handler(mLooper.getLooper()); - } - - @NonNull - public INetd getNetd() { - return mNetd; - } - - @NonNull - public NetworkStatsManager getNetworkStatsManager() { - return mStatsManager; - } - - @NonNull - public SharedLog getSharedLog() { - return mSharedLog; - } - - @Nullable - public TetheringConfiguration getTetherConfig() { - return mTetherConfig; - } - - @NonNull - public ConntrackMonitor getConntrackMonitor( - ConntrackMonitor.ConntrackEventConsumer consumer) { - return mConntrackMonitor; - } - - @Nullable - public BpfMap getBpfDownstream4Map() { - return mBpfDownstream4Map; - } - - @Nullable - public BpfMap getBpfUpstream4Map() { - return mBpfUpstream4Map; - } - - @Nullable - public BpfMap getBpfDownstream6Map() { - return mBpfDownstream6Map; - } - - @Nullable - public BpfMap getBpfUpstream6Map() { - return mBpfUpstream6Map; - } - - @Nullable - public BpfMap getBpfStatsMap() { - return mBpfStatsMap; - } - - @Nullable - public BpfMap getBpfLimitMap() { - return mBpfLimitMap; - } - - @Nullable - public BpfMap getBpfDevMap() { - return mBpfDevMap; - } - - @Nullable - public BpfMap getBpfErrorMap() { - return mBpfErrorMap; - } - }; - mBpfCoordinator = spy(new BpfCoordinator(mBpfDeps)); + // Simulate the behavior of RoutingCoordinator + if (null != mRoutingCoordinatorManager.value) { + doAnswer(it -> { + final String fromIface = (String) it.getArguments()[0]; + final String toIface = (String) it.getArguments()[1]; + mNetd.tetherAddForward(fromIface, toIface); + mNetd.ipfwdAddInterfaceForward(fromIface, toIface); + return null; + }).when(mRoutingCoordinatorManager.value).addInterfaceForward(any(), any()); + doAnswer(it -> { + final String fromIface = (String) it.getArguments()[0]; + final String toIface = (String) it.getArguments()[1]; + mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface); + mNetd.tetherRemoveForward(fromIface, toIface); + return null; + }).when(mRoutingCoordinatorManager.value).removeInterfaceForward(any(), any()); + } setUpDhcpServer(); } + // In order to interact with syncSM from the test, IpServer must be created in test thread. + private IpServer createIpServer(final int interfaceType) { + mLooper = new TestLooper(); + mHandler = new Handler(mLooper.getLooper()); + return new IpServer(IFACE_NAME, mHandler, interfaceType, mSharedLog, mNetd, mBpfCoordinator, + mRoutingCoordinatorManager, mCallback, mTetherConfig, mAddressCoordinator, + mTetheringMetrics, mDependencies); + + } + @Test - public void startsOutAvailable() { + public void startsOutAvailable() throws Exception { when(mDependencies.getIpNeighborMonitor(any(), any(), any())) .thenReturn(mIpNeighborMonitor); - mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog, - mNetd, mBpfCoordinator, mCallback, mTetherConfig, mAddressCoordinator, - mTetheringMetrics, mDependencies); + mIpServer = createIpServer(TETHERING_BLUETOOTH); mIpServer.start(); mLooper.dispatchAll(); verify(mCallback).updateInterfaceState( @@ -529,7 +461,7 @@ public class IpServerTest { InOrder inOrder = inOrder(mNetd, mBpfCoordinator); // Add the forwarding pair . - inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, + inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE); inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE); @@ -551,7 +483,7 @@ public class IpServerTest { inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE); // Add the forwarding pair . - inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX2, + inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX2, UPSTREAM_IFACE2); inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); @@ -576,7 +508,7 @@ public class IpServerTest { // Add the forwarding pair and expect that failed on // tetherAddForward. - inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX2, + inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX2, UPSTREAM_IFACE2); inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); @@ -604,7 +536,7 @@ public class IpServerTest { // Add the forwarding pair and expect that failed on // ipfwdAddInterfaceForward. - inOrder.verify(mBpfCoordinator).addUpstreamNameToLookupTable(UPSTREAM_IFINDEX2, + inOrder.verify(mBpfCoordinator).maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX2, UPSTREAM_IFACE2); inOrder.verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, UPSTREAM_IFACE2); inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2); @@ -625,7 +557,12 @@ public class IpServerTest { inOrder.verify(mBpfCoordinator).maybeDetachProgram(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE); inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE); - inOrder.verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer); + inOrder.verify(mBpfCoordinator).updateAllIpv6Rules( + mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES); + // When tethering stops, upstream interface is set to zero and thus clearing all upstream + // rules. Downstream rules are needed to be cleared explicitly by calling + // BpfCoordinator#clearAllIpv6Rules in TetheredState#exit. + inOrder.verify(mBpfCoordinator).clearAllIpv6Rules(mIpServer); inOrder.verify(mNetd).tetherApplyDnsInterfaces(); inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME); inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME); @@ -811,458 +748,89 @@ public class IpServerTest { @Test public void doesNotStartDhcpServerIfDisabled() throws Exception { - initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */, - DEFAULT_USING_BPF_OFFLOAD); + initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, NO_ADDRESSES, + true /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD); dispatchTetherConnectionChanged(UPSTREAM_IFACE); verify(mDependencies, never()).makeDhcpServer(any(), any(), any()); } - private InetAddress addr(String addr) throws Exception { - return InetAddresses.parseNumericAddress(addr); - } - - private void recvNewNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) { - mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_NEWNEIGH, ifindex, addr, - nudState, mac)); - mLooper.dispatchAll(); - } - - private void recvDelNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) { - mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_DELNEIGH, ifindex, addr, - nudState, mac)); - mLooper.dispatchAll(); - } - - /** - * Custom ArgumentMatcher for TetherOffloadRuleParcel. This is needed because generated stable - * AIDL classes don't have equals(), so we cannot just use eq(). A custom assert, such as: - * - * private void checkFooCalled(StableParcelable p, ...) { - * ArgumentCaptor captor = ArgumentCaptor.forClass(FooParam.class); - * verify(mMock).foo(captor.capture()); - * Foo foo = captor.getValue(); - * assertFooMatchesExpectations(foo); - * } - * - * almost works, but not quite. This is because if the code under test calls foo() twice, the - * first call to checkFooCalled() matches both the calls, putting both calls into the captor, - * and then fails with TooManyActualInvocations. It also makes it harder to use other mockito - * features such as never(), inOrder(), etc. - * - * This approach isn't great because if the match fails, the error message is unhelpful - * (actual: "android.net.TetherOffloadRuleParcel@8c827b0" or some such), but at least it does - * work. - * - * TODO: consider making the error message more readable by adding a method that catching the - * AssertionFailedError and throwing a new assertion with more details. See - * NetworkMonitorTest#verifyNetworkTested. - * - * See ConnectivityServiceTest#assertRoutesAdded for an alternative approach which solves the - * TooManyActualInvocations problem described above by forcing the caller of the custom assert - * method to specify all expected invocations in one call. This is useful when the stable - * parcelable class being asserted on has a corresponding Java object (eg., RouteInfo and - * RouteInfoParcelable), and the caller can just pass in a list of them. It not useful here - * because there is no such object. - */ - private static class TetherOffloadRuleParcelMatcher implements - ArgumentMatcher { - public final int upstreamIfindex; - public final InetAddress dst; - public final MacAddress dstMac; - - TetherOffloadRuleParcelMatcher(int upstreamIfindex, InetAddress dst, MacAddress dstMac) { - this.upstreamIfindex = upstreamIfindex; - this.dst = dst; - this.dstMac = dstMac; - } - - public boolean matches(TetherOffloadRuleParcel parcel) { - return upstreamIfindex == parcel.inputInterfaceIndex - && (TEST_IFACE_PARAMS.index == parcel.outputInterfaceIndex) - && Arrays.equals(dst.getAddress(), parcel.destination) - && (128 == parcel.prefixLength) - && Arrays.equals(TEST_IFACE_PARAMS.macAddr.toByteArray(), parcel.srcL2Address) - && Arrays.equals(dstMac.toByteArray(), parcel.dstL2Address); - } - - public String toString() { - return String.format("TetherOffloadRuleParcelMatcher(%d, %s, %s", - upstreamIfindex, dst.getHostAddress(), dstMac); - } - } - - @NonNull - private static TetherOffloadRuleParcel matches( - int upstreamIfindex, InetAddress dst, MacAddress dstMac) { - return argThat(new TetherOffloadRuleParcelMatcher(upstreamIfindex, dst, dstMac)); - } - - @NonNull - private static Ipv6ForwardingRule makeForwardingRule( - int upstreamIfindex, @NonNull InetAddress dst, @NonNull MacAddress dstMac) { - return new Ipv6ForwardingRule(upstreamIfindex, TEST_IFACE_PARAMS.index, - (Inet6Address) dst, TEST_IFACE_PARAMS.macAddr, dstMac); - } - - @NonNull - private static TetherDownstream6Key makeDownstream6Key(int upstreamIfindex, - @NonNull MacAddress upstreamMac, @NonNull final InetAddress dst) { - return new TetherDownstream6Key(upstreamIfindex, upstreamMac, dst.getAddress()); - } - - @NonNull - private static Tether6Value makeDownstream6Value(@NonNull final MacAddress dstMac) { - return new Tether6Value(TEST_IFACE_PARAMS.index, dstMac, - TEST_IFACE_PARAMS.macAddr, ETH_P_IPV6, NetworkStackConstants.ETHER_MTU); - } - - private T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) { - if (inOrder != null) { - return inOrder.verify(t); - } else { - return verify(t); - } - } - - private void verifyTetherOffloadRuleAdd(@Nullable InOrder inOrder, int upstreamIfindex, - @NonNull MacAddress upstreamMac, @NonNull final InetAddress dst, - @NonNull final MacAddress dstMac) throws Exception { - if (mBpfDeps.isAtLeastS()) { - verifyWithOrder(inOrder, mBpfDownstream6Map).updateEntry( - makeDownstream6Key(upstreamIfindex, upstreamMac, dst), - makeDownstream6Value(dstMac)); - } else { - verifyWithOrder(inOrder, mNetd).tetherOffloadRuleAdd(matches(upstreamIfindex, dst, - dstMac)); - } - } - - private void verifyNeverTetherOffloadRuleAdd(int upstreamIfindex, - @NonNull MacAddress upstreamMac, @NonNull final InetAddress dst, - @NonNull final MacAddress dstMac) throws Exception { - if (mBpfDeps.isAtLeastS()) { - verify(mBpfDownstream6Map, never()).updateEntry( - makeDownstream6Key(upstreamIfindex, upstreamMac, dst), - makeDownstream6Value(dstMac)); - } else { - verify(mNetd, never()).tetherOffloadRuleAdd(matches(upstreamIfindex, dst, dstMac)); - } - } - - private void verifyNeverTetherOffloadRuleAdd() throws Exception { - if (mBpfDeps.isAtLeastS()) { - verify(mBpfDownstream6Map, never()).updateEntry(any(), any()); - } else { - verify(mNetd, never()).tetherOffloadRuleAdd(any()); - } - } - - private void verifyTetherOffloadRuleRemove(@Nullable InOrder inOrder, int upstreamIfindex, - @NonNull MacAddress upstreamMac, @NonNull final InetAddress dst, - @NonNull final MacAddress dstMac) throws Exception { - if (mBpfDeps.isAtLeastS()) { - verifyWithOrder(inOrder, mBpfDownstream6Map).deleteEntry(makeDownstream6Key( - upstreamIfindex, upstreamMac, dst)); - } else { - // |dstMac| is not required for deleting rules. Used bacause tetherOffloadRuleRemove - // uses a whole rule to be a argument. - // See system/netd/server/TetherController.cpp/TetherController#removeOffloadRule. - verifyWithOrder(inOrder, mNetd).tetherOffloadRuleRemove(matches(upstreamIfindex, dst, - dstMac)); - } - } - - private void verifyNeverTetherOffloadRuleRemove() throws Exception { - if (mBpfDeps.isAtLeastS()) { - verify(mBpfDownstream6Map, never()).deleteEntry(any()); - } else { - verify(mNetd, never()).tetherOffloadRuleRemove(any()); - } - } - - private void verifyStartUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int upstreamIfindex) - throws Exception { - if (!mBpfDeps.isAtLeastS()) return; - final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index, - TEST_IFACE_PARAMS.macAddr); - final Tether6Value value = new Tether6Value(upstreamIfindex, - MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS, - ETH_P_IPV6, NetworkStackConstants.ETHER_MTU); - verifyWithOrder(inOrder, mBpfUpstream6Map).insertEntry(key, value); - } - - private void verifyStopUpstreamIpv6Forwarding(@Nullable InOrder inOrder) - throws Exception { - if (!mBpfDeps.isAtLeastS()) return; - final TetherUpstream6Key key = new TetherUpstream6Key(TEST_IFACE_PARAMS.index, - TEST_IFACE_PARAMS.macAddr); - verifyWithOrder(inOrder, mBpfUpstream6Map).deleteEntry(key); - } - - private void verifyNoUpstreamIpv6ForwardingChange(@Nullable InOrder inOrder) throws Exception { - if (!mBpfDeps.isAtLeastS()) return; - if (inOrder != null) { - inOrder.verify(mBpfUpstream6Map, never()).deleteEntry(any()); - inOrder.verify(mBpfUpstream6Map, never()).insertEntry(any(), any()); - inOrder.verify(mBpfUpstream6Map, never()).updateEntry(any(), any()); - } else { - verify(mBpfUpstream6Map, never()).deleteEntry(any()); - verify(mBpfUpstream6Map, never()).insertEntry(any(), any()); - verify(mBpfUpstream6Map, never()).updateEntry(any(), any()); - } - } - - @NonNull - private static TetherStatsParcel buildEmptyTetherStatsParcel(int ifIndex) { - TetherStatsParcel parcel = new TetherStatsParcel(); - parcel.ifIndex = ifIndex; - return parcel; - } - - private void resetNetdBpfMapAndCoordinator() throws Exception { - reset(mNetd, mBpfDownstream6Map, mBpfUpstream6Map, mBpfCoordinator); - // When the last rule is removed, tetherOffloadGetAndClearStats will log a WTF (and - // potentially crash the test) if the stats map is empty. - when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]); - when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX)) - .thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX)); - when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX2)) - .thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX2)); - // When the last rule is removed, tetherOffloadGetAndClearStats will log a WTF (and - // potentially crash the test) if the stats map is empty. - final TetherStatsValue allZeros = new TetherStatsValue(0, 0, 0, 0, 0, 0); - when(mBpfStatsMap.getValue(new TetherStatsKey(UPSTREAM_IFINDEX))).thenReturn(allZeros); - when(mBpfStatsMap.getValue(new TetherStatsKey(UPSTREAM_IFINDEX2))).thenReturn(allZeros); - } - @Test - public void addRemoveipv6ForwardingRules() throws Exception { - initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */, - DEFAULT_USING_BPF_OFFLOAD); + public void ipv6UpstreamInterfaceChanges() throws Exception { + initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, UPSTREAM_ADDRESSES, + false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD); - final int myIfindex = TEST_IFACE_PARAMS.index; - final int notMyIfindex = myIfindex - 1; - - final InetAddress neighA = InetAddresses.parseNumericAddress("2001:db8::1"); - final InetAddress neighB = InetAddresses.parseNumericAddress("2001:db8::2"); - final InetAddress neighLL = InetAddresses.parseNumericAddress("fe80::1"); - final InetAddress neighMC = InetAddresses.parseNumericAddress("ff02::1234"); - final MacAddress macNull = MacAddress.fromString("00:00:00:00:00:00"); - final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a"); - final MacAddress macB = MacAddress.fromString("11:22:33:00:00:0b"); - - resetNetdBpfMapAndCoordinator(); - verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map); - - // TODO: Perhaps verify the interaction of tetherOffloadSetInterfaceQuota and - // tetherOffloadGetAndClearStats in netd while the rules are changed. - - // Events on other interfaces are ignored. - recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map); - - // Events on this interface are received and sent to netd. - recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); - verify(mBpfCoordinator).tetherOffloadRuleAdd( - mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA)); - verifyTetherOffloadRuleAdd(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); - verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX); - resetNetdBpfMapAndCoordinator(); - - recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); - verify(mBpfCoordinator).tetherOffloadRuleAdd( - mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB)); - verifyTetherOffloadRuleAdd(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); - verifyNoUpstreamIpv6ForwardingChange(null); - resetNetdBpfMapAndCoordinator(); - - // Link-local and multicast neighbors are ignored. - recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map); - recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map); - - // A neighbor that is no longer valid causes the rule to be removed. - // NUD_FAILED events do not have a MAC address. - recvNewNeigh(myIfindex, neighA, NUD_FAILED, null); - verify(mBpfCoordinator).tetherOffloadRuleRemove( - mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macNull)); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macNull); - verifyNoUpstreamIpv6ForwardingChange(null); - resetNetdBpfMapAndCoordinator(); - - // A neighbor that is deleted causes the rule to be removed. - recvDelNeigh(myIfindex, neighB, NUD_STALE, macB); - verify(mBpfCoordinator).tetherOffloadRuleRemove( - mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macNull)); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macNull); - verifyStopUpstreamIpv6Forwarding(null); - resetNetdBpfMapAndCoordinator(); - - // Upstream changes result in updating the rules. - recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); - verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX); - recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); - resetNetdBpfMapAndCoordinator(); - - InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); + // Upstream interface changes result in updating the rules. LinkProperties lp = new LinkProperties(); lp.setInterfaceName(UPSTREAM_IFACE2); + lp.setLinkAddresses(UPSTREAM_ADDRESSES); dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp, -1); - verify(mBpfCoordinator).tetherOffloadRuleUpdate(mIpServer, UPSTREAM_IFINDEX2); - verifyTetherOffloadRuleRemove(inOrder, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); - verifyTetherOffloadRuleRemove(inOrder, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); - verifyStopUpstreamIpv6Forwarding(inOrder); - verifyTetherOffloadRuleAdd(inOrder, - UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA); - verifyStartUpstreamIpv6Forwarding(inOrder, UPSTREAM_IFINDEX2); - verifyTetherOffloadRuleAdd(inOrder, - UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighB, macB); - verifyNoUpstreamIpv6ForwardingChange(inOrder); - resetNetdBpfMapAndCoordinator(); + verify(mBpfCoordinator).updateAllIpv6Rules( + mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES); + reset(mBpfCoordinator); + + // Upstream link addresses change result in updating the rules. + LinkProperties lp2 = new LinkProperties(); + lp2.setInterfaceName(UPSTREAM_IFACE2); + lp2.setLinkAddresses(UPSTREAM_ADDRESSES2); + dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp2, -1); + verify(mBpfCoordinator).updateAllIpv6Rules( + mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2); + reset(mBpfCoordinator); // When the upstream is lost, rules are removed. dispatchTetherConnectionChanged(null, null, 0); - // Clear function is called two times by: + // Upstream clear function is called two times by: // - processMessage CMD_TETHER_CONNECTION_CHANGED for the upstream is lost. // - processMessage CMD_IPV6_TETHER_UPDATE for the IPv6 upstream is lost. // See dispatchTetherConnectionChanged. - verify(mBpfCoordinator, times(2)).tetherOffloadRuleClear(mIpServer); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighA, macA); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX2, UPSTREAM_IFACE_PARAMS2.macAddr, neighB, macB); - verifyStopUpstreamIpv6Forwarding(inOrder); - resetNetdBpfMapAndCoordinator(); + verify(mBpfCoordinator, times(2)).updateAllIpv6Rules( + mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES); + reset(mBpfCoordinator); // If the upstream is IPv4-only, no rules are added. dispatchTetherConnectionChanged(UPSTREAM_IFACE); - resetNetdBpfMapAndCoordinator(); - recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); - // Clear function is called by #updateIpv6ForwardingRules for the IPv6 upstream is lost. - verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer); - verifyNoUpstreamIpv6ForwardingChange(null); - verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfDownstream6Map, mBpfUpstream6Map); - - // Rules can be added again once upstream IPv6 connectivity is available. + verify(mBpfCoordinator, never()).updateAllIpv6Rules( + mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES); + reset(mBpfCoordinator); + + // Rules are added again once upstream IPv6 connectivity is available. lp.setInterfaceName(UPSTREAM_IFACE); dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1); - recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); - verify(mBpfCoordinator).tetherOffloadRuleAdd( - mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB)); - verifyTetherOffloadRuleAdd(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); - verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX); - verify(mBpfCoordinator, never()).tetherOffloadRuleAdd( - mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA)); - verifyNeverTetherOffloadRuleAdd( - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); + verify(mBpfCoordinator).updateAllIpv6Rules( + mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES); + reset(mBpfCoordinator); // If upstream IPv6 connectivity is lost, rules are removed. - resetNetdBpfMapAndCoordinator(); dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0); - verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); - verifyStopUpstreamIpv6Forwarding(null); + verify(mBpfCoordinator).updateAllIpv6Rules( + mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES); + reset(mBpfCoordinator); - // When the interface goes down, rules are removed. + // When upstream IPv6 connectivity comes back, rules are added. lp.setInterfaceName(UPSTREAM_IFACE); dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp, -1); - recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); - recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); - verify(mBpfCoordinator).tetherOffloadRuleAdd( - mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA)); - verifyTetherOffloadRuleAdd(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); - verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX); - verify(mBpfCoordinator).tetherOffloadRuleAdd( - mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB)); - verifyTetherOffloadRuleAdd(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); - resetNetdBpfMapAndCoordinator(); + verify(mBpfCoordinator).updateAllIpv6Rules( + mIpServer, TEST_IFACE_PARAMS, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES); + reset(mBpfCoordinator); + // When the downstream interface goes down, rules are removed. mIpServer.stop(); mLooper.dispatchAll(); - verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighA, macA); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neighB, macB); - verifyStopUpstreamIpv6Forwarding(null); - verify(mIpNeighborMonitor).stop(); - resetNetdBpfMapAndCoordinator(); - } - - @Test - public void enableDisableUsingBpfOffload() throws Exception { - final int myIfindex = TEST_IFACE_PARAMS.index; - final InetAddress neigh = InetAddresses.parseNumericAddress("2001:db8::1"); - final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a"); - final MacAddress macNull = MacAddress.fromString("00:00:00:00:00:00"); - - // Expect that rules can be only added/removed when the BPF offload config is enabled. - // Note that the BPF offload disabled case is not a realistic test case. Because IP - // neighbor monitor doesn't start if BPF offload is disabled, there should have no - // neighbor event listening. This is used for testing the protection check just in case. - // TODO: Perhaps remove the BPF offload disabled case test once this check isn't needed - // anymore. - - // [1] Enable BPF offload. - // A neighbor that is added or deleted causes the rule to be added or removed. - initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */, - true /* usingBpfOffload */); - resetNetdBpfMapAndCoordinator(); - - recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA); - verify(mBpfCoordinator).tetherOffloadRuleAdd( - mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macA)); - verifyTetherOffloadRuleAdd(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neigh, macA); - verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX); - resetNetdBpfMapAndCoordinator(); - - recvDelNeigh(myIfindex, neigh, NUD_STALE, macA); - verify(mBpfCoordinator).tetherOffloadRuleRemove( - mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macNull)); - verifyTetherOffloadRuleRemove(null, - UPSTREAM_IFINDEX, UPSTREAM_IFACE_PARAMS.macAddr, neigh, macNull); - verifyStopUpstreamIpv6Forwarding(null); - resetNetdBpfMapAndCoordinator(); - - // [2] Disable BPF offload. - // A neighbor that is added or deleted doesn’t cause the rule to be added or removed. - initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */, - false /* usingBpfOffload */); - resetNetdBpfMapAndCoordinator(); - - recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA); - verify(mBpfCoordinator, never()).tetherOffloadRuleAdd(any(), any()); - verifyNeverTetherOffloadRuleAdd(); - verifyNoUpstreamIpv6ForwardingChange(null); - resetNetdBpfMapAndCoordinator(); - - recvDelNeigh(myIfindex, neigh, NUD_STALE, macA); - verify(mBpfCoordinator, never()).tetherOffloadRuleRemove(any(), any()); - verifyNeverTetherOffloadRuleRemove(); - verifyNoUpstreamIpv6ForwardingChange(null); - resetNetdBpfMapAndCoordinator(); + verify(mBpfCoordinator).clearAllIpv6Rules(mIpServer); + verify(mBpfCoordinator).updateAllIpv6Rules( + mIpServer, TEST_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES); + reset(mBpfCoordinator); } @Test - public void doesNotStartIpNeighborMonitorIfBpfOffloadDisabled() throws Exception { - initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */, - false /* usingBpfOffload */); + public void stopNeighborMonitoringWhenInterfaceDown() throws Exception { + initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, UPSTREAM_ADDRESSES, + false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD); - // IP neighbor monitor doesn't start if BPF offload is disabled. - verify(mIpNeighborMonitor, never()).start(); + mIpServer.stop(); + mLooper.dispatchAll(); + verify(mIpNeighborMonitor).stop(); } private LinkProperties buildIpv6OnlyLinkProperties(final String iface) { @@ -1518,75 +1086,4 @@ public class IpServerTest { public void testDadProxyUpdates_EnabledAfterR() throws Exception { checkDadProxyEnabled(true); } - - @Test - public void testSkipVirtualNetworkInBpf() throws Exception { - initTetheredStateMachine(TETHERING_BLUETOOTH, null); - final LinkProperties v6Only = new LinkProperties(); - v6Only.setInterfaceName(IPSEC_IFACE); - dispatchTetherConnectionChanged(IPSEC_IFACE, v6Only, 0); - - verify(mBpfCoordinator).maybeAttachProgram(IFACE_NAME, IPSEC_IFACE); - verify(mNetd).tetherAddForward(IFACE_NAME, IPSEC_IFACE); - verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, IPSEC_IFACE); - - final int myIfindex = TEST_IFACE_PARAMS.index; - final InetAddress neigh = InetAddresses.parseNumericAddress("2001:db8::1"); - final MacAddress mac = MacAddress.fromString("00:00:00:00:00:0a"); - recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, mac); - verify(mBpfCoordinator, never()).tetherOffloadRuleAdd( - mIpServer, makeForwardingRule(IPSEC_IFINDEX, neigh, mac)); - } - - // TODO: move to BpfCoordinatorTest once IpNeighborMonitor is migrated to BpfCoordinator. - @Test - public void addRemoveTetherClient() throws Exception { - initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */, - DEFAULT_USING_BPF_OFFLOAD); - - final int myIfindex = TEST_IFACE_PARAMS.index; - final int notMyIfindex = myIfindex - 1; - - final InetAddress neighA = InetAddresses.parseNumericAddress("192.168.80.1"); - final InetAddress neighB = InetAddresses.parseNumericAddress("192.168.80.2"); - final InetAddress neighLL = InetAddresses.parseNumericAddress("169.254.0.1"); - final InetAddress neighMC = InetAddresses.parseNumericAddress("224.0.0.1"); - final MacAddress macNull = MacAddress.fromString("00:00:00:00:00:00"); - final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a"); - final MacAddress macB = MacAddress.fromString("11:22:33:00:00:0b"); - - // Events on other interfaces are ignored. - recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mBpfCoordinator); - - // Events on this interface are received and sent to BpfCoordinator. - recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA); - verify(mBpfCoordinator).tetherOffloadClientAdd(mIpServer, new ClientInfo(myIfindex, - TEST_IFACE_PARAMS.macAddr, (Inet4Address) neighA, macA)); - clearInvocations(mBpfCoordinator); - - recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB); - verify(mBpfCoordinator).tetherOffloadClientAdd(mIpServer, new ClientInfo(myIfindex, - TEST_IFACE_PARAMS.macAddr, (Inet4Address) neighB, macB)); - clearInvocations(mBpfCoordinator); - - // Link-local and multicast neighbors are ignored. - recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mBpfCoordinator); - recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, macA); - verifyNoMoreInteractions(mBpfCoordinator); - clearInvocations(mBpfCoordinator); - - // A neighbor that is no longer valid causes the client to be removed. - // NUD_FAILED events do not have a MAC address. - recvNewNeigh(myIfindex, neighA, NUD_FAILED, null); - verify(mBpfCoordinator).tetherOffloadClientRemove(mIpServer, new ClientInfo(myIfindex, - TEST_IFACE_PARAMS.macAddr, (Inet4Address) neighA, macNull)); - clearInvocations(mBpfCoordinator); - - // A neighbor that is deleted causes the client to be removed. - recvDelNeigh(myIfindex, neighB, NUD_STALE, macB); - verify(mBpfCoordinator).tetherOffloadClientRemove(mIpServer, new ClientInfo(myIfindex, - TEST_IFACE_PARAMS.macAddr, (Inet4Address) neighB, macNull)); - } } diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java index 4f32f3c216a1758174f5f825f401cbeae3966e17..47ecf585a8b35cd147bcd3ec61f43988465791ed 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java @@ -25,6 +25,8 @@ import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkStats.UID_TETHERING; +import static android.net.TetheringManager.TETHERING_WIFI; +import static android.net.ip.IpServer.STATE_TETHERED; import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; import static android.system.OsConstants.ETH_P_IP; import static android.system.OsConstants.ETH_P_IPV6; @@ -43,6 +45,11 @@ import static com.android.net.module.util.netlink.ConntrackMessage.TupleIpv4; import static com.android.net.module.util.netlink.ConntrackMessage.TupleProto; import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE; import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH; +import static com.android.net.module.util.netlink.StructNdMsg.NUD_FAILED; +import static com.android.net.module.util.netlink.StructNdMsg.NUD_REACHABLE; +import static com.android.net.module.util.netlink.StructNdMsg.NUD_STALE; import static com.android.networkstack.tethering.BpfCoordinator.CONNTRACK_TIMEOUT_UPDATE_INTERVAL_MS; import static com.android.networkstack.tethering.BpfCoordinator.INVALID_MTU; import static com.android.networkstack.tethering.BpfCoordinator.NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED; @@ -55,7 +62,9 @@ import static com.android.networkstack.tethering.BpfCoordinator.toIpv4MappedAddr import static com.android.networkstack.tethering.BpfUtils.DOWNSTREAM; import static com.android.networkstack.tethering.BpfUtils.UPSTREAM; import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; +import static com.android.testutils.MiscAsserts.assertSameElements; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -70,10 +79,15 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.usage.NetworkStatsManager; @@ -86,12 +100,16 @@ import android.net.MacAddress; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkStats; +import android.net.RoutingCoordinatorManager; import android.net.TetherOffloadRuleParcel; import android.net.TetherStatsParcel; import android.net.ip.IpServer; +import android.net.ip.RouterAdvertisementDaemon; import android.os.Build; import android.os.Handler; import android.os.test.TestLooper; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.SparseArray; import androidx.annotation.NonNull; @@ -101,10 +119,12 @@ import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.util.IndentingPrintWriter; +import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.IBpfMap; import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.NetworkStackConstants; +import com.android.net.module.util.SdkUtil.LateSdk; import com.android.net.module.util.SharedLog; import com.android.net.module.util.Struct.S32; import com.android.net.module.util.bpf.Tether4Key; @@ -113,12 +133,18 @@ import com.android.net.module.util.bpf.TetherStatsKey; import com.android.net.module.util.bpf.TetherStatsValue; import com.android.net.module.util.ip.ConntrackMonitor; import com.android.net.module.util.ip.ConntrackMonitor.ConntrackEventConsumer; +import com.android.net.module.util.ip.IpNeighborMonitor; +import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEvent; +import com.android.net.module.util.ip.IpNeighborMonitor.NeighborEventConsumer; import com.android.net.module.util.netlink.ConntrackMessage; import com.android.net.module.util.netlink.NetlinkConstants; import com.android.net.module.util.netlink.NetlinkUtils; import com.android.networkstack.tethering.BpfCoordinator.BpfConntrackEventConsumer; import com.android.networkstack.tethering.BpfCoordinator.ClientInfo; -import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule; +import com.android.networkstack.tethering.BpfCoordinator.Ipv6UpstreamRule; +import com.android.networkstack.tethering.metrics.TetheringMetrics; +import com.android.networkstack.tethering.util.InterfaceSet; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; @@ -135,6 +161,7 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; +import org.mockito.verification.VerificationMode; import java.io.StringWriter; import java.net.Inet4Address; @@ -144,7 +171,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest @@ -158,25 +187,46 @@ public class BpfCoordinatorTest { private static final int TEST_NET_ID = 24; private static final int TEST_NET_ID2 = 25; - private static final int INVALID_IFINDEX = 0; + private static final int NO_UPSTREAM = 0; private static final int UPSTREAM_IFINDEX = 1001; private static final int UPSTREAM_XLAT_IFINDEX = 1002; private static final int UPSTREAM_IFINDEX2 = 1003; private static final int DOWNSTREAM_IFINDEX = 2001; private static final int DOWNSTREAM_IFINDEX2 = 2002; + private static final int IPSEC_IFINDEX = 103; private static final String UPSTREAM_IFACE = "rmnet0"; private static final String UPSTREAM_XLAT_IFACE = "v4-rmnet0"; private static final String UPSTREAM_IFACE2 = "wlan0"; + private static final String DOWNSTREAM_IFACE = "downstream1"; + private static final String DOWNSTREAM_IFACE2 = "downstream2"; + private static final String IPSEC_IFACE = "ipsec0"; private static final MacAddress DOWNSTREAM_MAC = MacAddress.fromString("12:34:56:78:90:ab"); private static final MacAddress DOWNSTREAM_MAC2 = MacAddress.fromString("ab:90:78:56:34:12"); private static final MacAddress MAC_A = MacAddress.fromString("00:00:00:00:00:0a"); private static final MacAddress MAC_B = MacAddress.fromString("11:22:33:00:00:0b"); - - private static final InetAddress NEIGH_A = InetAddresses.parseNumericAddress("2001:db8::1"); - private static final InetAddress NEIGH_B = InetAddresses.parseNumericAddress("2001:db8::2"); + private static final MacAddress MAC_NULL = MacAddress.fromString("00:00:00:00:00:00"); + + private static final LinkAddress UPSTREAM_ADDRESS = new LinkAddress("2001:db8:0:1234::168/64"); + private static final LinkAddress UPSTREAM_ADDRESS2 = new LinkAddress("2001:db8:0:abcd::168/64"); + private static final Set UPSTREAM_ADDRESSES = Set.of(UPSTREAM_ADDRESS); + private static final Set UPSTREAM_ADDRESSES2 = + Set.of(UPSTREAM_ADDRESS, UPSTREAM_ADDRESS2); + private static final IpPrefix UPSTREAM_PREFIX = new IpPrefix("2001:db8:0:1234::/64"); + private static final IpPrefix UPSTREAM_PREFIX2 = new IpPrefix("2001:db8:0:abcd::/64"); + private static final Set UPSTREAM_PREFIXES = Set.of(UPSTREAM_PREFIX); + private static final Set UPSTREAM_PREFIXES2 = + Set.of(UPSTREAM_PREFIX, UPSTREAM_PREFIX2); + private static final Set NO_PREFIXES = Set.of(); + + private static final InetAddress NEIGH_A = + InetAddresses.parseNumericAddress("2001:db8:0:1234::1"); + private static final InetAddress NEIGH_B = + InetAddresses.parseNumericAddress("2001:db8:0:1234::2"); + private static final InetAddress NEIGH_LL = InetAddresses.parseNumericAddress("fe80::1"); + private static final InetAddress NEIGH_MC = InetAddresses.parseNumericAddress("ff02::1234"); private static final Inet4Address REMOTE_ADDR = (Inet4Address) InetAddresses.parseNumericAddress("140.112.8.116"); @@ -211,6 +261,14 @@ public class BpfCoordinatorTest { private static final InterfaceParams UPSTREAM_IFACE_PARAMS2 = new InterfaceParams( UPSTREAM_IFACE2, UPSTREAM_IFINDEX2, MacAddress.fromString("44:55:66:00:00:0c"), NetworkStackConstants.ETHER_MTU); + private static final InterfaceParams DOWNSTREAM_IFACE_PARAMS = new InterfaceParams( + DOWNSTREAM_IFACE, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC, NetworkStackConstants.ETHER_MTU); + private static final InterfaceParams DOWNSTREAM_IFACE_PARAMS2 = new InterfaceParams( + DOWNSTREAM_IFACE2, DOWNSTREAM_IFINDEX2, DOWNSTREAM_MAC2, + NetworkStackConstants.ETHER_MTU); + private static final InterfaceParams IPSEC_IFACE_PARAMS = new InterfaceParams( + IPSEC_IFACE, IPSEC_IFINDEX, MacAddress.ALL_ZEROS_ADDRESS, + NetworkStackConstants.ETHER_MTU); private static final Map UPSTREAM_INFORMATIONS = Map.of( UPSTREAM_IFINDEX, new UpstreamInformation(UPSTREAM_IFACE_PARAMS, @@ -390,6 +448,14 @@ public class BpfCoordinatorTest { @Mock private IpServer mIpServer2; @Mock private TetheringConfiguration mTetherConfig; @Mock private ConntrackMonitor mConntrackMonitor; + @Mock private IpNeighborMonitor mIpNeighborMonitor; + @Mock private RouterAdvertisementDaemon mRaDaemon; + @Mock private IpServer.Dependencies mIpServerDeps; + @Mock private IpServer.Callback mIpServerCallback; + @Mock private PrivateAddressCoordinator mAddressCoordinator; + private final LateSdk mRoutingCoordinatorManager = + new LateSdk<>(SdkLevel.isAtLeastS() ? mock(RoutingCoordinatorManager.class) : null); + @Mock private TetheringMetrics mTetheringMetrics; // Late init since methods must be called by the thread that created this object. private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb; @@ -398,6 +464,7 @@ public class BpfCoordinatorTest { // Late init since the object must be initialized by the BPF coordinator instance because // it has to access the non-static function of BPF coordinator. private BpfConntrackEventConsumer mConsumer; + private NeighborEventConsumer mNeighborEventConsumer; private HashMap> mTetherClients; private long mElapsedRealtimeNanos = 0; @@ -405,6 +472,7 @@ public class BpfCoordinatorTest { private final ArgumentCaptor mStringArrayCaptor = ArgumentCaptor.forClass(ArrayList.class); private final TestLooper mTestLooper = new TestLooper(); + private final Handler mHandler = new Handler(mTestLooper.getLooper()); private final IBpfMap mBpfDownstream4Map = spy(new TestBpfMap<>(Tether4Key.class, Tether4Value.class)); private final IBpfMap mBpfUpstream4Map = @@ -425,7 +493,7 @@ public class BpfCoordinatorTest { spy(new BpfCoordinator.Dependencies() { @NonNull public Handler getHandler() { - return new Handler(mTestLooper.getLooper()); + return mHandler; } @NonNull @@ -505,6 +573,24 @@ public class BpfCoordinatorTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(true /* default value */); + + // Simulate the behavior of RoutingCoordinator + if (null != mRoutingCoordinatorManager.value) { + doAnswer(it -> { + final String fromIface = (String) it.getArguments()[0]; + final String toIface = (String) it.getArguments()[1]; + mNetd.tetherAddForward(fromIface, toIface); + mNetd.ipfwdAddInterfaceForward(fromIface, toIface); + return null; + }).when(mRoutingCoordinatorManager.value).addInterfaceForward(any(), any()); + doAnswer(it -> { + final String fromIface = (String) it.getArguments()[0]; + final String toIface = (String) it.getArguments()[1]; + mNetd.ipfwdRemoveInterfaceForward(fromIface, toIface); + mNetd.tetherRemoveForward(fromIface, toIface); + return null; + }).when(mRoutingCoordinatorManager.value).removeInterfaceForward(any(), any()); + } } private void waitForIdle() { @@ -517,8 +603,69 @@ public class BpfCoordinatorTest { when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]); } + @NonNull + private IpServer makeAndStartIpServer(String interfaceName, BpfCoordinator bpfCoordinator) + throws Exception { + final LinkAddress testAddress = new LinkAddress("192.168.42.5/24"); + when(mIpServerDeps.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon); + when(mIpServerDeps.getInterfaceParams(DOWNSTREAM_IFACE)).thenReturn( + DOWNSTREAM_IFACE_PARAMS); + when(mIpServerDeps.getInterfaceParams(UPSTREAM_IFACE)).thenReturn(UPSTREAM_IFACE_PARAMS); + when(mIpServerDeps.getInterfaceParams(UPSTREAM_IFACE2)).thenReturn(UPSTREAM_IFACE_PARAMS2); + when(mIpServerDeps.getInterfaceParams(IPSEC_IFACE)).thenReturn(IPSEC_IFACE_PARAMS); + when(mAddressCoordinator.requestDownstreamAddress(any(), anyInt(), + anyBoolean())).thenReturn(testAddress); + when(mRaDaemon.start()).thenReturn(true); + ArgumentCaptor neighborEventCaptor = + ArgumentCaptor.forClass(NeighborEventConsumer.class); + doReturn(mIpNeighborMonitor).when(mIpServerDeps).getIpNeighborMonitor(any(), any(), + neighborEventCaptor.capture()); + final IpServer ipServer = new IpServer( + interfaceName, mHandler, TETHERING_WIFI, new SharedLog("test"), mNetd, + bpfCoordinator, mRoutingCoordinatorManager, mIpServerCallback, mTetherConfig, + mAddressCoordinator, mTetheringMetrics, mIpServerDeps); + ipServer.start(); + ipServer.sendMessage(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED); + mTestLooper.dispatchAll(); + + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(UPSTREAM_IFACE); + lp.setLinkAddresses(UPSTREAM_ADDRESSES); + dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, lp, 0); + + mNeighborEventConsumer = neighborEventCaptor.getValue(); + return ipServer; + } + + private void dispatchTetherConnectionChanged(IpServer ipServer, String upstreamIface, + LinkProperties v6lp, int ttlAdjustment) { + dispatchTetherConnectionChanged(ipServer, upstreamIface); + ipServer.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, ttlAdjustment, 0, v6lp); + mTestLooper.dispatchAll(); + } + + private void dispatchTetherConnectionChanged(IpServer ipServer, String upstreamIface) { + final InterfaceSet ifs = (upstreamIface != null) ? new InterfaceSet(upstreamIface) : null; + ipServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED, ifs); + mTestLooper.dispatchAll(); + } + + private void recvNewNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) { + mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_NEWNEIGH, ifindex, addr, + nudState, mac)); + mTestLooper.dispatchAll(); + } + + private void recvDelNeigh(int ifindex, InetAddress addr, short nudState, MacAddress mac) { + mNeighborEventConsumer.accept(new NeighborEvent(0, RTM_DELNEIGH, ifindex, addr, + nudState, mac)); + mTestLooper.dispatchAll(); + } + @NonNull private BpfCoordinator makeBpfCoordinator() throws Exception { + // mStatsManager will be invoked twice if BpfCoordinator is created the second time. + clearInvocations(mStatsManager); final BpfCoordinator coordinator = new BpfCoordinator(mDeps); mConsumer = coordinator.getBpfConntrackEventConsumerForTesting(); @@ -615,10 +762,14 @@ public class BpfCoordinatorTest { } private T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) { + return verifyWithOrder(inOrder, t, times(1)); + } + + private T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t, VerificationMode mode) { if (inOrder != null) { - return inOrder.verify(t); + return inOrder.verify(t, mode); } else { - return verify(t); + return verify(t, mode); } } @@ -638,22 +789,49 @@ public class BpfCoordinatorTest { } } - private void verifyStartUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int downstreamIfIndex, - MacAddress downstreamMac, int upstreamIfindex) throws Exception { + private void verifyStartUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int upstreamIfindex, + @NonNull Set upstreamPrefixes) throws Exception { if (!mDeps.isAtLeastS()) return; - final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfIndex, downstreamMac); - final Tether6Value value = new Tether6Value(upstreamIfindex, - MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS, - ETH_P_IPV6, NetworkStackConstants.ETHER_MTU); - verifyWithOrder(inOrder, mBpfUpstream6Map).insertEntry(key, value); + ArrayMap expected = new ArrayMap<>(); + for (IpPrefix upstreamPrefix : upstreamPrefixes) { + final byte[] prefix64 = prefixToIp64(upstreamPrefix); + final TetherUpstream6Key key = new TetherUpstream6Key(DOWNSTREAM_IFACE_PARAMS.index, + DOWNSTREAM_IFACE_PARAMS.macAddr, prefix64); + final Tether6Value value = new Tether6Value(upstreamIfindex, + MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS, ETH_P_IPV6, + NetworkStackConstants.ETHER_MTU); + expected.put(key, value); + } + ArgumentCaptor keyCaptor = + ArgumentCaptor.forClass(TetherUpstream6Key.class); + ArgumentCaptor valueCaptor = + ArgumentCaptor.forClass(Tether6Value.class); + verifyWithOrder(inOrder, mBpfUpstream6Map, times(expected.size())).insertEntry( + keyCaptor.capture(), valueCaptor.capture()); + List keys = keyCaptor.getAllValues(); + List values = valueCaptor.getAllValues(); + ArrayMap captured = new ArrayMap<>(); + for (int i = 0; i < keys.size(); i++) { + captured.put(keys.get(i), values.get(i)); + } + assertEquals(expected, captured); } - private void verifyStopUpstreamIpv6Forwarding(@Nullable InOrder inOrder, int downstreamIfIndex, - MacAddress downstreamMac) - throws Exception { + private void verifyStopUpstreamIpv6Forwarding(@Nullable InOrder inOrder, + @NonNull Set upstreamPrefixes) throws Exception { if (!mDeps.isAtLeastS()) return; - final TetherUpstream6Key key = new TetherUpstream6Key(downstreamIfIndex, downstreamMac); - verifyWithOrder(inOrder, mBpfUpstream6Map).deleteEntry(key); + Set expected = new ArraySet<>(); + for (IpPrefix upstreamPrefix : upstreamPrefixes) { + final byte[] prefix64 = prefixToIp64(upstreamPrefix); + final TetherUpstream6Key key = new TetherUpstream6Key(DOWNSTREAM_IFACE_PARAMS.index, + DOWNSTREAM_IFACE_PARAMS.macAddr, prefix64); + expected.add(key); + } + ArgumentCaptor keyCaptor = + ArgumentCaptor.forClass(TetherUpstream6Key.class); + verifyWithOrder(inOrder, mBpfUpstream6Map, times(expected.size())).deleteEntry( + keyCaptor.capture()); + assertEquals(expected, new ArraySet(keyCaptor.getAllValues())); } private void verifyNoUpstreamIpv6ForwardingChange(@Nullable InOrder inOrder) throws Exception { @@ -669,8 +847,41 @@ public class BpfCoordinatorTest { } } - private void verifyTetherOffloadRuleAdd(@Nullable InOrder inOrder, - @NonNull Ipv6ForwardingRule rule) throws Exception { + private void verifyAddUpstreamRule(@Nullable InOrder inOrder, + @NonNull Ipv6UpstreamRule rule) throws Exception { + if (!mDeps.isAtLeastS()) return; + verifyWithOrder(inOrder, mBpfUpstream6Map).insertEntry( + rule.makeTetherUpstream6Key(), rule.makeTether6Value()); + } + + private void verifyAddUpstreamRules(@Nullable InOrder inOrder, + @NonNull Set rules) throws Exception { + if (!mDeps.isAtLeastS()) return; + ArrayMap expected = new ArrayMap<>(); + for (Ipv6UpstreamRule rule : rules) { + expected.put(rule.makeTetherUpstream6Key(), rule.makeTether6Value()); + } + ArgumentCaptor keyCaptor = + ArgumentCaptor.forClass(TetherUpstream6Key.class); + ArgumentCaptor valueCaptor = + ArgumentCaptor.forClass(Tether6Value.class); + verifyWithOrder(inOrder, mBpfUpstream6Map, times(expected.size())).insertEntry( + keyCaptor.capture(), valueCaptor.capture()); + List keys = keyCaptor.getAllValues(); + List values = valueCaptor.getAllValues(); + ArrayMap captured = new ArrayMap<>(); + for (int i = 0; i < keys.size(); i++) { + captured.put(keys.get(i), values.get(i)); + } + assertEquals(expected, captured); + } + + private void verifyAddDownstreamRule(@NonNull Ipv6DownstreamRule rule) throws Exception { + verifyAddDownstreamRule(null, rule); + } + + private void verifyAddDownstreamRule(@Nullable InOrder inOrder, + @NonNull Ipv6DownstreamRule rule) throws Exception { if (mDeps.isAtLeastS()) { verifyWithOrder(inOrder, mBpfDownstream6Map).updateEntry( rule.makeTetherDownstream6Key(), rule.makeTether6Value()); @@ -679,7 +890,12 @@ public class BpfCoordinatorTest { } } - private void verifyNeverTetherOffloadRuleAdd() throws Exception { + private void verifyNeverAddUpstreamRule() throws Exception { + if (!mDeps.isAtLeastS()) return; + verify(mBpfUpstream6Map, never()).insertEntry(any(), any()); + } + + private void verifyNeverAddDownstreamRule() throws Exception { if (mDeps.isAtLeastS()) { verify(mBpfDownstream6Map, never()).updateEntry(any(), any()); } else { @@ -687,8 +903,34 @@ public class BpfCoordinatorTest { } } - private void verifyTetherOffloadRuleRemove(@Nullable InOrder inOrder, - @NonNull final Ipv6ForwardingRule rule) throws Exception { + private void verifyRemoveUpstreamRule(@Nullable InOrder inOrder, + @NonNull final Ipv6UpstreamRule rule) throws Exception { + if (!mDeps.isAtLeastS()) return; + verifyWithOrder(inOrder, mBpfUpstream6Map).deleteEntry( + rule.makeTetherUpstream6Key()); + } + + private void verifyRemoveUpstreamRules(@Nullable InOrder inOrder, + @NonNull Set rules) throws Exception { + if (!mDeps.isAtLeastS()) return; + List expected = new ArrayList<>(); + for (Ipv6UpstreamRule rule : rules) { + expected.add(rule.makeTetherUpstream6Key()); + } + ArgumentCaptor keyCaptor = + ArgumentCaptor.forClass(TetherUpstream6Key.class); + verifyWithOrder(inOrder, mBpfUpstream6Map, times(expected.size())).deleteEntry( + keyCaptor.capture()); + assertSameElements(expected, keyCaptor.getAllValues()); + } + + private void verifyRemoveDownstreamRule(@NonNull final Ipv6DownstreamRule rule) + throws Exception { + verifyRemoveDownstreamRule(null, rule); + } + + private void verifyRemoveDownstreamRule(@Nullable InOrder inOrder, + @NonNull final Ipv6DownstreamRule rule) throws Exception { if (mDeps.isAtLeastS()) { verifyWithOrder(inOrder, mBpfDownstream6Map).deleteEntry( rule.makeTetherDownstream6Key()); @@ -697,7 +939,12 @@ public class BpfCoordinatorTest { } } - private void verifyNeverTetherOffloadRuleRemove() throws Exception { + private void verifyNeverRemoveUpstreamRule() throws Exception { + if (!mDeps.isAtLeastS()) return; + verify(mBpfUpstream6Map, never()).deleteEntry(any()); + } + + private void verifyNeverRemoveDownstreamRule() throws Exception { if (mDeps.isAtLeastS()) { verify(mBpfDownstream6Map, never()).deleteEntry(any()); } else { @@ -761,24 +1008,33 @@ public class BpfCoordinatorTest { final String mobileIface = "rmnet_data0"; final Integer mobileIfIndex = 100; - coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); + coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface); // InOrder is required because mBpfStatsMap may be accessed by both // BpfCoordinator#tetherOffloadRuleAdd and BpfCoordinator#tetherOffloadGetAndClearStats. // The #verifyTetherOffloadGetAndClearStats can't distinguish who has ever called // mBpfStatsMap#getValue and get a wrong calling count which counts all. - final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfLimitMap, mBpfStatsMap); - final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); - coordinator.tetherOffloadRuleAdd(mIpServer, rule); - verifyTetherOffloadRuleAdd(inOrder, rule); + final InOrder inOrder = inOrder(mNetd, mBpfUpstream6Map, mBpfDownstream6Map, mBpfLimitMap, + mBpfStatsMap); + final Ipv6UpstreamRule upstreamRule = buildTestUpstreamRule( + mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC); + final Ipv6DownstreamRule downstreamRule = buildTestDownstreamRule( + mobileIfIndex, NEIGH_A, MAC_A); + coordinator.updateAllIpv6Rules( + mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES); verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED, true /* isInit */); + verifyAddUpstreamRule(inOrder, upstreamRule); + coordinator.addIpv6DownstreamRule(mIpServer, downstreamRule); + verifyAddDownstreamRule(inOrder, downstreamRule); - // Removing the last rule on current upstream immediately sends the cleanup stuff to netd. + // Removing the last rule on current upstream immediately sends the cleanup stuff to BPF. updateStatsEntryForTetherOffloadGetAndClearStats( buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0)); - coordinator.tetherOffloadRuleRemove(mIpServer, rule); - verifyTetherOffloadRuleRemove(inOrder, rule); + coordinator.updateAllIpv6Rules( + mIpServer, DOWNSTREAM_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES); + verifyRemoveDownstreamRule(inOrder, downstreamRule); + verifyRemoveUpstreamRule(inOrder, upstreamRule); verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex); } @@ -804,7 +1060,7 @@ public class BpfCoordinatorTest { final String mobileIface = "rmnet_data0"; final Integer mobileIfIndex = 100; - coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); + coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface); updateStatsEntriesAndWaitForUpdate(new TetherStatsParcel[] { buildTestTetherStatsParcel(mobileIfIndex, 1000, 100, 2000, 200)}); @@ -845,8 +1101,8 @@ public class BpfCoordinatorTest { // Add interface name to lookup table. In realistic case, the upstream interface name will // be added by IpServer when IpServer has received with a new IPv6 upstream update event. - coordinator.addUpstreamNameToLookupTable(wlanIfIndex, wlanIface); - coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); + coordinator.maybeAddUpstreamToLookupTable(wlanIfIndex, wlanIface); + coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface); // [1] Both interface stats are changed. // Setup the tether stats of wlan and mobile interface. Note that move forward the time of @@ -910,7 +1166,7 @@ public class BpfCoordinatorTest { final String mobileIface = "rmnet_data0"; final Integer mobileIfIndex = 100; - coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); + coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface); // Verify that set quota to 0 will immediately triggers a callback. mTetherStatsProvider.onSetAlert(0); @@ -937,8 +1193,37 @@ public class BpfCoordinatorTest { mTetherStatsProviderCb.assertNoCallback(); } - // The custom ArgumentMatcher simply comes from IpServerTest. - // TODO: move both of them into a common utility class for reusing the code. + /** + * Custom ArgumentMatcher for TetherOffloadRuleParcel. This is needed because generated stable + * AIDL classes don't have equals(), so we cannot just use eq(). A custom assert, such as: + * + * private void checkFooCalled(StableParcelable p, ...) { + * ArgumentCaptor<@FooParam> captor = ArgumentCaptor.forClass(FooParam.class); + * verify(mMock).foo(captor.capture()); + * Foo foo = captor.getValue(); + * assertFooMatchesExpectations(foo); + * } + * + * almost works, but not quite. This is because if the code under test calls foo() twice, the + * first call to checkFooCalled() matches both the calls, putting both calls into the captor, + * and then fails with TooManyActualInvocations. It also makes it harder to use other mockito + * features such as never(), inOrder(), etc. + * + * This approach isn't great because if the match fails, the error message is unhelpful + * (actual: "android.net.TetherOffloadRuleParcel@8c827b0" or some such), but at least it does + * work. + * + * TODO: consider making the error message more readable by adding a method that catching the + * AssertionFailedError and throwing a new assertion with more details. See + * NetworkMonitorTest#verifyNetworkTested. + * + * See ConnectivityServiceTest#assertRoutesAdded for an alternative approach which solves the + * TooManyActualInvocations problem described above by forcing the caller of the custom assert + * method to specify all expected invocations in one call. This is useful when the stable + * parcelable class being asserted on has a corresponding Java object (eg., RouteInfo and + * RouteInfoParcelable), and the caller can just pass in a list of them. It not useful here + * because there is no such object. + */ private static class TetherOffloadRuleParcelMatcher implements ArgumentMatcher { public final int upstreamIfindex; @@ -947,7 +1232,7 @@ public class BpfCoordinatorTest { public final MacAddress srcMac; public final MacAddress dstMac; - TetherOffloadRuleParcelMatcher(@NonNull Ipv6ForwardingRule rule) { + TetherOffloadRuleParcelMatcher(@NonNull Ipv6DownstreamRule rule) { upstreamIfindex = rule.upstreamIfindex; downstreamIfindex = rule.downstreamIfindex; address = rule.address; @@ -971,45 +1256,92 @@ public class BpfCoordinatorTest { } @NonNull - private TetherOffloadRuleParcel matches(@NonNull Ipv6ForwardingRule rule) { + private TetherOffloadRuleParcel matches(@NonNull Ipv6DownstreamRule rule) { return argThat(new TetherOffloadRuleParcelMatcher(rule)); } @NonNull - private static Ipv6ForwardingRule buildTestForwardingRule( + private static Ipv6UpstreamRule buildTestUpstreamRule(int upstreamIfindex, + int downstreamIfindex, @NonNull IpPrefix sourcePrefix, @NonNull MacAddress inDstMac) { + return new Ipv6UpstreamRule(upstreamIfindex, downstreamIfindex, sourcePrefix, inDstMac, + MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + } + + @NonNull + private static Ipv6DownstreamRule buildTestDownstreamRule( int upstreamIfindex, @NonNull InetAddress address, @NonNull MacAddress dstMac) { - return new Ipv6ForwardingRule(upstreamIfindex, DOWNSTREAM_IFINDEX, (Inet6Address) address, - DOWNSTREAM_MAC, dstMac); + return new Ipv6DownstreamRule(upstreamIfindex, DOWNSTREAM_IFINDEX, + (Inet6Address) address, DOWNSTREAM_MAC, dstMac); } @Test - public void testRuleMakeTetherDownstream6Key() throws Exception { + public void testIpv6DownstreamRuleMakeTetherDownstream6Key() throws Exception { final int mobileIfIndex = 100; - final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); + final Ipv6DownstreamRule rule = buildTestDownstreamRule(mobileIfIndex, NEIGH_A, MAC_A); final TetherDownstream6Key key = rule.makeTetherDownstream6Key(); - assertEquals(key.iif, mobileIfIndex); - assertEquals(key.dstMac, MacAddress.ALL_ZEROS_ADDRESS); // rawip upstream - assertTrue(Arrays.equals(key.neigh6, NEIGH_A.getAddress())); + assertEquals(mobileIfIndex, key.iif); + assertEquals(MacAddress.ALL_ZEROS_ADDRESS, key.dstMac); // rawip upstream + assertArrayEquals(NEIGH_A.getAddress(), key.neigh6); // iif (4) + dstMac(6) + padding(2) + neigh6 (16) = 28. assertEquals(28, key.writeToBytes().length); } @Test - public void testRuleMakeTether6Value() throws Exception { + public void testIpv6DownstreamRuleMakeTether6Value() throws Exception { final int mobileIfIndex = 100; - final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); + final Ipv6DownstreamRule rule = buildTestDownstreamRule(mobileIfIndex, NEIGH_A, MAC_A); final Tether6Value value = rule.makeTether6Value(); - assertEquals(value.oif, DOWNSTREAM_IFINDEX); - assertEquals(value.ethDstMac, MAC_A); - assertEquals(value.ethSrcMac, DOWNSTREAM_MAC); - assertEquals(value.ethProto, ETH_P_IPV6); - assertEquals(value.pmtu, NetworkStackConstants.ETHER_MTU); - // oif (4) + ethDstMac (6) + ethSrcMac (6) + ethProto (2) + pmtu (2) = 20. + assertEquals(DOWNSTREAM_IFINDEX, value.oif); + assertEquals(MAC_A, value.ethDstMac); + assertEquals(DOWNSTREAM_MAC, value.ethSrcMac); + assertEquals(ETH_P_IPV6, value.ethProto); + assertEquals(NetworkStackConstants.ETHER_MTU, value.pmtu); + // oif (4) + ethDstMac (6) + ethSrcMac (6) + ethProto (2) + pmtu (2) = 20 assertEquals(20, value.writeToBytes().length); } + @Test + public void testIpv6UpstreamRuleMakeTetherUpstream6Key() { + final byte[] bytes = new byte[]{(byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0xab, (byte) 0xcd, (byte) 0xfe, (byte) 0x00}; + final IpPrefix prefix = new IpPrefix("2001:db8:abcd:fe00::/64"); + final Ipv6UpstreamRule rule = buildTestUpstreamRule(UPSTREAM_IFINDEX, + DOWNSTREAM_IFINDEX, prefix, DOWNSTREAM_MAC); + + final TetherUpstream6Key key = rule.makeTetherUpstream6Key(); + assertEquals(DOWNSTREAM_IFINDEX, key.iif); + assertEquals(DOWNSTREAM_MAC, key.dstMac); + assertArrayEquals(bytes, key.src64); + // iif (4) + dstMac (6) + padding (6) + src64 (8) = 24 + assertEquals(24, key.writeToBytes().length); + } + + @Test + public void testIpv6UpstreamRuleMakeTether6Value() { + final IpPrefix prefix = new IpPrefix("2001:db8:abcd:fe00::/64"); + final Ipv6UpstreamRule rule = buildTestUpstreamRule(UPSTREAM_IFINDEX, + DOWNSTREAM_IFINDEX, prefix, DOWNSTREAM_MAC); + + final Tether6Value value = rule.makeTether6Value(); + assertEquals(UPSTREAM_IFINDEX, value.oif); + assertEquals(MAC_NULL, value.ethDstMac); + assertEquals(MAC_NULL, value.ethSrcMac); + assertEquals(ETH_P_IPV6, value.ethProto); + assertEquals(NetworkStackConstants.ETHER_MTU, value.pmtu); + // oif (4) + ethDstMac (6) + ethSrcMac (6) + ethProto (2) + pmtu (2) = 20 + assertEquals(20, value.writeToBytes().length); + } + + @Test + public void testBytesToPrefix() { + final byte[] bytes = new byte[]{(byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8, + (byte) 0x00, (byte) 0x00, (byte) 0x12, (byte) 0x34}; + final IpPrefix prefix = new IpPrefix("2001:db8:0:1234::/64"); + assertEquals(prefix, BpfCoordinator.bytesToPrefix(bytes)); + } + @Test public void testSetDataLimit() throws Exception { setupFunctioningNetdInterface(); @@ -1018,17 +1350,19 @@ public class BpfCoordinatorTest { final String mobileIface = "rmnet_data0"; final int mobileIfIndex = 100; - coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); + coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface); // [1] Default limit. // Set the unlimited quota as default if the service has never applied a data limit for a // given upstream. Note that the data limit only be applied on an upstream which has rules. - final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); - final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfLimitMap, mBpfStatsMap); - coordinator.tetherOffloadRuleAdd(mIpServer, rule); - verifyTetherOffloadRuleAdd(inOrder, rule); + final Ipv6UpstreamRule rule = buildTestUpstreamRule( + mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC); + final InOrder inOrder = inOrder(mNetd, mBpfUpstream6Map, mBpfLimitMap, mBpfStatsMap); + coordinator.updateAllIpv6Rules( + mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES); verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED, true /* isInit */); + verifyAddUpstreamRule(inOrder, rule); inOrder.verifyNoMoreInteractions(); // [2] Specific limit. @@ -1053,7 +1387,6 @@ public class BpfCoordinatorTest { } } - // TODO: Test the case in which the rules are changed from different IpServer objects. @Test public void testSetDataLimitOnRule6Change() throws Exception { setupFunctioningNetdInterface(); @@ -1062,39 +1395,45 @@ public class BpfCoordinatorTest { final String mobileIface = "rmnet_data0"; final int mobileIfIndex = 100; - coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); + coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface); // Applying a data limit to the current upstream does not take any immediate action. // The data limit could be only set on an upstream which has rules. final long limit = 12345; - final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfLimitMap, mBpfStatsMap); + final InOrder inOrder = inOrder(mNetd, mBpfUpstream6Map, mBpfLimitMap, mBpfStatsMap); mTetherStatsProvider.onSetLimit(mobileIface, limit); waitForIdle(); verifyNeverTetherOffloadSetInterfaceQuota(inOrder); - // Adding the first rule on current upstream immediately sends the quota to netd. - final Ipv6ForwardingRule ruleA = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); - coordinator.tetherOffloadRuleAdd(mIpServer, ruleA); - verifyTetherOffloadRuleAdd(inOrder, ruleA); + // Adding the first rule on current upstream immediately sends the quota to BPF. + final Ipv6UpstreamRule ruleA = buildTestUpstreamRule( + mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC); + coordinator.updateAllIpv6Rules( + mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES); verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, limit, true /* isInit */); + verifyAddUpstreamRule(inOrder, ruleA); inOrder.verifyNoMoreInteractions(); - // Adding the second rule on current upstream does not send the quota to netd. - final Ipv6ForwardingRule ruleB = buildTestForwardingRule(mobileIfIndex, NEIGH_B, MAC_B); - coordinator.tetherOffloadRuleAdd(mIpServer, ruleB); - verifyTetherOffloadRuleAdd(inOrder, ruleB); + // Adding the second rule on current upstream does not send the quota to BPF. + final Ipv6UpstreamRule ruleB = buildTestUpstreamRule( + mobileIfIndex, DOWNSTREAM_IFINDEX2, UPSTREAM_PREFIX, DOWNSTREAM_MAC2); + coordinator.updateAllIpv6Rules( + mIpServer2, DOWNSTREAM_IFACE_PARAMS2, mobileIfIndex, UPSTREAM_PREFIXES); + verifyAddUpstreamRule(inOrder, ruleB); verifyNeverTetherOffloadSetInterfaceQuota(inOrder); - // Removing the second rule on current upstream does not send the quota to netd. - coordinator.tetherOffloadRuleRemove(mIpServer, ruleB); - verifyTetherOffloadRuleRemove(inOrder, ruleB); + // Removing the second rule on current upstream does not send the quota to BPF. + coordinator.updateAllIpv6Rules( + mIpServer2, DOWNSTREAM_IFACE_PARAMS2, NO_UPSTREAM, NO_PREFIXES); + verifyRemoveUpstreamRule(inOrder, ruleB); verifyNeverTetherOffloadSetInterfaceQuota(inOrder); - // Removing the last rule on current upstream immediately sends the cleanup stuff to netd. + // Removing the last rule on current upstream immediately sends the cleanup stuff to BPF. updateStatsEntryForTetherOffloadGetAndClearStats( buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0)); - coordinator.tetherOffloadRuleRemove(mIpServer, ruleA); - verifyTetherOffloadRuleRemove(inOrder, ruleA); + coordinator.updateAllIpv6Rules( + mIpServer, DOWNSTREAM_IFACE_PARAMS, NO_UPSTREAM, NO_PREFIXES); + verifyRemoveUpstreamRule(inOrder, ruleA); verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex); inOrder.verifyNoMoreInteractions(); } @@ -1109,8 +1448,8 @@ public class BpfCoordinatorTest { final String mobileIface = "rmnet_data0"; final Integer ethIfIndex = 100; final Integer mobileIfIndex = 101; - coordinator.addUpstreamNameToLookupTable(ethIfIndex, ethIface); - coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); + coordinator.maybeAddUpstreamToLookupTable(ethIfIndex, ethIface); + coordinator.maybeAddUpstreamToLookupTable(mobileIfIndex, mobileIface); final InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map, mBpfLimitMap, mBpfStatsMap); @@ -1124,48 +1463,56 @@ public class BpfCoordinatorTest { // [1] Adding rules on the upstream Ethernet. // Note that the default data limit is applied after the first rule is added. - final Ipv6ForwardingRule ethernetRuleA = buildTestForwardingRule( + final Ipv6UpstreamRule ethernetUpstreamRule = buildTestUpstreamRule( + ethIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC); + final Ipv6DownstreamRule ethernetRuleA = buildTestDownstreamRule( ethIfIndex, NEIGH_A, MAC_A); - final Ipv6ForwardingRule ethernetRuleB = buildTestForwardingRule( + final Ipv6DownstreamRule ethernetRuleB = buildTestDownstreamRule( ethIfIndex, NEIGH_B, MAC_B); - coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleA); - verifyTetherOffloadRuleAdd(inOrder, ethernetRuleA); + coordinator.updateAllIpv6Rules( + mIpServer, DOWNSTREAM_IFACE_PARAMS, ethIfIndex, UPSTREAM_PREFIXES); verifyTetherOffloadSetInterfaceQuota(inOrder, ethIfIndex, QUOTA_UNLIMITED, true /* isInit */); - verifyStartUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC, ethIfIndex); - coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleB); - verifyTetherOffloadRuleAdd(inOrder, ethernetRuleB); + verifyAddUpstreamRule(inOrder, ethernetUpstreamRule); + coordinator.addIpv6DownstreamRule(mIpServer, ethernetRuleA); + verifyAddDownstreamRule(inOrder, ethernetRuleA); + coordinator.addIpv6DownstreamRule(mIpServer, ethernetRuleB); + verifyAddDownstreamRule(inOrder, ethernetRuleB); // [2] Update the existing rules from Ethernet to cellular. - final Ipv6ForwardingRule mobileRuleA = buildTestForwardingRule( + final Ipv6UpstreamRule mobileUpstreamRule = buildTestUpstreamRule( + mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC); + final Ipv6UpstreamRule mobileUpstreamRule2 = buildTestUpstreamRule( + mobileIfIndex, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX2, DOWNSTREAM_MAC); + final Ipv6DownstreamRule mobileRuleA = buildTestDownstreamRule( mobileIfIndex, NEIGH_A, MAC_A); - final Ipv6ForwardingRule mobileRuleB = buildTestForwardingRule( + final Ipv6DownstreamRule mobileRuleB = buildTestDownstreamRule( mobileIfIndex, NEIGH_B, MAC_B); updateStatsEntryForTetherOffloadGetAndClearStats( buildTestTetherStatsParcel(ethIfIndex, 10, 20, 30, 40)); // Update the existing rules for upstream changes. The rules are removed and re-added one - // by one for updating upstream interface index by #tetherOffloadRuleUpdate. - coordinator.tetherOffloadRuleUpdate(mIpServer, mobileIfIndex); - verifyTetherOffloadRuleRemove(inOrder, ethernetRuleA); - verifyTetherOffloadRuleRemove(inOrder, ethernetRuleB); - verifyStopUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC); + // by one for updating upstream interface index and prefixes by #tetherOffloadRuleUpdate. + coordinator.updateAllIpv6Rules( + mIpServer, DOWNSTREAM_IFACE_PARAMS, mobileIfIndex, UPSTREAM_PREFIXES2); + verifyRemoveDownstreamRule(inOrder, ethernetRuleA); + verifyRemoveDownstreamRule(inOrder, ethernetRuleB); + verifyRemoveUpstreamRule(inOrder, ethernetUpstreamRule); verifyTetherOffloadGetAndClearStats(inOrder, ethIfIndex); - verifyTetherOffloadRuleAdd(inOrder, mobileRuleA); verifyTetherOffloadSetInterfaceQuota(inOrder, mobileIfIndex, QUOTA_UNLIMITED, true /* isInit */); - verifyStartUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC, - mobileIfIndex); - verifyTetherOffloadRuleAdd(inOrder, mobileRuleB); + verifyAddUpstreamRules(inOrder, Set.of(mobileUpstreamRule, mobileUpstreamRule2)); + verifyAddDownstreamRule(inOrder, mobileRuleA); + verifyAddDownstreamRule(inOrder, mobileRuleB); // [3] Clear all rules for a given IpServer. updateStatsEntryForTetherOffloadGetAndClearStats( buildTestTetherStatsParcel(mobileIfIndex, 50, 60, 70, 80)); - coordinator.tetherOffloadRuleClear(mIpServer); - verifyTetherOffloadRuleRemove(inOrder, mobileRuleA); - verifyTetherOffloadRuleRemove(inOrder, mobileRuleB); - verifyStopUpstreamIpv6Forwarding(inOrder, DOWNSTREAM_IFINDEX, DOWNSTREAM_MAC); + coordinator.clearAllIpv6Rules(mIpServer); + verifyRemoveDownstreamRule(inOrder, mobileRuleA); + verifyRemoveDownstreamRule(inOrder, mobileRuleB); + verifyRemoveUpstreamRules(inOrder, Set.of(mobileUpstreamRule, mobileUpstreamRule2)); verifyTetherOffloadGetAndClearStats(inOrder, mobileIfIndex); // [4] Force pushing stats update to verify that the last diff of stats is reported on all @@ -1195,43 +1542,44 @@ public class BpfCoordinatorTest { // The interface name lookup table can't be added. final String iface = "rmnet_data0"; final Integer ifIndex = 100; - coordinator.addUpstreamNameToLookupTable(ifIndex, iface); + coordinator.maybeAddUpstreamToLookupTable(ifIndex, iface); assertEquals(0, coordinator.getInterfaceNamesForTesting().size()); // The rule can't be added. final InetAddress neigh = InetAddresses.parseNumericAddress("2001:db8::1"); final MacAddress mac = MacAddress.fromString("00:00:00:00:00:0a"); - final Ipv6ForwardingRule rule = buildTestForwardingRule(ifIndex, neigh, mac); - coordinator.tetherOffloadRuleAdd(mIpServer, rule); - verifyNeverTetherOffloadRuleAdd(); - LinkedHashMap rules = - coordinator.getForwardingRulesForTesting().get(mIpServer); + final Ipv6DownstreamRule rule = buildTestDownstreamRule(ifIndex, neigh, mac); + coordinator.addIpv6DownstreamRule(mIpServer, rule); + verifyNeverAddDownstreamRule(); + LinkedHashMap rules = + coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer); assertNull(rules); // The rule can't be removed. This is not a realistic case because adding rule is not // allowed. That implies no rule could be removed, cleared or updated. Verify these // cases just in case. - rules = new LinkedHashMap(); + rules = new LinkedHashMap(); rules.put(rule.address, rule); - coordinator.getForwardingRulesForTesting().put(mIpServer, rules); - coordinator.tetherOffloadRuleRemove(mIpServer, rule); - verifyNeverTetherOffloadRuleRemove(); - rules = coordinator.getForwardingRulesForTesting().get(mIpServer); + coordinator.getIpv6DownstreamRulesForTesting().put(mIpServer, rules); + coordinator.removeIpv6DownstreamRule(mIpServer, rule); + verifyNeverRemoveDownstreamRule(); + rules = coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer); assertNotNull(rules); assertEquals(1, rules.size()); // The rule can't be cleared. - coordinator.tetherOffloadRuleClear(mIpServer); - verifyNeverTetherOffloadRuleRemove(); - rules = coordinator.getForwardingRulesForTesting().get(mIpServer); + coordinator.clearAllIpv6Rules(mIpServer); + verifyNeverRemoveDownstreamRule(); + rules = coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer); assertNotNull(rules); assertEquals(1, rules.size()); // The rule can't be updated. - coordinator.tetherOffloadRuleUpdate(mIpServer, rule.upstreamIfindex + 1 /* new */); - verifyNeverTetherOffloadRuleRemove(); - verifyNeverTetherOffloadRuleAdd(); - rules = coordinator.getForwardingRulesForTesting().get(mIpServer); + coordinator.updateAllIpv6Rules(mIpServer, DOWNSTREAM_IFACE_PARAMS, + rule.upstreamIfindex + 1 /* new */, UPSTREAM_PREFIXES); + verifyNeverRemoveDownstreamRule(); + verifyNeverAddDownstreamRule(); + rules = coordinator.getIpv6DownstreamRulesForTesting().get(mIpServer); assertNotNull(rules); assertEquals(1, rules.size()); } @@ -1524,12 +1872,12 @@ public class BpfCoordinatorTest { // // @param coordinator BpfCoordinator instance. // @param upstreamIfindex upstream interface index. can be the following values. - // INVALID_IFINDEX: no upstream interface + // NO_UPSTREAM: no upstream interface // UPSTREAM_IFINDEX: CELLULAR (raw ip interface) // UPSTREAM_IFINDEX2: WIFI (ethernet interface) private void setUpstreamInformationTo(final BpfCoordinator coordinator, @Nullable Integer upstreamIfindex) { - if (upstreamIfindex == INVALID_IFINDEX) { + if (upstreamIfindex == NO_UPSTREAM) { coordinator.updateUpstreamNetworkState(null); return; } @@ -1543,7 +1891,7 @@ public class BpfCoordinatorTest { // interface index. doReturn(upstreamInfo.interfaceParams).when(mDeps).getInterfaceParams( upstreamInfo.interfaceParams.name); - coordinator.addUpstreamNameToLookupTable(upstreamInfo.interfaceParams.index, + coordinator.maybeAddUpstreamToLookupTable(upstreamInfo.interfaceParams.index, upstreamInfo.interfaceParams.name); final LinkProperties lp = new LinkProperties(); @@ -1668,19 +2016,23 @@ public class BpfCoordinatorTest { public void testAddDevMapRule6() throws Exception { final BpfCoordinator coordinator = makeBpfCoordinator(); - coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE); - final Ipv6ForwardingRule ruleA = buildTestForwardingRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A); - final Ipv6ForwardingRule ruleB = buildTestForwardingRule(UPSTREAM_IFINDEX, NEIGH_B, MAC_B); - - coordinator.tetherOffloadRuleAdd(mIpServer, ruleA); + coordinator.maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE); + coordinator.updateAllIpv6Rules( + mIpServer, DOWNSTREAM_IFACE_PARAMS, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES); verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)), eq(new TetherDevValue(UPSTREAM_IFINDEX))); verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX)), eq(new TetherDevValue(DOWNSTREAM_IFINDEX))); clearInvocations(mBpfDevMap); - coordinator.tetherOffloadRuleAdd(mIpServer, ruleB); - verify(mBpfDevMap, never()).updateEntry(any(), any()); + // Adding the second downstream, only the second downstream ifindex is added to DevMap, + // the existing upstream ifindex won't be added again. + coordinator.updateAllIpv6Rules( + mIpServer2, DOWNSTREAM_IFACE_PARAMS2, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES); + verify(mBpfDevMap).updateEntry(eq(new TetherDevKey(DOWNSTREAM_IFINDEX2)), + eq(new TetherDevValue(DOWNSTREAM_IFINDEX2))); + verify(mBpfDevMap, never()).updateEntry(eq(new TetherDevKey(UPSTREAM_IFINDEX)), + eq(new TetherDevValue(UPSTREAM_IFINDEX))); } @Test @@ -1957,6 +2309,10 @@ public class BpfCoordinatorTest { 100 /* nonzero, CT_NEW */); } + private static byte[] prefixToIp64(IpPrefix prefix) { + return Arrays.copyOf(prefix.getRawAddress(), 8); + } + void checkRule4ExistInUpstreamDownstreamMap() throws Exception { assertEquals(UPSTREAM4_RULE_VALUE_A, mBpfUpstream4Map.getValue(UPSTREAM4_RULE_KEY_A)); assertEquals(DOWNSTREAM4_RULE_VALUE_A, mBpfDownstream4Map.getValue( @@ -2024,7 +2380,7 @@ public class BpfCoordinatorTest { assertNull(mTetherClients.get(mIpServer2)); } - private void asseertClientInfoExist(@NonNull IpServer ipServer, + private void assertClientInfoExists(@NonNull IpServer ipServer, @NonNull ClientInfo clientInfo) { HashMap clients = mTetherClients.get(ipServer); assertNotNull(clients); @@ -2033,16 +2389,16 @@ public class BpfCoordinatorTest { // Although either ClientInfo for a given downstream (IpServer) is not found or a given // client address is not found on a given downstream can be treated "ClientInfo not - // exist", we still want to know the real reason exactly. For example, we don't the + // exist", we still want to know the real reason exactly. For example, we don't know the // exact reason in the following: - // assertNull(clients == null ? clients : clients.get(clientInfo.clientAddress)); + // assertNull(clients == null ? clients : clients.get(clientAddress)); // This helper only verifies the case that the downstream still has at least one client. // In other words, ClientInfo for a given IpServer has not been removed yet. - private void asseertClientInfoNotExist(@NonNull IpServer ipServer, - @NonNull ClientInfo clientInfo) { + private void assertClientInfoDoesNotExist(@NonNull IpServer ipServer, + @NonNull Inet4Address clientAddress) { HashMap clients = mTetherClients.get(ipServer); assertNotNull(clients); - assertNull(clients.get(clientInfo.clientAddress)); + assertNull(clients.get(clientAddress)); } @Test @@ -2074,12 +2430,12 @@ public class BpfCoordinatorTest { // [3] Switch upstream from the first upstream (rawip, bpf supported) to no upstream. Clear // all rules. - setUpstreamInformationTo(coordinator, INVALID_IFINDEX); + setUpstreamInformationTo(coordinator, NO_UPSTREAM); checkRule4NotExistInUpstreamDownstreamMap(); // Client information should be not deleted. - asseertClientInfoExist(mIpServer, CLIENT_INFO_A); - asseertClientInfoExist(mIpServer2, CLIENT_INFO_B); + assertClientInfoExists(mIpServer, CLIENT_INFO_A); + assertClientInfoExists(mIpServer2, CLIENT_INFO_B); } @Test @@ -2094,8 +2450,8 @@ public class BpfCoordinatorTest { PRIVATE_ADDR2, MAC_B); coordinator.tetherOffloadClientAdd(mIpServer, clientA); coordinator.tetherOffloadClientAdd(mIpServer, clientB); - asseertClientInfoExist(mIpServer, clientA); - asseertClientInfoExist(mIpServer, clientB); + assertClientInfoExists(mIpServer, clientA); + assertClientInfoExists(mIpServer, clientB); // Add the rules for client A and client B. final Tether4Key upstream4KeyA = makeUpstream4Key( @@ -2119,8 +2475,8 @@ public class BpfCoordinatorTest { // [2] Remove client information A. Only the rules on client A should be removed and // the rules on client B should exist. coordinator.tetherOffloadClientRemove(mIpServer, clientA); - asseertClientInfoNotExist(mIpServer, clientA); - asseertClientInfoExist(mIpServer, clientB); + assertClientInfoDoesNotExist(mIpServer, clientA.clientAddress); + assertClientInfoExists(mIpServer, clientB); assertNull(mBpfUpstream4Map.getValue(upstream4KeyA)); assertNull(mBpfDownstream4Map.getValue(downstream4KeyA)); assertEquals(upstream4ValueB, mBpfUpstream4Map.getValue(upstream4KeyB)); @@ -2128,9 +2484,9 @@ public class BpfCoordinatorTest { // [3] Remove client information B. The rules on client B should be removed. // Exactly, ClientInfo for a given IpServer is removed because the last client B - // has been removed from the downstream. Can't use the helper #asseertClientInfoExist + // has been removed from the downstream. Can't use the helper #assertClientInfoExists // to check because the container ClientInfo for a given downstream has been removed. - // See #asseertClientInfoExist. + // See #assertClientInfoExists. coordinator.tetherOffloadClientRemove(mIpServer, clientB); assertNull(mTetherClients.get(mIpServer)); assertNull(mBpfUpstream4Map.getValue(upstream4KeyB)); @@ -2139,9 +2495,17 @@ public class BpfCoordinatorTest { @Test public void testIpv6ForwardingRuleToString() throws Exception { - final Ipv6ForwardingRule rule = buildTestForwardingRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A); - assertEquals("upstreamIfindex: 1001, downstreamIfindex: 2001, address: 2001:db8::1, " - + "srcMac: 12:34:56:78:90:ab, dstMac: 00:00:00:00:00:0a", rule.toString()); + final Ipv6DownstreamRule downstreamRule = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_A, + MAC_A); + assertEquals("upstreamIfindex: 1001, downstreamIfindex: 2001, address: 2001:db8:0:1234::1, " + + "srcMac: 12:34:56:78:90:ab, dstMac: 00:00:00:00:00:0a", + downstreamRule.toString()); + final Ipv6UpstreamRule upstreamRule = buildTestUpstreamRule( + UPSTREAM_IFINDEX, DOWNSTREAM_IFINDEX, UPSTREAM_PREFIX, DOWNSTREAM_MAC); + assertEquals("upstreamIfindex: 1001, downstreamIfindex: 2001, " + + "sourcePrefix: 2001:db8:0:1234::/64, inDstMac: 12:34:56:78:90:ab, " + + "outSrcMac: 00:00:00:00:00:00, outDstMac: 00:00:00:00:00:00", + upstreamRule.toString()); } private void verifyDump(@NonNull final BpfCoordinator coordinator) { @@ -2177,7 +2541,7 @@ public class BpfCoordinatorTest { // - dumpCounters // * mBpfErrorMap // - dumpIpv6ForwardingRulesByDownstream - // * mIpv6ForwardingRules + // * mIpv6DownstreamRules // dumpBpfForwardingRulesIpv4 mBpfDownstream4Map.insertEntry( @@ -2188,11 +2552,12 @@ public class BpfCoordinatorTest { new TestUpstream4Value.Builder().build()); // dumpBpfForwardingRulesIpv6 - final Ipv6ForwardingRule rule = buildTestForwardingRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A); + final Ipv6DownstreamRule rule = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A); mBpfDownstream6Map.insertEntry(rule.makeTetherDownstream6Key(), rule.makeTether6Value()); + final byte[] prefix64 = prefixToIp64(UPSTREAM_PREFIX); final TetherUpstream6Key upstream6Key = new TetherUpstream6Key(DOWNSTREAM_IFINDEX, - DOWNSTREAM_MAC); + DOWNSTREAM_MAC, prefix64); final Tether6Value upstream6Value = new Tether6Value(UPSTREAM_IFINDEX, MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS, ETH_P_IPV6, NetworkStackConstants.ETHER_MTU); @@ -2206,7 +2571,7 @@ public class BpfCoordinatorTest { 0L /* txPackets */, 0L /* txBytes */, 0L /* txErrors */)); // dumpDevmap - coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE); + coordinator.maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE); mBpfDevMap.insertEntry( new TetherDevKey(UPSTREAM_IFINDEX), new TetherDevValue(UPSTREAM_IFINDEX)); @@ -2218,12 +2583,12 @@ public class BpfCoordinatorTest { new S32(1000 /* count */)); // dumpIpv6ForwardingRulesByDownstream - final HashMap> - ipv6ForwardingRules = coordinator.getForwardingRulesForTesting(); - final LinkedHashMap addressRuleMap = + final HashMap> + ipv6DownstreamRules = coordinator.getIpv6DownstreamRulesForTesting(); + final LinkedHashMap addressRuleMap = new LinkedHashMap<>(); addressRuleMap.put(rule.address, rule); - ipv6ForwardingRules.put(mIpServer, addressRuleMap); + ipv6DownstreamRules.put(mIpServer, addressRuleMap); verifyDump(coordinator); } @@ -2375,7 +2740,7 @@ public class BpfCoordinatorTest { // +-------+-------+-------+-------+-------+ // [1] Mobile IPv4 only - coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE); + coordinator.maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX, UPSTREAM_IFACE); doReturn(UPSTREAM_IFACE_PARAMS).when(mDeps).getInterfaceParams(UPSTREAM_IFACE); final UpstreamNetworkState mobileIPv4UpstreamState = new UpstreamNetworkState( buildUpstreamLinkProperties(UPSTREAM_IFACE, @@ -2427,7 +2792,7 @@ public class BpfCoordinatorTest { verifyIpv4Upstream(ipv4UpstreamIndices, interfaceNames); // Mobile IPv6 and xlat - // IpServer doesn't add xlat interface mapping via #addUpstreamNameToLookupTable on + // IpServer doesn't add xlat interface mapping via #maybeAddUpstreamToLookupTable on // S and T devices. coordinator.updateUpstreamNetworkState(mobile464xlatUpstreamState); // Upstream IPv4 address mapping is removed because xlat interface is not supported. @@ -2442,7 +2807,7 @@ public class BpfCoordinatorTest { // [6] Wifi IPv4 and IPv6 // Expect that upstream index map is cleared because ether ip is not supported. - coordinator.addUpstreamNameToLookupTable(UPSTREAM_IFINDEX2, UPSTREAM_IFACE2); + coordinator.maybeAddUpstreamToLookupTable(UPSTREAM_IFINDEX2, UPSTREAM_IFACE2); doReturn(UPSTREAM_IFACE_PARAMS2).when(mDeps).getInterfaceParams(UPSTREAM_IFACE2); final UpstreamNetworkState wifiDualStackUpstreamState = new UpstreamNetworkState( buildUpstreamLinkProperties(UPSTREAM_IFACE2, @@ -2461,4 +2826,296 @@ public class BpfCoordinatorTest { public void testUpdateUpstreamNetworkState() throws Exception { verifyUpdateUpstreamNetworkState(); } + + @NonNull + private static TetherStatsParcel buildEmptyTetherStatsParcel(int ifIndex) { + TetherStatsParcel parcel = new TetherStatsParcel(); + parcel.ifIndex = ifIndex; + return parcel; + } + + private void resetNetdAndBpfMaps() throws Exception { + reset(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); + // When the last rule is removed, tetherOffloadGetAndClearStats will log a WTF (and + // potentially crash the test) if the stats map is empty. + when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]); + when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX)) + .thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX)); + when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX2)) + .thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX2)); + // When the last rule is removed, tetherOffloadGetAndClearStats will log a WTF (and + // potentially crash the test) if the stats map is empty. + final TetherStatsValue allZeros = new TetherStatsValue(0, 0, 0, 0, 0, 0); + when(mBpfStatsMap.getValue(new TetherStatsKey(UPSTREAM_IFINDEX))).thenReturn(allZeros); + when(mBpfStatsMap.getValue(new TetherStatsKey(UPSTREAM_IFINDEX2))).thenReturn(allZeros); + } + + @Test + public void addRemoveIpv6ForwardingRules() throws Exception { + final int myIfindex = DOWNSTREAM_IFINDEX; + final int notMyIfindex = myIfindex - 1; + final BpfCoordinator coordinator = makeBpfCoordinator(); + final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator); + + resetNetdAndBpfMaps(); + verifyNoMoreInteractions(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); + + // TODO: Perhaps verify the interaction of tetherOffloadSetInterfaceQuota and + // tetherOffloadGetAndClearStats in netd while the rules are changed. + + // Events on other interfaces are ignored. + recvNewNeigh(notMyIfindex, NEIGH_A, NUD_REACHABLE, MAC_A); + verifyNoMoreInteractions(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); + + // Events on this interface are received and sent to BpfCoordinator. + recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A); + final Ipv6DownstreamRule ruleA = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A); + verifyAddDownstreamRule(ruleA); + resetNetdAndBpfMaps(); + + recvNewNeigh(myIfindex, NEIGH_B, NUD_REACHABLE, MAC_B); + final Ipv6DownstreamRule ruleB = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_B, MAC_B); + verifyAddDownstreamRule(ruleB); + resetNetdAndBpfMaps(); + + // Link-local and multicast neighbors are ignored. + recvNewNeigh(myIfindex, NEIGH_LL, NUD_REACHABLE, MAC_A); + verifyNoMoreInteractions(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); + recvNewNeigh(myIfindex, NEIGH_MC, NUD_REACHABLE, MAC_A); + verifyNoMoreInteractions(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); + + // A neighbor that is no longer valid causes the rule to be removed. + // NUD_FAILED events do not have a MAC address. + recvNewNeigh(myIfindex, NEIGH_A, NUD_FAILED, null); + final Ipv6DownstreamRule ruleANull = buildTestDownstreamRule( + UPSTREAM_IFINDEX, NEIGH_A, MAC_NULL); + verifyRemoveDownstreamRule(ruleANull); + resetNetdAndBpfMaps(); + + // A neighbor that is deleted causes the rule to be removed. + recvDelNeigh(myIfindex, NEIGH_B, NUD_STALE, MAC_B); + final Ipv6DownstreamRule ruleBNull = buildTestDownstreamRule( + UPSTREAM_IFINDEX, NEIGH_B, MAC_NULL); + verifyRemoveDownstreamRule(ruleBNull); + resetNetdAndBpfMaps(); + + // Upstream interface changes result in updating the rules. + recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A); + recvNewNeigh(myIfindex, NEIGH_B, NUD_REACHABLE, MAC_B); + resetNetdAndBpfMaps(); + + InOrder inOrder = inOrder(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(UPSTREAM_IFACE2); + lp.setLinkAddresses(UPSTREAM_ADDRESSES); + dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE2, lp, -1); + final Ipv6DownstreamRule ruleA2 = buildTestDownstreamRule( + UPSTREAM_IFINDEX2, NEIGH_A, MAC_A); + final Ipv6DownstreamRule ruleB2 = buildTestDownstreamRule( + UPSTREAM_IFINDEX2, NEIGH_B, MAC_B); + verifyRemoveDownstreamRule(inOrder, ruleA); + verifyRemoveDownstreamRule(inOrder, ruleB); + verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES); + verifyStartUpstreamIpv6Forwarding(inOrder, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES); + verifyAddDownstreamRule(inOrder, ruleA2); + verifyAddDownstreamRule(inOrder, ruleB2); + verifyNoUpstreamIpv6ForwardingChange(inOrder); + resetNetdAndBpfMaps(); + + // Upstream link addresses change result in updating the rules. + LinkProperties lp2 = new LinkProperties(); + lp2.setInterfaceName(UPSTREAM_IFACE2); + lp2.setLinkAddresses(UPSTREAM_ADDRESSES2); + dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE2, lp2, -1); + verifyRemoveDownstreamRule(inOrder, ruleA2); + verifyRemoveDownstreamRule(inOrder, ruleB2); + verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES); + verifyStartUpstreamIpv6Forwarding(inOrder, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2); + verifyAddDownstreamRule(inOrder, ruleA2); + verifyAddDownstreamRule(inOrder, ruleB2); + resetNetdAndBpfMaps(); + + // When the upstream is lost, rules are removed. + dispatchTetherConnectionChanged(ipServer, null, null, 0); + verifyStopUpstreamIpv6Forwarding(inOrder, UPSTREAM_PREFIXES2); + verifyRemoveDownstreamRule(ruleA2); + verifyRemoveDownstreamRule(ruleB2); + // Upstream lost doesn't clear the downstream rules from the maps. + // Do that here. + recvDelNeigh(myIfindex, NEIGH_A, NUD_STALE, MAC_A); + recvDelNeigh(myIfindex, NEIGH_B, NUD_STALE, MAC_B); + resetNetdAndBpfMaps(); + + // If the upstream is IPv4-only, no IPv6 rules are added to BPF map. + dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE); + resetNetdAndBpfMaps(); + recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A); + verifyNoUpstreamIpv6ForwardingChange(null); + // Downstream rules are only added to BpfCoordinator but not BPF map. + verifyNeverAddDownstreamRule(); + verifyNoMoreInteractions(mNetd, mBpfDownstream6Map, mBpfUpstream6Map); + + // Rules can be added again once upstream IPv6 connectivity is available. The existing rules + // with an upstream of NO_UPSTREAM are reapplied. + lp.setInterfaceName(UPSTREAM_IFACE); + dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, lp, -1); + verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES); + verifyAddDownstreamRule(ruleA); + recvNewNeigh(myIfindex, NEIGH_B, NUD_REACHABLE, MAC_B); + verifyAddDownstreamRule(ruleB); + + // If upstream IPv6 connectivity is lost, rules are removed. + resetNetdAndBpfMaps(); + dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, null, 0); + verifyRemoveDownstreamRule(ruleA); + verifyRemoveDownstreamRule(ruleB); + verifyStopUpstreamIpv6Forwarding(null, UPSTREAM_PREFIXES); + + // When upstream IPv6 connectivity comes back, upstream rules are added and downstream rules + // are reapplied. + lp.setInterfaceName(UPSTREAM_IFACE); + dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE, lp, -1); + verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX, UPSTREAM_PREFIXES); + verifyAddDownstreamRule(ruleA); + verifyAddDownstreamRule(ruleB); + resetNetdAndBpfMaps(); + + // When the downstream interface goes down, rules are removed. + ipServer.stop(); + mTestLooper.dispatchAll(); + verifyStopUpstreamIpv6Forwarding(null, UPSTREAM_PREFIXES); + verifyRemoveDownstreamRule(ruleA); + verifyRemoveDownstreamRule(ruleB); + verify(mIpNeighborMonitor).stop(); + resetNetdAndBpfMaps(); + } + + @Test + public void enableDisableUsingBpfOffload() throws Exception { + final int myIfindex = DOWNSTREAM_IFINDEX; + + // Expect that rules can be only added/removed when the BPF offload config is enabled. + // Note that the BPF offload disabled case is not a realistic test case. Because IP + // neighbor monitor doesn't start if BPF offload is disabled, there should have no + // neighbor event listening. This is used for testing the protection check just in case. + // TODO: Perhaps remove the BPF offload disabled case test once this check isn't needed + // anymore. + + // [1] Enable BPF offload. + // A neighbor that is added or deleted causes the rule to be added or removed. + final BpfCoordinator coordinator = makeBpfCoordinator(); + final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator); + resetNetdAndBpfMaps(); + + recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A); + final Ipv6DownstreamRule rule = buildTestDownstreamRule(UPSTREAM_IFINDEX, NEIGH_A, MAC_A); + verifyAddDownstreamRule(rule); + resetNetdAndBpfMaps(); + + recvDelNeigh(myIfindex, NEIGH_A, NUD_STALE, MAC_A); + final Ipv6DownstreamRule ruleNull = buildTestDownstreamRule( + UPSTREAM_IFINDEX, NEIGH_A, MAC_NULL); + verifyRemoveDownstreamRule(ruleNull); + resetNetdAndBpfMaps(); + + // Upstream IPv6 connectivity change causes upstream rules change. + LinkProperties lp2 = new LinkProperties(); + lp2.setInterfaceName(UPSTREAM_IFACE2); + lp2.setLinkAddresses(UPSTREAM_ADDRESSES2); + dispatchTetherConnectionChanged(ipServer, UPSTREAM_IFACE2, lp2, 0); + verifyStartUpstreamIpv6Forwarding(null, UPSTREAM_IFINDEX2, UPSTREAM_PREFIXES2); + resetNetdAndBpfMaps(); + + // [2] Disable BPF offload. + // A neighbor that is added or deleted doesn’t cause the rule to be added or removed. + when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(false); + final BpfCoordinator coordinator2 = makeBpfCoordinator(); + final IpServer ipServer2 = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator2); + verifyNoUpstreamIpv6ForwardingChange(null); + resetNetdAndBpfMaps(); + + recvNewNeigh(myIfindex, NEIGH_A, NUD_REACHABLE, MAC_A); + verifyNeverAddDownstreamRule(); + resetNetdAndBpfMaps(); + + recvDelNeigh(myIfindex, NEIGH_A, NUD_STALE, MAC_A); + verifyNeverRemoveDownstreamRule(); + resetNetdAndBpfMaps(); + + // Upstream IPv6 connectivity change doesn't cause the rule to be added or removed. + dispatchTetherConnectionChanged(ipServer2, UPSTREAM_IFACE2, lp2, 0); + verifyNoUpstreamIpv6ForwardingChange(null); + verifyNeverRemoveDownstreamRule(); + resetNetdAndBpfMaps(); + } + + @Test + public void doesNotStartIpNeighborMonitorIfBpfOffloadDisabled() throws Exception { + when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(false); + final BpfCoordinator coordinator = makeBpfCoordinator(); + final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator); + + // IP neighbor monitor doesn't start if BPF offload is disabled. + verify(mIpNeighborMonitor, never()).start(); + } + + @Test + public void testSkipVirtualNetworkInBpf() throws Exception { + final BpfCoordinator coordinator = makeBpfCoordinator(); + final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator); + final LinkProperties v6Only = new LinkProperties(); + v6Only.setInterfaceName(IPSEC_IFACE); + v6Only.setLinkAddresses(UPSTREAM_ADDRESSES); + + resetNetdAndBpfMaps(); + dispatchTetherConnectionChanged(ipServer, IPSEC_IFACE, v6Only, 0); + verify(mNetd).tetherAddForward(DOWNSTREAM_IFACE, IPSEC_IFACE); + verify(mNetd).ipfwdAddInterfaceForward(DOWNSTREAM_IFACE, IPSEC_IFACE); + verifyNeverAddUpstreamRule(); + + recvNewNeigh(DOWNSTREAM_IFINDEX, NEIGH_A, NUD_REACHABLE, MAC_A); + verifyNeverAddDownstreamRule(); + } + + @Test + public void addRemoveTetherClient() throws Exception { + final BpfCoordinator coordinator = makeBpfCoordinator(); + final IpServer ipServer = makeAndStartIpServer(DOWNSTREAM_IFACE, coordinator); + final int myIfindex = DOWNSTREAM_IFINDEX; + final int notMyIfindex = myIfindex - 1; + + final InetAddress neighA = InetAddresses.parseNumericAddress("192.168.80.1"); + final InetAddress neighB = InetAddresses.parseNumericAddress("192.168.80.2"); + final InetAddress neighLL = InetAddresses.parseNumericAddress("169.254.0.1"); + final InetAddress neighMC = InetAddresses.parseNumericAddress("224.0.0.1"); + + // Events on other interfaces are ignored. + recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, MAC_A); + assertNull(mTetherClients.get(ipServer)); + + // Events on this interface are received and sent to BpfCoordinator. + recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, MAC_A); + assertClientInfoExists(ipServer, + new ClientInfo(myIfindex, DOWNSTREAM_MAC, (Inet4Address) neighA, MAC_A)); + + recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, MAC_B); + assertClientInfoExists(ipServer, + new ClientInfo(myIfindex, DOWNSTREAM_MAC, (Inet4Address) neighB, MAC_B)); + + // Link-local and multicast neighbors are ignored. + recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, MAC_A); + assertClientInfoDoesNotExist(ipServer, (Inet4Address) neighLL); + recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, MAC_A); + assertClientInfoDoesNotExist(ipServer, (Inet4Address) neighMC); + + // A neighbor that is no longer valid causes the client to be removed. + // NUD_FAILED events do not have a MAC address. + recvNewNeigh(myIfindex, neighA, NUD_FAILED, null); + assertClientInfoDoesNotExist(ipServer, (Inet4Address) neighA); + + // A neighbor that is deleted causes the client to be removed. + recvDelNeigh(myIfindex, neighB, NUD_STALE, MAC_B); + // When last client information is deleted, IpServer will be removed from mTetherClients + assertNull(mTetherClients.get(ipServer)); + } } diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java index 9e287a007f09e6774580d8e63ceb8671b834006b..087be2678f979bdf0b86d7c6d6b354949ae2530f 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/FakeTetheringConfiguration.java @@ -28,9 +28,8 @@ public class FakeTetheringConfiguration extends TetheringConfiguration { FakeTetheringConfiguration(Context ctx, SharedLog log, int id) { super(ctx, log, id, new Dependencies() { @Override - boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace, - @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) { - return defaultEnabled; + boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) { + return false; } @Override @@ -38,6 +37,11 @@ public class FakeTetheringConfiguration extends TetheringConfiguration { boolean defaultValue) { return defaultValue; } + + @Override + boolean isTetherForceUpstreamAutomaticFeatureEnabled() { + return false; + } }); } diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java index b1f875b6155169d7af3f64eebf5303876b212904..4413d268e21d49c97a348cb99a9ff6d40a3327d2 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java @@ -20,6 +20,8 @@ import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_UNIX; import static android.system.OsConstants.SOCK_STREAM; +import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_DESTROY; +import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_NEW; import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_AIDL; import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0; import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_1; @@ -34,6 +36,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -57,7 +60,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.FileDescriptor; @@ -75,8 +77,9 @@ public final class OffloadHardwareInterfaceTest { private OffloadHardwareInterface mOffloadHw; private OffloadHalCallback mOffloadHalCallback; - @Mock private IOffloadHal mIOffload; - @Mock private NativeHandle mNativeHandle; + private IOffloadHal mIOffload; + private NativeHandle mNativeHandle1; + private NativeHandle mNativeHandle2; // Random values to test Netlink message. private static final short TEST_TYPE = 184; @@ -97,7 +100,9 @@ public final class OffloadHardwareInterfaceTest { @Override public NativeHandle createConntrackSocket(final int groups) { - return mNativeHandle; + return groups == (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY) + ? mNativeHandle1 + : mNativeHandle2; } } @@ -105,45 +110,89 @@ public final class OffloadHardwareInterfaceTest { public void setUp() { MockitoAnnotations.initMocks(this); mOffloadHalCallback = new OffloadHalCallback(); - when(mIOffload.initOffload(any(NativeHandle.class), any(NativeHandle.class), - any(OffloadHalCallback.class))).thenReturn(true); + mIOffload = mock(IOffloadHal.class); } - private void startOffloadHardwareInterface(int offloadHalVersion) + private void startOffloadHardwareInterface(int offloadHalVersion, boolean isHalInitSuccess) throws Exception { + startOffloadHardwareInterface(offloadHalVersion, isHalInitSuccess, mock(NativeHandle.class), + mock(NativeHandle.class)); + } + + private void startOffloadHardwareInterface(int offloadHalVersion, boolean isHalInitSuccess, + NativeHandle handle1, NativeHandle handle2) throws Exception { final SharedLog log = new SharedLog("test"); final Handler handler = new Handler(mTestLooper.getLooper()); - final int num = offloadHalVersion != OFFLOAD_HAL_VERSION_NONE ? 1 : 0; + final boolean hasNullHandle = handle1 == null || handle2 == null; + // If offloadHalVersion is OFFLOAD_HAL_VERSION_NONE or it has null NativeHandle arguments, + // mIOffload.initOffload() shouldn't be called. + final int initNum = (offloadHalVersion != OFFLOAD_HAL_VERSION_NONE && !hasNullHandle) + ? 1 + : 0; + // If it is HIDL or has null NativeHandle argument, NativeHandles should be closed. + final int handleCloseNum = (hasNullHandle + || offloadHalVersion == OFFLOAD_HAL_VERSION_HIDL_1_0 + || offloadHalVersion == OFFLOAD_HAL_VERSION_HIDL_1_1) ? 1 : 0; + mNativeHandle1 = handle1; + mNativeHandle2 = handle2; + when(mIOffload.initOffload(any(NativeHandle.class), any(NativeHandle.class), + any(OffloadHalCallback.class))).thenReturn(isHalInitSuccess); mOffloadHw = new OffloadHardwareInterface(handler, log, new MyDependencies(handler, log, offloadHalVersion)); - assertEquals(offloadHalVersion, mOffloadHw.initOffload(mOffloadHalCallback)); - verify(mIOffload, times(num)).initOffload(any(NativeHandle.class), any(NativeHandle.class), - eq(mOffloadHalCallback)); + assertEquals(isHalInitSuccess && !hasNullHandle + ? offloadHalVersion + : OFFLOAD_HAL_VERSION_NONE, + mOffloadHw.initOffload(mOffloadHalCallback)); + verify(mIOffload, times(initNum)).initOffload(any(NativeHandle.class), + any(NativeHandle.class), eq(mOffloadHalCallback)); + if (mNativeHandle1 != null) verify(mNativeHandle1, times(handleCloseNum)).close(); + if (mNativeHandle2 != null) verify(mNativeHandle2, times(handleCloseNum)).close(); } @Test public void testInitFailureWithNoHal() throws Exception { - startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_NONE); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_NONE, true); } @Test public void testInitSuccessWithAidl() throws Exception { - startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_AIDL); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_AIDL, true); } @Test public void testInitSuccessWithHidl_1_0() throws Exception { - startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0, true); } @Test public void testInitSuccessWithHidl_1_1() throws Exception { - startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_1); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_1, true); + } + + @Test + public void testInitFailWithAidl() throws Exception { + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_AIDL, false); + } + + @Test + public void testInitFailWithHidl_1_0() throws Exception { + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0, false); + } + + @Test + public void testInitFailWithHidl_1_1() throws Exception { + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_1, false); + } + + @Test + public void testInitFailDueToNullHandles() throws Exception { + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_AIDL, true, mock(NativeHandle.class), + null); } @Test public void testGetForwardedStats() throws Exception { - startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0, true); ForwardedStats stats = new ForwardedStats(12345, 56780); when(mIOffload.getForwardedStats(anyString())).thenReturn(stats); assertEquals(mOffloadHw.getForwardedStats(RMNET0), stats); @@ -152,7 +201,7 @@ public final class OffloadHardwareInterfaceTest { @Test public void testSetLocalPrefixes() throws Exception { - startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0, true); final ArrayList localPrefixes = new ArrayList<>(); localPrefixes.add("127.0.0.0/8"); localPrefixes.add("fe80::/64"); @@ -165,7 +214,7 @@ public final class OffloadHardwareInterfaceTest { @Test public void testSetDataLimit() throws Exception { - startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0, true); final long limit = 12345; when(mIOffload.setDataLimit(anyString(), anyLong())).thenReturn(true); assertTrue(mOffloadHw.setDataLimit(RMNET0, limit)); @@ -177,7 +226,7 @@ public final class OffloadHardwareInterfaceTest { @Test public void testSetDataWarningAndLimitFailureWithHidl_1_0() throws Exception { // Verify V1.0 control HAL would reject the function call with exception. - startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0, true); final long warning = 12345; final long limit = 67890; assertThrows(UnsupportedOperationException.class, @@ -187,7 +236,7 @@ public final class OffloadHardwareInterfaceTest { @Test public void testSetDataWarningAndLimit() throws Exception { // Verify V1.1 control HAL could receive this function call. - startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_1); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_1, true); final long warning = 12345; final long limit = 67890; when(mIOffload.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(true); @@ -199,7 +248,7 @@ public final class OffloadHardwareInterfaceTest { @Test public void testSetUpstreamParameters() throws Exception { - startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0, true); final String v4addr = "192.168.10.1"; final String v4gateway = "192.168.10.255"; final ArrayList v6gws = new ArrayList<>(0); @@ -220,7 +269,7 @@ public final class OffloadHardwareInterfaceTest { @Test public void testUpdateDownstream() throws Exception { - startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0, true); final String ifName = "wlan1"; final String prefix = "192.168.43.0/24"; when(mIOffload.addDownstream(anyString(), anyString())).thenReturn(true); @@ -237,7 +286,7 @@ public final class OffloadHardwareInterfaceTest { @Test public void testSendIpv4NfGenMsg() throws Exception { - startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_HIDL_1_0, true); FileDescriptor writeSocket = new FileDescriptor(); FileDescriptor readSocket = new FileDescriptor(); try { @@ -246,9 +295,9 @@ public final class OffloadHardwareInterfaceTest { fail(); return; } - when(mNativeHandle.getFileDescriptor()).thenReturn(writeSocket); + when(mNativeHandle1.getFileDescriptor()).thenReturn(writeSocket); - mOffloadHw.sendIpv4NfGenMsg(mNativeHandle, TEST_TYPE, TEST_FLAGS); + mOffloadHw.sendIpv4NfGenMsg(mNativeHandle1, TEST_TYPE, TEST_FLAGS); ByteBuffer buffer = ByteBuffer.allocate(9823); // Arbitrary value > expectedLen. buffer.order(ByteOrder.nativeOrder()); diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java index 91b092ad0f1db09041366c143fc5d826f8c0950d..2298a1ad5f27347025abc0aa2c622757d29bf25f 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java @@ -30,6 +30,7 @@ import static com.android.networkstack.tethering.util.PrefixUtils.asIpPrefix; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; @@ -104,6 +105,7 @@ public final class PrivateAddressCoordinatorTest { when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mConnectivityMgr); when(mConnectivityMgr.getAllNetworks()).thenReturn(mAllNetworks); when(mConfig.shouldEnableWifiP2pDedicatedIp()).thenReturn(false); + when(mConfig.isRandomPrefixBaseEnabled()).thenReturn(false); setUpIpServers(); mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext, mConfig)); } @@ -126,16 +128,17 @@ public final class PrivateAddressCoordinatorTest { final LinkAddress newAddress = requestDownstreamAddress(mHotspotIpServer, CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */); - final IpPrefix testDupRequest = asIpPrefix(newAddress); - assertNotEquals(hotspotPrefix, testDupRequest); - assertNotEquals(bluetoothPrefix, testDupRequest); - mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer); + final IpPrefix newHotspotPrefix = asIpPrefix(newAddress); + assertNotEquals(hotspotPrefix, newHotspotPrefix); + assertNotEquals(bluetoothPrefix, newHotspotPrefix); final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer, CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */); final IpPrefix usbPrefix = asIpPrefix(usbAddress); assertNotEquals(usbPrefix, bluetoothPrefix); - assertNotEquals(usbPrefix, hotspotPrefix); + assertNotEquals(usbPrefix, newHotspotPrefix); + + mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer); mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer); } @@ -571,4 +574,41 @@ public final class PrivateAddressCoordinatorTest { assertEquals("Wrong local hotspot prefix: ", new LinkAddress("192.168.134.5/24"), localHotspotAddress); } + + @Test + public void testStartedPrefixRange() throws Exception { + when(mConfig.isRandomPrefixBaseEnabled()).thenReturn(true); + + startedPrefixBaseTest("192.168.0.0/16", 0); + + startedPrefixBaseTest("192.168.0.0/16", 1); + + startedPrefixBaseTest("192.168.0.0/16", 0xffff); + + startedPrefixBaseTest("172.16.0.0/12", 0x10000); + + startedPrefixBaseTest("172.16.0.0/12", 0x11111); + + startedPrefixBaseTest("172.16.0.0/12", 0xfffff); + + startedPrefixBaseTest("10.0.0.0/8", 0x100000); + + startedPrefixBaseTest("10.0.0.0/8", 0x1fffff); + + startedPrefixBaseTest("10.0.0.0/8", 0xffffff); + + startedPrefixBaseTest("192.168.0.0/16", 0x1000000); + } + + private void startedPrefixBaseTest(final String expected, final int randomIntForPrefixBase) + throws Exception { + mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext, mConfig)); + when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomIntForPrefixBase); + final LinkAddress address = requestDownstreamAddress(mHotspotIpServer, + CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */); + final IpPrefix prefixBase = new IpPrefix(expected); + assertTrue(address + " is not part of " + prefixBase, + prefixBase.containsPrefix(asIpPrefix(address))); + + } } diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java index 3382af80d84423e7d884674e4ca2dd6ebdaf0bbb..19c6e5a6a1c2ce1c0466737e006f47cf2cef8057 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java @@ -155,9 +155,8 @@ public class TetheringConfigurationTest { private ArrayMap mMockFlags = new ArrayMap<>(); @Override - boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace, - @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) { - return isMockFlagEnabled(name, defaultEnabled); + boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) { + return isMockFlagEnabled(name, false /* defaultEnabled */); } @Override @@ -172,6 +171,12 @@ public class TetheringConfigurationTest { return isMockFlagEnabled(name, defaultValue); } + @Override + boolean isTetherForceUpstreamAutomaticFeatureEnabled() { + return isMockFlagEnabled(TetheringConfiguration.TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION, + false /* defaultEnabled */); + } + private boolean isMockFlagEnabled(@NonNull String name, boolean defaultEnabled) { final Boolean flag = mMockFlags.getOrDefault(name, defaultEnabled); // Value in the map can also be null @@ -749,4 +754,26 @@ public class TetheringConfigurationTest { new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps); assertEquals(p2pLeasesSubnetPrefixLength, p2pCfg.getP2pLeasesSubnetPrefixLength()); } + + private void setTetherEnableSyncSMFlagEnabled(Boolean enabled) { + mDeps.setFeatureEnabled(TetheringConfiguration.TETHER_ENABLE_SYNC_SM, enabled); + } + + private void assertEnableSyncSMIs(boolean value) { + assertEquals(value, new TetheringConfiguration( + mMockContext, mLog, INVALID_SUBSCRIPTION_ID, mDeps).isSyncSM()); + } + + @Test + public void testEnableSyncSMFlag() throws Exception { + // Test default disabled + setTetherEnableSyncSMFlagEnabled(null); + assertEnableSyncSMIs(false); + + setTetherEnableSyncSMFlagEnabled(true); + assertEnableSyncSMIs(true); + + setTetherEnableSyncSMFlagEnabled(false); + assertEnableSyncSMIs(false); + } } diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index c15b85e6a13718555a429e27652ba13199ec6988..82b884509996ba5bee6289cdd2cd2f9926e3dc31 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -68,6 +68,7 @@ import static com.android.modules.utils.build.SdkLevel.isAtLeastS; import static com.android.modules.utils.build.SdkLevel.isAtLeastT; import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH; import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH; +import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_HIDL_1_0; import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE; import static com.android.networkstack.tethering.TestConnectivityManager.BROADCAST_FIRST; @@ -141,6 +142,7 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.RouteInfo; +import android.net.RoutingCoordinatorManager; import android.net.TetherStatesParcel; import android.net.TetheredClient; import android.net.TetheredClient.AddressInfo; @@ -157,7 +159,6 @@ import android.net.dhcp.IDhcpServer; import android.net.ip.DadProxy; import android.net.ip.IpServer; import android.net.ip.RouterAdvertisementDaemon; -import android.net.util.NetworkConstants; import android.net.wifi.SoftApConfiguration; import android.net.wifi.WifiClient; import android.net.wifi.WifiManager; @@ -186,11 +187,11 @@ import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.util.StateMachine; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.FakeSettingsProvider; import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.InterfaceParams; +import com.android.net.module.util.SdkUtil.LateSdk; import com.android.net.module.util.SharedLog; import com.android.net.module.util.ip.IpNeighborMonitor; import com.android.networkstack.apishim.common.BluetoothPanShim; @@ -240,6 +241,7 @@ public class TetheringTest { private static final String TEST_RNDIS_IFNAME = "test_rndis0"; private static final String TEST_WIFI_IFNAME = "test_wlan0"; private static final String TEST_WLAN_IFNAME = "test_wlan1"; + private static final String TEST_WLAN2_IFNAME = "test_wlan2"; private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0"; private static final String TEST_NCM_IFNAME = "test_ncm0"; private static final String TEST_ETH_IFNAME = "test_eth0"; @@ -299,7 +301,7 @@ public class TetheringTest { // Like so many Android system APIs, these cannot be mocked because it is marked final. // We have to use the real versions. private final PersistableBundle mCarrierConfig = new PersistableBundle(); - private final TestLooper mLooper = new TestLooper(); + private TestLooper mLooper; private Vector mIntents; private BroadcastInterceptingContext mServiceContext; @@ -307,6 +309,7 @@ public class TetheringTest { private BroadcastReceiver mBroadcastReceiver; private Tethering mTethering; private TestTetheringEventCallback mTetheringEventCallback; + private Tethering.TetherMainSM mTetherMainSM; private PhoneStateListener mPhoneStateListener; private InterfaceConfigurationParcel mInterfaceConfiguration; private TetheringConfiguration mConfig; @@ -316,6 +319,7 @@ public class TetheringTest { private SoftApCallback mSoftApCallback; private SoftApCallback mLocalOnlyHotspotCallback; private UpstreamNetworkMonitor mUpstreamNetworkMonitor; + private UpstreamNetworkMonitor.EventListener mEventListener; private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim; private TestConnectivityManager mCm; @@ -392,6 +396,7 @@ public class TetheringTest { assertTrue("Non-mocked interface " + ifName, ifName.equals(TEST_RNDIS_IFNAME) || ifName.equals(TEST_WLAN_IFNAME) + || ifName.equals(TEST_WLAN2_IFNAME) || ifName.equals(TEST_WIFI_IFNAME) || ifName.equals(TEST_MOBILE_IFNAME) || ifName.equals(TEST_DUN_IFNAME) @@ -400,8 +405,9 @@ public class TetheringTest { || ifName.equals(TEST_ETH_IFNAME) || ifName.equals(TEST_BT_IFNAME)); final String[] ifaces = new String[] { - TEST_RNDIS_IFNAME, TEST_WLAN_IFNAME, TEST_WIFI_IFNAME, TEST_MOBILE_IFNAME, - TEST_DUN_IFNAME, TEST_P2P_IFNAME, TEST_NCM_IFNAME, TEST_ETH_IFNAME}; + TEST_RNDIS_IFNAME, TEST_WLAN_IFNAME, TEST_WLAN2_IFNAME, TEST_WIFI_IFNAME, + TEST_MOBILE_IFNAME, TEST_DUN_IFNAME, TEST_P2P_IFNAME, TEST_NCM_IFNAME, + TEST_ETH_IFNAME}; return new InterfaceParams(ifName, CollectionUtils.indexOf(ifaces, ifName) + IFINDEX_OFFSET, MacAddress.ALL_ZEROS_ADDRESS); @@ -427,58 +433,63 @@ public class TetheringTest { } public class MockTetheringDependencies extends TetheringDependencies { - StateMachine mUpstreamNetworkMonitorSM; - ArrayList mIpv6CoordinatorNotifyList; + ArrayList mAllDownstreams; @Override - public BpfCoordinator getBpfCoordinator( + public BpfCoordinator makeBpfCoordinator( BpfCoordinator.Dependencies deps) { return mBpfCoordinator; } @Override - public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) { + public OffloadHardwareInterface makeOffloadHardwareInterface(Handler h, SharedLog log) { return mOffloadHardwareInterface; } @Override - public OffloadController getOffloadController(Handler h, SharedLog log, + public OffloadController makeOffloadController(Handler h, SharedLog log, OffloadController.Dependencies deps) { - mOffloadCtrl = spy(super.getOffloadController(h, log, deps)); + mOffloadCtrl = spy(super.makeOffloadController(h, log, deps)); // Return real object here instead of mock because // testReportFailCallbackIfOffloadNotSupported depend on real OffloadController object. return mOffloadCtrl; } @Override - public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, - StateMachine target, SharedLog log, int what) { + public UpstreamNetworkMonitor makeUpstreamNetworkMonitor(Context ctx, Handler h, + SharedLog log, UpstreamNetworkMonitor.EventListener listener) { // Use a real object instead of a mock so that some tests can use a real UNM and some // can use a mock. - mUpstreamNetworkMonitorSM = target; - mUpstreamNetworkMonitor = spy(super.getUpstreamNetworkMonitor(ctx, target, log, what)); + mEventListener = listener; + mUpstreamNetworkMonitor = spy(super.makeUpstreamNetworkMonitor(ctx, h, log, listener)); return mUpstreamNetworkMonitor; } @Override - public IPv6TetheringCoordinator getIPv6TetheringCoordinator( + public IPv6TetheringCoordinator makeIPv6TetheringCoordinator( ArrayList notifyList, SharedLog log) { - mIpv6CoordinatorNotifyList = notifyList; + mAllDownstreams = notifyList; return mIPv6TetheringCoordinator; } @Override - public IpServer.Dependencies getIpServerDependencies() { + public IpServer.Dependencies makeIpServerDependencies() { return mIpServerDependencies; } @Override - public EntitlementManager getEntitlementManager(Context ctx, Handler h, SharedLog log, + public EntitlementManager makeEntitlementManager(Context ctx, Handler h, SharedLog log, Runnable callback) { - mEntitleMgr = spy(super.getEntitlementManager(ctx, h, log, callback)); + mEntitleMgr = spy(super.makeEntitlementManager(ctx, h, log, callback)); return mEntitleMgr; } + @Nullable + @Override + public LateSdk getRoutingCoordinator(final Context context) { + return new LateSdk<>(null); + } + @Override public TetheringConfiguration generateTetheringConfiguration(Context ctx, SharedLog log, int subId) { @@ -492,7 +503,7 @@ public class TetheringTest { } @Override - public Looper getTetheringLooper() { + public Looper makeTetheringLooper() { return mLooper.getLooper(); } @@ -507,7 +518,7 @@ public class TetheringTest { } @Override - public TetheringNotificationUpdater getNotificationUpdater(Context ctx, Looper looper) { + public TetheringNotificationUpdater makeNotificationUpdater(Context ctx, Looper looper) { return mNotificationUpdater; } @@ -517,19 +528,19 @@ public class TetheringTest { } @Override - public TetheringMetrics getTetheringMetrics() { + public TetheringMetrics makeTetheringMetrics() { return mTetheringMetrics; } @Override - public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx, + public PrivateAddressCoordinator makePrivateAddressCoordinator(Context ctx, TetheringConfiguration cfg) { - mPrivateAddressCoordinator = super.getPrivateAddressCoordinator(ctx, cfg); + mPrivateAddressCoordinator = super.makePrivateAddressCoordinator(ctx, cfg); return mPrivateAddressCoordinator; } @Override - public BluetoothPanShim getBluetoothPanShim(BluetoothPan pan) { + public BluetoothPanShim makeBluetoothPanShim(BluetoothPan pan) { try { when(mBluetoothPanShim.requestTetheredInterface( any(), any())).thenReturn(mTetheredInterfaceRequestShim); @@ -556,7 +567,7 @@ public class TetheringTest { prop.addDnsServer(InetAddresses.parseNumericAddress("2001:db8::2")); prop.addLinkAddress( new LinkAddress(InetAddresses.parseNumericAddress("2001:db8::"), - NetworkConstants.RFC7421_PREFIX_LENGTH)); + RFC7421_PREFIX_LENGTH)); prop.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), InetAddresses.parseNumericAddress("2001:db8::1"), interfaceName, RTN_UNICAST)); @@ -642,8 +653,8 @@ public class TetheringTest { false); when(mNetd.interfaceGetList()) .thenReturn(new String[] { - TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_RNDIS_IFNAME, TEST_P2P_IFNAME, - TEST_NCM_IFNAME, TEST_ETH_IFNAME, TEST_BT_IFNAME}); + TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_WLAN2_IFNAME, TEST_RNDIS_IFNAME, + TEST_P2P_IFNAME, TEST_NCM_IFNAME, TEST_ETH_IFNAME, TEST_BT_IFNAME}); when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn(""); mInterfaceConfiguration = new InterfaceConfigurationParcel(); mInterfaceConfiguration.flags = new String[0]; @@ -669,7 +680,15 @@ public class TetheringTest { mCm = spy(new TestConnectivityManager(mServiceContext, mock(IConnectivityManager.class))); - mTethering = makeTethering(); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)).thenReturn(true); + } + + // In order to interact with syncSM from the test, tethering must be created in test thread. + private void initTetheringOnTestThread() throws Exception { + mLooper = new TestLooper(); + mTethering = new Tethering(mTetheringDependencies); + mTetherMainSM = mTethering.getTetherMainSMForTesting(); verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any()); verify(mNetd).registerUnsolicitedEventListener(any()); verifyDefaultNetworkRequestFiled(); @@ -693,9 +712,6 @@ public class TetheringTest { localOnlyCallbackCaptor.capture()); mLocalOnlyHotspotCallback = localOnlyCallbackCaptor.getValue(); } - - when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true); - when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)).thenReturn(true); } private void setTetheringSupported(final boolean supported) { @@ -727,10 +743,6 @@ public class TetheringTest { doReturn(upstreamState).when(mUpstreamNetworkMonitor).selectPreferredUpstreamType(any()); } - private Tethering makeTethering() { - return new Tethering(mTetheringDependencies); - } - private TetheringRequestParcel createTetheringRequestParcel(final int type) { return createTetheringRequestParcel(type, null, null, false, CONNECTIVITY_SCOPE_GLOBAL); } @@ -874,6 +886,7 @@ public class TetheringTest { public void failingLocalOnlyHotspotLegacyApBroadcast( boolean emulateInterfaceStatusChanged) throws Exception { + initTetheringOnTestThread(); // Emulate externally-visible WifiManager effects, causing the // per-interface state machine to start up, and telling us that // hotspot mode is to be started. @@ -925,6 +938,7 @@ public class TetheringTest { @Test public void testUsbConfiguredBroadcastStartsTethering() throws Exception { + initTetheringOnTestThread(); UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState(); initTetheringUpstream(upstreamState); prepareUsbTethering(); @@ -1001,6 +1015,7 @@ public class TetheringTest { public void workingLocalOnlyHotspotEnrichedApBroadcast( boolean emulateInterfaceStatusChanged) throws Exception { + initTetheringOnTestThread(); // Emulate externally-visible WifiManager effects, causing the // per-interface state machine to start up, and telling us that // hotspot mode is to be started. @@ -1026,7 +1041,7 @@ public class TetheringTest { */ private void sendIPv6TetherUpdates(UpstreamNetworkState upstreamState) { // IPv6TetheringCoordinator must have been notified of downstream - for (IpServer ipSrv : mTetheringDependencies.mIpv6CoordinatorNotifyList) { + for (IpServer ipSrv : mTetheringDependencies.mAllDownstreams) { UpstreamNetworkState ipv6OnlyState = buildMobileUpstreamState(false, true, false); ipSrv.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, 0, 0, upstreamState.linkProperties.isIpv6Provisioned() @@ -1064,6 +1079,7 @@ public class TetheringTest { @Test public void workingMobileUsbTethering_IPv4() throws Exception { + initTetheringOnTestThread(); UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState(); runUsbTethering(upstreamState); @@ -1078,7 +1094,8 @@ public class TetheringTest { } @Test - public void workingMobileUsbTethering_IPv4LegacyDhcp() { + public void workingMobileUsbTethering_IPv4LegacyDhcp() throws Exception { + initTetheringOnTestThread(); when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn( true); sendConfigurationChanged(); @@ -1091,6 +1108,7 @@ public class TetheringTest { @Test public void workingMobileUsbTethering_IPv6() throws Exception { + initTetheringOnTestThread(); UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState(); runUsbTethering(upstreamState); @@ -1106,6 +1124,7 @@ public class TetheringTest { @Test public void workingMobileUsbTethering_DualStack() throws Exception { + initTetheringOnTestThread(); UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState(); runUsbTethering(upstreamState); @@ -1123,6 +1142,7 @@ public class TetheringTest { @Test public void workingMobileUsbTethering_MultipleUpstreams() throws Exception { + initTetheringOnTestThread(); UpstreamNetworkState upstreamState = buildMobile464xlatUpstreamState(); runUsbTethering(upstreamState); @@ -1142,6 +1162,7 @@ public class TetheringTest { @Test public void workingMobileUsbTethering_v6Then464xlat() throws Exception { + initTetheringOnTestThread(); when(mResources.getInteger(R.integer.config_tether_usb_functions)).thenReturn( TetheringConfiguration.TETHER_USB_NCM_FUNCTION); when(mResources.getStringArray(R.array.config_tether_usb_regexs)) @@ -1162,10 +1183,7 @@ public class TetheringTest { initTetheringUpstream(upstreamState); // Upstream LinkProperties changed: UpstreamNetworkMonitor sends EVENT_ON_LINKPROPERTIES. - mTetheringDependencies.mUpstreamNetworkMonitorSM.sendMessage( - Tethering.TetherMainSM.EVENT_UPSTREAM_CALLBACK, - UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES, - 0, + mEventListener.onUpstreamEvent(UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES, upstreamState); mLooper.dispatchAll(); @@ -1183,6 +1201,7 @@ public class TetheringTest { @Test public void configTetherUpstreamAutomaticIgnoresConfigTetherUpstreamTypes() throws Exception { + initTetheringOnTestThread(); when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(true); sendConfigurationChanged(); @@ -1231,6 +1250,7 @@ public class TetheringTest { } private void verifyAutomaticUpstreamSelection(boolean configAutomatic) throws Exception { + initTetheringOnTestThread(); TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState()); TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState()); InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor); @@ -1330,6 +1350,7 @@ public class TetheringTest { @Test @IgnoreAfter(Build.VERSION_CODES.TIRAMISU) public void testLegacyUpstreamSelection() throws Exception { + initTetheringOnTestThread(); TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState()); TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState()); InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor); @@ -1480,6 +1501,7 @@ public class TetheringTest { // +-------+-------+-------+-------+-------+ // private void verifyChooseDunUpstreamByAutomaticMode(boolean configAutomatic) throws Exception { + initTetheringOnTestThread(); // Enable automatic upstream selection. TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState()); TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState()); @@ -1540,6 +1562,7 @@ public class TetheringTest { // @Test public void testChooseDunUpstreamByAutomaticMode_defaultNetworkWifi() throws Exception { + initTetheringOnTestThread(); TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState()); TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState()); TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState()); @@ -1591,6 +1614,7 @@ public class TetheringTest { // @Test public void testChooseDunUpstreamByAutomaticMode_loseDefaultNetworkWifi() throws Exception { + initTetheringOnTestThread(); TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState()); TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState()); final InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor); @@ -1632,6 +1656,7 @@ public class TetheringTest { // @Test public void testChooseDunUpstreamByAutomaticMode_defaultNetworkCell() throws Exception { + initTetheringOnTestThread(); TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState()); TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState()); final InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor); @@ -1676,6 +1701,7 @@ public class TetheringTest { // @Test public void testChooseDunUpstreamByAutomaticMode_loseAndRegainDun() throws Exception { + initTetheringOnTestThread(); TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState()); final InOrder inOrder = inOrder(mCm, mUpstreamNetworkMonitor); setupDunUpstreamTest(true /* configAutomatic */, inOrder); @@ -1717,6 +1743,7 @@ public class TetheringTest { @Test public void testChooseDunUpstreamByAutomaticMode_switchDefaultFromWifiToCell() throws Exception { + initTetheringOnTestThread(); TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState()); TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState()); TestNetworkAgent dun = new TestNetworkAgent(mCm, buildDunUpstreamState()); @@ -1754,6 +1781,7 @@ public class TetheringTest { @Test @IgnoreAfter(Build.VERSION_CODES.TIRAMISU) public void testChooseDunUpstreamByLegacyMode() throws Exception { + initTetheringOnTestThread(); // Enable Legacy upstream selection. TestNetworkAgent mobile = new TestNetworkAgent(mCm, buildMobileDualStackUpstreamState()); TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState()); @@ -1846,6 +1874,7 @@ public class TetheringTest { @Test public void workingNcmTethering() throws Exception { + initTetheringOnTestThread(); runNcmTethering(); verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks( @@ -1853,7 +1882,8 @@ public class TetheringTest { } @Test - public void workingNcmTethering_LegacyDhcp() { + public void workingNcmTethering_LegacyDhcp() throws Exception { + initTetheringOnTestThread(); when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn( true); sendConfigurationChanged(); @@ -1875,6 +1905,7 @@ public class TetheringTest { // TODO: Test with and without interfaceStatusChanged(). @Test public void failingWifiTetheringLegacyApBroadcast() throws Exception { + initTetheringOnTestThread(); when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true); // Emulate pressing the WiFi tethering button. @@ -1903,6 +1934,7 @@ public class TetheringTest { // TODO: Test with and without interfaceStatusChanged(). @Test public void workingWifiTetheringEnrichedApBroadcast() throws Exception { + initTetheringOnTestThread(); when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true); // Emulate pressing the WiFi tethering button. @@ -1951,6 +1983,7 @@ public class TetheringTest { // TODO: Test with and without interfaceStatusChanged(). @Test public void failureEnablingIpForwarding() throws Exception { + initTetheringOnTestThread(); when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true); doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME); @@ -2098,7 +2131,8 @@ public class TetheringTest { } @Test - public void testUntetherUsbWhenRestrictionIsOn() { + public void testUntetherUsbWhenRestrictionIsOn() throws Exception { + initTetheringOnTestThread(); // Start usb tethering and check that usb interface is tethered. final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState(); runUsbTethering(upstreamState); @@ -2275,6 +2309,7 @@ public class TetheringTest { @Test public void testRegisterTetheringEventCallback() throws Exception { + initTetheringOnTestThread(); TestTetheringEventCallback callback = new TestTetheringEventCallback(); TestTetheringEventCallback callback2 = new TestTetheringEventCallback(); final TetheringInterface wifiIface = new TetheringInterface( @@ -2339,6 +2374,7 @@ public class TetheringTest { @Test public void testReportFailCallbackIfOffloadNotSupported() throws Exception { + initTetheringOnTestThread(); final UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState(); TestTetheringEventCallback callback = new TestTetheringEventCallback(); mTethering.registerTetheringEventCallback(callback); @@ -2378,6 +2414,7 @@ public class TetheringTest { @Test public void testMultiSimAware() throws Exception { + initTetheringOnTestThread(); final TetheringConfiguration initailConfig = mTethering.getTetheringConfiguration(); assertEquals(INVALID_SUBSCRIPTION_ID, initailConfig.activeDataSubId); @@ -2390,6 +2427,7 @@ public class TetheringTest { @Test public void testNoDuplicatedEthernetRequest() throws Exception { + initTetheringOnTestThread(); final TetheredInterfaceRequest mockRequest = mock(TetheredInterfaceRequest.class); when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest); mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), TEST_CALLER_PKG, @@ -2410,6 +2448,7 @@ public class TetheringTest { private void workingWifiP2pGroupOwner( boolean emulateInterfaceStatusChanged) throws Exception { + initTetheringOnTestThread(); if (emulateInterfaceStatusChanged) { mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true); } @@ -2449,6 +2488,7 @@ public class TetheringTest { private void workingWifiP2pGroupClient( boolean emulateInterfaceStatusChanged) throws Exception { + initTetheringOnTestThread(); if (emulateInterfaceStatusChanged) { mTethering.interfaceStatusChanged(TEST_P2P_IFNAME, true); } @@ -2489,6 +2529,7 @@ public class TetheringTest { private void workingWifiP2pGroupOwnerLegacyMode( boolean emulateInterfaceStatusChanged) throws Exception { + initTetheringOnTestThread(); // change to legacy mode and update tethering information by chaning SIM when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs)) .thenReturn(new String[]{}); @@ -2538,7 +2579,8 @@ public class TetheringTest { } @Test - public void testDataSaverChanged() { + public void testDataSaverChanged() throws Exception { + initTetheringOnTestThread(); // Start Tethering. final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState(); runUsbTethering(upstreamState); @@ -2593,6 +2635,7 @@ public class TetheringTest { @Test public void testMultipleStartTethering() throws Exception { + initTetheringOnTestThread(); final LinkAddress serverLinkAddr = new LinkAddress("192.168.20.1/24"); final LinkAddress clientLinkAddr = new LinkAddress("192.168.20.42/24"); final String serverAddr = "192.168.20.1"; @@ -2636,6 +2679,7 @@ public class TetheringTest { @Test public void testRequestStaticIp() throws Exception { + initTetheringOnTestThread(); when(mResources.getInteger(R.integer.config_tether_usb_functions)).thenReturn( TetheringConfiguration.TETHER_USB_NCM_FUNCTION); when(mResources.getStringArray(R.array.config_tether_usb_regexs)) @@ -2665,15 +2709,14 @@ public class TetheringTest { } @Test - public void testUpstreamNetworkChanged() { - final Tethering.TetherMainSM stateMachine = (Tethering.TetherMainSM) - mTetheringDependencies.mUpstreamNetworkMonitorSM; + public void testUpstreamNetworkChanged() throws Exception { + initTetheringOnTestThread(); final InOrder inOrder = inOrder(mNotificationUpdater); // Gain upstream. final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState(); initTetheringUpstream(upstreamState); - stateMachine.chooseUpstreamType(true); + mTetherMainSM.chooseUpstreamType(true); mTetheringEventCallback.expectUpstreamChanged(upstreamState.network); inOrder.verify(mNotificationUpdater) .onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities); @@ -2681,43 +2724,43 @@ public class TetheringTest { // Set the upstream with the same network ID but different object and the same capability. final UpstreamNetworkState upstreamState2 = buildMobileIPv4UpstreamState(); initTetheringUpstream(upstreamState2); - stateMachine.chooseUpstreamType(true); - // Bug: duplicated upstream change event. - mTetheringEventCallback.expectUpstreamChanged(upstreamState2.network); - inOrder.verify(mNotificationUpdater) - .onUpstreamCapabilitiesChanged(upstreamState2.networkCapabilities); + mTetherMainSM.chooseUpstreamType(true); + // Expect that no upstream change event and capabilities changed event. + mTetheringEventCallback.assertNoUpstreamChangeCallback(); + inOrder.verify(mNotificationUpdater, never()).onUpstreamCapabilitiesChanged(any()); // Set the upstream with the same network ID but different object and different capability. final UpstreamNetworkState upstreamState3 = buildMobileIPv4UpstreamState(); assertFalse(upstreamState3.networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)); upstreamState3.networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED); initTetheringUpstream(upstreamState3); - stateMachine.chooseUpstreamType(true); - // Bug: duplicated upstream change event. - mTetheringEventCallback.expectUpstreamChanged(upstreamState3.network); + mTetherMainSM.chooseUpstreamType(true); + // Expect that no upstream change event and capabilities changed event. + mTetheringEventCallback.assertNoUpstreamChangeCallback(); + mTetherMainSM.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState3); inOrder.verify(mNotificationUpdater) .onUpstreamCapabilitiesChanged(upstreamState3.networkCapabilities); + // Lose upstream. initTetheringUpstream(null); - stateMachine.chooseUpstreamType(true); + mTetherMainSM.chooseUpstreamType(true); mTetheringEventCallback.expectUpstreamChanged(NULL_NETWORK); inOrder.verify(mNotificationUpdater).onUpstreamCapabilitiesChanged(null); } @Test - public void testUpstreamCapabilitiesChanged() { - final Tethering.TetherMainSM stateMachine = (Tethering.TetherMainSM) - mTetheringDependencies.mUpstreamNetworkMonitorSM; + public void testUpstreamCapabilitiesChanged() throws Exception { + initTetheringOnTestThread(); final InOrder inOrder = inOrder(mNotificationUpdater); final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState(); initTetheringUpstream(upstreamState); - stateMachine.chooseUpstreamType(true); + mTetherMainSM.chooseUpstreamType(true); inOrder.verify(mNotificationUpdater) .onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities); - stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState); + mTetherMainSM.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState); inOrder.verify(mNotificationUpdater) .onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities); @@ -2726,7 +2769,7 @@ public class TetheringTest { // Expect that capability is changed with new capability VALIDATED. assertFalse(upstreamState.networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)); upstreamState.networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED); - stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState); + mTetherMainSM.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState); inOrder.verify(mNotificationUpdater) .onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities); @@ -2735,12 +2778,13 @@ public class TetheringTest { final UpstreamNetworkState upstreamState2 = new UpstreamNetworkState( upstreamState.linkProperties, upstreamState.networkCapabilities, new Network(WIFI_NETID)); - stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState2); + mTetherMainSM.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState2); inOrder.verify(mNotificationUpdater, never()).onUpstreamCapabilitiesChanged(any()); } @Test public void testUpstreamCapabilitiesChanged_startStopTethering() throws Exception { + initTetheringOnTestThread(); final TestNetworkAgent wifi = new TestNetworkAgent(mCm, buildWifiUpstreamState()); // Start USB tethering with no current upstream. @@ -2762,6 +2806,7 @@ public class TetheringTest { @Test public void testDumpTetheringLog() throws Exception { + initTetheringOnTestThread(); final FileDescriptor mockFd = mock(FileDescriptor.class); final PrintWriter mockPw = mock(PrintWriter.class); runUsbTethering(null); @@ -2775,6 +2820,7 @@ public class TetheringTest { @Test public void testExemptFromEntitlementCheck() throws Exception { + initTetheringOnTestThread(); setupForRequiredProvisioning(); final TetheringRequestParcel wifiNotExemptRequest = createTetheringRequestParcel(TETHERING_WIFI, null, null, false, @@ -2855,41 +2901,48 @@ public class TetheringTest { final String iface, final int transportType) { final UpstreamNetworkState upstream = buildV4UpstreamState(ipv4Address, network, iface, transportType); - mTetheringDependencies.mUpstreamNetworkMonitorSM.sendMessage( - Tethering.TetherMainSM.EVENT_UPSTREAM_CALLBACK, - UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES, - 0, - upstream); + mEventListener.onUpstreamEvent(UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES, upstream); mLooper.dispatchAll(); } @Test public void testHandleIpConflict() throws Exception { + initTetheringOnTestThread(); final Network wifiNetwork = new Network(200); final Network[] allNetworks = { wifiNetwork }; doReturn(allNetworks).when(mCm).getAllNetworks(); + InOrder inOrder = inOrder(mUsbManager, mNetd); runUsbTethering(null); + + inOrder.verify(mNetd).tetherInterfaceAdd(TEST_RNDIS_IFNAME); + final ArgumentCaptor ifaceConfigCaptor = ArgumentCaptor.forClass(InterfaceConfigurationParcel.class); verify(mNetd).interfaceSetCfg(ifaceConfigCaptor.capture()); final String ipv4Address = ifaceConfigCaptor.getValue().ipv4Addr; verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks( any(), any()); - reset(mUsbManager); // Cause a prefix conflict by assigning a /30 out of the downstream's /24 to the upstream. updateV4Upstream(new LinkAddress(InetAddresses.parseNumericAddress(ipv4Address), 30), wifiNetwork, TEST_WIFI_IFNAME, TRANSPORT_WIFI); // verify turn off usb tethering - verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE); + inOrder.verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE); sendUsbBroadcast(true, true, -1 /* function */); mLooper.dispatchAll(); + inOrder.verify(mNetd).tetherInterfaceRemove(TEST_RNDIS_IFNAME); + // verify restart usb tethering - verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_RNDIS); + inOrder.verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_RNDIS); + + sendUsbBroadcast(true, true, TETHER_USB_RNDIS_FUNCTION); + mLooper.dispatchAll(); + inOrder.verify(mNetd).tetherInterfaceAdd(TEST_RNDIS_IFNAME); } @Test public void testNoAddressAvailable() throws Exception { + initTetheringOnTestThread(); final Network wifiNetwork = new Network(200); final Network btNetwork = new Network(201); final Network mobileNetwork = new Network(202); @@ -2951,6 +3004,7 @@ public class TetheringTest { @Test public void testProvisioningNeededButUnavailable() throws Exception { + initTetheringOnTestThread(); assertTrue(mTethering.isTetheringSupported()); verify(mPackageManager, never()).getPackageInfo(PROVISIONING_APP_NAME[0], GET_ACTIVITIES); @@ -2968,6 +3022,7 @@ public class TetheringTest { @Test public void testUpdateConnectedClients() throws Exception { + initTetheringOnTestThread(); TestTetheringEventCallback callback = new TestTetheringEventCallback(); runAsShell(NETWORK_SETTINGS, () -> { mTethering.registerTetheringEventCallback(callback); @@ -2988,9 +3043,9 @@ public class TetheringTest { final MacAddress testMac1 = MacAddress.fromString("11:11:11:11:11:11"); final DhcpLeaseParcelable p2pLease = createDhcpLeaseParcelable("clientId1", testMac1, "192.168.50.24", 24, Long.MAX_VALUE, "test1"); - final List p2pClients = notifyDhcpLeasesChanged(TETHERING_WIFI_P2P, + final List connectedClients = notifyDhcpLeasesChanged(TETHERING_WIFI_P2P, eventCallbacks, p2pLease); - callback.expectTetheredClientChanged(p2pClients); + callback.expectTetheredClientChanged(connectedClients); reset(mDhcpServer); // Run wifi tethering. @@ -2999,21 +3054,11 @@ public class TetheringTest { verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks( any(), dhcpEventCbsCaptor.capture()); eventCallbacks = dhcpEventCbsCaptor.getValue(); - // Update mac address from softAp callback before getting dhcp lease. final MacAddress testMac2 = MacAddress.fromString("22:22:22:22:22:22"); - final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac2, - false /* isLocalOnly */); - final List p2pAndNoAddrClients = new ArrayList<>(p2pClients); - p2pAndNoAddrClients.add(noAddrClient); - callback.expectTetheredClientChanged(p2pAndNoAddrClients); - - // Update dhcp lease for wifi tethering. final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId2", testMac2, "192.168.43.24", 24, Long.MAX_VALUE, "test2"); - final List p2pAndWifiClients = new ArrayList<>(p2pClients); - p2pAndWifiClients.addAll(notifyDhcpLeasesChanged(TETHERING_WIFI, - eventCallbacks, wifiLease)); - callback.expectTetheredClientChanged(p2pAndWifiClients); + verifyHotspotClientUpdate(false /* isLocalOnly */, testMac2, wifiLease, connectedClients, + eventCallbacks, callback); // Test onStarted callback that register second callback when tethering is running. TestTetheringEventCallback callback2 = new TestTetheringEventCallback(); @@ -3021,12 +3066,13 @@ public class TetheringTest { mTethering.registerTetheringEventCallback(callback2); mLooper.dispatchAll(); }); - callback2.expectTetheredClientChanged(p2pAndWifiClients); + callback2.expectTetheredClientChanged(connectedClients); } @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2) public void testUpdateConnectedClientsForLocalOnlyHotspot() throws Exception { + initTetheringOnTestThread(); TestTetheringEventCallback callback = new TestTetheringEventCallback(); runAsShell(NETWORK_SETTINGS, () -> { mTethering.registerTetheringEventCallback(callback); @@ -3043,26 +3089,87 @@ public class TetheringTest { verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks( any(), dhcpEventCbsCaptor.capture()); final IDhcpEventCallbacks eventCallbacks = dhcpEventCbsCaptor.getValue(); - // Update mac address from softAp callback before getting dhcp lease. - final MacAddress testMac = MacAddress.fromString("22:22:22:22:22:22"); - final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac, - true /* isLocalOnly */); - final List noAddrLocalOnlyClients = new ArrayList<>(); - noAddrLocalOnlyClients.add(noAddrClient); - callback.expectTetheredClientChanged(noAddrLocalOnlyClients); - // Update dhcp lease for local only hotspot. + final List connectedClients = new ArrayList<>(); + final MacAddress testMac = MacAddress.fromString("22:22:22:22:22:22"); final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId", testMac, "192.168.43.24", 24, Long.MAX_VALUE, "test"); - final List localOnlyClients = notifyDhcpLeasesChanged(TETHERING_WIFI, - eventCallbacks, wifiLease); - callback.expectTetheredClientChanged(localOnlyClients); + verifyHotspotClientUpdate(true /* isLocalOnly */, testMac, wifiLease, connectedClients, + eventCallbacks, callback); // Client disconnect from local only hotspot. mLocalOnlyHotspotCallback.onConnectedClientsChanged(Collections.emptyList()); callback.expectTetheredClientChanged(Collections.emptyList()); } + @Test + @IgnoreUpTo(Build.VERSION_CODES.S_V2) + public void testConnectedClientsForSapAndLohsConcurrency() throws Exception { + initTetheringOnTestThread(); + TestTetheringEventCallback callback = new TestTetheringEventCallback(); + runAsShell(NETWORK_SETTINGS, () -> { + mTethering.registerTetheringEventCallback(callback); + mLooper.dispatchAll(); + }); + callback.expectTetheredClientChanged(Collections.emptyList()); + + mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); + sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); + final ArgumentCaptor dhcpEventCbsCaptor = + ArgumentCaptor.forClass(IDhcpEventCallbacks.class); + verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks( + any(), dhcpEventCbsCaptor.capture()); + IDhcpEventCallbacks eventCallbacks = dhcpEventCbsCaptor.getValue(); + final List connectedClients = new ArrayList<>(); + final MacAddress wifiMac = MacAddress.fromString("11:11:11:11:11:11"); + final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId", wifiMac, + "192.168.2.12", 24, Long.MAX_VALUE, "test"); + verifyHotspotClientUpdate(false /* isLocalOnly */, wifiMac, wifiLease, connectedClients, + eventCallbacks, callback); + reset(mDhcpServer); + + mTethering.interfaceStatusChanged(TEST_WLAN2_IFNAME, true); + sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN2_IFNAME, IFACE_IP_MODE_LOCAL_ONLY); + + verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks( + any(), dhcpEventCbsCaptor.capture()); + eventCallbacks = dhcpEventCbsCaptor.getValue(); + final MacAddress localOnlyMac = MacAddress.fromString("22:22:22:22:22:22"); + final DhcpLeaseParcelable localOnlyLease = createDhcpLeaseParcelable("clientId", + localOnlyMac, "192.168.43.24", 24, Long.MAX_VALUE, "test"); + verifyHotspotClientUpdate(true /* isLocalOnly */, localOnlyMac, localOnlyLease, + connectedClients, eventCallbacks, callback); + + assertTrue(isIpServerActive(TETHERING_WIFI, TEST_WLAN_IFNAME, IpServer.STATE_TETHERED)); + assertTrue(isIpServerActive(TETHERING_WIFI, TEST_WLAN2_IFNAME, IpServer.STATE_LOCAL_ONLY)); + } + + private boolean isIpServerActive(int type, String ifName, int mode) { + for (IpServer ipSrv : mTetheringDependencies.mAllDownstreams) { + if (ipSrv.interfaceType() == type && ipSrv.interfaceName().equals(ifName) + && ipSrv.servingMode() == mode) { + return true; + } + } + + return false; + } + + private void verifyHotspotClientUpdate(final boolean isLocalOnly, final MacAddress testMac, + final DhcpLeaseParcelable dhcpLease, final List currentClients, + final IDhcpEventCallbacks dhcpCallback, final TestTetheringEventCallback callback) + throws Exception { + // Update mac address from softAp callback before getting dhcp lease. + final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac, isLocalOnly); + final List withNoAddrClients = new ArrayList<>(currentClients); + withNoAddrClients.add(noAddrClient); + callback.expectTetheredClientChanged(withNoAddrClients); + + // Update dhcp lease for hotspot. + currentClients.addAll(notifyDhcpLeasesChanged(TETHERING_WIFI, dhcpCallback, dhcpLease)); + callback.expectTetheredClientChanged(currentClients); + } + private TetheredClient notifyConnectedWifiClientsChanged(final MacAddress mac, boolean isLocalOnly) throws Exception { final ArrayList wifiClients = new ArrayList<>(); @@ -3124,6 +3231,7 @@ public class TetheringTest { @Test public void testBluetoothTethering() throws Exception { + initTetheringOnTestThread(); // Switch to @IgnoreUpTo(Build.VERSION_CODES.S_V2) when it is available for AOSP. assumeTrue(isAtLeastT()); @@ -3160,6 +3268,7 @@ public class TetheringTest { @Test public void testBluetoothTetheringBeforeT() throws Exception { + initTetheringOnTestThread(); // Switch to @IgnoreAfter(Build.VERSION_CODES.S_V2) when it is available for AOSP. assumeFalse(isAtLeastT()); @@ -3207,6 +3316,7 @@ public class TetheringTest { @Test public void testBluetoothServiceDisconnects() throws Exception { + initTetheringOnTestThread(); final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR); mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */); mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), @@ -3361,6 +3471,7 @@ public class TetheringTest { @Test public void testUsbFunctionConfigurationChange() throws Exception { + initTetheringOnTestThread(); // Run TETHERING_NCM. runNcmTethering(); verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks( @@ -3419,6 +3530,7 @@ public class TetheringTest { @Test public void testTetheringSupported() throws Exception { + initTetheringOnTestThread(); final ArraySet expectedTypes = getAllSupportedTetheringTypes(); // Check tethering is supported after initialization. TestTetheringEventCallback callback = new TestTetheringEventCallback(); @@ -3488,6 +3600,30 @@ public class TetheringTest { assertEquals(expectedTypes.size() > 0, mTethering.isTetheringSupported()); callback.expectSupportedTetheringTypes(expectedTypes); } + + @Test + public void testIpv4AddressForSapAndLohsConcurrency() throws Exception { + initTetheringOnTestThread(); + mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true); + sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED); + + ArgumentCaptor ifaceConfigCaptor = + ArgumentCaptor.forClass(InterfaceConfigurationParcel.class); + verify(mNetd).interfaceSetCfg(ifaceConfigCaptor.capture()); + InterfaceConfigurationParcel ifaceConfig = ifaceConfigCaptor.getValue(); + final IpPrefix sapPrefix = new IpPrefix( + InetAddresses.parseNumericAddress(ifaceConfig.ipv4Addr), ifaceConfig.prefixLength); + + mTethering.interfaceStatusChanged(TEST_WLAN2_IFNAME, true); + sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN2_IFNAME, IFACE_IP_MODE_LOCAL_ONLY); + + ifaceConfigCaptor = ArgumentCaptor.forClass(InterfaceConfigurationParcel.class); + verify(mNetd, times(2)).interfaceSetCfg(ifaceConfigCaptor.capture()); + ifaceConfig = ifaceConfigCaptor.getValue(); + final IpPrefix lohsPrefix = new IpPrefix( + InetAddresses.parseNumericAddress(ifaceConfig.ipv4Addr), ifaceConfig.prefixLength); + assertFalse(sapPrefix.equals(lohsPrefix)); + } // TODO: Test that a request for hotspot mode doesn't interfere with an // already operating tethering mode interface. } diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java index e756bd3d6cf40308c4ec50533171b2ce8eec96cf..90fd7099efdafa77ffe028c641f3ddbf90cce374 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java @@ -30,10 +30,12 @@ import static com.android.networkstack.tethering.UpstreamNetworkMonitor.TYPE_NON import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -51,27 +53,23 @@ import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.os.Build; import android.os.Handler; -import android.os.Looper; -import android.os.Message; import android.os.test.TestLooper; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; import com.android.net.module.util.SharedLog; import com.android.networkstack.tethering.TestConnectivityManager.NetworkRequestInfo; import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -84,8 +82,6 @@ import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest public class UpstreamNetworkMonitorTest { - private static final int EVENT_UNM_UPDATE = 1; - private static final boolean INCLUDES = true; private static final boolean EXCLUDES = false; @@ -102,32 +98,24 @@ public class UpstreamNetworkMonitorTest { @Mock private EntitlementManager mEntitleMgr; @Mock private IConnectivityManager mCS; @Mock private SharedLog mLog; + @Mock private UpstreamNetworkMonitor.EventListener mListener; - private TestStateMachine mSM; private TestConnectivityManager mCM; private UpstreamNetworkMonitor mUNM; private final TestLooper mLooper = new TestLooper(); + private InOrder mCallbackOrder; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - reset(mContext); - reset(mCS); - reset(mLog); when(mLog.forSubComponent(anyString())).thenReturn(mLog); when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); + mCallbackOrder = inOrder(mListener); mCM = spy(new TestConnectivityManager(mContext, mCS)); when(mContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE))).thenReturn(mCM); - mSM = new TestStateMachine(mLooper.getLooper()); - mUNM = new UpstreamNetworkMonitor(mContext, mSM, mLog, EVENT_UNM_UPDATE); - } - - @After public void tearDown() throws Exception { - if (mSM != null) { - mSM.quit(); - mSM = null; - } + mUNM = new UpstreamNetworkMonitor(mContext, new Handler(mLooper.getLooper()), mLog, + mListener); } @Test @@ -603,14 +591,17 @@ public class UpstreamNetworkMonitorTest { mCM.makeDefaultNetwork(cellAgent); mLooper.dispatchAll(); verifyCurrentLinkProperties(cellAgent); - int messageIndex = mSM.messages.size() - 1; + verifyNotifyNetworkCapabilitiesChange(cellAgent.networkCapabilities); + verifyNotifyLinkPropertiesChange(cellLp); + verifyNotifyDefaultSwitch(cellAgent); + verifyNoMoreInteractions(mListener); addLinkAddresses(cellLp, ipv6Addr1); mCM.sendLinkProperties(cellAgent, false /* updateDefaultFirst */); mLooper.dispatchAll(); verifyCurrentLinkProperties(cellAgent); - verifyNotifyLinkPropertiesChange(messageIndex); - messageIndex = mSM.messages.size() - 1; + verifyNotifyLinkPropertiesChange(cellLp); + verifyNoMoreInteractions(mListener); removeLinkAddresses(cellLp, ipv6Addr1); addLinkAddresses(cellLp, ipv6Addr2); @@ -618,7 +609,8 @@ public class UpstreamNetworkMonitorTest { mLooper.dispatchAll(); assertEquals(cellAgent.linkProperties, mUNM.getCurrentPreferredUpstream().linkProperties); verifyCurrentLinkProperties(cellAgent); - verifyNotifyLinkPropertiesChange(messageIndex); + verifyNotifyLinkPropertiesChange(cellLp); + verifyNoMoreInteractions(mListener); } private void verifyCurrentLinkProperties(TestNetworkAgent agent) { @@ -626,12 +618,33 @@ public class UpstreamNetworkMonitorTest { assertEquals(agent.linkProperties, mUNM.getCurrentPreferredUpstream().linkProperties); } - private void verifyNotifyLinkPropertiesChange(int lastMessageIndex) { - assertEquals(UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES, - mSM.messages.get(++lastMessageIndex).arg1); - assertEquals(UpstreamNetworkMonitor.NOTIFY_LOCAL_PREFIXES, - mSM.messages.get(++lastMessageIndex).arg1); - assertEquals(lastMessageIndex + 1, mSM.messages.size()); + private void verifyNotifyNetworkCapabilitiesChange(final NetworkCapabilities cap) { + mCallbackOrder.verify(mListener).onUpstreamEvent( + eq(UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES), + argThat(uns -> uns instanceof UpstreamNetworkState + && cap.equals(((UpstreamNetworkState) uns).networkCapabilities))); + + } + + private void verifyNotifyLinkPropertiesChange(final LinkProperties lp) { + mCallbackOrder.verify(mListener).onUpstreamEvent( + eq(UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES), + argThat(uns -> uns instanceof UpstreamNetworkState + && lp.equals(((UpstreamNetworkState) uns).linkProperties))); + + mCallbackOrder.verify(mListener).onUpstreamEvent( + eq(UpstreamNetworkMonitor.NOTIFY_LOCAL_PREFIXES), any()); + } + + private void verifyNotifyDefaultSwitch(TestNetworkAgent agent) { + mCallbackOrder.verify(mListener).onUpstreamEvent( + eq(UpstreamNetworkMonitor.EVENT_DEFAULT_SWITCHED), + argThat(uns -> + uns instanceof UpstreamNetworkState + && agent.networkId.equals(((UpstreamNetworkState) uns).network) + && agent.linkProperties.equals(((UpstreamNetworkState) uns).linkProperties) + && agent.networkCapabilities.equals( + ((UpstreamNetworkState) uns).networkCapabilities))); } private void addLinkAddresses(LinkProperties lp, String... addrs) { @@ -673,33 +686,6 @@ public class UpstreamNetworkMonitorTest { return false; } - public static class TestStateMachine extends StateMachine { - public final ArrayList messages = new ArrayList<>(); - private final State mLoggingState = new LoggingState(); - - class LoggingState extends State { - @Override public void enter() { - messages.clear(); - } - - @Override public void exit() { - messages.clear(); - } - - @Override public boolean processMessage(Message msg) { - messages.add(msg); - return true; - } - } - - public TestStateMachine(Looper looper) { - super("UpstreamNetworkMonitor.TestStateMachine", looper); - addState(mLoggingState); - setInitialState(mLoggingState); - super.start(); - } - } - static void assertPrefixSet(Set prefixes, boolean expectation, String... expected) { final Set expectedSet = new HashSet<>(); Collections.addAll(expectedSet, expected); diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..f8e98e34b8d48465869df8a75ca9fddacc4db5ff --- /dev/null +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/StateMachineShimTest.kt @@ -0,0 +1,135 @@ +/** + * Copyright (C) 2023 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.networkstack.tethering.util + +import android.os.Looper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.util.State +import com.android.networkstack.tethering.util.StateMachineShim.AsyncStateMachine +import com.android.networkstack.tethering.util.StateMachineShim.Dependencies +import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo +import kotlin.test.assertFailsWith +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions + +@RunWith(AndroidJUnit4::class) +@SmallTest +class StateMachineShimTest { + private val mSyncSM = mock(SyncStateMachine::class.java) + private val mAsyncSM = mock(AsyncStateMachine::class.java) + private val mState1 = mock(State::class.java) + private val mState2 = mock(State::class.java) + + inner class MyDependencies() : Dependencies() { + + override fun makeSyncStateMachine(name: String, thread: Thread) = mSyncSM + + override fun makeAsyncStateMachine(name: String, looper: Looper) = mAsyncSM + } + + @Test + fun testUsingSyncStateMachine() { + val inOrder = inOrder(mSyncSM, mAsyncSM) + val shimUsingSyncSM = StateMachineShim("ShimTest", null, MyDependencies()) + shimUsingSyncSM.start(mState1) + inOrder.verify(mSyncSM).start(mState1) + + val allStates = ArrayList() + allStates.add(StateInfo(mState1, null)) + allStates.add(StateInfo(mState2, mState1)) + shimUsingSyncSM.addAllStates(allStates) + inOrder.verify(mSyncSM).addAllStates(allStates) + + shimUsingSyncSM.transitionTo(mState1) + inOrder.verify(mSyncSM).transitionTo(mState1) + + val what = 10 + shimUsingSyncSM.sendMessage(what) + inOrder.verify(mSyncSM).processMessage(what, 0, 0, null) + val obj = Object() + shimUsingSyncSM.sendMessage(what, obj) + inOrder.verify(mSyncSM).processMessage(what, 0, 0, obj) + val arg1 = 11 + shimUsingSyncSM.sendMessage(what, arg1) + inOrder.verify(mSyncSM).processMessage(what, arg1, 0, null) + val arg2 = 12 + shimUsingSyncSM.sendMessage(what, arg1, arg2, obj) + inOrder.verify(mSyncSM).processMessage(what, arg1, arg2, obj) + + assertFailsWith(IllegalStateException::class) { + shimUsingSyncSM.sendMessageDelayedToAsyncSM(what, 1000 /* delayMillis */) + } + + assertFailsWith(IllegalStateException::class) { + shimUsingSyncSM.sendMessageAtFrontOfQueueToAsyncSM(what, arg1) + } + + shimUsingSyncSM.sendSelfMessageToSyncSM(what, obj) + inOrder.verify(mSyncSM).sendSelfMessage(what, 0, 0, obj) + + verifyNoMoreInteractions(mSyncSM, mAsyncSM) + } + + @Test + fun testUsingAsyncStateMachine() { + val inOrder = inOrder(mSyncSM, mAsyncSM) + val shimUsingAsyncSM = StateMachineShim("ShimTest", mock(Looper::class.java), + MyDependencies()) + shimUsingAsyncSM.start(mState1) + inOrder.verify(mAsyncSM).setInitialState(mState1) + inOrder.verify(mAsyncSM).start() + + val allStates = ArrayList() + allStates.add(StateInfo(mState1, null)) + allStates.add(StateInfo(mState2, mState1)) + shimUsingAsyncSM.addAllStates(allStates) + inOrder.verify(mAsyncSM).addState(mState1, null) + inOrder.verify(mAsyncSM).addState(mState2, mState1) + + shimUsingAsyncSM.transitionTo(mState1) + inOrder.verify(mAsyncSM).transitionTo(mState1) + + val what = 10 + shimUsingAsyncSM.sendMessage(what) + inOrder.verify(mAsyncSM).sendMessage(what, 0, 0, null) + val obj = Object() + shimUsingAsyncSM.sendMessage(what, obj) + inOrder.verify(mAsyncSM).sendMessage(what, 0, 0, obj) + val arg1 = 11 + shimUsingAsyncSM.sendMessage(what, arg1) + inOrder.verify(mAsyncSM).sendMessage(what, arg1, 0, null) + val arg2 = 12 + shimUsingAsyncSM.sendMessage(what, arg1, arg2, obj) + inOrder.verify(mAsyncSM).sendMessage(what, arg1, arg2, obj) + + shimUsingAsyncSM.sendMessageDelayedToAsyncSM(what, 1000 /* delayMillis */) + inOrder.verify(mAsyncSM).sendMessageDelayed(what, 1000) + + shimUsingAsyncSM.sendMessageAtFrontOfQueueToAsyncSM(what, arg1) + inOrder.verify(mAsyncSM).sendMessageAtFrontOfQueueToAsyncSM(what, arg1) + + assertFailsWith(IllegalStateException::class) { + shimUsingAsyncSM.sendSelfMessageToSyncSM(what, obj) + } + + verifyNoMoreInteractions(mSyncSM, mAsyncSM) + } +} diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/util/SyncStateMachineTest.kt b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/SyncStateMachineTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..3a57fdd01a13dbc2b2efd33fa431bab294d6bc1c --- /dev/null +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/util/SyncStateMachineTest.kt @@ -0,0 +1,294 @@ +/** + * Copyright (C) 2023 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.networkstack.tethering.util + +import android.os.Message +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.util.State +import com.android.networkstack.tethering.util.SyncStateMachine.StateInfo +import java.util.ArrayDeque +import java.util.ArrayList +import kotlin.test.assertFailsWith +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.spy +import org.mockito.Mockito.verifyNoMoreInteractions + +private const val MSG_INVALID = -1 +private const val MSG_1 = 1 +private const val MSG_2 = 2 +private const val MSG_3 = 3 +private const val MSG_4 = 4 +private const val MSG_5 = 5 +private const val MSG_6 = 6 +private const val MSG_7 = 7 +private const val ARG_1 = 100 +private const val ARG_2 = 200 + +@RunWith(AndroidJUnit4::class) +@SmallTest +class SynStateMachineTest { + private val mState1 = spy(object : TestState(MSG_1) {}) + private val mState2 = spy(object : TestState(MSG_2) {}) + private val mState3 = spy(object : TestState(MSG_3) {}) + private val mState4 = spy(object : TestState(MSG_4) {}) + private val mState5 = spy(object : TestState(MSG_5) {}) + private val mState6 = spy(object : TestState(MSG_6) {}) + private val mState7 = spy(object : TestState(MSG_7) {}) + private val mInOrder = inOrder(mState1, mState2, mState3, mState4, mState5, mState6, mState7) + // Lazy initialize to make sure running in test thread. + private val mSM by lazy { + SyncStateMachine("TestSyncStateMachine", Thread.currentThread(), true /* debug */) + } + private val mAllStates = ArrayList() + + private val mMsgProcessedResults = ArrayDeque>() + + open inner class TestState(val expected: Int) : State() { + // Control destination state in obj field for testing. + override fun processMessage(msg: Message): Boolean { + mMsgProcessedResults.add(this to msg.what) + assertEquals(ARG_1, msg.arg1) + assertEquals(ARG_2, msg.arg2) + + if (msg.what == expected) { + msg.obj?.let { mSM.transitionTo(it as State) } + return true + } + + return false + } + } + + private fun verifyNoMoreInteractions() { + verifyNoMoreInteractions(mState1, mState2, mState3, mState4, mState5, mState6) + } + + private fun processMessage(what: Int, toState: State?) { + mSM.processMessage(what, ARG_1, ARG_2, toState) + } + + private fun verifyMessageProcessedBy(what: Int, vararg processedStates: State) { + for (state in processedStates) { + // InOrder.verify can't check the Message content here because SyncSM will recycle the + // message after it's been processed. SyncSM reuses the same Message instance for all + // messages it processes. So, if using InOrder.verify to verify the content of a message + // after SyncSM has processed it, the content would be wrong. + mInOrder.verify(state).processMessage(any()) + val (processedState, msgWhat) = mMsgProcessedResults.remove() + assertEquals(state, processedState) + assertEquals(what, msgWhat) + } + assertTrue(mMsgProcessedResults.isEmpty()) + } + + @Test + fun testInitialState() { + // mState1 -> initial + // | + // mState2 + mAllStates.add(StateInfo(mState1, null)) + mAllStates.add(StateInfo(mState2, mState1)) + mSM.addAllStates(mAllStates) + + mSM.start(mState1) + mInOrder.verify(mState1).enter() + verifyNoMoreInteractions() + } + + @Test + fun testStartFromLeafState() { + // mState1 -> initial + // | + // mState2 + // | + // mState3 + mAllStates.add(StateInfo(mState1, null)) + mAllStates.add(StateInfo(mState2, mState1)) + mAllStates.add(StateInfo(mState3, mState2)) + mSM.addAllStates(mAllStates) + + mSM.start(mState3) + mInOrder.verify(mState1).enter() + mInOrder.verify(mState2).enter() + mInOrder.verify(mState3).enter() + verifyNoMoreInteractions() + } + + private fun verifyStart() { + mSM.addAllStates(mAllStates) + mSM.start(mState1) + mInOrder.verify(mState1).enter() + verifyNoMoreInteractions() + } + + fun addState(state: State, parent: State? = null) { + mAllStates.add(StateInfo(state, parent)) + } + + @Test + fun testAddState() { + // Add duplicated states. + mAllStates.add(StateInfo(mState1, null)) + mAllStates.add(StateInfo(mState1, null)) + assertFailsWith(IllegalStateException::class) { + mSM.addAllStates(mAllStates) + } + } + + @Test + fun testProcessMessage() { + // mState1 + // | + // mState2 + addState(mState1) + addState(mState2, mState1) + verifyStart() + + processMessage(MSG_1, null) + verifyMessageProcessedBy(MSG_1, mState1) + verifyNoMoreInteractions() + } + + @Test + fun testTwoStates() { + // mState1 <-initial, mState2 + addState(mState1) + addState(mState2) + verifyStart() + + // Test transition to mState2 + processMessage(MSG_1, mState2) + verifyMessageProcessedBy(MSG_1, mState1) + mInOrder.verify(mState1).exit() + mInOrder.verify(mState2).enter() + verifyNoMoreInteractions() + + // If set destState to mState2 (current state), no state transition. + processMessage(MSG_2, mState2) + verifyMessageProcessedBy(MSG_2, mState2) + verifyNoMoreInteractions() + } + + @Test + fun testTwoStateTrees() { + // mState1 -> initial mState4 + // / \ / \ + // mState2 mState3 mState5 mState6 + addState(mState1) + addState(mState2, mState1) + addState(mState3, mState1) + addState(mState4) + addState(mState5, mState4) + addState(mState6, mState4) + verifyStart() + + // mState1 -> current mState4 + // / \ / \ + // mState2 mState3 -> dest mState5 mState6 + processMessage(MSG_1, mState3) + verifyMessageProcessedBy(MSG_1, mState1) + mInOrder.verify(mState3).enter() + verifyNoMoreInteractions() + + // mState1 mState4 + // / \ / \ + // dest <- mState2 mState3 -> current mState5 mState6 + processMessage(MSG_1, mState2) + verifyMessageProcessedBy(MSG_1, mState3, mState1) + mInOrder.verify(mState3).exit() + mInOrder.verify(mState2).enter() + verifyNoMoreInteractions() + + // mState1 mState4 + // / \ / \ + // current <- mState2 mState3 mState5 mState6 -> dest + processMessage(MSG_2, mState6) + verifyMessageProcessedBy(MSG_2, mState2) + mInOrder.verify(mState2).exit() + mInOrder.verify(mState1).exit() + mInOrder.verify(mState4).enter() + mInOrder.verify(mState6).enter() + verifyNoMoreInteractions() + } + + @Test + fun testMultiDepthTransition() { + // mState1 -> current + // | \ + // mState2 mState6 + // | \ | + // mState3 mState5 mState7 + // | + // mState4 + addState(mState1) + addState(mState2, mState1) + addState(mState6, mState1) + addState(mState3, mState2) + addState(mState5, mState2) + addState(mState7, mState6) + addState(mState4, mState3) + verifyStart() + + // mState1 -> current + // | \ + // mState2 mState6 + // | \ | + // mState3 mState5 mState7 + // | + // mState4 -> dest + processMessage(MSG_1, mState4) + verifyMessageProcessedBy(MSG_1, mState1) + mInOrder.verify(mState2).enter() + mInOrder.verify(mState3).enter() + mInOrder.verify(mState4).enter() + verifyNoMoreInteractions() + + // mState1 + // / \ + // mState2 mState6 + // | \ \ + // mState3 mState5 -> dest mState7 + // | + // mState4 -> current + processMessage(MSG_1, mState5) + verifyMessageProcessedBy(MSG_1, mState4, mState3, mState2, mState1) + mInOrder.verify(mState4).exit() + mInOrder.verify(mState3).exit() + mInOrder.verify(mState5).enter() + verifyNoMoreInteractions() + + // mState1 + // / \ + // mState2 mState6 + // | \ \ + // mState3 mState5 -> current mState7 -> dest + // | + // mState4 + processMessage(MSG_2, mState7) + verifyMessageProcessedBy(MSG_2, mState5, mState2) + mInOrder.verify(mState5).exit() + mInOrder.verify(mState2).exit() + mInOrder.verify(mState6).enter() + mInOrder.verify(mState7).enter() + verifyNoMoreInteractions() + } +} diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp index b3f8ed616083783775c45ad4d1b4599eee7f86e6..cdf47e7da50ba562db759da56707efdeabaa02a5 100644 --- a/bpf_progs/Android.bp +++ b/bpf_progs/Android.bp @@ -45,6 +45,7 @@ cc_library_headers { "com.android.tethering", ], visibility: [ + "//packages/modules/Connectivity/DnsResolver", "//packages/modules/Connectivity/netd", "//packages/modules/Connectivity/service", "//packages/modules/Connectivity/service/native/libs/libclat", diff --git a/bpf_progs/block.c b/bpf_progs/block.c index f2a3e62a055e57a9b463cf81435cd856d8d5d063..0a2b0b872810f7acca52cda7d52f859b429f75a1 100644 --- a/bpf_progs/block.c +++ b/bpf_progs/block.c @@ -19,13 +19,13 @@ #include #include -// The resulting .o needs to load on the Android T beta 3 bpfloader -#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION +// The resulting .o needs to load on the Android T bpfloader +#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION #include "bpf_helpers.h" -#define ALLOW 1 -#define DISALLOW 0 +static const int ALLOW = 1; +static const int DISALLOW = 0; DEFINE_BPF_MAP_GRW(blocked_ports_map, ARRAY, int, uint64_t, 1024 /* 64K ports -> 1024 u64s */, AID_SYSTEM) @@ -57,17 +57,22 @@ static inline __always_inline int block_port(struct bpf_sock_addr *ctx) { return ALLOW; } -DEFINE_BPF_PROG_KVER("bind4/block_port", AID_ROOT, AID_SYSTEM, - bind4_block_port, KVER(5, 4, 0)) +// the program need to be accessible/loadable by netd (from netd updatable plugin) +#define DEFINE_NETD_RO_BPF_PROG(SECTION_NAME, the_prog, min_kver) \ + DEFINE_BPF_PROG_EXT(SECTION_NAME, AID_ROOT, AID_ROOT, the_prog, min_kver, KVER_INF, \ + BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, MANDATORY, \ + "", "netd_readonly/", LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG) + +DEFINE_NETD_RO_BPF_PROG("bind4/block_port", bind4_block_port, KVER_4_19) (struct bpf_sock_addr *ctx) { return block_port(ctx); } -DEFINE_BPF_PROG_KVER("bind6/block_port", AID_ROOT, AID_SYSTEM, - bind6_block_port, KVER(5, 4, 0)) +DEFINE_NETD_RO_BPF_PROG("bind6/block_port", bind6_block_port, KVER_4_19) (struct bpf_sock_addr *ctx) { return block_port(ctx); } LICENSE("Apache 2.0"); CRITICAL("ConnectivityNative"); +DISABLE_BTF_ON_USER_BUILDS(); diff --git a/bpf_progs/bpf_net_helpers.h b/bpf_progs/bpf_net_helpers.h index ed33cc938c30c102a199866a1bddf6632ebfd220..f3c7de513ddf85bc9d6ae509c8dd7103aecb47da 100644 --- a/bpf_progs/bpf_net_helpers.h +++ b/bpf_progs/bpf_net_helpers.h @@ -87,29 +87,18 @@ static inline __always_inline void try_make_writable(struct __sk_buff* skb, int if (skb->data_end - skb->data < len) bpf_skb_pull_data(skb, len); } -// constants for passing in to 'bool egress' -static const bool INGRESS = false; -static const bool EGRESS = true; - -// constants for passing in to 'bool downstream' -static const bool UPSTREAM = false; -static const bool DOWNSTREAM = true; - -// constants for passing in to 'bool is_ethernet' -static const bool RAWIP = false; -static const bool ETHER = true; - -// constants for passing in to 'bool updatetime' -static const bool NO_UPDATETIME = false; -static const bool UPDATETIME = true; - -// constants for passing in to ignore_on_eng / ignore_on_user / ignore_on_userdebug -// define's instead of static const due to tm-mainline-prod compiler static_assert limitations -#define LOAD_ON_ENG false -#define LOAD_ON_USER false -#define LOAD_ON_USERDEBUG false -#define IGNORE_ON_ENG true -#define IGNORE_ON_USER true -#define IGNORE_ON_USERDEBUG true - -#define KVER_4_14 KVER(4, 14, 0) +struct egress_bool { bool egress; }; +#define INGRESS ((struct egress_bool){ .egress = false }) +#define EGRESS ((struct egress_bool){ .egress = true }) + +struct stream_bool { bool down; }; +#define UPSTREAM ((struct stream_bool){ .down = false }) +#define DOWNSTREAM ((struct stream_bool){ .down = true }) + +struct rawip_bool { bool rawip; }; +#define ETHER ((struct rawip_bool){ .rawip = false }) +#define RAWIP ((struct rawip_bool){ .rawip = true }) + +struct updatetime_bool { bool updatetime; }; +#define NO_UPDATETIME ((struct updatetime_bool){ .updatetime = false }) +#define UPDATETIME ((struct updatetime_bool){ .updatetime = true }) diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c index f05b93e428c17c5ab1111fe5ca83267030d35485..addb02fe8b7015ac18f323252dcab10174297ce8 100644 --- a/bpf_progs/clatd.c +++ b/bpf_progs/clatd.c @@ -30,8 +30,8 @@ #define __kernel_udphdr udphdr #include -// The resulting .o needs to load on the Android T beta 3 bpfloader -#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION +// The resulting .o needs to load on the Android T bpfloader +#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION #include "bpf_helpers.h" #include "bpf_net_helpers.h" @@ -55,8 +55,10 @@ struct frag_hdr { DEFINE_BPF_MAP_GRW(clat_ingress6_map, HASH, ClatIngress6Key, ClatIngress6Value, 16, AID_SYSTEM) static inline __always_inline int nat64(struct __sk_buff* skb, - const bool is_ethernet, - const unsigned kver) { + const struct rawip_bool rawip, + const struct kver_uint kver) { + const bool is_ethernet = !rawip.rawip; + // Require ethernet dst mac address to be our unicast address. if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE; @@ -115,7 +117,7 @@ static inline __always_inline int nat64(struct __sk_buff* skb, if (proto == IPPROTO_FRAGMENT) { // Fragment handling requires bpf_skb_adjust_room which is 4.14+ - if (kver < KVER_4_14) return TC_ACT_PIPE; + if (!KVER_IS_AT_LEAST(kver, 4, 14, 0)) return TC_ACT_PIPE; // Must have (ethernet and) ipv6 header and ipv6 fragment extension header if (data + l2_header_size + sizeof(*ip6) + sizeof(struct frag_hdr) > data_end) @@ -138,10 +140,11 @@ static inline __always_inline int nat64(struct __sk_buff* skb, } switch (proto) { - case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6 - case IPPROTO_UDP: // address means there is no need to update their checksums. - case IPPROTO_GRE: // We do not need to bother looking at GRE/ESP headers, - case IPPROTO_ESP: // since there is never a checksum to update. + case IPPROTO_TCP: // For TCP, UDP & UDPLITE the checksum neutrality of the chosen + case IPPROTO_UDP: // IPv6 address means there is no need to update their checksums. + case IPPROTO_UDPLITE: // + case IPPROTO_GRE: // We do not need to bother looking at GRE/ESP headers, + case IPPROTO_ESP: // since there is never a checksum to update. break; default: // do not know how to handle anything else @@ -232,13 +235,15 @@ static inline __always_inline int nat64(struct __sk_buff* skb, // // Note: we currently have no TreeHugger coverage for 4.9-T devices (there are no such // Pixel or cuttlefish devices), so likely you won't notice for months if this breaks... - if (kver >= KVER_4_14 && frag_off != htons(IP_DF)) { + if (KVER_IS_AT_LEAST(kver, 4, 14, 0) && frag_off != htons(IP_DF)) { // If we're converting an IPv6 Fragment, we need to trim off 8 more bytes // We're beyond recovery on error here... but hard to imagine how this could fail. if (bpf_skb_adjust_room(skb, -(__s32)sizeof(struct frag_hdr), BPF_ADJ_ROOM_NET, /*flags*/0)) return TC_ACT_SHOT; } + try_make_writable(skb, l2_header_size + sizeof(struct iphdr)); + // bpf_skb_change_proto() invalidates all pointers - reload them. data = (void*)(long)skb->data; data_end = (void*)(long)skb->data_end; @@ -328,12 +333,13 @@ DEFINE_BPF_PROG("schedcls/egress4/clat_rawip", AID_ROOT, AID_SYSTEM, sched_cls_e if (ip4->frag_off & ~htons(IP_DF)) return TC_ACT_PIPE; switch (ip4->protocol) { - case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6 - case IPPROTO_GRE: // address means there is no need to update their checksums. - case IPPROTO_ESP: // We do not need to bother looking at GRE/ESP headers, - break; // since there is never a checksum to update. + case IPPROTO_TCP: // For TCP, UDP & UDPLITE the checksum neutrality of the chosen + case IPPROTO_UDPLITE: // IPv6 address means there is no need to update their checksums. + case IPPROTO_GRE: // We do not need to bother looking at GRE/ESP headers, + case IPPROTO_ESP: // since there is never a checksum to update. + break; - case IPPROTO_UDP: // See above comment, but must also have UDP header... + case IPPROTO_UDP: // See above comment, but must also have UDP header... if (data + sizeof(*ip4) + sizeof(struct udphdr) > data_end) return TC_ACT_PIPE; const struct udphdr* uh = (const struct udphdr*)(ip4 + 1); // If IPv4/UDP checksum is 0 then fallback to clatd so it can calculate the @@ -416,3 +422,4 @@ DEFINE_BPF_PROG("schedcls/egress4/clat_rawip", AID_ROOT, AID_SYSTEM, sched_cls_e LICENSE("Apache 2.0"); CRITICAL("Connectivity"); +DISABLE_BTF_ON_USER_BUILDS(); diff --git a/bpf_progs/dscpPolicy.c b/bpf_progs/dscpPolicy.c index 72f63c6f5b113ac2314cde27f5dca5a080e5d048..e845a699e96fe806e0503c2181b5a01c21107c84 100644 --- a/bpf_progs/dscpPolicy.c +++ b/bpf_progs/dscpPolicy.c @@ -27,8 +27,8 @@ #include #include -// The resulting .o needs to load on the Android T beta 3 bpfloader -#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION +// The resulting .o needs to load on the Android T bpfloader +#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION #include "bpf_helpers.h" #include "dscpPolicy.h" @@ -222,7 +222,7 @@ static inline __always_inline void match_policy(struct __sk_buff* skb, bool ipv4 } DEFINE_BPF_PROG_KVER("schedcls/set_dscp_ether", AID_ROOT, AID_SYSTEM, schedcls_set_dscp_ether, - KVER(5, 15, 0)) + KVER_5_15) (struct __sk_buff* skb) { if (skb->pkt_type != PACKET_HOST) return TC_ACT_PIPE; @@ -238,3 +238,4 @@ DEFINE_BPF_PROG_KVER("schedcls/set_dscp_ether", AID_ROOT, AID_SYSTEM, schedcls_s LICENSE("Apache 2.0"); CRITICAL("Connectivity"); +DISABLE_BTF_ON_USER_BUILDS(); diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c index d1dfb7b7188d5b01ae95abfd728f053331929096..59bbfbae515f5a5d8dc8551fd0fa0f083d3aa7b9 100644 --- a/bpf_progs/netd.c +++ b/bpf_progs/netd.c @@ -14,8 +14,8 @@ * limitations under the License. */ -// The resulting .o needs to load on the Android T Beta 3 bpfloader -#define BPFLOADER_MIN_VER BPFLOADER_T_BETA3_VERSION +// The resulting .o needs to load on the Android T bpfloader +#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION #include #include @@ -56,19 +56,21 @@ static const bool TRACE_OFF = false; // see include/uapi/linux/tcp.h #define TCP_FLAG32_OFF 12 +#define TCP_FLAG8_OFF (TCP_FLAG32_OFF + 1) + // For maps netd does not need to access -#define DEFINE_BPF_MAP_NO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \ - DEFINE_BPF_MAP_EXT(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, \ - AID_ROOT, AID_NET_BW_ACCT, 0060, "fs_bpf_net_shared", "", false, \ - BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, LOAD_ON_ENG, \ - LOAD_ON_USER, LOAD_ON_USERDEBUG) +#define DEFINE_BPF_MAP_NO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \ + DEFINE_BPF_MAP_EXT(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, \ + AID_ROOT, AID_NET_BW_ACCT, 0060, "fs_bpf_net_shared", "", \ + PRIVATE, BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, \ + LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG) // For maps netd only needs read only access to -#define DEFINE_BPF_MAP_RO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \ - DEFINE_BPF_MAP_EXT(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, \ - AID_ROOT, AID_NET_BW_ACCT, 0460, "fs_bpf_netd_readonly", "", false, \ - BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, LOAD_ON_ENG, \ - LOAD_ON_USER, LOAD_ON_USERDEBUG) +#define DEFINE_BPF_MAP_RO_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \ + DEFINE_BPF_MAP_EXT(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, \ + AID_ROOT, AID_NET_BW_ACCT, 0460, "fs_bpf_netd_readonly", "", \ + PRIVATE, BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, \ + LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG) // For maps netd needs to be able to read and write #define DEFINE_BPF_MAP_RW_NETD(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \ @@ -87,27 +89,31 @@ DEFINE_BPF_MAP_RO_NETD(configuration_map, ARRAY, uint32_t, uint32_t, CONFIGURATI DEFINE_BPF_MAP_RW_NETD(cookie_tag_map, HASH, uint64_t, UidTagValue, COOKIE_UID_MAP_SIZE) DEFINE_BPF_MAP_NO_NETD(uid_counterset_map, HASH, uint32_t, uint8_t, UID_COUNTERSET_MAP_SIZE) DEFINE_BPF_MAP_NO_NETD(app_uid_stats_map, HASH, uint32_t, StatsValue, APP_STATS_MAP_SIZE) -DEFINE_BPF_MAP_RW_NETD(stats_map_A, HASH, StatsKey, StatsValue, STATS_MAP_SIZE) +DEFINE_BPF_MAP_RO_NETD(stats_map_A, HASH, StatsKey, StatsValue, STATS_MAP_SIZE) DEFINE_BPF_MAP_RO_NETD(stats_map_B, HASH, StatsKey, StatsValue, STATS_MAP_SIZE) DEFINE_BPF_MAP_NO_NETD(iface_stats_map, HASH, uint32_t, StatsValue, IFACE_STATS_MAP_SIZE) -DEFINE_BPF_MAP_NO_NETD(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE) -DEFINE_BPF_MAP_RW_NETD(uid_permission_map, HASH, uint32_t, uint8_t, UID_OWNER_MAP_SIZE) +DEFINE_BPF_MAP_RO_NETD(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE) +DEFINE_BPF_MAP_RO_NETD(uid_permission_map, HASH, uint32_t, uint8_t, UID_OWNER_MAP_SIZE) +DEFINE_BPF_MAP_NO_NETD(ingress_discard_map, HASH, IngressDiscardKey, IngressDiscardValue, + INGRESS_DISCARD_MAP_SIZE) /* never actually used from ebpf */ DEFINE_BPF_MAP_NO_NETD(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE) // A single-element configuration array, packet tracing is enabled when 'true'. DEFINE_BPF_MAP_EXT(packet_trace_enabled_map, ARRAY, uint32_t, bool, 1, - AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", false, + AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", PRIVATE, BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG, - IGNORE_ON_USER, LOAD_ON_USERDEBUG) + LOAD_ON_USER, LOAD_ON_USERDEBUG) -// A ring buffer on which packet information is pushed. This map will only be loaded -// on eng and userdebug devices. User devices won't load this to save memory. +// A ring buffer on which packet information is pushed. DEFINE_BPF_RINGBUF_EXT(packet_trace_ringbuf, PacketTrace, PACKET_TRACE_BUF_SIZE, - AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", false, + AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", PRIVATE, BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG, - IGNORE_ON_USER, LOAD_ON_USERDEBUG); + LOAD_ON_USER, LOAD_ON_USERDEBUG); + +DEFINE_BPF_MAP_RO_NETD(data_saver_enabled_map, ARRAY, uint32_t, bool, + DATA_SAVER_ENABLED_MAP_SIZE) // iptables xt_bpf programs need to be usable by both netd and netutils_wrappers // selinux contexts, because even non-xt_bpf iptables mutations are implemented as @@ -124,8 +130,8 @@ DEFINE_BPF_RINGBUF_EXT(packet_trace_ringbuf, PacketTrace, PACKET_TRACE_BUF_SIZE, // which is loaded into netd and thus runs as netd uid/gid/selinux context) #define DEFINE_NETD_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, minKV, maxKV) \ DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, \ - minKV, maxKV, BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, false, \ - "fs_bpf_netd_readonly", "", false, false, false) + minKV, maxKV, BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, MANDATORY, \ + "fs_bpf_netd_readonly", "", LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG) #define DEFINE_NETD_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv) \ DEFINE_NETD_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, KVER_INF) @@ -136,14 +142,8 @@ DEFINE_BPF_RINGBUF_EXT(packet_trace_ringbuf, PacketTrace, PACKET_TRACE_BUF_SIZE, // programs that only need to be usable by the system server #define DEFINE_SYS_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \ DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, KVER_NONE, KVER_INF, \ - BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, false, "fs_bpf_net_shared", \ - "", false, false, false) - -static __always_inline int is_system_uid(uint32_t uid) { - // MIN_SYSTEM_UID is AID_ROOT == 0, so uint32_t is *always* >= 0 - // MAX_SYSTEM_UID is AID_NOBODY == 9999, while AID_APP_START == 10000 - return (uid < AID_APP_START); -} + BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, MANDATORY, \ + "fs_bpf_net_shared", "", LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG) /* * Note: this blindly assumes an MTU of 1500, and that packets > MTU are always TCP, @@ -175,8 +175,8 @@ static __always_inline int is_system_uid(uint32_t uid) { #define DEFINE_UPDATE_STATS(the_stats_map, TypeOfKey) \ static __always_inline inline void update_##the_stats_map(const struct __sk_buff* const skb, \ const TypeOfKey* const key, \ - const bool egress, \ - const unsigned kver) { \ + const struct egress_bool egress, \ + const struct kver_uint kver) { \ StatsValue* value = bpf_##the_stats_map##_lookup_elem(key); \ if (!value) { \ StatsValue newValue = {}; \ @@ -196,7 +196,7 @@ static __always_inline int is_system_uid(uint32_t uid) { packets = (payload + mss - 1) / mss; \ bytes = tcp_overhead * packets + payload; \ } \ - if (egress) { \ + if (egress.egress) { \ __sync_fetch_and_add(&value->txPackets, packets); \ __sync_fetch_and_add(&value->txBytes, bytes); \ } else { \ @@ -216,7 +216,7 @@ static __always_inline inline int bpf_skb_load_bytes_net(const struct __sk_buff* const int L3_off, void* const to, const int len, - const unsigned kver) { + const struct kver_uint kver) { // 'kver' (here and throughout) is the compile time guaranteed minimum kernel version, // ie. we're building (a version of) the bpf program for kver (or newer!) kernels. // @@ -233,16 +233,16 @@ static __always_inline inline int bpf_skb_load_bytes_net(const struct __sk_buff* // // For similar reasons this will fail with non-offloaded VLAN tags on < 4.19 kernels, // since those extend the ethernet header from 14 to 18 bytes. - return kver >= KVER(4, 19, 0) + return KVER_IS_AT_LEAST(kver, 4, 19, 0) ? bpf_skb_load_bytes_relative(skb, L3_off, to, len, BPF_HDR_START_NET) : bpf_skb_load_bytes(skb, L3_off, to, len); } static __always_inline inline void do_packet_tracing( - const struct __sk_buff* const skb, const bool egress, const uint32_t uid, - const uint32_t tag, const bool enable_tracing, const unsigned kver) { + const struct __sk_buff* const skb, const struct egress_bool egress, const uint32_t uid, + const uint32_t tag, const bool enable_tracing, const struct kver_uint kver) { if (!enable_tracing) return; - if (kver < KVER(5, 8, 0)) return; + if (!KVER_IS_AT_LEAST(kver, 5, 8, 0)) return; uint32_t mapKey = 0; bool* traceConfig = bpf_packet_trace_enabled_map_lookup_elem(&mapKey); @@ -268,17 +268,41 @@ static __always_inline inline void do_packet_tracing( (void)bpf_skb_load_bytes_net(skb, IP6_OFFSET(nexthdr), &proto, sizeof(proto), kver); L4_off = sizeof(struct ipv6hdr); ipVersion = 6; + // skip over a *single* HOPOPTS or DSTOPTS extension header (if present) + if (proto == IPPROTO_HOPOPTS || proto == IPPROTO_DSTOPTS) { + struct { + uint8_t proto, len; + } ext_hdr; + if (!bpf_skb_load_bytes_net(skb, L4_off, &ext_hdr, sizeof(ext_hdr), kver)) { + proto = ext_hdr.proto; + L4_off += (ext_hdr.len + 1) * 8; + } + } } uint8_t flags = 0; __be16 sport = 0, dport = 0; - if (proto == IPPROTO_TCP && L4_off >= 20) { - (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_FLAG32_OFF + 1, &flags, sizeof(flags), kver); - (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_OFFSET(source), &sport, sizeof(sport), kver); - (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_OFFSET(dest), &dport, sizeof(dport), kver); - } else if (proto == IPPROTO_UDP && L4_off >= 20) { - (void)bpf_skb_load_bytes_net(skb, L4_off + UDP_OFFSET(source), &sport, sizeof(sport), kver); - (void)bpf_skb_load_bytes_net(skb, L4_off + UDP_OFFSET(dest), &dport, sizeof(dport), kver); + if (L4_off >= 20) { + switch (proto) { + case IPPROTO_TCP: + (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_FLAG8_OFF, &flags, sizeof(flags), kver); + // fallthrough + case IPPROTO_DCCP: + case IPPROTO_UDP: + case IPPROTO_UDPLITE: + case IPPROTO_SCTP: + // all of these L4 protocols start with be16 src & dst port + (void)bpf_skb_load_bytes_net(skb, L4_off + 0, &sport, sizeof(sport), kver); + (void)bpf_skb_load_bytes_net(skb, L4_off + 2, &dport, sizeof(dport), kver); + break; + case IPPROTO_ICMP: + case IPPROTO_ICMPV6: + // Both IPv4 and IPv6 icmp start with u8 type & code, which we store in the bottom + // (ie. second) byte of sport/dport (which are be16s), the top byte is already zero. + (void)bpf_skb_load_bytes_net(skb, L4_off + 0, (char *)&sport + 1, 1, kver); //type + (void)bpf_skb_load_bytes_net(skb, L4_off + 1, (char *)&dport + 1, 1, kver); //code + break; + } } pkt->timestampNs = bpf_ktime_get_boot_ns(); @@ -290,7 +314,8 @@ static __always_inline inline void do_packet_tracing( pkt->sport = sport; pkt->dport = dport; - pkt->egress = egress; + pkt->egress = egress.egress; + pkt->wakeup = !egress.egress && (skb->mark & 0x80000000); // Fwmark.ingress_cpu_wakeup pkt->ipProto = proto; pkt->tcpFlags = flags; pkt->ipVersion = ipVersion; @@ -298,8 +323,9 @@ static __always_inline inline void do_packet_tracing( bpf_packet_trace_ringbuf_submit(pkt); } -static __always_inline inline bool skip_owner_match(struct __sk_buff* skb, bool egress, - const unsigned kver) { +static __always_inline inline bool skip_owner_match(struct __sk_buff* skb, + const struct egress_bool egress, + const struct kver_uint kver) { uint32_t flag = 0; if (skb->protocol == htons(ETH_P_IP)) { uint8_t proto; @@ -330,7 +356,7 @@ static __always_inline inline bool skip_owner_match(struct __sk_buff* skb, bool return false; } // Always allow RST's, and additionally allow ingress FINs - return flag & (TCP_FLAG_RST | (egress ? 0 : TCP_FLAG_FIN)); // false on read failure + return flag & (TCP_FLAG_RST | (egress.egress ? 0 : TCP_FLAG_FIN)); // false on read failure } static __always_inline inline BpfConfig getConfig(uint32_t configKey) { @@ -343,10 +369,35 @@ static __always_inline inline BpfConfig getConfig(uint32_t configKey) { return *config; } -#define FIREWALL_DROP_IF_SET (OEM_DENY_1_MATCH) -#define FIREWALL_DROP_IF_UNSET (RESTRICTED_MATCH) +static __always_inline inline bool ingress_should_discard(struct __sk_buff* skb, + const struct kver_uint kver) { + // Require 4.19, since earlier kernels don't have bpf_skb_load_bytes_relative() which + // provides relative to L3 header reads. Without that we could fetch the wrong bytes. + // Additionally earlier bpf verifiers are much harder to please. + if (!KVER_IS_AT_LEAST(kver, 4, 19, 0)) return false; + + IngressDiscardKey k = {}; + if (skb->protocol == htons(ETH_P_IP)) { + k.daddr.s6_addr32[2] = htonl(0xFFFF); + (void)bpf_skb_load_bytes_net(skb, IP4_OFFSET(daddr), &k.daddr.s6_addr32[3], 4, kver); + } else if (skb->protocol == htons(ETH_P_IPV6)) { + (void)bpf_skb_load_bytes_net(skb, IP6_OFFSET(daddr), &k.daddr, sizeof(k.daddr), kver); + } else { + return false; // non IPv4/IPv6, so no IP to match on + } + + // we didn't check for load success, because destination bytes will be zeroed if + // bpf_skb_load_bytes_net() fails, instead we rely on daddr of '::' and '::ffff:0.0.0.0' + // never being present in the map itself + + IngressDiscardValue* v = bpf_ingress_discard_map_lookup_elem(&k); + if (!v) return false; // lookup failure -> no protection in place -> allow + // if (skb->ifindex == 1) return false; // allow 'lo', but can't happen - see callsite + if (skb->ifindex == v->iif[0]) return false; // allowed interface + if (skb->ifindex == v->iif[1]) return false; // allowed interface + return true; // disallowed interface +} -// Must be __always_inline or the call from inet_socket_create will crash-reboot the system static __always_inline inline int bpf_owner_firewall_match(uint32_t uid) { if (is_system_uid(uid)) return PASS; @@ -362,13 +413,9 @@ static __always_inline inline int bpf_owner_firewall_match(uint32_t uid) { return PASS; } -// DROP_IF_SET is set of rules that DROP if rule is globally enabled, and per-uid bit is set -#define DROP_IF_SET (STANDBY_MATCH | OEM_DENY_1_MATCH | OEM_DENY_2_MATCH | OEM_DENY_3_MATCH) -// DROP_IF_UNSET is set of rules that should DROP if globally enabled, and per-uid bit is NOT set -#define DROP_IF_UNSET (DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH | LOW_POWER_STANDBY_MATCH) - static __always_inline inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid, - bool egress, const unsigned kver) { + const struct egress_bool egress, + const struct kver_uint kver) { if (is_system_uid(uid)) return PASS; if (skip_owner_match(skb, egress, kver)) return PASS; @@ -379,14 +426,10 @@ static __always_inline inline int bpf_owner_match(struct __sk_buff* skb, uint32_ uint32_t uidRules = uidEntry ? uidEntry->rule : 0; uint32_t allowed_iif = uidEntry ? uidEntry->iif : 0; - // Warning: funky bit-wise arithmetic: in parallel, for all DROP_IF_SET/UNSET rules - // check whether the rules are globally enabled, and if so whether the rules are - // set/unset for the specific uid. DROP if that is the case for ANY of the rules. - // We achieve this by masking out only the bits/rules we're interested in checking, - // and negating (via bit-wise xor) the bits/rules that should drop if unset. - if (enabledRules & (DROP_IF_SET | DROP_IF_UNSET) & (uidRules ^ DROP_IF_UNSET)) return DROP; + if (isBlockedByUidRules(enabledRules, uidRules)) return DROP; - if (!egress && skb->ifindex != 1) { + if (!egress.egress && skb->ifindex != 1) { + if (ingress_should_discard(skb, kver)) return DROP; if (uidRules & IIF_MATCH) { if (allowed_iif && skb->ifindex != allowed_iif) { // Drops packets not coming from lo nor the allowed interface @@ -405,8 +448,8 @@ static __always_inline inline int bpf_owner_match(struct __sk_buff* skb, uint32_ static __always_inline inline void update_stats_with_config(const uint32_t selectedMap, const struct __sk_buff* const skb, const StatsKey* const key, - const bool egress, - const unsigned kver) { + const struct egress_bool egress, + const struct kver_uint kver) { if (selectedMap == SELECT_MAP_A) { update_stats_map_A(skb, key, egress, kver); } else { @@ -414,9 +457,10 @@ static __always_inline inline void update_stats_with_config(const uint32_t selec } } -static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, bool egress, +static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, + const struct egress_bool egress, const bool enable_tracing, - const unsigned kver) { + const struct kver_uint kver) { uint32_t sock_uid = bpf_get_socket_uid(skb); uint64_t cookie = bpf_get_socket_cookie(skb); UidTagValue* utag = bpf_cookie_tag_map_lookup_elem(&cookie); @@ -431,10 +475,9 @@ static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, boo // Always allow and never count clat traffic. Only the IPv4 traffic on the stacked // interface is accounted for and subject to usage restrictions. - // TODO: remove sock_uid check once Nat464Xlat javaland adds the socket tag AID_CLAT for clat. - if (sock_uid == AID_CLAT || uid == AID_CLAT) { - return PASS; - } + // CLAT IPv6 TX sockets are *always* tagged with CLAT uid, see tagSocketAsClat() + // CLAT daemon receives via an untagged AF_PACKET socket. + if (egress.egress && uid == AID_CLAT) return PASS; int match = bpf_owner_match(skb, sock_uid, egress, kver); @@ -450,7 +493,7 @@ static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, boo } // If an outbound packet is going to be dropped, we do not count that traffic. - if (egress && (match == DROP)) return DROP; + if (egress.egress && (match == DROP)) return DROP; StatsKey key = {.uid = uid, .tag = tag, .counterSet = 0, .ifaceIndex = skb->ifindex}; @@ -460,57 +503,81 @@ static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, boo uint32_t mapSettingKey = CURRENT_STATS_MAP_CONFIGURATION_KEY; uint32_t* selectedMap = bpf_configuration_map_lookup_elem(&mapSettingKey); - // Use asm("%0 &= 1" : "+r"(match)) before return match, - // to help kernel's bpf verifier, so that it can be 100% certain - // that the returned value is always BPF_NOMATCH(0) or BPF_MATCH(1). - if (!selectedMap) { - asm("%0 &= 1" : "+r"(match)); - return match; - } + if (!selectedMap) return PASS; // cannot happen, needed to keep bpf verifier happy do_packet_tracing(skb, egress, uid, tag, enable_tracing, kver); update_stats_with_config(*selectedMap, skb, &key, egress, kver); update_app_uid_stats_map(skb, &uid, egress, kver); + + // We've already handled DROP_UNLESS_DNS up above, thus when we reach here the only + // possible values of match are DROP(0) or PASS(1), however we need to use + // "match &= 1" before 'return match' to help the kernel's bpf verifier, + // so that it can be 100% certain that the returned value is always 0 or 1. + // We use assembly so that it cannot be optimized out by a too smart compiler. asm("%0 &= 1" : "+r"(match)); return match; } +// This program is optional, and enables tracing on Android U+, 5.8+ on user builds. +DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace_user", AID_ROOT, AID_SYSTEM, + bpf_cgroup_ingress_trace_user, KVER_5_8, KVER_INF, + BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, OPTIONAL, + "fs_bpf_netd_readonly", "", + IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG) +(struct __sk_buff* skb) { + return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER_5_8); +} + +// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng. DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace", AID_ROOT, AID_SYSTEM, - bpf_cgroup_ingress_trace, KVER(5, 8, 0), KVER_INF, - BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, false, - "fs_bpf_netd_readonly", "", false, true, false) + bpf_cgroup_ingress_trace, KVER_5_8, KVER_INF, + BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, MANDATORY, + "fs_bpf_netd_readonly", "", + LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG) (struct __sk_buff* skb) { - return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER(5, 8, 0)); + return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER_5_8); } DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/ingress/stats$4_19", AID_ROOT, AID_SYSTEM, - bpf_cgroup_ingress_4_19, KVER(4, 19, 0), KVER_INF) + bpf_cgroup_ingress_4_19, KVER_4_19, KVER_INF) (struct __sk_buff* skb) { - return bpf_traffic_account(skb, INGRESS, TRACE_OFF, KVER(4, 19, 0)); + return bpf_traffic_account(skb, INGRESS, TRACE_OFF, KVER_4_19); } DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/ingress/stats$4_14", AID_ROOT, AID_SYSTEM, - bpf_cgroup_ingress_4_14, KVER_NONE, KVER(4, 19, 0)) + bpf_cgroup_ingress_4_14, KVER_NONE, KVER_4_19) (struct __sk_buff* skb) { return bpf_traffic_account(skb, INGRESS, TRACE_OFF, KVER_NONE); } +// This program is optional, and enables tracing on Android U+, 5.8+ on user builds. +DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace_user", AID_ROOT, AID_SYSTEM, + bpf_cgroup_egress_trace_user, KVER_5_8, KVER_INF, + BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, OPTIONAL, + "fs_bpf_netd_readonly", "", + LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG) +(struct __sk_buff* skb) { + return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER_5_8); +} + +// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng. DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace", AID_ROOT, AID_SYSTEM, - bpf_cgroup_egress_trace, KVER(5, 8, 0), KVER_INF, - BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, false, - "fs_bpf_netd_readonly", "", false, true, false) + bpf_cgroup_egress_trace, KVER_5_8, KVER_INF, + BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, MANDATORY, + "fs_bpf_netd_readonly", "", + LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG) (struct __sk_buff* skb) { - return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER(5, 8, 0)); + return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER_5_8); } DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/egress/stats$4_19", AID_ROOT, AID_SYSTEM, - bpf_cgroup_egress_4_19, KVER(4, 19, 0), KVER_INF) + bpf_cgroup_egress_4_19, KVER_4_19, KVER_INF) (struct __sk_buff* skb) { - return bpf_traffic_account(skb, EGRESS, TRACE_OFF, KVER(4, 19, 0)); + return bpf_traffic_account(skb, EGRESS, TRACE_OFF, KVER_4_19); } DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/egress/stats$4_14", AID_ROOT, AID_SYSTEM, - bpf_cgroup_egress_4_14, KVER_NONE, KVER(4, 19, 0)) + bpf_cgroup_egress_4_14, KVER_NONE, KVER_4_19) (struct __sk_buff* skb) { return bpf_traffic_account(skb, EGRESS, TRACE_OFF, KVER_NONE); } @@ -521,9 +588,8 @@ DEFINE_XTBPF_PROG("skfilter/egress/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_egres // Clat daemon does not generate new traffic, all its traffic is accounted for already // on the v4-* interfaces (except for the 20 (or 28) extra bytes of IPv6 vs IPv4 overhead, // but that can be corrected for later when merging v4-foo stats into interface foo's). - // TODO: remove sock_uid check once Nat464Xlat javaland adds the socket tag AID_CLAT for clat. + // CLAT sockets are created by system server and tagged as uid CLAT, see tagSocketAsClat() uint32_t sock_uid = bpf_get_socket_uid(skb); - if (sock_uid == AID_CLAT) return BPF_NOMATCH; if (sock_uid == AID_SYSTEM) { uint64_t cookie = bpf_get_socket_cookie(skb); UidTagValue* utag = bpf_cookie_tag_map_lookup_elem(&cookie); @@ -586,10 +652,7 @@ DEFINE_XTBPF_PROG("skfilter/denylist/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_den return BPF_NOMATCH; } -DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create, - KVER(4, 14, 0)) -(struct bpf_sock* sk) { - uint32_t uid = (bpf_get_current_uid_gid() & 0xffffffff); +static __always_inline inline uint8_t get_app_permissions(uint32_t uid) { /* * A given app is guaranteed to have the same app ID in all the profiles in * which it is installed, and install permission is granted to app for all @@ -598,13 +661,22 @@ DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_soc */ uint32_t appId = uid % AID_USER_OFFSET; // == PER_USER_RANGE == 100000 uint8_t* permissions = bpf_uid_permission_map_lookup_elem(&appId); - // If UID is in map but missing BPF_PERMISSION_INTERNET, app has no INTERNET permission. - if (permissions && ((*permissions & BPF_PERMISSION_INTERNET) != BPF_PERMISSION_INTERNET)) { - return DROP; + // if UID not in map, then default to just INTERNET permission. + return permissions ? *permissions : BPF_PERMISSION_INTERNET; +} + +DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create, + KVER_4_14) +(struct bpf_sock* sk) { + uint64_t uid = bpf_get_current_uid_gid() & 0xffffffff; + // A return value of 1 means allow, everything else means deny. + if (get_app_permissions(uid) & BPF_PERMISSION_INTERNET) { + return bpf_owner_firewall_match(uid) == PASS ? 1 : 0; + } else { + return 0; } - // Only allow if uid is not blocked by the user firewall. - return bpf_owner_firewall_match(uid); } LICENSE("Apache 2.0"); CRITICAL("Connectivity and netd"); +DISABLE_BTF_ON_USER_BUILDS(); diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h index be604f9a1ab053a41429fa6fba0302af1ea25554..681cb0066e8eaa5973b5448a316affd607c5e66a 100644 --- a/bpf_progs/netd.h +++ b/bpf_progs/netd.h @@ -16,6 +16,7 @@ #pragma once +#include #include #include #include @@ -55,20 +56,21 @@ typedef struct { } StatsValue; STRUCT_SIZE(StatsValue, 4 * 8); // 32 +#ifdef __cplusplus +static inline StatsValue& operator+=(StatsValue& lhs, const StatsValue& rhs) { + lhs.rxPackets += rhs.rxPackets; + lhs.rxBytes += rhs.rxBytes; + lhs.txPackets += rhs.txPackets; + lhs.txBytes += rhs.txBytes; + return lhs; +} +#endif + typedef struct { char name[IFNAMSIZ]; } IfaceValue; STRUCT_SIZE(IfaceValue, 16); -typedef struct { - uint64_t rxBytes; - uint64_t rxPackets; - uint64_t txBytes; - uint64_t txPackets; - uint64_t tcpRxPackets; - uint64_t tcpTxPackets; -} Stats; - typedef struct { uint64_t timestampNs; uint32_t ifindex; @@ -80,7 +82,8 @@ typedef struct { __be16 sport; __be16 dport; - bool egress; + bool egress:1, + wakeup:1; uint8_t ipProto; uint8_t tcpFlags; uint8_t ipVersion; // 4=IPv4, 6=IPv6, 0=unknown @@ -121,7 +124,9 @@ static const int IFACE_INDEX_NAME_MAP_SIZE = 1000; static const int IFACE_STATS_MAP_SIZE = 1000; static const int CONFIGURATION_MAP_SIZE = 2; static const int UID_OWNER_MAP_SIZE = 4000; +static const int INGRESS_DISCARD_MAP_SIZE = 100; static const int PACKET_TRACE_BUF_SIZE = 32 * 1024; +static const int DATA_SAVER_ENABLED_MAP_SIZE = 1; #ifdef __cplusplus @@ -165,8 +170,10 @@ ASSERT_STRING_EQUAL(XT_BPF_DENYLIST_PROG_PATH, BPF_NETD_PATH "prog_netd_skfilte #define CONFIGURATION_MAP_PATH BPF_NETD_PATH "map_netd_configuration_map" #define UID_OWNER_MAP_PATH BPF_NETD_PATH "map_netd_uid_owner_map" #define UID_PERMISSION_MAP_PATH BPF_NETD_PATH "map_netd_uid_permission_map" +#define INGRESS_DISCARD_MAP_PATH BPF_NETD_PATH "map_netd_ingress_discard_map" #define PACKET_TRACE_RINGBUF_PATH BPF_NETD_PATH "map_netd_packet_trace_ringbuf" #define PACKET_TRACE_ENABLED_MAP_PATH BPF_NETD_PATH "map_netd_packet_trace_enabled_map" +#define DATA_SAVER_ENABLED_MAP_PATH BPF_NETD_PATH "map_netd_data_saver_enabled_map" #endif // __cplusplus @@ -185,8 +192,9 @@ enum UidOwnerMatchType { OEM_DENY_1_MATCH = (1 << 9), OEM_DENY_2_MATCH = (1 << 10), OEM_DENY_3_MATCH = (1 << 11), + BACKGROUND_MATCH = (1 << 12) }; -// LINT.ThenChange(packages/modules/Connectivity/service/src/com/android/server/BpfNetMaps.java) +// LINT.ThenChange(../framework/src/android/net/BpfNetMapsConstants.java) enum BpfPermissionMatch { BPF_PERMISSION_INTERNET = 1 << 2, @@ -213,9 +221,47 @@ typedef struct { } UidOwnerValue; STRUCT_SIZE(UidOwnerValue, 2 * 4); // 8 +typedef struct { + // The destination ip of the incoming packet. IPv4 uses IPv4-mapped IPv6 address format. + struct in6_addr daddr; +} IngressDiscardKey; +STRUCT_SIZE(IngressDiscardKey, 16); // 16 + +typedef struct { + // Allowed interface indexes. Use same value multiple times if you just want to match 1 value. + uint32_t iif[2]; +} IngressDiscardValue; +STRUCT_SIZE(IngressDiscardValue, 2 * 4); // 8 + // Entry in the configuration map that stores which UID rules are enabled. #define UID_RULES_CONFIGURATION_KEY 0 // Entry in the configuration map that stores which stats map is currently in use. #define CURRENT_STATS_MAP_CONFIGURATION_KEY 1 +// Entry in the data saver enabled map that stores whether data saver is enabled or not. +#define DATA_SAVER_ENABLED_KEY 0 #undef STRUCT_SIZE + +// DROP_IF_SET is set of rules that DROP if rule is globally enabled, and per-uid bit is set +#define DROP_IF_SET (STANDBY_MATCH | OEM_DENY_1_MATCH | OEM_DENY_2_MATCH | OEM_DENY_3_MATCH) +// DROP_IF_UNSET is set of rules that should DROP if globally enabled, and per-uid bit is NOT set +#define DROP_IF_UNSET (DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH \ + | LOW_POWER_STANDBY_MATCH | BACKGROUND_MATCH) + +#define FIREWALL_DROP_IF_SET (OEM_DENY_1_MATCH) +#define FIREWALL_DROP_IF_UNSET (RESTRICTED_MATCH) + +// Warning: funky bit-wise arithmetic: in parallel, for all DROP_IF_SET/UNSET rules +// check whether the rules are globally enabled, and if so whether the rules are +// set/unset for the specific uid. DROP if that is the case for ANY of the rules. +// We achieve this by masking out only the bits/rules we're interested in checking, +// and negating (via bit-wise xor) the bits/rules that should drop if unset. +static inline bool isBlockedByUidRules(BpfConfig enabledRules, uint32_t uidRules) { + return enabledRules & (DROP_IF_SET | DROP_IF_UNSET) & (uidRules ^ DROP_IF_UNSET); +} + +static inline bool is_system_uid(uint32_t uid) { + // MIN_SYSTEM_UID is AID_ROOT == 0, so uint32_t is *always* >= 0 + // MAX_SYSTEM_UID is AID_NOBODY == 9999, while AID_APP_START == 10000 + return (uid < AID_APP_START); +} diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c index 80d1a419d51f51846c039c59e62f72f730184d6c..90f96a14c8e176f7825bae16b47ff2ad9a796b52 100644 --- a/bpf_progs/offload.c +++ b/bpf_progs/offload.c @@ -124,8 +124,12 @@ DEFINE_BPF_MAP_GRW(tether_downstream64_map, HASH, TetherDownstream64Key, TetherD DEFINE_BPF_MAP_GRW(tether_upstream6_map, HASH, TetherUpstream6Key, Tether6Value, 64, TETHERING_GID) -static inline __always_inline int do_forward6(struct __sk_buff* skb, const bool is_ethernet, - const bool downstream, const unsigned kver) { +static inline __always_inline int do_forward6(struct __sk_buff* skb, + const struct rawip_bool rawip, + const struct stream_bool stream, + const struct kver_uint kver) { + const bool is_ethernet = !rawip.rawip; + // Must be meta-ethernet IPv6 frame if (skb->protocol != htons(ETH_P_IPV6)) return TC_ACT_PIPE; @@ -184,7 +188,7 @@ static inline __always_inline int do_forward6(struct __sk_buff* skb, const bool TC_PUNT(NON_GLOBAL_DST); // In the upstream direction do not forward traffic within the same /64 subnet. - if (!downstream && (src32 == dst32) && (ip6->saddr.s6_addr32[1] == ip6->daddr.s6_addr32[1])) + if (!stream.down && (src32 == dst32) && (ip6->saddr.s6_addr32[1] == ip6->daddr.s6_addr32[1])) TC_PUNT(LOCAL_SRC_DST); TetherDownstream6Key kd = { @@ -194,16 +198,18 @@ static inline __always_inline int do_forward6(struct __sk_buff* skb, const bool TetherUpstream6Key ku = { .iif = skb->ifindex, + // Retrieve the first 64 bits of the source IPv6 address in network order + .src64 = *(uint64_t*)&(ip6->saddr.s6_addr32[0]), }; - if (is_ethernet) __builtin_memcpy(downstream ? kd.dstMac : ku.dstMac, eth->h_dest, ETH_ALEN); + if (is_ethernet) __builtin_memcpy(stream.down ? kd.dstMac : ku.dstMac, eth->h_dest, ETH_ALEN); - Tether6Value* v = downstream ? bpf_tether_downstream6_map_lookup_elem(&kd) - : bpf_tether_upstream6_map_lookup_elem(&ku); + Tether6Value* v = stream.down ? bpf_tether_downstream6_map_lookup_elem(&kd) + : bpf_tether_upstream6_map_lookup_elem(&ku); // If we don't find any offload information then simply let the core stack handle it... if (!v) return TC_ACT_PIPE; - uint32_t stat_and_limit_k = downstream ? skb->ifindex : v->oif; + uint32_t stat_and_limit_k = stream.down ? skb->ifindex : v->oif; TetherStatsValue* stat_v = bpf_tether_stats_map_lookup_elem(&stat_and_limit_k); @@ -248,7 +254,7 @@ static inline __always_inline int do_forward6(struct __sk_buff* skb, const bool // We do this even if TX interface is RAWIP and thus does not need an ethernet header, // because this is easier and the kernel will strip extraneous ethernet header. if (bpf_skb_change_head(skb, sizeof(struct ethhdr), /*flags*/ 0)) { - __sync_fetch_and_add(downstream ? &stat_v->rxErrors : &stat_v->txErrors, 1); + __sync_fetch_and_add(stream.down ? &stat_v->rxErrors : &stat_v->txErrors, 1); TC_PUNT(CHANGE_HEAD_FAILED); } @@ -260,7 +266,7 @@ static inline __always_inline int do_forward6(struct __sk_buff* skb, const bool // I do not believe this can ever happen, but keep the verifier happy... if (data + sizeof(struct ethhdr) + sizeof(*ip6) > data_end) { - __sync_fetch_and_add(downstream ? &stat_v->rxErrors : &stat_v->txErrors, 1); + __sync_fetch_and_add(stream.down ? &stat_v->rxErrors : &stat_v->txErrors, 1); TC_DROP(TOO_SHORT); } }; @@ -280,8 +286,8 @@ static inline __always_inline int do_forward6(struct __sk_buff* skb, const bool // (-ENOTSUPP) if it isn't. bpf_csum_update(skb, 0xFFFF - ntohs(old_hl) + ntohs(new_hl)); - __sync_fetch_and_add(downstream ? &stat_v->rxPackets : &stat_v->txPackets, packets); - __sync_fetch_and_add(downstream ? &stat_v->rxBytes : &stat_v->txBytes, L3_bytes); + __sync_fetch_and_add(stream.down ? &stat_v->rxPackets : &stat_v->txPackets, packets); + __sync_fetch_and_add(stream.down ? &stat_v->rxBytes : &stat_v->txBytes, L3_bytes); // Overwrite any mac header with the new one // For a rawip tx interface it will simply be a bunch of zeroes and later stripped. @@ -323,26 +329,26 @@ DEFINE_BPF_PROG("schedcls/tether_upstream6_ether", TETHERING_UID, TETHERING_GID, // // Hence, these mandatory (must load successfully) implementations for 4.14+ kernels: DEFINE_BPF_PROG_KVER("schedcls/tether_downstream6_rawip$4_14", TETHERING_UID, TETHERING_GID, - sched_cls_tether_downstream6_rawip_4_14, KVER(4, 14, 0)) + sched_cls_tether_downstream6_rawip_4_14, KVER_4_14) (struct __sk_buff* skb) { - return do_forward6(skb, RAWIP, DOWNSTREAM, KVER(4, 14, 0)); + return do_forward6(skb, RAWIP, DOWNSTREAM, KVER_4_14); } DEFINE_BPF_PROG_KVER("schedcls/tether_upstream6_rawip$4_14", TETHERING_UID, TETHERING_GID, - sched_cls_tether_upstream6_rawip_4_14, KVER(4, 14, 0)) + sched_cls_tether_upstream6_rawip_4_14, KVER_4_14) (struct __sk_buff* skb) { - return do_forward6(skb, RAWIP, UPSTREAM, KVER(4, 14, 0)); + return do_forward6(skb, RAWIP, UPSTREAM, KVER_4_14); } // and define no-op stubs for pre-4.14 kernels. DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream6_rawip$stub", TETHERING_UID, TETHERING_GID, - sched_cls_tether_downstream6_rawip_stub, KVER_NONE, KVER(4, 14, 0)) + sched_cls_tether_downstream6_rawip_stub, KVER_NONE, KVER_4_14) (struct __sk_buff* skb) { return TC_ACT_PIPE; } DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream6_rawip$stub", TETHERING_UID, TETHERING_GID, - sched_cls_tether_upstream6_rawip_stub, KVER_NONE, KVER(4, 14, 0)) + sched_cls_tether_upstream6_rawip_stub, KVER_NONE, KVER_4_14) (struct __sk_buff* skb) { return TC_ACT_PIPE; } @@ -355,9 +361,10 @@ DEFINE_BPF_MAP_GRW(tether_upstream4_map, HASH, Tether4Key, Tether4Value, 1024, T static inline __always_inline int do_forward4_bottom(struct __sk_buff* skb, const int l2_header_size, void* data, const void* data_end, - struct ethhdr* eth, struct iphdr* ip, const bool is_ethernet, - const bool downstream, const bool updatetime, const bool is_tcp, - const unsigned kver) { + struct ethhdr* eth, struct iphdr* ip, const struct rawip_bool rawip, + const struct stream_bool stream, const struct updatetime_bool updatetime, + const bool is_tcp, const struct kver_uint kver) { + const bool is_ethernet = !rawip.rawip; struct tcphdr* tcph = is_tcp ? (void*)(ip + 1) : NULL; struct udphdr* udph = is_tcp ? NULL : (void*)(ip + 1); @@ -415,13 +422,13 @@ static inline __always_inline int do_forward4_bottom(struct __sk_buff* skb, }; if (is_ethernet) __builtin_memcpy(k.dstMac, eth->h_dest, ETH_ALEN); - Tether4Value* v = downstream ? bpf_tether_downstream4_map_lookup_elem(&k) - : bpf_tether_upstream4_map_lookup_elem(&k); + Tether4Value* v = stream.down ? bpf_tether_downstream4_map_lookup_elem(&k) + : bpf_tether_upstream4_map_lookup_elem(&k); // If we don't find any offload information then simply let the core stack handle it... if (!v) return TC_ACT_PIPE; - uint32_t stat_and_limit_k = downstream ? skb->ifindex : v->oif; + uint32_t stat_and_limit_k = stream.down ? skb->ifindex : v->oif; TetherStatsValue* stat_v = bpf_tether_stats_map_lookup_elem(&stat_and_limit_k); @@ -466,7 +473,7 @@ static inline __always_inline int do_forward4_bottom(struct __sk_buff* skb, // We do this even if TX interface is RAWIP and thus does not need an ethernet header, // because this is easier and the kernel will strip extraneous ethernet header. if (bpf_skb_change_head(skb, sizeof(struct ethhdr), /*flags*/ 0)) { - __sync_fetch_and_add(downstream ? &stat_v->rxErrors : &stat_v->txErrors, 1); + __sync_fetch_and_add(stream.down ? &stat_v->rxErrors : &stat_v->txErrors, 1); TC_PUNT(CHANGE_HEAD_FAILED); } @@ -480,7 +487,7 @@ static inline __always_inline int do_forward4_bottom(struct __sk_buff* skb, // I do not believe this can ever happen, but keep the verifier happy... if (data + sizeof(struct ethhdr) + sizeof(*ip) + (is_tcp ? sizeof(*tcph) : sizeof(*udph)) > data_end) { - __sync_fetch_and_add(downstream ? &stat_v->rxErrors : &stat_v->txErrors, 1); + __sync_fetch_and_add(stream.down ? &stat_v->rxErrors : &stat_v->txErrors, 1); TC_DROP(TOO_SHORT); } }; @@ -532,10 +539,10 @@ static inline __always_inline int do_forward4_bottom(struct __sk_buff* skb, // This requires the bpf_ktime_get_boot_ns() helper which was added in 5.8, // and backported to all Android Common Kernel 4.14+ trees. - if (updatetime) v->last_used = bpf_ktime_get_boot_ns(); + if (updatetime.updatetime) v->last_used = bpf_ktime_get_boot_ns(); - __sync_fetch_and_add(downstream ? &stat_v->rxPackets : &stat_v->txPackets, packets); - __sync_fetch_and_add(downstream ? &stat_v->rxBytes : &stat_v->txBytes, L3_bytes); + __sync_fetch_and_add(stream.down ? &stat_v->rxPackets : &stat_v->txPackets, packets); + __sync_fetch_and_add(stream.down ? &stat_v->rxBytes : &stat_v->txBytes, L3_bytes); // Redirect to forwarded interface. // @@ -546,8 +553,13 @@ static inline __always_inline int do_forward4_bottom(struct __sk_buff* skb, return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */); } -static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool is_ethernet, - const bool downstream, const bool updatetime, const unsigned kver) { +static inline __always_inline int do_forward4(struct __sk_buff* skb, + const struct rawip_bool rawip, + const struct stream_bool stream, + const struct updatetime_bool updatetime, + const struct kver_uint kver) { + const bool is_ethernet = !rawip.rawip; + // Require ethernet dst mac address to be our unicast address. if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE; @@ -605,16 +617,16 @@ static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool // in such a situation we can only support TCP. This also has the added nice benefit of // using a separate error counter, and thus making it obvious which version of the program // is loaded. - if (!updatetime && ip->protocol != IPPROTO_TCP) TC_PUNT(NON_TCP); + if (!updatetime.updatetime && ip->protocol != IPPROTO_TCP) TC_PUNT(NON_TCP); // We do not support offloading anything besides IPv4 TCP and UDP, due to need for NAT, // but no need to check this if !updatetime due to check immediately above. - if (updatetime && (ip->protocol != IPPROTO_TCP) && (ip->protocol != IPPROTO_UDP)) + if (updatetime.updatetime && (ip->protocol != IPPROTO_TCP) && (ip->protocol != IPPROTO_UDP)) TC_PUNT(NON_TCP_UDP); // We want to make sure that the compiler will, in the !updatetime case, entirely optimize // out all the non-tcp logic. Also note that at this point is_udp === !is_tcp. - const bool is_tcp = !updatetime || (ip->protocol == IPPROTO_TCP); + const bool is_tcp = !updatetime.updatetime || (ip->protocol == IPPROTO_TCP); // This is a bit of a hack to make things easier on the bpf verifier. // (In particular I believe the Linux 4.14 kernel's verifier can get confused later on about @@ -635,37 +647,37 @@ static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool // if the underlying requisite kernel support (bpf_ktime_get_boot_ns) was backported. if (is_tcp) { return do_forward4_bottom(skb, l2_header_size, data, data_end, eth, ip, - is_ethernet, downstream, updatetime, /* is_tcp */ true, kver); + rawip, stream, updatetime, /* is_tcp */ true, kver); } else { return do_forward4_bottom(skb, l2_header_size, data, data_end, eth, ip, - is_ethernet, downstream, updatetime, /* is_tcp */ false, kver); + rawip, stream, updatetime, /* is_tcp */ false, kver); } } // Full featured (required) implementations for 5.8+ kernels (these are S+ by definition) DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_rawip$5_8", TETHERING_UID, TETHERING_GID, - sched_cls_tether_downstream4_rawip_5_8, KVER(5, 8, 0)) + sched_cls_tether_downstream4_rawip_5_8, KVER_5_8) (struct __sk_buff* skb) { - return do_forward4(skb, RAWIP, DOWNSTREAM, UPDATETIME, KVER(5, 8, 0)); + return do_forward4(skb, RAWIP, DOWNSTREAM, UPDATETIME, KVER_5_8); } DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_rawip$5_8", TETHERING_UID, TETHERING_GID, - sched_cls_tether_upstream4_rawip_5_8, KVER(5, 8, 0)) + sched_cls_tether_upstream4_rawip_5_8, KVER_5_8) (struct __sk_buff* skb) { - return do_forward4(skb, RAWIP, UPSTREAM, UPDATETIME, KVER(5, 8, 0)); + return do_forward4(skb, RAWIP, UPSTREAM, UPDATETIME, KVER_5_8); } DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_ether$5_8", TETHERING_UID, TETHERING_GID, - sched_cls_tether_downstream4_ether_5_8, KVER(5, 8, 0)) + sched_cls_tether_downstream4_ether_5_8, KVER_5_8) (struct __sk_buff* skb) { - return do_forward4(skb, ETHER, DOWNSTREAM, UPDATETIME, KVER(5, 8, 0)); + return do_forward4(skb, ETHER, DOWNSTREAM, UPDATETIME, KVER_5_8); } DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_ether$5_8", TETHERING_UID, TETHERING_GID, - sched_cls_tether_upstream4_ether_5_8, KVER(5, 8, 0)) + sched_cls_tether_upstream4_ether_5_8, KVER_5_8) (struct __sk_buff* skb) { - return do_forward4(skb, ETHER, UPSTREAM, UPDATETIME, KVER(5, 8, 0)); + return do_forward4(skb, ETHER, UPSTREAM, UPDATETIME, KVER_5_8); } // Full featured (optional) implementations for 4.14-S, 4.19-S & 5.4-S kernels @@ -674,33 +686,33 @@ DEFINE_BPF_PROG_KVER("schedcls/tether_upstream4_ether$5_8", TETHERING_UID, TETHE DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$opt", TETHERING_UID, TETHERING_GID, sched_cls_tether_downstream4_rawip_opt, - KVER(4, 14, 0), KVER(5, 8, 0)) + KVER_4_14, KVER_5_8) (struct __sk_buff* skb) { - return do_forward4(skb, RAWIP, DOWNSTREAM, UPDATETIME, KVER(4, 14, 0)); + return do_forward4(skb, RAWIP, DOWNSTREAM, UPDATETIME, KVER_4_14); } DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$opt", TETHERING_UID, TETHERING_GID, sched_cls_tether_upstream4_rawip_opt, - KVER(4, 14, 0), KVER(5, 8, 0)) + KVER_4_14, KVER_5_8) (struct __sk_buff* skb) { - return do_forward4(skb, RAWIP, UPSTREAM, UPDATETIME, KVER(4, 14, 0)); + return do_forward4(skb, RAWIP, UPSTREAM, UPDATETIME, KVER_4_14); } DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$opt", TETHERING_UID, TETHERING_GID, sched_cls_tether_downstream4_ether_opt, - KVER(4, 14, 0), KVER(5, 8, 0)) + KVER_4_14, KVER_5_8) (struct __sk_buff* skb) { - return do_forward4(skb, ETHER, DOWNSTREAM, UPDATETIME, KVER(4, 14, 0)); + return do_forward4(skb, ETHER, DOWNSTREAM, UPDATETIME, KVER_4_14); } DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$opt", TETHERING_UID, TETHERING_GID, sched_cls_tether_upstream4_ether_opt, - KVER(4, 14, 0), KVER(5, 8, 0)) + KVER_4_14, KVER_5_8) (struct __sk_buff* skb) { - return do_forward4(skb, ETHER, UPSTREAM, UPDATETIME, KVER(4, 14, 0)); + return do_forward4(skb, ETHER, UPSTREAM, UPDATETIME, KVER_4_14); } // Partial (TCP-only: will not update 'last_used' field) implementations for 4.14+ kernels. @@ -718,15 +730,15 @@ DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$opt", // RAWIP: Required for 5.4-R kernels -- which always support bpf_skb_change_head(). DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$5_4", TETHERING_UID, TETHERING_GID, - sched_cls_tether_downstream4_rawip_5_4, KVER(5, 4, 0), KVER(5, 8, 0)) + sched_cls_tether_downstream4_rawip_5_4, KVER_5_4, KVER_5_8) (struct __sk_buff* skb) { - return do_forward4(skb, RAWIP, DOWNSTREAM, NO_UPDATETIME, KVER(5, 4, 0)); + return do_forward4(skb, RAWIP, DOWNSTREAM, NO_UPDATETIME, KVER_5_4); } DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$5_4", TETHERING_UID, TETHERING_GID, - sched_cls_tether_upstream4_rawip_5_4, KVER(5, 4, 0), KVER(5, 8, 0)) + sched_cls_tether_upstream4_rawip_5_4, KVER_5_4, KVER_5_8) (struct __sk_buff* skb) { - return do_forward4(skb, RAWIP, UPSTREAM, NO_UPDATETIME, KVER(5, 4, 0)); + return do_forward4(skb, RAWIP, UPSTREAM, NO_UPDATETIME, KVER_5_4); } // RAWIP: Optional for 4.14/4.19 (R) kernels -- which support bpf_skb_change_head(). @@ -735,31 +747,31 @@ DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$5_4", TETHERING_UID, DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$4_14", TETHERING_UID, TETHERING_GID, sched_cls_tether_downstream4_rawip_4_14, - KVER(4, 14, 0), KVER(5, 4, 0)) + KVER_4_14, KVER_5_4) (struct __sk_buff* skb) { - return do_forward4(skb, RAWIP, DOWNSTREAM, NO_UPDATETIME, KVER(4, 14, 0)); + return do_forward4(skb, RAWIP, DOWNSTREAM, NO_UPDATETIME, KVER_4_14); } DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$4_14", TETHERING_UID, TETHERING_GID, sched_cls_tether_upstream4_rawip_4_14, - KVER(4, 14, 0), KVER(5, 4, 0)) + KVER_4_14, KVER_5_4) (struct __sk_buff* skb) { - return do_forward4(skb, RAWIP, UPSTREAM, NO_UPDATETIME, KVER(4, 14, 0)); + return do_forward4(skb, RAWIP, UPSTREAM, NO_UPDATETIME, KVER_4_14); } // ETHER: Required for 4.14-Q/R, 4.19-Q/R & 5.4-R kernels. DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$4_14", TETHERING_UID, TETHERING_GID, - sched_cls_tether_downstream4_ether_4_14, KVER(4, 14, 0), KVER(5, 8, 0)) + sched_cls_tether_downstream4_ether_4_14, KVER_4_14, KVER_5_8) (struct __sk_buff* skb) { - return do_forward4(skb, ETHER, DOWNSTREAM, NO_UPDATETIME, KVER(4, 14, 0)); + return do_forward4(skb, ETHER, DOWNSTREAM, NO_UPDATETIME, KVER_4_14); } DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$4_14", TETHERING_UID, TETHERING_GID, - sched_cls_tether_upstream4_ether_4_14, KVER(4, 14, 0), KVER(5, 8, 0)) + sched_cls_tether_upstream4_ether_4_14, KVER_4_14, KVER_5_8) (struct __sk_buff* skb) { - return do_forward4(skb, ETHER, UPSTREAM, NO_UPDATETIME, KVER(4, 14, 0)); + return do_forward4(skb, ETHER, UPSTREAM, NO_UPDATETIME, KVER_4_14); } // Placeholder (no-op) implementations for older Q kernels @@ -767,13 +779,13 @@ DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$4_14", TETHERING_UID // RAWIP: 4.9-P/Q, 4.14-P/Q & 4.19-Q kernels -- without bpf_skb_change_head() for tc programs DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$stub", TETHERING_UID, TETHERING_GID, - sched_cls_tether_downstream4_rawip_stub, KVER_NONE, KVER(5, 4, 0)) + sched_cls_tether_downstream4_rawip_stub, KVER_NONE, KVER_5_4) (struct __sk_buff* skb) { return TC_ACT_PIPE; } DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$stub", TETHERING_UID, TETHERING_GID, - sched_cls_tether_upstream4_rawip_stub, KVER_NONE, KVER(5, 4, 0)) + sched_cls_tether_upstream4_rawip_stub, KVER_NONE, KVER_5_4) (struct __sk_buff* skb) { return TC_ACT_PIPE; } @@ -781,13 +793,13 @@ DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$stub", TETHERING_UID // ETHER: 4.9-P/Q kernel DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$stub", TETHERING_UID, TETHERING_GID, - sched_cls_tether_downstream4_ether_stub, KVER_NONE, KVER(4, 14, 0)) + sched_cls_tether_downstream4_ether_stub, KVER_NONE, KVER_4_14) (struct __sk_buff* skb) { return TC_ACT_PIPE; } DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$stub", TETHERING_UID, TETHERING_GID, - sched_cls_tether_upstream4_ether_stub, KVER_NONE, KVER(4, 14, 0)) + sched_cls_tether_upstream4_ether_stub, KVER_NONE, KVER_4_14) (struct __sk_buff* skb) { return TC_ACT_PIPE; } @@ -796,17 +808,18 @@ DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$stub", TETHERING_UID DEFINE_BPF_MAP_GRW(tether_dev_map, DEVMAP_HASH, uint32_t, uint32_t, 64, TETHERING_GID) -static inline __always_inline int do_xdp_forward6(struct xdp_md *ctx, const bool is_ethernet, - const bool downstream) { +static inline __always_inline int do_xdp_forward6(struct xdp_md *ctx, const struct rawip_bool rawip, + const struct stream_bool stream) { return XDP_PASS; } -static inline __always_inline int do_xdp_forward4(struct xdp_md *ctx, const bool is_ethernet, - const bool downstream) { +static inline __always_inline int do_xdp_forward4(struct xdp_md *ctx, const struct rawip_bool rawip, + const struct stream_bool stream) { return XDP_PASS; } -static inline __always_inline int do_xdp_forward_ether(struct xdp_md *ctx, const bool downstream) { +static inline __always_inline int do_xdp_forward_ether(struct xdp_md *ctx, + const struct stream_bool stream) { const void* data = (void*)(long)ctx->data; const void* data_end = (void*)(long)ctx->data_end; const struct ethhdr* eth = data; @@ -815,15 +828,16 @@ static inline __always_inline int do_xdp_forward_ether(struct xdp_md *ctx, const if ((void*)(eth + 1) > data_end) return XDP_PASS; if (eth->h_proto == htons(ETH_P_IPV6)) - return do_xdp_forward6(ctx, ETHER, downstream); + return do_xdp_forward6(ctx, ETHER, stream); if (eth->h_proto == htons(ETH_P_IP)) - return do_xdp_forward4(ctx, ETHER, downstream); + return do_xdp_forward4(ctx, ETHER, stream); // Anything else we don't know how to handle... return XDP_PASS; } -static inline __always_inline int do_xdp_forward_rawip(struct xdp_md *ctx, const bool downstream) { +static inline __always_inline int do_xdp_forward_rawip(struct xdp_md *ctx, + const struct stream_bool stream) { const void* data = (void*)(long)ctx->data; const void* data_end = (void*)(long)ctx->data_end; @@ -831,15 +845,15 @@ static inline __always_inline int do_xdp_forward_rawip(struct xdp_md *ctx, const if (data_end - data < 1) return XDP_PASS; const uint8_t v = (*(uint8_t*)data) >> 4; - if (v == 6) return do_xdp_forward6(ctx, RAWIP, downstream); - if (v == 4) return do_xdp_forward4(ctx, RAWIP, downstream); + if (v == 6) return do_xdp_forward6(ctx, RAWIP, stream); + if (v == 4) return do_xdp_forward4(ctx, RAWIP, stream); // Anything else we don't know how to handle... return XDP_PASS; } #define DEFINE_XDP_PROG(str, func) \ - DEFINE_BPF_PROG_KVER(str, TETHERING_UID, TETHERING_GID, func, KVER(5, 9, 0))(struct xdp_md *ctx) + DEFINE_BPF_PROG_KVER(str, TETHERING_UID, TETHERING_GID, func, KVER_5_9)(struct xdp_md *ctx) DEFINE_XDP_PROG("xdp/tether_downstream_ether", xdp_tether_downstream_ether) { @@ -863,3 +877,4 @@ DEFINE_XDP_PROG("xdp/tether_upstream_rawip", LICENSE("Apache 2.0"); CRITICAL("Connectivity (Tethering)"); +DISABLE_BTF_ON_USER_BUILDS(); diff --git a/bpf_progs/offload.h b/bpf_progs/offload.h index 9dae6c987933f827a42dcd47a5e8ba1dfa3509ed..1e28f01f7ca1099e48c3243b25c2a19123c48ff7 100644 --- a/bpf_progs/offload.h +++ b/bpf_progs/offload.h @@ -135,10 +135,10 @@ STRUCT_SIZE(TetherDownstream64Value, 4 + 14 + 2 + 4 + 4 + 2 + 2 + 8); // 40 typedef struct { uint32_t iif; // The input interface index uint8_t dstMac[ETH_ALEN]; // destination ethernet mac address (zeroed iff rawip ingress) - uint8_t zero[2]; // zero pad for 8 byte alignment - // TODO: extend this to include src ip /64 subnet + uint8_t zero[6]; // zero pad for 8 byte alignment + uint64_t src64; // Top 64-bits of the src ip } TetherUpstream6Key; -STRUCT_SIZE(TetherUpstream6Key, 12); +STRUCT_SIZE(TetherUpstream6Key, 4 + 6 + 6 + 8); // 24 typedef struct { uint32_t iif; // The input interface index diff --git a/bpf_progs/test.c b/bpf_progs/test.c index 091743c5ba459e1bb8352f143ca90df34ca62553..70b08b795fcb17b5aa2f8a4ffe4eca7a7e8300a7 100644 --- a/bpf_progs/test.c +++ b/bpf_progs/test.c @@ -49,7 +49,7 @@ DEFINE_BPF_MAP_GRW(tether_downstream6_map, HASH, TetherDownstream6Key, Tether6Va DEFINE_BPF_MAP_GRW(bitmap, ARRAY, int, uint64_t, 2, TETHERING_GID) DEFINE_BPF_PROG_KVER("xdp/drop_ipv4_udp_ether", TETHERING_UID, TETHERING_GID, - xdp_test, KVER(5, 9, 0)) + xdp_test, KVER_5_9) (struct xdp_md *ctx) { void *data = (void *)(long)ctx->data; void *data_end = (void *)(long)ctx->data_end; @@ -67,3 +67,4 @@ DEFINE_BPF_PROG_KVER("xdp/drop_ipv4_udp_ether", TETHERING_UID, TETHERING_GID, } LICENSE("Apache 2.0"); +DISABLE_BTF_ON_USER_BUILDS(); diff --git a/clatd/.clang-format b/clatd/.clang-format new file mode 100644 index 0000000000000000000000000000000000000000..f1debbd461f8a1756a5a0d9b5a5f0433f366aa93 --- /dev/null +++ b/clatd/.clang-format @@ -0,0 +1,8 @@ +BasedOnStyle: Google +AlignConsecutiveAssignments: true +AlignEscapedNewlines: Right +ColumnLimit: 100 +CommentPragmas: NOLINT:.* +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: false +TabWidth: 2 diff --git a/clatd/Android.bp b/clatd/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..595c6b924db9bbf3c818c07763ff8a9ee61bf537 --- /dev/null +++ b/clatd/Android.bp @@ -0,0 +1,118 @@ +package { + default_applicable_licenses: ["external_android-clat_license"], +} + +// Added automatically by a large-scale-change +// +// large-scale-change included anything that looked like it might be a license +// text as a license_text. e.g. LICENSE, NOTICE, COPYING etc. +// +// Please consider removing redundant or irrelevant files from 'license_text:'. +// See: http://go/android-license-faq +license { + name: "external_android-clat_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "LICENSE", + "NOTICE", + ], +} + +cc_defaults { + name: "clatd_defaults", + + cflags: [ + "-Wall", + "-Werror", + "-Wunused-parameter", + + // Bug: http://b/33566695 + "-Wno-address-of-packed-member", + ], +} + +// Code used both by the daemon and by unit tests. +filegroup { + name: "clatd_common", + srcs: [ + "clatd.c", + "dump.c", + "icmp.c", + "ipv4.c", + "ipv6.c", + "logging.c", + "translate.c", + ], +} + +// The clat daemon. +cc_binary { + name: "clatd", + defaults: ["clatd_defaults"], + srcs: [ + ":clatd_common", + "main.c" + ], + static_libs: [ + "libip_checksum", + ], + shared_libs: [ + "liblog", + ], + relative_install_path: "for-system", + + // Static libc++ for smaller apex size while shipping clatd in the mainline module. + // See b/213123047 + stl: "libc++_static", + + // Only enable clang-tidy for the daemon, not the tests, because enabling it for the + // tests substantially increases build/compile cycle times and doesn't really provide a + // security benefit. + tidy: true, + tidy_checks: [ + "-*", + "cert-*", + "clang-analyzer-security*", + // b/2043314, warnings on memcpy_s, memset_s, snprintf_s calls + // are blocking the migration from gnu99 to gnu11. + // Until those warnings are fixed, disable these checks. + "-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling", + "android-*", + ], + tidy_checks_as_errors: [ + "clang-analyzer-security*", + "cert-*", + "android-*", + ], + + apex_available: [ + "com.android.tethering", + "//apex_available:platform", + ], + min_sdk_version: "30", +} + +// Unit tests. +cc_test { + name: "clatd_test", + defaults: ["clatd_defaults"], + srcs: [ + ":clatd_common", + "clatd_test.cpp" + ], + static_libs: [ + "libbase", + "libip_checksum", + "libnetd_test_tun_interface", + ], + shared_libs: [ + "libcutils", + "liblog", + "libnetutils", + ], + test_suites: ["device-tests"], + require_root: true, +} diff --git a/clatd/BUGS b/clatd/BUGS new file mode 100644 index 0000000000000000000000000000000000000000..24e6639d11ebef6eaf5ada9a99e49c51bef483f3 --- /dev/null +++ b/clatd/BUGS @@ -0,0 +1,5 @@ +known problems/assumptions: + - does not handle protocols other than ICMP, UDP, TCP and GRE/ESP + - assumes the handset has its own (routed) /64 ipv6 subnet + - assumes the /128 ipv6 subnet it generates can use the nat64 gateway + - assumes the nat64 gateway has the ipv4 address in the last 32 bits of the ipv6 address (that it uses a /96 plat subnet) diff --git a/clatd/LICENSE b/clatd/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/clatd/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/clatd/METADATA b/clatd/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..d97975ca3b99b2c09e1d43944d83625c0aee561e --- /dev/null +++ b/clatd/METADATA @@ -0,0 +1,3 @@ +third_party { + license_type: NOTICE +} diff --git a/Tethering/apex/out-of-process b/clatd/MODULE_LICENSE_APACHE2 similarity index 100% rename from Tethering/apex/out-of-process rename to clatd/MODULE_LICENSE_APACHE2 diff --git a/clatd/NOTICE b/clatd/NOTICE new file mode 100644 index 0000000000000000000000000000000000000000..5943b54b26d3454380caed4f02594439cbef6756 --- /dev/null +++ b/clatd/NOTICE @@ -0,0 +1,189 @@ + Copyright (c) 2010-2012, Daniel Drown + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/clatd/PREUPLOAD.cfg b/clatd/PREUPLOAD.cfg new file mode 100644 index 0000000000000000000000000000000000000000..c8dbf77fa1106c209ff0c6df0073ccf0eb74ce1e --- /dev/null +++ b/clatd/PREUPLOAD.cfg @@ -0,0 +1,5 @@ +[Builtin Hooks] +clang_format = true + +[Builtin Hooks Options] +clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp diff --git a/clatd/TEST_MAPPING b/clatd/TEST_MAPPING new file mode 100644 index 0000000000000000000000000000000000000000..d36908adfcb455fe972ed5215e92410500e09f77 --- /dev/null +++ b/clatd/TEST_MAPPING @@ -0,0 +1,10 @@ +{ + "presubmit": [ + { "name": "clatd_test" }, + { "name": "netd_integration_test" }, + { "name": "netd_unit_test" }, + { "name": "netdutils_test" }, + { "name": "resolv_integration_test" }, + { "name": "resolv_unit_test" } + ] +} diff --git a/clatd/clatd.c b/clatd/clatd.c new file mode 100644 index 0000000000000000000000000000000000000000..bac8b1da4f1b27ec36b4e735ac618ba0259b8b93 --- /dev/null +++ b/clatd/clatd.c @@ -0,0 +1,307 @@ +/* + * Copyright 2012 Daniel Drown + * + * 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. + * + * clatd.c - tun interface setup and main event loop + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clatd.h" +#include "checksum.h" +#include "config.h" +#include "dump.h" +#include "logging.h" +#include "translate.h" + +struct clat_config Global_Clatd_Config; + +volatile sig_atomic_t running = 1; + +// reads IPv6 packet from AF_PACKET socket, translates to IPv4, writes to tun +void process_packet_6_to_4(struct tun_data *tunnel) { + // ethernet header is 14 bytes, plus 4 for a normal VLAN tag or 8 for Q-in-Q + // we don't really support vlans (or especially Q-in-Q)... + // but a few bytes of extra buffer space doesn't hurt... + struct { + struct virtio_net_hdr vnet; + uint8_t payload[22 + MAXMTU]; + char pad; // +1 to make packet truncation obvious + } buf; + struct iovec iov = { + .iov_base = &buf, + .iov_len = sizeof(buf), + }; + char cmsg_buf[CMSG_SPACE(sizeof(struct tpacket_auxdata))]; + struct msghdr msgh = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsg_buf, + .msg_controllen = sizeof(cmsg_buf), + }; + ssize_t readlen = recvmsg(tunnel->read_fd6, &msgh, /*flags*/ 0); + + if (readlen < 0) { + if (errno != EAGAIN) { + logmsg(ANDROID_LOG_WARN, "%s: read error: %s", __func__, strerror(errno)); + } + return; + } else if (readlen == 0) { + logmsg(ANDROID_LOG_WARN, "%s: packet socket removed?", __func__); + running = 0; + return; + } else if (readlen >= sizeof(buf)) { + logmsg(ANDROID_LOG_WARN, "%s: read truncation - ignoring pkt", __func__); + return; + } + + bool ok = false; + __u32 tp_status = 0; + __u16 tp_net = 0; + + for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh,cmsg)) { + if (cmsg->cmsg_level == SOL_PACKET && cmsg->cmsg_type == PACKET_AUXDATA) { + struct tpacket_auxdata *aux = (struct tpacket_auxdata *)CMSG_DATA(cmsg); + ok = true; + tp_status = aux->tp_status; + tp_net = aux->tp_net; + break; + } + } + + if (!ok) { + // theoretically this should not happen... + static bool logged = false; + if (!logged) { + logmsg(ANDROID_LOG_ERROR, "%s: failed to fetch tpacket_auxdata cmsg", __func__); + logged = true; + } + } + + const int payload_offset = offsetof(typeof(buf), payload); + if (readlen < payload_offset + tp_net) { + logmsg(ANDROID_LOG_WARN, "%s: ignoring %zd byte pkt shorter than %d+%u L2 header", + __func__, readlen, payload_offset, tp_net); + return; + } + + const int pkt_len = readlen - payload_offset; + + // This will detect a skb->ip_summed == CHECKSUM_PARTIAL packet with non-final L4 checksum + if (tp_status & TP_STATUS_CSUMNOTREADY) { + static bool logged = false; + if (!logged) { + logmsg(ANDROID_LOG_WARN, "%s: L4 checksum calculation required", __func__); + logged = true; + } + + // These are non-negative by virtue of csum_start/offset being u16 + const int cs_start = buf.vnet.csum_start; + const int cs_offset = cs_start + buf.vnet.csum_offset; + if (cs_start > pkt_len) { + logmsg(ANDROID_LOG_ERROR, "%s: out of range - checksum start %d > %d", + __func__, cs_start, pkt_len); + } else if (cs_offset + 1 >= pkt_len) { + logmsg(ANDROID_LOG_ERROR, "%s: out of range - checksum offset %d + 1 >= %d", + __func__, cs_offset, pkt_len); + } else { + uint16_t csum = ip_checksum(buf.payload + cs_start, pkt_len - cs_start); + if (!csum) csum = 0xFFFF; // required fixup for UDP, TCP must live with it + buf.payload[cs_offset] = csum & 0xFF; + buf.payload[cs_offset + 1] = csum >> 8; + } + } + + translate_packet(tunnel->fd4, 0 /* to_ipv6 */, buf.payload + tp_net, pkt_len - tp_net); +} + +// reads TUN_PI + L3 IPv4 packet from tun, translates to IPv6, writes to AF_INET6/RAW socket +void process_packet_4_to_6(struct tun_data *tunnel) { + struct { + struct tun_pi pi; + uint8_t payload[MAXMTU]; + char pad; // +1 byte to make packet truncation obvious + } buf; + ssize_t readlen = read(tunnel->fd4, &buf, sizeof(buf)); + + if (readlen < 0) { + if (errno != EAGAIN) { + logmsg(ANDROID_LOG_WARN, "%s: read error: %s", __func__, strerror(errno)); + } + return; + } else if (readlen == 0) { + logmsg(ANDROID_LOG_WARN, "%s: tun interface removed", __func__); + running = 0; + return; + } else if (readlen >= sizeof(buf)) { + logmsg(ANDROID_LOG_WARN, "%s: read truncation - ignoring pkt", __func__); + return; + } + + const int payload_offset = offsetof(typeof(buf), payload); + + if (readlen < payload_offset) { + logmsg(ANDROID_LOG_WARN, "%s: short read: got %ld bytes", __func__, readlen); + return; + } + + const int pkt_len = readlen - payload_offset; + + uint16_t proto = ntohs(buf.pi.proto); + if (proto != ETH_P_IP) { + logmsg(ANDROID_LOG_WARN, "%s: unknown packet type = 0x%x", __func__, proto); + return; + } + + if (buf.pi.flags != 0) { + logmsg(ANDROID_LOG_WARN, "%s: unexpected flags = %d", __func__, buf.pi.flags); + } + + translate_packet(tunnel->write_fd6, 1 /* to_ipv6 */, buf.payload, pkt_len); +} + +// IPv6 DAD packet format: +// Ethernet header (if needed) will be added by the kernel: +// u8[6] src_mac; u8[6] dst_mac '33:33:ff:XX:XX:XX'; be16 ethertype '0x86DD' +// IPv6 header: +// be32 0x60000000 - ipv6, tclass 0, flowlabel 0 +// be16 payload_length '32'; u8 nxt_hdr ICMPv6 '58'; u8 hop limit '255' +// u128 src_ip6 '::' +// u128 dst_ip6 'ff02::1:ffXX:XXXX' +// ICMPv6 header: +// u8 type '135'; u8 code '0'; u16 icmp6 checksum; u32 reserved '0' +// ICMPv6 neighbour solicitation payload: +// u128 tgt_ip6 +// ICMPv6 ND options: +// u8 opt nr '14'; u8 length '1'; u8[6] nonce '6 random bytes' +void send_dad(int fd, const struct in6_addr* tgt) { + struct { + struct ip6_hdr ip6h; + struct nd_neighbor_solicit ns; + uint8_t ns_opt_nr; + uint8_t ns_opt_len; + uint8_t ns_opt_nonce[6]; + } dad_pkt = { + .ip6h = { + .ip6_flow = htonl(6 << 28), // v6, 0 tclass, 0 flowlabel + .ip6_plen = htons(sizeof(dad_pkt) - sizeof(struct ip6_hdr)), // payload length, ie. 32 + .ip6_nxt = IPPROTO_ICMPV6, // 58 + .ip6_hlim = 255, + .ip6_src = {}, // :: + .ip6_dst.s6_addr = { + 0xFF, 0x02, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 1, + 0xFF, tgt->s6_addr[13], tgt->s6_addr[14], tgt->s6_addr[15], + }, // ff02::1:ffXX:XXXX - multicast group address derived from bottom 24-bits of tgt + }, + .ns = { + .nd_ns_type = ND_NEIGHBOR_SOLICIT, // 135 + .nd_ns_code = 0, + .nd_ns_cksum = 0, // will be calculated later + .nd_ns_reserved = 0, + .nd_ns_target = *tgt, + }, + .ns_opt_nr = 14, // icmp6 option 'nonce' from RFC3971 + .ns_opt_len = 1, // in units of 8 bytes, including option nr and len + .ns_opt_nonce = {}, // opt_len *8 - sizeof u8(opt_nr) - sizeof u8(opt_len) = 6 ranodmized bytes + }; + arc4random_buf(&dad_pkt.ns_opt_nonce, sizeof(dad_pkt.ns_opt_nonce)); + + // 40 byte IPv6 header + 8 byte ICMPv6 header + 16 byte ipv6 target address + 8 byte nonce option + _Static_assert(sizeof(dad_pkt) == 40 + 8 + 16 + 8, "sizeof dad packet != 72"); + + // IPv6 header checksum is standard negated 16-bit one's complement sum over the icmpv6 pseudo + // header (which includes payload length, nextheader, and src/dst ip) and the icmpv6 payload. + // + // Src/dst ip immediately prefix the icmpv6 header itself, so can be handled along + // with the payload. We thus only need to manually account for payload len & next header. + // + // The magic '8' is simply the offset of the ip6_src field in the ipv6 header, + // ie. we're skipping over the ipv6 version, tclass, flowlabel, payload length, next header + // and hop limit fields, because they're not quite where we want them to be. + // + // ip6_plen is already in network order, while ip6_nxt is a single byte and thus needs htons(). + uint32_t csum = dad_pkt.ip6h.ip6_plen + htons(dad_pkt.ip6h.ip6_nxt); + csum = ip_checksum_add(csum, &dad_pkt.ip6h.ip6_src, sizeof(dad_pkt) - 8); + dad_pkt.ns.nd_ns_cksum = ip_checksum_finish(csum); + + const struct sockaddr_in6 dst = { + .sin6_family = AF_INET6, + .sin6_addr = dad_pkt.ip6h.ip6_dst, + .sin6_scope_id = if_nametoindex(Global_Clatd_Config.native_ipv6_interface), + }; + + sendto(fd, &dad_pkt, sizeof(dad_pkt), 0 /*flags*/, (const struct sockaddr *)&dst, sizeof(dst)); +} + +/* function: event_loop + * reads packets from the tun network interface and passes them down the stack + * tunnel - tun device data + */ +void event_loop(struct tun_data *tunnel) { + // Apparently some network gear will refuse to perform NS for IPs that aren't DAD'ed, + // this would then result in an ipv6-only network with working native ipv6, working + // IPv4 via DNS64, but non-functioning IPv4 via CLAT (ie. IPv4 literals + IPv4 only apps). + // The kernel itself doesn't do DAD for anycast ips (but does handle IPV6 MLD and handle ND). + // So we'll spoof dad here, and yeah, we really should check for a response and in + // case of failure pick a different IP. Seeing as 48-bits of the IP are utterly random + // (with the other 16 chosen to guarantee checksum neutrality) this seems like a remote + // concern... + // TODO: actually perform true DAD + send_dad(tunnel->write_fd6, &Global_Clatd_Config.ipv6_local_subnet); + + struct pollfd wait_fd[] = { + { tunnel->read_fd6, POLLIN, 0 }, + { tunnel->fd4, POLLIN, 0 }, + }; + + while (running) { + if (poll(wait_fd, ARRAY_SIZE(wait_fd), -1) == -1) { + if (errno != EINTR) { + logmsg(ANDROID_LOG_WARN, "event_loop/poll returned an error: %s", strerror(errno)); + } + } else { + // Call process_packet if the socket has data to be read, but also if an + // error is waiting. If we don't call read() after getting POLLERR, a + // subsequent poll() will return immediately with POLLERR again, + // causing this code to spin in a loop. Calling read() will clear the + // socket error flag instead. + if (wait_fd[0].revents) process_packet_6_to_4(tunnel); + if (wait_fd[1].revents) process_packet_4_to_6(tunnel); + } + } +} diff --git a/clatd/clatd.h b/clatd/clatd.h new file mode 100644 index 0000000000000000000000000000000000000000..e170c58dc83c9ca3b49458919a361d019939c237 --- /dev/null +++ b/clatd/clatd.h @@ -0,0 +1,81 @@ +/* + * Copyright 2011 Daniel Drown + * + * 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. + * + * clatd.h - main routines used by clatd + */ +#ifndef __CLATD_H__ +#define __CLATD_H__ + +#include +#include +#include + +struct tun_data; + +// IPv4 header has a u16 total length field, for maximum L3 mtu of 0xFFFF. +// +// Translating IPv4 to IPv6 requires removing the IPv4 header (20) and adding +// an IPv6 header (40), possibly with an extra ipv6 fragment extension header (8). +// +// As such the maximum IPv4 L3 mtu size is 0xFFFF (by u16 tot_len field) +// and the maximum IPv6 L3 mtu size is 0xFFFF + 28 (which is larger) +// +// A received non-jumbogram IPv6 frame could potentially be u16 payload_len = 0xFFFF +// + sizeof ipv6 header = 40, bytes in size. But such a packet cannot be meaningfully +// converted to IPv4 (it's too large). As such the restriction is the same: 0xFFFF + 28 +// +// (since there's no jumbogram support in IPv4, IPv6 jumbograms cannot be meaningfully +// converted to IPv4 anyway, and are thus entirely unsupported) +#define MAXMTU (0xFFFF + 28) + +// logcat_hexdump() maximum binary data length, this is the maximum packet size +// plus some extra space for various headers: +// struct tun_pi (4 bytes) +// struct virtio_net_hdr (10 bytes) +// ethernet (14 bytes), potentially including vlan tag (4) or tags (8 or 12) +// plus some extra just-in-case headroom, because it doesn't hurt. +#define MAXDUMPLEN (64 + MAXMTU) + +#define CLATD_VERSION "1.7" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +extern volatile sig_atomic_t running; + +void event_loop(struct tun_data *tunnel); + +/* function: parse_int + * parses a string as a decimal/hex/octal signed integer + * str - the string to parse + * out - the signed integer to write to, gets clobbered on failure + */ +static inline int parse_int(const char *str, int *out) { + char *end_ptr; + *out = strtol(str, &end_ptr, 0); + return *str && !*end_ptr; +} + +/* function: parse_unsigned + * parses a string as a decimal/hex/octal unsigned integer + * str - the string to parse + * out - the unsigned integer to write to, gets clobbered on failure + */ +static inline int parse_unsigned(const char *str, unsigned *out) { + char *end_ptr; + *out = strtoul(str, &end_ptr, 0); + return *str && !*end_ptr; +} + +#endif /* __CLATD_H__ */ diff --git a/clatd/clatd_test.cpp b/clatd/clatd_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0ed5f28423c2efb0cc4fd691d483cc41277de4f2 --- /dev/null +++ b/clatd/clatd_test.cpp @@ -0,0 +1,835 @@ +/* + * Copyright 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. + * + * clatd_test.cpp - unit tests for clatd + */ + +#include + +#include +#include +#include +#include +#include + +#include + +#include "netutils/ifc.h" +#include "tun_interface.h" + +extern "C" { +#include "checksum.h" +#include "clatd.h" +#include "config.h" +#include "translate.h" +} + +// For convenience. +#define ARRAYSIZE(x) sizeof((x)) / sizeof((x)[0]) + +using android::net::TunInterface; + +// Default translation parameters. +static const char kIPv4LocalAddr[] = "192.0.0.4"; +static const char kIPv6LocalAddr[] = "2001:db8:0:b11::464"; +static const char kIPv6PlatSubnet[] = "64:ff9b::"; + +// clang-format off +// Test packet portions. Defined as macros because it's easy to concatenate them to make packets. +#define IPV4_HEADER(p, c1, c2) \ + 0x45, 0x00, 0, 41, /* Version=4, IHL=5, ToS=0x80, len=41 */ \ + 0x00, 0x00, 0x40, 0x00, /* ID=0x0000, flags=IP_DF, offset=0 */ \ + 55, (p), (c1), (c2), /* TTL=55, protocol=p, checksum=c1,c2 */ \ + 192, 0, 0, 4, /* Src=192.0.0.4 */ \ + 8, 8, 8, 8, /* Dst=8.8.8.8 */ +#define IPV4_UDP_HEADER IPV4_HEADER(IPPROTO_UDP, 0x73, 0xb0) +#define IPV4_ICMP_HEADER IPV4_HEADER(IPPROTO_ICMP, 0x73, 0xc0) + +#define IPV6_HEADER(p) \ + 0x60, 0x00, 0, 0, /* Version=6, tclass=0x00, flowlabel=0 */ \ + 0, 21, (p), 55, /* plen=11, nxthdr=p, hlim=55 */ \ + 0x20, 0x01, 0x0d, 0xb8, /* Src=2001:db8:0:b11::464 */ \ + 0x00, 0x00, 0x0b, 0x11, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x04, 0x64, \ + 0x00, 0x64, 0xff, 0x9b, /* Dst=64:ff9b::8.8.8.8 */ \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x08, 0x08, 0x08, 0x08, +#define IPV6_UDP_HEADER IPV6_HEADER(IPPROTO_UDP) +#define IPV6_ICMPV6_HEADER IPV6_HEADER(IPPROTO_ICMPV6) + +#define UDP_LEN 21 +#define UDP_HEADER \ + 0xc8, 0x8b, 0, 53, /* Port 51339->53 */ \ + 0x00, UDP_LEN, 0, 0, /* Length 21, checksum empty for now */ + +#define PAYLOAD 'H', 'e', 'l', 'l', 'o', ' ', 0x4e, 0xb8, 0x96, 0xe7, 0x95, 0x8c, 0x00 + +#define IPV4_PING \ + 0x08, 0x00, 0x88, 0xd0, /* Type 8, code 0, checksum 0x88d0 */ \ + 0xd0, 0x0d, 0x00, 0x03, /* ID=0xd00d, seq=3 */ + +#define IPV6_PING \ + 0x80, 0x00, 0xc3, 0x42, /* Type 128, code 0, checksum 0xc342 */ \ + 0xd0, 0x0d, 0x00, 0x03, /* ID=0xd00d, seq=3 */ + +// Macros to return pseudo-headers from packets. +#define IPV4_PSEUDOHEADER(ip, tlen) \ + ip[12], ip[13], ip[14], ip[15], /* Source address */ \ + ip[16], ip[17], ip[18], ip[19], /* Destination address */ \ + 0, ip[9], /* 0, protocol */ \ + ((tlen) >> 16) & 0xff, (tlen) & 0xff, /* Transport length */ + +#define IPV6_PSEUDOHEADER(ip6, protocol, tlen) \ + ip6[8], ip6[9], ip6[10], ip6[11], /* Source address */ \ + ip6[12], ip6[13], ip6[14], ip6[15], \ + ip6[16], ip6[17], ip6[18], ip6[19], \ + ip6[20], ip6[21], ip6[22], ip6[23], \ + ip6[24], ip6[25], ip6[26], ip6[27], /* Destination address */ \ + ip6[28], ip6[29], ip6[30], ip6[31], \ + ip6[32], ip6[33], ip6[34], ip6[35], \ + ip6[36], ip6[37], ip6[38], ip6[39], \ + ((tlen) >> 24) & 0xff, /* Transport length */ \ + ((tlen) >> 16) & 0xff, \ + ((tlen) >> 8) & 0xff, \ + (tlen) & 0xff, \ + 0, 0, 0, (protocol), + +// A fragmented DNS request. +static const uint8_t kIPv4Frag1[] = { + 0x45, 0x00, 0x00, 0x24, 0xfe, 0x47, 0x20, 0x00, 0x40, 0x11, + 0x8c, 0x6d, 0xc0, 0x00, 0x00, 0x04, 0x08, 0x08, 0x08, 0x08, + 0x14, 0x5d, 0x00, 0x35, 0x00, 0x29, 0x68, 0xbb, 0x50, 0x47, + 0x01, 0x00, 0x00, 0x01, 0x00, 0x00 +}; +static const uint8_t kIPv4Frag2[] = { + 0x45, 0x00, 0x00, 0x24, 0xfe, 0x47, 0x20, 0x02, 0x40, 0x11, + 0x8c, 0x6b, 0xc0, 0x00, 0x00, 0x04, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x69, 0x70, 0x76, 0x34, 0x06, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65 +}; +static const uint8_t kIPv4Frag3[] = { + 0x45, 0x00, 0x00, 0x1d, 0xfe, 0x47, 0x00, 0x04, 0x40, 0x11, + 0xac, 0x70, 0xc0, 0x00, 0x00, 0x04, 0x08, 0x08, 0x08, 0x08, + 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01 +}; +static const uint8_t *kIPv4Fragments[] = { kIPv4Frag1, kIPv4Frag2, kIPv4Frag3 }; +static const size_t kIPv4FragLengths[] = { sizeof(kIPv4Frag1), sizeof(kIPv4Frag2), + sizeof(kIPv4Frag3) }; + +static const uint8_t kIPv6Frag1[] = { + 0x60, 0x00, 0x00, 0x00, 0x00, 0x18, 0x2c, 0x40, 0x20, 0x01, + 0x0d, 0xb8, 0x00, 0x00, 0x0b, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x64, 0x00, 0x64, 0xff, 0x9b, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, + 0x11, 0x00, 0x00, 0x01, 0x00, 0x00, 0xfe, 0x47, 0x14, 0x5d, + 0x00, 0x35, 0x00, 0x29, 0xeb, 0x91, 0x50, 0x47, 0x01, 0x00, + 0x00, 0x01, 0x00, 0x00 +}; + +static const uint8_t kIPv6Frag2[] = { + 0x60, 0x00, 0x00, 0x00, 0x00, 0x18, 0x2c, 0x40, 0x20, 0x01, + 0x0d, 0xb8, 0x00, 0x00, 0x0b, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x64, 0x00, 0x64, 0xff, 0x9b, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, + 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0xfe, 0x47, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x69, 0x70, 0x76, 0x34, 0x06, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65 +}; + +static const uint8_t kIPv6Frag3[] = { + 0x60, 0x00, 0x00, 0x00, 0x00, 0x11, 0x2c, 0x40, 0x20, 0x01, + 0x0d, 0xb8, 0x00, 0x00, 0x0b, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x64, 0x00, 0x64, 0xff, 0x9b, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, + 0x11, 0x00, 0x00, 0x20, 0x00, 0x00, 0xfe, 0x47, 0x03, 0x63, + 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01 +}; +static const uint8_t *kIPv6Fragments[] = { kIPv6Frag1, kIPv6Frag2, kIPv6Frag3 }; +static const size_t kIPv6FragLengths[] = { sizeof(kIPv6Frag1), sizeof(kIPv6Frag2), + sizeof(kIPv6Frag3) }; + +static const uint8_t kReassembledIPv4[] = { + 0x45, 0x00, 0x00, 0x3d, 0xfe, 0x47, 0x00, 0x00, 0x40, 0x11, + 0xac, 0x54, 0xc0, 0x00, 0x00, 0x04, 0x08, 0x08, 0x08, 0x08, + 0x14, 0x5d, 0x00, 0x35, 0x00, 0x29, 0x68, 0xbb, 0x50, 0x47, + 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x69, 0x70, 0x76, 0x34, 0x06, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, + 0x01 +}; +// clang-format on + +// Expected checksums. +static const uint32_t kUdpPartialChecksum = 0xd5c8; +static const uint32_t kPayloadPartialChecksum = 0x31e9c; +static const uint16_t kUdpV4Checksum = 0xd0c7; +static const uint16_t kUdpV6Checksum = 0xa74a; + +uint8_t ip_version(const uint8_t *packet) { + uint8_t version = packet[0] >> 4; + return version; +} + +int is_ipv4_fragment(struct iphdr *ip) { + // A packet is a fragment if its fragment offset is nonzero or if the MF flag is set. + return ntohs(ip->frag_off) & (IP_OFFMASK | IP_MF); +} + +int is_ipv6_fragment(struct ip6_hdr *ip6, size_t len) { + if (ip6->ip6_nxt != IPPROTO_FRAGMENT) { + return 0; + } + struct ip6_frag *frag = (struct ip6_frag *)(ip6 + 1); + return len >= sizeof(*ip6) + sizeof(*frag) && + (frag->ip6f_offlg & (IP6F_OFF_MASK | IP6F_MORE_FRAG)); +} + +int ipv4_fragment_offset(struct iphdr *ip) { + return ntohs(ip->frag_off) & IP_OFFMASK; +} + +int ipv6_fragment_offset(struct ip6_frag *frag) { + return ntohs((frag->ip6f_offlg & IP6F_OFF_MASK) >> 3); +} + +void check_packet(const uint8_t *packet, size_t len, const char *msg) { + void *payload; + size_t payload_length = 0; + uint32_t pseudo_checksum = 0; + uint8_t protocol = 0; + int version = ip_version(packet); + switch (version) { + case 4: { + struct iphdr *ip = (struct iphdr *)packet; + ASSERT_GE(len, sizeof(*ip)) << msg << ": IPv4 packet shorter than IPv4 header\n"; + EXPECT_EQ(5, ip->ihl) << msg << ": Unsupported IP header length\n"; + EXPECT_EQ(len, ntohs(ip->tot_len)) << msg << ": Incorrect IPv4 length\n"; + EXPECT_EQ(0, ip_checksum(ip, sizeof(*ip))) << msg << ": Incorrect IP checksum\n"; + protocol = ip->protocol; + payload = ip + 1; + if (!is_ipv4_fragment(ip)) { + payload_length = len - sizeof(*ip); + pseudo_checksum = ipv4_pseudo_header_checksum(ip, payload_length); + } + ASSERT_TRUE(protocol == IPPROTO_TCP || protocol == IPPROTO_UDP || protocol == IPPROTO_ICMP) + << msg << ": Unsupported IPv4 protocol " << protocol << "\n"; + break; + } + case 6: { + struct ip6_hdr *ip6 = (struct ip6_hdr *)packet; + ASSERT_GE(len, sizeof(*ip6)) << msg << ": IPv6 packet shorter than IPv6 header\n"; + EXPECT_EQ(len - sizeof(*ip6), htons(ip6->ip6_plen)) << msg << ": Incorrect IPv6 length\n"; + + if (ip6->ip6_nxt == IPPROTO_FRAGMENT) { + struct ip6_frag *frag = (struct ip6_frag *)(ip6 + 1); + ASSERT_GE(len, sizeof(*ip6) + sizeof(*frag)) + << msg << ": IPv6 fragment: short fragment header\n"; + protocol = frag->ip6f_nxt; + payload = frag + 1; + // Even though the packet has a Fragment header, it might not be a fragment. + if (!is_ipv6_fragment(ip6, len)) { + payload_length = len - sizeof(*ip6) - sizeof(*frag); + } + } else { + // Since there are no extension headers except Fragment, this must be the payload. + protocol = ip6->ip6_nxt; + payload = ip6 + 1; + payload_length = len - sizeof(*ip6); + } + ASSERT_TRUE(protocol == IPPROTO_TCP || protocol == IPPROTO_UDP || protocol == IPPROTO_ICMPV6) + << msg << ": Unsupported IPv6 next header " << protocol; + if (payload_length) { + pseudo_checksum = ipv6_pseudo_header_checksum(ip6, payload_length, protocol); + } + break; + } + default: + FAIL() << msg << ": Unsupported IP version " << version << "\n"; + return; + } + + // If we understand the payload, verify the checksum. + if (payload_length) { + uint16_t checksum; + switch (protocol) { + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_ICMPV6: + checksum = ip_checksum_finish(ip_checksum_add(pseudo_checksum, payload, payload_length)); + break; + case IPPROTO_ICMP: + checksum = ip_checksum(payload, payload_length); + break; + default: + checksum = 0; // Don't check. + break; + } + EXPECT_EQ(0, checksum) << msg << ": Incorrect transport checksum\n"; + } + + if (protocol == IPPROTO_UDP) { + struct udphdr *udp = (struct udphdr *)payload; + EXPECT_NE(0, udp->check) << msg << ": UDP checksum 0 should be 0xffff"; + // If this is not a fragment, check the UDP length field. + if (payload_length) { + EXPECT_EQ(payload_length, ntohs(udp->len)) << msg << ": Incorrect UDP length\n"; + } + } +} + +void reassemble_packet(const uint8_t **fragments, const size_t lengths[], int numpackets, + uint8_t *reassembled, size_t *reassembled_len, const char *msg) { + struct iphdr *ip = nullptr; + struct ip6_hdr *ip6 = nullptr; + size_t total_length, pos = 0; + uint8_t protocol = 0; + uint8_t version = ip_version(fragments[0]); + + for (int i = 0; i < numpackets; i++) { + const uint8_t *packet = fragments[i]; + int len = lengths[i]; + int headersize, payload_offset; + + ASSERT_EQ(ip_version(packet), version) << msg << ": Inconsistent fragment versions\n"; + check_packet(packet, len, "Fragment sanity check"); + + switch (version) { + case 4: { + struct iphdr *ip_orig = (struct iphdr *)packet; + headersize = sizeof(*ip_orig); + ASSERT_TRUE(is_ipv4_fragment(ip_orig)) + << msg << ": IPv4 fragment #" << i + 1 << " not a fragment\n"; + ASSERT_EQ(pos, ipv4_fragment_offset(ip_orig) * 8 + ((i != 0) ? sizeof(*ip) : 0)) + << msg << ": IPv4 fragment #" << i + 1 << ": inconsistent offset\n"; + + headersize = sizeof(*ip_orig); + payload_offset = headersize; + if (pos == 0) { + ip = (struct iphdr *)reassembled; + } + break; + } + case 6: { + struct ip6_hdr *ip6_orig = (struct ip6_hdr *)packet; + struct ip6_frag *frag = (struct ip6_frag *)(ip6_orig + 1); + ASSERT_TRUE(is_ipv6_fragment(ip6_orig, len)) + << msg << ": IPv6 fragment #" << i + 1 << " not a fragment\n"; + ASSERT_EQ(pos, ipv6_fragment_offset(frag) * 8 + ((i != 0) ? sizeof(*ip6) : 0)) + << msg << ": IPv6 fragment #" << i + 1 << ": inconsistent offset\n"; + + headersize = sizeof(*ip6_orig); + payload_offset = sizeof(*ip6_orig) + sizeof(*frag); + if (pos == 0) { + ip6 = (struct ip6_hdr *)reassembled; + protocol = frag->ip6f_nxt; + } + break; + } + default: + FAIL() << msg << ": Invalid IP version << " << version; + } + + // If this is the first fragment, copy the header. + if (pos == 0) { + ASSERT_LT(headersize, (int)*reassembled_len) << msg << ": Reassembly buffer too small\n"; + memcpy(reassembled, packet, headersize); + total_length = headersize; + pos += headersize; + } + + // Copy the payload. + int payload_length = len - payload_offset; + total_length += payload_length; + ASSERT_LT(total_length, *reassembled_len) << msg << ": Reassembly buffer too small\n"; + memcpy(reassembled + pos, packet + payload_offset, payload_length); + pos += payload_length; + } + + // Fix up the reassembled headers to reflect fragmentation and length (and IPv4 checksum). + ASSERT_EQ(total_length, pos) << msg << ": Reassembled packet length incorrect\n"; + if (ip) { + ip->frag_off &= ~htons(IP_MF); + ip->tot_len = htons(total_length); + ip->check = 0; + ip->check = ip_checksum(ip, sizeof(*ip)); + ASSERT_FALSE(is_ipv4_fragment(ip)) << msg << ": reassembled IPv4 packet is a fragment!\n"; + } + if (ip6) { + ip6->ip6_nxt = protocol; + ip6->ip6_plen = htons(total_length - sizeof(*ip6)); + ASSERT_FALSE(is_ipv6_fragment(ip6, ip6->ip6_plen)) + << msg << ": reassembled IPv6 packet is a fragment!\n"; + } + + *reassembled_len = total_length; +} + +void check_data_matches(const void *expected, const void *actual, size_t len, const char *msg) { + if (memcmp(expected, actual, len)) { + // Hex dump, 20 bytes per line, one space between bytes (1 byte = 3 chars), indented by 4. + int hexdump_len = len * 3 + (len / 20 + 1) * 5; + char expected_hexdump[hexdump_len], actual_hexdump[hexdump_len]; + unsigned pos = 0; + for (unsigned i = 0; i < len; i++) { + if (i % 20 == 0) { + snprintf(expected_hexdump + pos, hexdump_len - pos, "\n "); + snprintf(actual_hexdump + pos, hexdump_len - pos, "\n "); + pos += 4; + } + snprintf(expected_hexdump + pos, hexdump_len - pos, " %02x", ((uint8_t *)expected)[i]); + snprintf(actual_hexdump + pos, hexdump_len - pos, " %02x", ((uint8_t *)actual)[i]); + pos += 3; + } + FAIL() << msg << ": Data doesn't match" + << "\n Expected:" << (char *) expected_hexdump + << "\n Actual:" << (char *) actual_hexdump << "\n"; + } +} + +void fix_udp_checksum(uint8_t *packet) { + uint32_t pseudo_checksum; + uint8_t version = ip_version(packet); + struct udphdr *udp; + switch (version) { + case 4: { + struct iphdr *ip = (struct iphdr *)packet; + udp = (struct udphdr *)(ip + 1); + pseudo_checksum = ipv4_pseudo_header_checksum(ip, ntohs(udp->len)); + break; + } + case 6: { + struct ip6_hdr *ip6 = (struct ip6_hdr *)packet; + udp = (struct udphdr *)(ip6 + 1); + pseudo_checksum = ipv6_pseudo_header_checksum(ip6, ntohs(udp->len), IPPROTO_UDP); + break; + } + default: + FAIL() << "unsupported IP version" << version << "\n"; + return; + } + + udp->check = 0; + udp->check = ip_checksum_finish(ip_checksum_add(pseudo_checksum, udp, ntohs(udp->len))); +} + +// Testing stub for send_rawv6. The real version uses sendmsg() with a +// destination IPv6 address, and attempting to call that on our test socketpair +// fd results in EINVAL. +extern "C" void send_rawv6(int fd, clat_packet out, int iov_len) { writev(fd, out, iov_len); } + +void do_translate_packet(const uint8_t *original, size_t original_len, uint8_t *out, size_t *outlen, + const char *msg) { + int fds[2]; + if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0, fds)) { + abort(); + } + + char foo[512]; + snprintf(foo, sizeof(foo), "%s: Invalid original packet", msg); + check_packet(original, original_len, foo); + + int read_fd, write_fd; + uint16_t expected_proto; + int version = ip_version(original); + switch (version) { + case 4: + expected_proto = htons(ETH_P_IPV6); + read_fd = fds[1]; + write_fd = fds[0]; + break; + case 6: + expected_proto = htons(ETH_P_IP); + read_fd = fds[0]; + write_fd = fds[1]; + break; + default: + FAIL() << msg << ": Unsupported IP version " << version << "\n"; + break; + } + + translate_packet(write_fd, (version == 4), original, original_len); + + snprintf(foo, sizeof(foo), "%s: Invalid translated packet", msg); + if (version == 6) { + // Translating to IPv4. Expect a tun header. + struct tun_pi new_tun_header; + struct iovec iov[] = { + { &new_tun_header, sizeof(new_tun_header) }, + { out, *outlen }, + }; + + int len = readv(read_fd, iov, 2); + if (len > (int)sizeof(new_tun_header)) { + ASSERT_LT((size_t)len, *outlen) << msg << ": Translated packet buffer too small\n"; + EXPECT_EQ(expected_proto, new_tun_header.proto) << msg << "Unexpected tun proto\n"; + *outlen = len - sizeof(new_tun_header); + check_packet(out, *outlen, msg); + } else { + FAIL() << msg << ": Packet was not translated: len=" << len; + *outlen = 0; + } + } else { + // Translating to IPv6. Expect raw packet. + *outlen = read(read_fd, out, *outlen); + check_packet(out, *outlen, msg); + } +} + +void check_translated_packet(const uint8_t *original, size_t original_len, const uint8_t *expected, + size_t expected_len, const char *msg) { + uint8_t translated[MAXMTU]; + size_t translated_len = sizeof(translated); + do_translate_packet(original, original_len, translated, &translated_len, msg); + EXPECT_EQ(expected_len, translated_len) << msg << ": Translated packet length incorrect\n"; + check_data_matches(expected, translated, translated_len, msg); +} + +void check_fragment_translation(const uint8_t *original[], const size_t original_lengths[], + const uint8_t *expected[], const size_t expected_lengths[], + int numfragments, const char *msg) { + for (int i = 0; i < numfragments; i++) { + // Check that each of the fragments translates as expected. + char frag_msg[512]; + snprintf(frag_msg, sizeof(frag_msg), "%s: fragment #%d", msg, i + 1); + check_translated_packet(original[i], original_lengths[i], expected[i], expected_lengths[i], + frag_msg); + } + + // Sanity check that reassembling the original and translated fragments produces valid packets. + uint8_t reassembled[MAXMTU]; + size_t reassembled_len = sizeof(reassembled); + reassemble_packet(original, original_lengths, numfragments, reassembled, &reassembled_len, msg); + check_packet(reassembled, reassembled_len, msg); + + uint8_t translated[MAXMTU]; + size_t translated_len = sizeof(translated); + do_translate_packet(reassembled, reassembled_len, translated, &translated_len, msg); + check_packet(translated, translated_len, msg); +} + +int get_transport_checksum(const uint8_t *packet) { + struct iphdr *ip; + struct ip6_hdr *ip6; + uint8_t protocol; + const void *payload; + + int version = ip_version(packet); + switch (version) { + case 4: + ip = (struct iphdr *)packet; + if (is_ipv4_fragment(ip)) { + return -1; + } + protocol = ip->protocol; + payload = ip + 1; + break; + case 6: + ip6 = (struct ip6_hdr *)packet; + protocol = ip6->ip6_nxt; + payload = ip6 + 1; + break; + default: + return -1; + } + + switch (protocol) { + case IPPROTO_UDP: + return ((struct udphdr *)payload)->check; + + case IPPROTO_TCP: + return ((struct tcphdr *)payload)->check; + + case IPPROTO_FRAGMENT: + default: + return -1; + } +} + +class ClatdTest : public ::testing::Test { + protected: + static TunInterface sTun; + + virtual void SetUp() { + inet_pton(AF_INET, kIPv4LocalAddr, &Global_Clatd_Config.ipv4_local_subnet); + inet_pton(AF_INET6, kIPv6PlatSubnet, &Global_Clatd_Config.plat_subnet); + memset(&Global_Clatd_Config.ipv6_local_subnet, 0, sizeof(in6_addr)); + Global_Clatd_Config.native_ipv6_interface = const_cast(sTun.name().c_str()); + } + + // Static because setting up the tun interface takes about 40ms. + static void SetUpTestCase() { ASSERT_EQ(0, sTun.init()); } + + // Closing the socket removes the interface and IP addresses. + static void TearDownTestCase() { sTun.destroy(); } +}; + +TunInterface ClatdTest::sTun; + +void expect_ipv6_addr_equal(struct in6_addr *expected, struct in6_addr *actual) { + if (!IN6_ARE_ADDR_EQUAL(expected, actual)) { + char expected_str[INET6_ADDRSTRLEN], actual_str[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, expected, expected_str, sizeof(expected_str)); + inet_ntop(AF_INET6, actual, actual_str, sizeof(actual_str)); + FAIL() + << "Unexpected IPv6 address:: " + << "\n Expected: " << expected_str + << "\n Actual: " << actual_str + << "\n"; + } +} + +TEST_F(ClatdTest, TestIPv6PrefixEqual) { + EXPECT_TRUE(ipv6_prefix_equal(&Global_Clatd_Config.plat_subnet, + &Global_Clatd_Config.plat_subnet)); + EXPECT_FALSE(ipv6_prefix_equal(&Global_Clatd_Config.plat_subnet, + &Global_Clatd_Config.ipv6_local_subnet)); + + struct in6_addr subnet2 = Global_Clatd_Config.ipv6_local_subnet; + EXPECT_TRUE(ipv6_prefix_equal(&Global_Clatd_Config.ipv6_local_subnet, &subnet2)); + EXPECT_TRUE(ipv6_prefix_equal(&subnet2, &Global_Clatd_Config.ipv6_local_subnet)); + + subnet2.s6_addr[6] = 0xff; + EXPECT_FALSE(ipv6_prefix_equal(&Global_Clatd_Config.ipv6_local_subnet, &subnet2)); + EXPECT_FALSE(ipv6_prefix_equal(&subnet2, &Global_Clatd_Config.ipv6_local_subnet)); +} + +TEST_F(ClatdTest, DataSanitycheck) { + // Sanity checks the data. + uint8_t v4_header[] = { IPV4_UDP_HEADER }; + ASSERT_EQ(sizeof(struct iphdr), sizeof(v4_header)) << "Test IPv4 header: incorrect length\n"; + + uint8_t v6_header[] = { IPV6_UDP_HEADER }; + ASSERT_EQ(sizeof(struct ip6_hdr), sizeof(v6_header)) << "Test IPv6 header: incorrect length\n"; + + uint8_t udp_header[] = { UDP_HEADER }; + ASSERT_EQ(sizeof(struct udphdr), sizeof(udp_header)) << "Test UDP header: incorrect length\n"; + + // Sanity checks check_packet. + struct udphdr *udp; + uint8_t v4_udp_packet[] = { IPV4_UDP_HEADER UDP_HEADER PAYLOAD }; + udp = (struct udphdr *)(v4_udp_packet + sizeof(struct iphdr)); + fix_udp_checksum(v4_udp_packet); + ASSERT_EQ(kUdpV4Checksum, udp->check) << "UDP/IPv4 packet checksum sanity check\n"; + check_packet(v4_udp_packet, sizeof(v4_udp_packet), "UDP/IPv4 packet sanity check"); + + uint8_t v6_udp_packet[] = { IPV6_UDP_HEADER UDP_HEADER PAYLOAD }; + udp = (struct udphdr *)(v6_udp_packet + sizeof(struct ip6_hdr)); + fix_udp_checksum(v6_udp_packet); + ASSERT_EQ(kUdpV6Checksum, udp->check) << "UDP/IPv6 packet checksum sanity check\n"; + check_packet(v6_udp_packet, sizeof(v6_udp_packet), "UDP/IPv6 packet sanity check"); + + uint8_t ipv4_ping[] = { IPV4_ICMP_HEADER IPV4_PING PAYLOAD }; + check_packet(ipv4_ping, sizeof(ipv4_ping), "IPv4 ping sanity check"); + + uint8_t ipv6_ping[] = { IPV6_ICMPV6_HEADER IPV6_PING PAYLOAD }; + check_packet(ipv6_ping, sizeof(ipv6_ping), "IPv6 ping sanity check"); + + // Sanity checks reassemble_packet. + uint8_t reassembled[MAXMTU]; + size_t total_length = sizeof(reassembled); + reassemble_packet(kIPv4Fragments, kIPv4FragLengths, ARRAYSIZE(kIPv4Fragments), reassembled, + &total_length, "Reassembly sanity check"); + check_packet(reassembled, total_length, "IPv4 Reassembled packet is valid"); + ASSERT_EQ(sizeof(kReassembledIPv4), total_length) << "IPv4 reassembly sanity check: length\n"; + ASSERT_TRUE(!is_ipv4_fragment((struct iphdr *)reassembled)) + << "Sanity check: reassembled packet is a fragment!\n"; + check_data_matches(kReassembledIPv4, reassembled, total_length, "IPv4 reassembly sanity check"); + + total_length = sizeof(reassembled); + reassemble_packet(kIPv6Fragments, kIPv6FragLengths, ARRAYSIZE(kIPv6Fragments), reassembled, + &total_length, "IPv6 reassembly sanity check"); + ASSERT_TRUE(!is_ipv6_fragment((struct ip6_hdr *)reassembled, total_length)) + << "Sanity check: reassembled packet is a fragment!\n"; + check_packet(reassembled, total_length, "IPv6 Reassembled packet is valid"); +} + +TEST_F(ClatdTest, PseudoChecksum) { + uint32_t pseudo_checksum; + + uint8_t v4_header[] = { IPV4_UDP_HEADER }; + uint8_t v4_pseudo_header[] = { IPV4_PSEUDOHEADER(v4_header, UDP_LEN) }; + pseudo_checksum = ipv4_pseudo_header_checksum((struct iphdr *)v4_header, UDP_LEN); + EXPECT_EQ(ip_checksum_finish(pseudo_checksum), + ip_checksum(v4_pseudo_header, sizeof(v4_pseudo_header))) + << "ipv4_pseudo_header_checksum incorrect\n"; + + uint8_t v6_header[] = { IPV6_UDP_HEADER }; + uint8_t v6_pseudo_header[] = { IPV6_PSEUDOHEADER(v6_header, IPPROTO_UDP, UDP_LEN) }; + pseudo_checksum = ipv6_pseudo_header_checksum((struct ip6_hdr *)v6_header, UDP_LEN, IPPROTO_UDP); + EXPECT_EQ(ip_checksum_finish(pseudo_checksum), + ip_checksum(v6_pseudo_header, sizeof(v6_pseudo_header))) + << "ipv6_pseudo_header_checksum incorrect\n"; +} + +TEST_F(ClatdTest, TransportChecksum) { + uint8_t udphdr[] = { UDP_HEADER }; + uint8_t payload[] = { PAYLOAD }; + EXPECT_EQ(kUdpPartialChecksum, ip_checksum_add(0, udphdr, sizeof(udphdr))) + << "UDP partial checksum\n"; + EXPECT_EQ(kPayloadPartialChecksum, ip_checksum_add(0, payload, sizeof(payload))) + << "Payload partial checksum\n"; + + uint8_t ip[] = { IPV4_UDP_HEADER }; + uint8_t ip6[] = { IPV6_UDP_HEADER }; + uint32_t ipv4_pseudo_sum = ipv4_pseudo_header_checksum((struct iphdr *)ip, UDP_LEN); + uint32_t ipv6_pseudo_sum = + ipv6_pseudo_header_checksum((struct ip6_hdr *)ip6, UDP_LEN, IPPROTO_UDP); + + EXPECT_NE(0, ipv4_pseudo_sum); + EXPECT_NE(0, ipv6_pseudo_sum); + EXPECT_EQ(0x3ad0U, ipv4_pseudo_sum % 0xFFFF) << "IPv4 pseudo-checksum sanity check\n"; + EXPECT_EQ(0x644dU, ipv6_pseudo_sum % 0xFFFF) << "IPv6 pseudo-checksum sanity check\n"; + EXPECT_EQ( + kUdpV4Checksum, + ip_checksum_finish(ipv4_pseudo_sum + kUdpPartialChecksum + kPayloadPartialChecksum)) + << "Unexpected UDP/IPv4 checksum\n"; + EXPECT_EQ( + kUdpV6Checksum, + ip_checksum_finish(ipv6_pseudo_sum + kUdpPartialChecksum + kPayloadPartialChecksum)) + << "Unexpected UDP/IPv6 checksum\n"; + + EXPECT_EQ(kUdpV6Checksum, + ip_checksum_adjust(kUdpV4Checksum, ipv4_pseudo_sum, ipv6_pseudo_sum)) + << "Adjust IPv4/UDP checksum to IPv6\n"; + EXPECT_EQ(kUdpV4Checksum, + ip_checksum_adjust(kUdpV6Checksum, ipv6_pseudo_sum, ipv4_pseudo_sum)) + << "Adjust IPv6/UDP checksum to IPv4\n"; +} + +TEST_F(ClatdTest, AdjustChecksum) { + struct checksum_data { + uint16_t checksum; + uint32_t old_hdr_sum; + uint32_t new_hdr_sum; + uint16_t result; + } DATA[] = { + { 0x1423, 0xb8ec, 0x2d757, 0xf5b5 }, + { 0xf5b5, 0x2d757, 0xb8ec, 0x1423 }, + { 0xdd2f, 0x5555, 0x3285, 0x0000 }, + { 0x1215, 0x5560, 0x15560 + 20, 0x1200 }, + { 0xd0c7, 0x3ad0, 0x2644b, 0xa74a }, + }; + unsigned i = 0; + + for (i = 0; i < ARRAYSIZE(DATA); i++) { + struct checksum_data *data = DATA + i; + uint16_t result = ip_checksum_adjust(data->checksum, data->old_hdr_sum, data->new_hdr_sum); + EXPECT_EQ(result, data->result) + << "Incorrect checksum" << std::showbase << std::hex + << "\n Expected: " << data->result + << "\n Actual: " << result + << "\n checksum=" << data->checksum + << " old_sum=" << data->old_hdr_sum << " new_sum=" << data->new_hdr_sum << "\n"; + } +} + +TEST_F(ClatdTest, Translate) { + // This test uses hardcoded packets so the clatd address must be fixed. + inet_pton(AF_INET6, kIPv6LocalAddr, &Global_Clatd_Config.ipv6_local_subnet); + + uint8_t udp_ipv4[] = { IPV4_UDP_HEADER UDP_HEADER PAYLOAD }; + uint8_t udp_ipv6[] = { IPV6_UDP_HEADER UDP_HEADER PAYLOAD }; + fix_udp_checksum(udp_ipv4); + fix_udp_checksum(udp_ipv6); + check_translated_packet(udp_ipv4, sizeof(udp_ipv4), udp_ipv6, sizeof(udp_ipv6), + "UDP/IPv4 -> UDP/IPv6 translation"); + check_translated_packet(udp_ipv6, sizeof(udp_ipv6), udp_ipv4, sizeof(udp_ipv4), + "UDP/IPv6 -> UDP/IPv4 translation"); + + uint8_t ipv4_ping[] = { IPV4_ICMP_HEADER IPV4_PING PAYLOAD }; + uint8_t ipv6_ping[] = { IPV6_ICMPV6_HEADER IPV6_PING PAYLOAD }; + check_translated_packet(ipv4_ping, sizeof(ipv4_ping), ipv6_ping, sizeof(ipv6_ping), + "ICMP->ICMPv6 translation"); + check_translated_packet(ipv6_ping, sizeof(ipv6_ping), ipv4_ping, sizeof(ipv4_ping), + "ICMPv6->ICMP translation"); +} + +TEST_F(ClatdTest, Fragmentation) { + // This test uses hardcoded packets so the clatd address must be fixed. + inet_pton(AF_INET6, kIPv6LocalAddr, &Global_Clatd_Config.ipv6_local_subnet); + + check_fragment_translation(kIPv4Fragments, kIPv4FragLengths, kIPv6Fragments, kIPv6FragLengths, + ARRAYSIZE(kIPv4Fragments), "IPv4->IPv6 fragment translation"); + + check_fragment_translation(kIPv6Fragments, kIPv6FragLengths, kIPv4Fragments, kIPv4FragLengths, + ARRAYSIZE(kIPv6Fragments), "IPv6->IPv4 fragment translation"); +} + +// picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix +void gen_random_iid(struct in6_addr *myaddr, struct in_addr *ipv4_local_subnet, + struct in6_addr *plat_subnet) { + // Fill last 8 bytes of IPv6 address with random bits. + arc4random_buf(&myaddr->s6_addr[8], 8); + + // Make the IID checksum-neutral. That is, make it so that: + // checksum(Local IPv4 | Remote IPv4) = checksum(Local IPv6 | Remote IPv6) + // in other words (because remote IPv6 = NAT64 prefix | Remote IPv4): + // checksum(Local IPv4) = checksum(Local IPv6 | NAT64 prefix) + // Do this by adjusting the two bytes in the middle of the IID. + + uint16_t middlebytes = (myaddr->s6_addr[11] << 8) + myaddr->s6_addr[12]; + + uint32_t c1 = ip_checksum_add(0, ipv4_local_subnet, sizeof(*ipv4_local_subnet)); + uint32_t c2 = ip_checksum_add(0, plat_subnet, sizeof(*plat_subnet)) + + ip_checksum_add(0, myaddr, sizeof(*myaddr)); + + uint16_t delta = ip_checksum_adjust(middlebytes, c1, c2); + myaddr->s6_addr[11] = delta >> 8; + myaddr->s6_addr[12] = delta & 0xff; +} + +void check_translate_checksum_neutral(const uint8_t *original, size_t original_len, + size_t expected_len, const char *msg) { + uint8_t translated[MAXMTU]; + size_t translated_len = sizeof(translated); + do_translate_packet(original, original_len, translated, &translated_len, msg); + EXPECT_EQ(expected_len, translated_len) << msg << ": Translated packet length incorrect\n"; + // do_translate_packet already checks packets for validity and verifies the checksum. + int original_check = get_transport_checksum(original); + int translated_check = get_transport_checksum(translated); + ASSERT_NE(-1, original_check); + ASSERT_NE(-1, translated_check); + ASSERT_EQ(original_check, translated_check) + << "Not checksum neutral: original and translated checksums differ\n"; +} + +TEST_F(ClatdTest, TranslateChecksumNeutral) { + // Generate a random clat IPv6 address and check that translation is checksum-neutral. + ASSERT_TRUE(inet_pton(AF_INET6, "2001:db8:1:2:f076:ae99:124e:aa54", + &Global_Clatd_Config.ipv6_local_subnet)); + + gen_random_iid(&Global_Clatd_Config.ipv6_local_subnet, &Global_Clatd_Config.ipv4_local_subnet, + &Global_Clatd_Config.plat_subnet); + + ASSERT_NE(htonl((uint32_t)0x00000464), Global_Clatd_Config.ipv6_local_subnet.s6_addr32[3]); + ASSERT_NE((uint32_t)0, Global_Clatd_Config.ipv6_local_subnet.s6_addr32[3]); + + // Check that translating UDP packets is checksum-neutral. First, IPv4. + uint8_t udp_ipv4[] = { IPV4_UDP_HEADER UDP_HEADER PAYLOAD }; + fix_udp_checksum(udp_ipv4); + check_translate_checksum_neutral(udp_ipv4, sizeof(udp_ipv4), sizeof(udp_ipv4) + 20, + "UDP/IPv4 -> UDP/IPv6 checksum neutral"); + + // Now try IPv6. + uint8_t udp_ipv6[] = { IPV6_UDP_HEADER UDP_HEADER PAYLOAD }; + // The test packet uses the static IID, not the random IID. Fix up the source address. + struct ip6_hdr *ip6 = (struct ip6_hdr *)udp_ipv6; + memcpy(&ip6->ip6_src, &Global_Clatd_Config.ipv6_local_subnet, sizeof(ip6->ip6_src)); + fix_udp_checksum(udp_ipv6); + check_translate_checksum_neutral(udp_ipv4, sizeof(udp_ipv4), sizeof(udp_ipv4) + 20, + "UDP/IPv4 -> UDP/IPv6 checksum neutral"); +} diff --git a/clatd/common.h b/clatd/common.h new file mode 100644 index 0000000000000000000000000000000000000000..e9551ee211cb118ff944bbd71d70e1e833fc1dba --- /dev/null +++ b/clatd/common.h @@ -0,0 +1,40 @@ +/* + * Copyright 2018 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. + * + * common.h - common definitions + */ +#ifndef __CLATD_COMMON_H__ +#define __CLATD_COMMON_H__ + +#include + +// A clat_packet is an array of iovec structures representing a packet that we are translating. +// The CLAT_POS_XXX constants represent the array indices within the clat_packet that contain +// specific parts of the packet. The packet_* functions operate on all the packet segments past a +// given position. +typedef enum { + CLAT_POS_TUNHDR, + CLAT_POS_IPHDR, + CLAT_POS_FRAGHDR, + CLAT_POS_TRANSPORTHDR, + CLAT_POS_ICMPERR_IPHDR, + CLAT_POS_ICMPERR_FRAGHDR, + CLAT_POS_ICMPERR_TRANSPORTHDR, + CLAT_POS_PAYLOAD, + CLAT_POS_MAX +} clat_packet_index; +typedef struct iovec clat_packet[CLAT_POS_MAX]; + +#endif /* __CLATD_COMMON_H__ */ diff --git a/clatd/config.h b/clatd/config.h new file mode 100644 index 0000000000000000000000000000000000000000..9612192be168942b67a586937d7f58b1226169ff --- /dev/null +++ b/clatd/config.h @@ -0,0 +1,48 @@ +/* + * Copyright 2011 Daniel Drown + * + * 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. + * + * config.h - configuration settings + */ +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +#include +#include + +struct tun_data { + char device4[IFNAMSIZ]; + int read_fd6, write_fd6, fd4; +}; + +struct clat_config { + struct in6_addr ipv6_local_subnet; + struct in_addr ipv4_local_subnet; + struct in6_addr plat_subnet; + const char *native_ipv6_interface; +}; + +extern struct clat_config Global_Clatd_Config; + +/* function: ipv6_prefix_equal + * compares the /64 prefixes of two ipv6 addresses. + * a1 - first address + * a2 - second address + * returns: 0 if the subnets are different, 1 if they are the same. + */ +static inline int ipv6_prefix_equal(struct in6_addr *a1, struct in6_addr *a2) { + return !memcmp(a1, a2, 8); +} + +#endif /* __CONFIG_H__ */ diff --git a/clatd/debug.h b/clatd/debug.h new file mode 100644 index 0000000000000000000000000000000000000000..8e096721bd0b4043322ef41ade5252519c0c38f9 --- /dev/null +++ b/clatd/debug.h @@ -0,0 +1,24 @@ +/* + * Copyright 2011 Daniel Drown + * + * 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. + * + * debug.h - debug settings + */ +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +// set to 1 to enable debug logging and packet dumping. +#define CLAT_DEBUG 0 + +#endif /* __DEBUG_H__ */ diff --git a/clatd/dump.c b/clatd/dump.c new file mode 100644 index 0000000000000000000000000000000000000000..dff3d5ec8d77ae7eab9e62eda59892862656395b --- /dev/null +++ b/clatd/dump.c @@ -0,0 +1,250 @@ +/* + * Copyright 2011 Daniel Drown + * + * 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. + * + * dump.c - print various headers for debugging + */ +#include "dump.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "checksum.h" +#include "clatd.h" +#include "debug.h" +#include "logging.h" + +#if CLAT_DEBUG + +/* print ip header */ +void dump_ip(struct iphdr *header) { + u_int16_t frag_flags; + char addrstr[INET6_ADDRSTRLEN]; + + frag_flags = ntohs(header->frag_off); + + printf("IP packet\n"); + printf("header_len = %x\n", header->ihl); + printf("version = %x\n", header->version); + printf("tos = %x\n", header->tos); + printf("tot_len = %x\n", ntohs(header->tot_len)); + printf("id = %x\n", ntohs(header->id)); + printf("frag: "); + if (frag_flags & IP_RF) { + printf("(RF) "); + } + if (frag_flags & IP_DF) { + printf("DF "); + } + if (frag_flags & IP_MF) { + printf("MF "); + } + printf("offset = %x\n", frag_flags & IP_OFFMASK); + printf("ttl = %x\n", header->ttl); + printf("protocol = %x\n", header->protocol); + printf("checksum = %x\n", ntohs(header->check)); + inet_ntop(AF_INET, &header->saddr, addrstr, sizeof(addrstr)); + printf("saddr = %s\n", addrstr); + inet_ntop(AF_INET, &header->daddr, addrstr, sizeof(addrstr)); + printf("daddr = %s\n", addrstr); +} + +/* print ip6 header */ +void dump_ip6(struct ip6_hdr *header) { + char addrstr[INET6_ADDRSTRLEN]; + + printf("ipv6\n"); + printf("version = %x\n", header->ip6_vfc >> 4); + printf("traffic class = %x\n", header->ip6_flow >> 20); + printf("flow label = %x\n", ntohl(header->ip6_flow & 0x000fffff)); + printf("payload len = %x\n", ntohs(header->ip6_plen)); + printf("next header = %x\n", header->ip6_nxt); + printf("hop limit = %x\n", header->ip6_hlim); + + inet_ntop(AF_INET6, &header->ip6_src, addrstr, sizeof(addrstr)); + printf("source = %s\n", addrstr); + + inet_ntop(AF_INET6, &header->ip6_dst, addrstr, sizeof(addrstr)); + printf("dest = %s\n", addrstr); +} + +/* print icmp header */ +void dump_icmp(struct icmphdr *icmp) { + printf("ICMP\n"); + + printf("icmp.type = %x ", icmp->type); + if (icmp->type == ICMP_ECHOREPLY) { + printf("echo reply"); + } else if (icmp->type == ICMP_ECHO) { + printf("echo request"); + } else { + printf("other"); + } + printf("\n"); + printf("icmp.code = %x\n", icmp->code); + printf("icmp.checksum = %x\n", ntohs(icmp->checksum)); + if (icmp->type == ICMP_ECHOREPLY || icmp->type == ICMP_ECHO) { + printf("icmp.un.echo.id = %x\n", ntohs(icmp->un.echo.id)); + printf("icmp.un.echo.sequence = %x\n", ntohs(icmp->un.echo.sequence)); + } +} + +/* print icmp6 header */ +void dump_icmp6(struct icmp6_hdr *icmp6) { + printf("ICMP6\n"); + printf("type = %x", icmp6->icmp6_type); + if (icmp6->icmp6_type == ICMP6_ECHO_REQUEST) { + printf("(echo request)"); + } else if (icmp6->icmp6_type == ICMP6_ECHO_REPLY) { + printf("(echo reply)"); + } + printf("\n"); + printf("code = %x\n", icmp6->icmp6_code); + + printf("checksum = %x\n", icmp6->icmp6_cksum); + + if ((icmp6->icmp6_type == ICMP6_ECHO_REQUEST) || (icmp6->icmp6_type == ICMP6_ECHO_REPLY)) { + printf("icmp6_id = %x\n", icmp6->icmp6_id); + printf("icmp6_seq = %x\n", icmp6->icmp6_seq); + } +} + +/* print udp header */ +void dump_udp_generic(const struct udphdr *udp, uint32_t temp_checksum, const uint8_t *payload, + size_t payload_size) { + uint16_t my_checksum; + + temp_checksum = ip_checksum_add(temp_checksum, udp, sizeof(struct udphdr)); + temp_checksum = ip_checksum_add(temp_checksum, payload, payload_size); + my_checksum = ip_checksum_finish(temp_checksum); + + printf("UDP\n"); + printf("source = %x\n", ntohs(udp->source)); + printf("dest = %x\n", ntohs(udp->dest)); + printf("len = %x\n", ntohs(udp->len)); + printf("check = %x (mine %x)\n", udp->check, my_checksum); +} + +/* print ipv4/udp header */ +void dump_udp(const struct udphdr *udp, const struct iphdr *ip, const uint8_t *payload, + size_t payload_size) { + uint32_t temp_checksum; + temp_checksum = ipv4_pseudo_header_checksum(ip, sizeof(*udp) + payload_size); + dump_udp_generic(udp, temp_checksum, payload, payload_size); +} + +/* print ipv6/udp header */ +void dump_udp6(const struct udphdr *udp, const struct ip6_hdr *ip6, const uint8_t *payload, + size_t payload_size) { + uint32_t temp_checksum; + temp_checksum = ipv6_pseudo_header_checksum(ip6, sizeof(*udp) + payload_size, IPPROTO_UDP); + dump_udp_generic(udp, temp_checksum, payload, payload_size); +} + +/* print tcp header */ +void dump_tcp_generic(const struct tcphdr *tcp, const uint8_t *options, size_t options_size, + uint32_t temp_checksum, const uint8_t *payload, size_t payload_size) { + uint16_t my_checksum; + + temp_checksum = ip_checksum_add(temp_checksum, tcp, sizeof(struct tcphdr)); + if (options) { + temp_checksum = ip_checksum_add(temp_checksum, options, options_size); + } + temp_checksum = ip_checksum_add(temp_checksum, payload, payload_size); + my_checksum = ip_checksum_finish(temp_checksum); + + printf("TCP\n"); + printf("source = %x\n", ntohs(tcp->source)); + printf("dest = %x\n", ntohs(tcp->dest)); + printf("seq = %x\n", ntohl(tcp->seq)); + printf("ack = %x\n", ntohl(tcp->ack_seq)); + printf("d_off = %x\n", tcp->doff); + printf("res1 = %x\n", tcp->res1); +#ifdef __BIONIC__ + printf("CWR = %x\n", tcp->cwr); + printf("ECE = %x\n", tcp->ece); +#else + printf("CWR/ECE = %x\n", tcp->res2); +#endif + printf("urg = %x ack = %x psh = %x rst = %x syn = %x fin = %x\n", tcp->urg, tcp->ack, + tcp->psh, tcp->rst, tcp->syn, tcp->fin); + printf("window = %x\n", ntohs(tcp->window)); + printf("check = %x [mine %x]\n", tcp->check, my_checksum); + printf("urgent = %x\n", tcp->urg_ptr); + + if (options) { + size_t i; + + printf("options: "); + for (i = 0; i < options_size; i++) { + printf("%x ", *(options + i)); + } + printf("\n"); + } +} + +/* print ipv4/tcp header */ +void dump_tcp(const struct tcphdr *tcp, const struct iphdr *ip, const uint8_t *payload, + size_t payload_size, const uint8_t *options, size_t options_size) { + uint32_t temp_checksum; + + temp_checksum = ipv4_pseudo_header_checksum(ip, sizeof(*tcp) + options_size + payload_size); + dump_tcp_generic(tcp, options, options_size, temp_checksum, payload, payload_size); +} + +/* print ipv6/tcp header */ +void dump_tcp6(const struct tcphdr *tcp, const struct ip6_hdr *ip6, const uint8_t *payload, + size_t payload_size, const uint8_t *options, size_t options_size) { + uint32_t temp_checksum; + + temp_checksum = + ipv6_pseudo_header_checksum(ip6, sizeof(*tcp) + options_size + payload_size, IPPROTO_TCP); + dump_tcp_generic(tcp, options, options_size, temp_checksum, payload, payload_size); +} + +/* generic hex dump */ +void logcat_hexdump(const char *info, const uint8_t *data, size_t len) { + char output[MAXDUMPLEN * 3 + 2]; + size_t i; + + output[0] = '\0'; + for (i = 0; i < len && i < MAXDUMPLEN; i++) { + snprintf(output + i * 3, 4, " %02x", data[i]); + } + output[len * 3 + 3] = '\0'; + + logmsg(ANDROID_LOG_WARN, "info %s len %d data%s", info, len, output); +} + +void dump_iovec(const struct iovec *iov, int iov_len) { + int i; + char *str; + for (i = 0; i < iov_len; i++) { + asprintf(&str, "iov[%d]: ", i); + logcat_hexdump(str, iov[i].iov_base, iov[i].iov_len); + free(str); + } +} +#endif // CLAT_DEBUG diff --git a/clatd/dump.h b/clatd/dump.h new file mode 100644 index 0000000000000000000000000000000000000000..6b96cd298d142c2d733b675c8140add887be1eea --- /dev/null +++ b/clatd/dump.h @@ -0,0 +1,46 @@ +/* + * Copyright 2011 Daniel Drown + * + * 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. + * + * dump.h - debug functions + */ +#ifndef __DUMP_H__ +#define __DUMP_H__ + +#include +#include +#include +#include +#include +#include +#include + +void dump_ip(struct iphdr *header); +void dump_icmp(struct icmphdr *icmp); +void dump_udp(const struct udphdr *udp, const struct iphdr *ip, const uint8_t *payload, + size_t payload_size); +void dump_tcp(const struct tcphdr *tcp, const struct iphdr *ip, const uint8_t *payload, + size_t payload_size, const uint8_t *options, size_t options_size); + +void dump_ip6(struct ip6_hdr *header); +void dump_icmp6(struct icmp6_hdr *icmp6); +void dump_udp6(const struct udphdr *udp, const struct ip6_hdr *ip6, const uint8_t *payload, + size_t payload_size); +void dump_tcp6(const struct tcphdr *tcp, const struct ip6_hdr *ip6, const uint8_t *payload, + size_t payload_size, const uint8_t *options, size_t options_size); + +void logcat_hexdump(const char *info, const uint8_t *data, size_t len); +void dump_iovec(const struct iovec *iov, int iov_len); + +#endif /* __DUMP_H__ */ diff --git a/clatd/icmp.c b/clatd/icmp.c new file mode 100644 index 0000000000000000000000000000000000000000..f9ba113eee6b2a98f3d18a6b4962243464aaa8a2 --- /dev/null +++ b/clatd/icmp.c @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2013 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. + * + * icmp.c - convenience functions for translating ICMP and ICMPv6 packets. + */ + +#include +#include +#include +#include + +#include "icmp.h" +#include "logging.h" + +/* function: icmp_guess_ttl + * Guesses the number of hops a received packet has traversed based on its TTL. + * ttl - the ttl of the received packet. + */ +uint8_t icmp_guess_ttl(uint8_t ttl) { + if (ttl > 128) { + return 255 - ttl; + } else if (ttl > 64) { + return 128 - ttl; + } else if (ttl > 32) { + return 64 - ttl; + } else { + return 32 - ttl; + } +} + +/* function: is_icmp_error + * Determines whether an ICMP type is an error message. + * type: the ICMP type + */ +int is_icmp_error(uint8_t type) { return type == 3 || type == 11 || type == 12; } + +/* function: is_icmp6_error + * Determines whether an ICMPv6 type is an error message. + * type: the ICMPv6 type + */ +int is_icmp6_error(uint8_t type) { return type < 128; } + +/* function: icmp_to_icmp6_type + * Maps ICMP types to ICMPv6 types. Partial implementation of RFC 6145, section 4.2. + * type - the ICMPv6 type + */ +uint8_t icmp_to_icmp6_type(uint8_t type, uint8_t code) { + switch (type) { + case ICMP_ECHO: + return ICMP6_ECHO_REQUEST; + + case ICMP_ECHOREPLY: + return ICMP6_ECHO_REPLY; + + case ICMP_TIME_EXCEEDED: + return ICMP6_TIME_EXCEEDED; + + case ICMP_DEST_UNREACH: + // These two types need special translation which we don't support yet. + if (code != ICMP_UNREACH_PROTOCOL && code != ICMP_UNREACH_NEEDFRAG) { + return ICMP6_DST_UNREACH; + } + } + + // We don't understand this ICMP type. Return parameter problem so the caller will bail out. + logmsg_dbg(ANDROID_LOG_DEBUG, "icmp_to_icmp6_type: unhandled ICMP type %d", type); + return ICMP6_PARAM_PROB; +} + +/* function: icmp_to_icmp6_code + * Maps ICMP codes to ICMPv6 codes. Partial implementation of RFC 6145, section 4.2. + * type - the ICMP type + * code - the ICMP code + */ +uint8_t icmp_to_icmp6_code(uint8_t type, uint8_t code) { + switch (type) { + case ICMP_ECHO: + case ICMP_ECHOREPLY: + return 0; + + case ICMP_TIME_EXCEEDED: + return code; + + case ICMP_DEST_UNREACH: + switch (code) { + case ICMP_UNREACH_NET: + case ICMP_UNREACH_HOST: + return ICMP6_DST_UNREACH_NOROUTE; + + case ICMP_UNREACH_PORT: + return ICMP6_DST_UNREACH_NOPORT; + + case ICMP_UNREACH_NET_PROHIB: + case ICMP_UNREACH_HOST_PROHIB: + case ICMP_UNREACH_FILTER_PROHIB: + case ICMP_UNREACH_PRECEDENCE_CUTOFF: + return ICMP6_DST_UNREACH_ADMIN; + + // Otherwise, we don't understand this ICMP type/code combination. Fall through. + } + } + logmsg_dbg(ANDROID_LOG_DEBUG, "icmp_to_icmp6_code: unhandled ICMP type/code %d/%d", type, code); + return 0; +} + +/* function: icmp6_to_icmp_type + * Maps ICMPv6 types to ICMP types. Partial implementation of RFC 6145, section 5.2. + * type - the ICMP type + */ +uint8_t icmp6_to_icmp_type(uint8_t type, uint8_t code) { + switch (type) { + case ICMP6_ECHO_REQUEST: + return ICMP_ECHO; + + case ICMP6_ECHO_REPLY: + return ICMP_ECHOREPLY; + + case ICMP6_DST_UNREACH: + return ICMP_DEST_UNREACH; + + case ICMP6_TIME_EXCEEDED: + return ICMP_TIME_EXCEEDED; + } + + // We don't understand this ICMP type. Return parameter problem so the caller will bail out. + logmsg_dbg(ANDROID_LOG_DEBUG, "icmp6_to_icmp_type: unhandled ICMP type/code %d/%d", type, code); + return ICMP_PARAMETERPROB; +} + +/* function: icmp6_to_icmp_code + * Maps ICMPv6 codes to ICMP codes. Partial implementation of RFC 6145, section 5.2. + * type - the ICMPv6 type + * code - the ICMPv6 code + */ +uint8_t icmp6_to_icmp_code(uint8_t type, uint8_t code) { + switch (type) { + case ICMP6_ECHO_REQUEST: + case ICMP6_ECHO_REPLY: + case ICMP6_TIME_EXCEEDED: + return code; + + case ICMP6_DST_UNREACH: + switch (code) { + case ICMP6_DST_UNREACH_NOROUTE: + return ICMP_UNREACH_HOST; + + case ICMP6_DST_UNREACH_ADMIN: + return ICMP_UNREACH_HOST_PROHIB; + + case ICMP6_DST_UNREACH_BEYONDSCOPE: + return ICMP_UNREACH_HOST; + + case ICMP6_DST_UNREACH_ADDR: + return ICMP_HOST_UNREACH; + + case ICMP6_DST_UNREACH_NOPORT: + return ICMP_UNREACH_PORT; + + // Otherwise, we don't understand this ICMPv6 type/code combination. Fall through. + } + } + + logmsg_dbg(ANDROID_LOG_DEBUG, "icmp6_to_icmp_code: unhandled ICMP type/code %d/%d", type, code); + return 0; +} diff --git a/clatd/icmp.h b/clatd/icmp.h new file mode 100644 index 0000000000000000000000000000000000000000..632e92d5e701cbe12fb8484a9635496d94914ba0 --- /dev/null +++ b/clatd/icmp.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 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. + * + * icmp.c - convenience functions for translating ICMP and ICMPv6 packets. + */ + +#ifndef __ICMP_H__ +#define __ICMP_H__ + +#include + +// Guesses the number of hops a received packet has traversed based on its TTL. +uint8_t icmp_guess_ttl(uint8_t ttl); + +// Determines whether an ICMP type is an error message. +int is_icmp_error(uint8_t type); + +// Determines whether an ICMPv6 type is an error message. +int is_icmp6_error(uint8_t type); + +// Maps ICMP types to ICMPv6 types. Partial implementation of RFC 6145, section 4.2. +uint8_t icmp_to_icmp6_type(uint8_t type, uint8_t code); + +// Maps ICMP codes to ICMPv6 codes. Partial implementation of RFC 6145, section 4.2. +uint8_t icmp_to_icmp6_code(uint8_t type, uint8_t code); + +// Maps ICMPv6 types to ICMP types. Partial implementation of RFC 6145, section 5.2. +uint8_t icmp6_to_icmp_type(uint8_t type, uint8_t code); + +// Maps ICMPv6 codes to ICMP codes. Partial implementation of RFC 6145, section 5.2. +uint8_t icmp6_to_icmp_code(uint8_t type, uint8_t code); + +#endif /* __ICMP_H__ */ diff --git a/clatd/ipv4.c b/clatd/ipv4.c new file mode 100644 index 0000000000000000000000000000000000000000..2be02e34c2da58335ce66a384297be5fe6f1cf6b --- /dev/null +++ b/clatd/ipv4.c @@ -0,0 +1,146 @@ +/* + * Copyright 2011 Daniel Drown + * + * 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. + * + * ipv4.c - takes ipv4 packets, finds their headers, and then calls translation functions on them + */ +#include + +#include "checksum.h" +#include "debug.h" +#include "dump.h" +#include "logging.h" +#include "translate.h" + +/* function: icmp_packet + * translates an icmp packet + * out - output packet + * icmp - pointer to icmp header in packet + * checksum - pseudo-header checksum + * len - size of ip payload + * returns: the highest position in the output clat_packet that's filled in + */ +int icmp_packet(clat_packet out, clat_packet_index pos, const struct icmphdr *icmp, + uint32_t checksum, size_t len) { + const uint8_t *payload; + size_t payload_size; + + if (len < sizeof(struct icmphdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "icmp_packet/(too small)"); + return 0; + } + + payload = (const uint8_t *)(icmp + 1); + payload_size = len - sizeof(struct icmphdr); + + return icmp_to_icmp6(out, pos, icmp, checksum, payload, payload_size); +} + +/* function: ipv4_packet + * translates an ipv4 packet + * out - output packet + * packet - packet data + * len - size of packet + * returns: the highest position in the output clat_packet that's filled in + */ +int ipv4_packet(clat_packet out, clat_packet_index pos, const uint8_t *packet, size_t len) { + const struct iphdr *header = (struct iphdr *)packet; + struct ip6_hdr *ip6_targ = (struct ip6_hdr *)out[pos].iov_base; + struct ip6_frag *frag_hdr; + size_t frag_hdr_len; + uint8_t nxthdr; + const uint8_t *next_header; + size_t len_left; + uint32_t old_sum, new_sum; + int iov_len; + + if (len < sizeof(struct iphdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/too short for an ip header"); + return 0; + } + + if (header->ihl < 5) { + logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/ip header length set to less than 5: %x", header->ihl); + return 0; + } + + if ((size_t)header->ihl * 4 > len) { // ip header length larger than entire packet + logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/ip header length set too large: %x", header->ihl); + return 0; + } + + if (header->version != 4) { + logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/ip header version not 4: %x", header->version); + return 0; + } + + /* rfc6145 - If any IPv4 options are present in the IPv4 packet, they MUST be + * ignored and the packet translated normally; there is no attempt to + * translate the options. + */ + + next_header = packet + header->ihl * 4; + len_left = len - header->ihl * 4; + + nxthdr = header->protocol; + if (nxthdr == IPPROTO_ICMP) { + // ICMP and ICMPv6 have different protocol numbers. + nxthdr = IPPROTO_ICMPV6; + } + + /* Fill in the IPv6 header. We need to do this before we translate the packet because TCP and + * UDP include parts of the IP header in the checksum. Set the length to zero because we don't + * know it yet. + */ + fill_ip6_header(ip6_targ, 0, nxthdr, header); + out[pos].iov_len = sizeof(struct ip6_hdr); + + /* Calculate the pseudo-header checksum. + * Technically, the length that is used in the pseudo-header checksum is the transport layer + * length, which is not the same as len_left in the case of fragmented packets. But since + * translation does not change the transport layer length, the checksum is unaffected. + */ + old_sum = ipv4_pseudo_header_checksum(header, len_left); + new_sum = ipv6_pseudo_header_checksum(ip6_targ, len_left, nxthdr); + + // If the IPv4 packet is fragmented, add a Fragment header. + frag_hdr = (struct ip6_frag *)out[pos + 1].iov_base; + frag_hdr_len = maybe_fill_frag_header(frag_hdr, ip6_targ, header); + out[pos + 1].iov_len = frag_hdr_len; + + if (frag_hdr_len && frag_hdr->ip6f_offlg & IP6F_OFF_MASK) { + // Non-first fragment. Copy the rest of the packet as is. + iov_len = generic_packet(out, pos + 2, next_header, len_left); + } else if (nxthdr == IPPROTO_ICMPV6) { + iov_len = icmp_packet(out, pos + 2, (const struct icmphdr *)next_header, new_sum, len_left); + } else if (nxthdr == IPPROTO_TCP) { + iov_len = + tcp_packet(out, pos + 2, (const struct tcphdr *)next_header, old_sum, new_sum, len_left); + } else if (nxthdr == IPPROTO_UDP) { + iov_len = + udp_packet(out, pos + 2, (const struct udphdr *)next_header, old_sum, new_sum, len_left); + } else if (nxthdr == IPPROTO_GRE || nxthdr == IPPROTO_ESP) { + iov_len = generic_packet(out, pos + 2, next_header, len_left); + } else { +#if CLAT_DEBUG + logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/unknown protocol: %x", header->protocol); + logcat_hexdump("ipv4/protocol", packet, len); +#endif + return 0; + } + + // Set the length. + ip6_targ->ip6_plen = htons(packet_length(out, pos)); + return iov_len; +} diff --git a/clatd/ipv6.c b/clatd/ipv6.c new file mode 100644 index 0000000000000000000000000000000000000000..05cd3ab116c5695f2a27e2a86e517c8b3a69cb4e --- /dev/null +++ b/clatd/ipv6.c @@ -0,0 +1,178 @@ +/* + * Copyright 2011 Daniel Drown + * + * 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. + * + * ipv6.c - takes ipv6 packets, finds their headers, and then calls translation functions on them + */ +#include +#include + +#include "checksum.h" +#include "config.h" +#include "debug.h" +#include "dump.h" +#include "logging.h" +#include "translate.h" + +/* function: icmp6_packet + * takes an icmp6 packet and sets it up for translation + * out - output packet + * icmp6 - pointer to icmp6 header in packet + * checksum - pseudo-header checksum (unused) + * len - size of ip payload + * returns: the highest position in the output clat_packet that's filled in + */ +int icmp6_packet(clat_packet out, clat_packet_index pos, const struct icmp6_hdr *icmp6, + size_t len) { + const uint8_t *payload; + size_t payload_size; + + if (len < sizeof(struct icmp6_hdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "icmp6_packet/(too small)"); + return 0; + } + + payload = (const uint8_t *)(icmp6 + 1); + payload_size = len - sizeof(struct icmp6_hdr); + + return icmp6_to_icmp(out, pos, icmp6, payload, payload_size); +} + +/* function: log_bad_address + * logs a bad address to android's log buffer if debugging is turned on + * fmt - printf-style format, use %s to place the address + * badaddr - the bad address in question + */ +#if CLAT_DEBUG +void log_bad_address(const char *fmt, const struct in6_addr *src, const struct in6_addr *dst) { + char srcstr[INET6_ADDRSTRLEN]; + char dststr[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, src, srcstr, sizeof(srcstr)); + inet_ntop(AF_INET6, dst, dststr, sizeof(dststr)); + logmsg_dbg(ANDROID_LOG_ERROR, fmt, srcstr, dststr); +} +#else +#define log_bad_address(fmt, src, dst) +#endif + +/* function: ipv6_packet + * takes an ipv6 packet and hands it off to the layer 4 protocol function + * out - output packet + * packet - packet data + * len - size of packet + * returns: the highest position in the output clat_packet that's filled in + */ +int ipv6_packet(clat_packet out, clat_packet_index pos, const uint8_t *packet, size_t len) { + const struct ip6_hdr *ip6 = (struct ip6_hdr *)packet; + struct iphdr *ip_targ = (struct iphdr *)out[pos].iov_base; + struct ip6_frag *frag_hdr = NULL; + uint8_t protocol; + const uint8_t *next_header; + size_t len_left; + uint32_t old_sum, new_sum; + int iov_len; + + if (len < sizeof(struct ip6_hdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "ipv6_packet/too short for an ip6 header: %d", len); + return 0; + } + + if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { + log_bad_address("ipv6_packet/multicast %s->%s", &ip6->ip6_src, &ip6->ip6_dst); + return 0; // silently ignore + } + + // If the packet is not from the plat subnet to the local subnet, or vice versa, drop it, unless + // it's an ICMP packet (which can come from anywhere). We do not send IPv6 packets from the plat + // subnet to the local subnet, but these can appear as inner packets in ICMP errors, so we need + // to translate them. We accept third-party ICMPv6 errors, even though their source addresses + // cannot be translated, so that things like unreachables and traceroute will work. fill_ip_header + // takes care of faking a source address for them. + if (!(is_in_plat_subnet(&ip6->ip6_src) && + IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &Global_Clatd_Config.ipv6_local_subnet)) && + !(is_in_plat_subnet(&ip6->ip6_dst) && + IN6_ARE_ADDR_EQUAL(&ip6->ip6_src, &Global_Clatd_Config.ipv6_local_subnet)) && + ip6->ip6_nxt != IPPROTO_ICMPV6) { + log_bad_address("ipv6_packet/wrong source address: %s->%s", &ip6->ip6_src, &ip6->ip6_dst); + return 0; + } + + next_header = packet + sizeof(struct ip6_hdr); + len_left = len - sizeof(struct ip6_hdr); + + protocol = ip6->ip6_nxt; + + /* Fill in the IPv4 header. We need to do this before we translate the packet because TCP and + * UDP include parts of the IP header in the checksum. Set the length to zero because we don't + * know it yet. + */ + fill_ip_header(ip_targ, 0, protocol, ip6); + out[pos].iov_len = sizeof(struct iphdr); + + // If there's a Fragment header, parse it and decide what the next header is. + // Do this before calculating the pseudo-header checksum because it updates the next header value. + if (protocol == IPPROTO_FRAGMENT) { + frag_hdr = (struct ip6_frag *)next_header; + if (len_left < sizeof(*frag_hdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "ipv6_packet/too short for fragment header: %d", len); + return 0; + } + + next_header += sizeof(*frag_hdr); + len_left -= sizeof(*frag_hdr); + + protocol = parse_frag_header(frag_hdr, ip_targ); + } + + // ICMP and ICMPv6 have different protocol numbers. + if (protocol == IPPROTO_ICMPV6) { + protocol = IPPROTO_ICMP; + ip_targ->protocol = IPPROTO_ICMP; + } + + /* Calculate the pseudo-header checksum. + * Technically, the length that is used in the pseudo-header checksum is the transport layer + * length, which is not the same as len_left in the case of fragmented packets. But since + * translation does not change the transport layer length, the checksum is unaffected. + */ + old_sum = ipv6_pseudo_header_checksum(ip6, len_left, protocol); + new_sum = ipv4_pseudo_header_checksum(ip_targ, len_left); + + // Does not support IPv6 extension headers except Fragment. + if (frag_hdr && (frag_hdr->ip6f_offlg & IP6F_OFF_MASK)) { + iov_len = generic_packet(out, pos + 2, next_header, len_left); + } else if (protocol == IPPROTO_ICMP) { + iov_len = icmp6_packet(out, pos + 2, (const struct icmp6_hdr *)next_header, len_left); + } else if (protocol == IPPROTO_TCP) { + iov_len = + tcp_packet(out, pos + 2, (const struct tcphdr *)next_header, old_sum, new_sum, len_left); + } else if (protocol == IPPROTO_UDP) { + iov_len = + udp_packet(out, pos + 2, (const struct udphdr *)next_header, old_sum, new_sum, len_left); + } else if (protocol == IPPROTO_GRE || protocol == IPPROTO_ESP) { + iov_len = generic_packet(out, pos + 2, next_header, len_left); + } else { +#if CLAT_DEBUG + logmsg(ANDROID_LOG_ERROR, "ipv6_packet/unknown next header type: %x", ip6->ip6_nxt); + logcat_hexdump("ipv6/nxthdr", packet, len); +#endif + return 0; + } + + // Set the length and calculate the checksum. + ip_targ->tot_len = htons(ntohs(ip_targ->tot_len) + packet_length(out, pos)); + ip_targ->check = ip_checksum(ip_targ, sizeof(struct iphdr)); + return iov_len; +} diff --git a/clatd/logging.c b/clatd/logging.c new file mode 100644 index 0000000000000000000000000000000000000000..79d98e17937aa2910ca45bf285b35f6462d59b18 --- /dev/null +++ b/clatd/logging.c @@ -0,0 +1,55 @@ +/* + * Copyright 2011 Daniel Drown + * + * 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. + * + * logging.c - print a log message + */ + +#include +#include + +#include "debug.h" +#include "logging.h" + +/* function: logmsg + * prints a log message to android's log buffer + * prio - the log message priority + * fmt - printf format specifier + * ... - printf format arguments + */ +void logmsg(int prio, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + __android_log_vprint(prio, "clatd", fmt, ap); + va_end(ap); +} + +/* function: logmsg_dbg + * prints a log message to android's log buffer if CLAT_DEBUG is set + * prio - the log message priority + * fmt - printf format specifier + * ... - printf format arguments + */ +#if CLAT_DEBUG +void logmsg_dbg(int prio, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + __android_log_vprint(prio, "clatd", fmt, ap); + va_end(ap); +} +#else +void logmsg_dbg(__attribute__((unused)) int prio, __attribute__((unused)) const char *fmt, ...) {} +#endif diff --git a/clatd/logging.h b/clatd/logging.h new file mode 100644 index 0000000000000000000000000000000000000000..1f4b6b60ce1d3ba40eb9e306cee9953117405cfa --- /dev/null +++ b/clatd/logging.h @@ -0,0 +1,27 @@ +/* + * Copyright 2011 Daniel Drown + * + * 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. + * + * logging.h - print a log message + */ + +#ifndef __LOGGING_H__ +#define __LOGGING_H__ +// for the priorities +#include + +void logmsg(int prio, const char *fmt, ...); +void logmsg_dbg(int prio, const char *fmt, ...); + +#endif diff --git a/clatd/main.c b/clatd/main.c new file mode 100644 index 0000000000000000000000000000000000000000..f888041c0ffa1595b9dc37b50655d8968a1309b9 --- /dev/null +++ b/clatd/main.c @@ -0,0 +1,207 @@ +/* + * Copyright 2018 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. + * + * main.c - main function + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clatd.h" +#include "common.h" +#include "config.h" +#include "logging.h" + +#define DEVICEPREFIX "v4-" + +/* function: stop_loop + * signal handler: stop the event loop + */ +static void stop_loop() { running = 0; }; + +/* function: print_help + * in case the user is running this on the command line + */ +void print_help() { + printf("android-clat arguments:\n"); + printf("-i [uplink interface]\n"); + printf("-p [plat prefix]\n"); + printf("-4 [IPv4 address]\n"); + printf("-6 [IPv6 address]\n"); + printf("-t [tun file descriptor number]\n"); + printf("-r [read socket descriptor number]\n"); + printf("-w [write socket descriptor number]\n"); +} + +/* function: main + * allocate and setup the tun device, then run the event loop + */ +int main(int argc, char **argv) { + struct tun_data tunnel; + int opt; + char *uplink_interface = NULL, *plat_prefix = NULL; + char *v4_addr = NULL, *v6_addr = NULL, *tunfd_str = NULL, *read_sock_str = NULL, + *write_sock_str = NULL; + unsigned len; + + while ((opt = getopt(argc, argv, "i:p:4:6:t:r:w:h")) != -1) { + switch (opt) { + case 'i': + uplink_interface = optarg; + break; + case 'p': + plat_prefix = optarg; + break; + case '4': + v4_addr = optarg; + break; + case '6': + v6_addr = optarg; + break; + case 't': + tunfd_str = optarg; + break; + case 'r': + read_sock_str = optarg; + break; + case 'w': + write_sock_str = optarg; + break; + case 'h': + print_help(); + exit(0); + default: + logmsg(ANDROID_LOG_FATAL, "Unknown option -%c. Exiting.", (char)optopt); + exit(1); + } + } + + if (uplink_interface == NULL) { + logmsg(ANDROID_LOG_FATAL, "clatd called without an interface"); + exit(1); + } + + if (tunfd_str != NULL && !parse_int(tunfd_str, &tunnel.fd4)) { + logmsg(ANDROID_LOG_FATAL, "invalid tunfd %s", tunfd_str); + exit(1); + } + if (!tunnel.fd4) { + logmsg(ANDROID_LOG_FATAL, "no tunfd specified on commandline."); + exit(1); + } + + if (read_sock_str != NULL && !parse_int(read_sock_str, &tunnel.read_fd6)) { + logmsg(ANDROID_LOG_FATAL, "invalid read socket %s", read_sock_str); + exit(1); + } + if (!tunnel.read_fd6) { + logmsg(ANDROID_LOG_FATAL, "no read_fd6 specified on commandline."); + exit(1); + } + + if (write_sock_str != NULL && !parse_int(write_sock_str, &tunnel.write_fd6)) { + logmsg(ANDROID_LOG_FATAL, "invalid write socket %s", write_sock_str); + exit(1); + } + if (!tunnel.write_fd6) { + logmsg(ANDROID_LOG_FATAL, "no write_fd6 specified on commandline."); + exit(1); + } + + len = snprintf(tunnel.device4, sizeof(tunnel.device4), "%s%s", DEVICEPREFIX, uplink_interface); + if (len >= sizeof(tunnel.device4)) { + logmsg(ANDROID_LOG_FATAL, "interface name too long '%s'", tunnel.device4); + exit(1); + } + + Global_Clatd_Config.native_ipv6_interface = uplink_interface; + if (!plat_prefix || inet_pton(AF_INET6, plat_prefix, &Global_Clatd_Config.plat_subnet) <= 0) { + logmsg(ANDROID_LOG_FATAL, "invalid IPv6 address specified for plat prefix: %s", plat_prefix); + exit(1); + } + + if (!v4_addr || !inet_pton(AF_INET, v4_addr, &Global_Clatd_Config.ipv4_local_subnet.s_addr)) { + logmsg(ANDROID_LOG_FATAL, "Invalid IPv4 address %s", v4_addr); + exit(1); + } + + if (!v6_addr || !inet_pton(AF_INET6, v6_addr, &Global_Clatd_Config.ipv6_local_subnet)) { + logmsg(ANDROID_LOG_FATAL, "Invalid source address %s", v6_addr); + exit(1); + } + + logmsg(ANDROID_LOG_INFO, "Starting clat version %s on %s plat=%s v4=%s v6=%s", CLATD_VERSION, + uplink_interface, plat_prefix ? plat_prefix : "(none)", v4_addr ? v4_addr : "(none)", + v6_addr ? v6_addr : "(none)"); + + { + // Compile time detection of 32 vs 64-bit build. (note: C does not have 'constexpr') + // Avoid use of preprocessor macros to get compile time syntax checking even on 64-bit. + const int user_bits = sizeof(void*) * 8; + const bool user32 = (user_bits == 32); + + // Note that on 64-bit all this personality related code simply compile optimizes out. + // 32-bit: fetch current personality (see 'man personality': 0xFFFFFFFF means retrieve only) + // On Linux fetching personality cannot fail. + const int prev_personality = user32 ? personality(0xFFFFFFFFuL) : PER_LINUX; + // 32-bit: attempt to get rid of kernel spoofing of 'uts.machine' architecture, + // In theory this cannot fail, as PER_LINUX should always be supported. + if (user32) (void)personality((prev_personality & ~PER_MASK) | PER_LINUX); + // 64-bit: this will compile time evaluate to false. + const bool was_linux32 = (prev_personality & PER_MASK) == PER_LINUX32; + + struct utsname uts = {}; + if (uname(&uts)) exit(1); // only possible error is EFAULT, but 'uts' is on stack + + // sysname is likely 'Linux', release is 'kver', machine is kernel's *true* architecture + logmsg(ANDROID_LOG_INFO, "%d-bit userspace on %s kernel %s for %s%s.", user_bits, + uts.sysname, uts.release, uts.machine, was_linux32 ? " (was spoofed)" : ""); + + // 32-bit: try to return to the 'default' personality + // In theory this cannot fail, because it was already previously in use. + if (user32) (void)personality(prev_personality); + } + + // Loop until someone sends us a signal or brings down the tun interface. + if (signal(SIGTERM, stop_loop) == SIG_ERR) { + logmsg(ANDROID_LOG_FATAL, "sigterm handler failed: %s", strerror(errno)); + exit(1); + } + + event_loop(&tunnel); + + logmsg(ANDROID_LOG_INFO, "Shutting down clat on %s", uplink_interface); + + if (running) { + logmsg(ANDROID_LOG_INFO, "Clatd on %s waiting for SIGTERM", uplink_interface); + // let's give higher level java code 15 seconds to kill us, + // but eventually terminate anyway, in case system server forgets about us... + // sleep() should be interrupted by SIGTERM, the handler should clear running + sleep(15); + logmsg(ANDROID_LOG_INFO, "Clatd on %s %s SIGTERM", uplink_interface, + running ? "timed out waiting for" : "received"); + } else { + logmsg(ANDROID_LOG_INFO, "Clatd on %s already received SIGTERM", uplink_interface); + } + return 0; +} diff --git a/clatd/translate.c b/clatd/translate.c new file mode 100644 index 0000000000000000000000000000000000000000..22830d890c76a646257f4ae4aa5baf62b938e2a0 --- /dev/null +++ b/clatd/translate.c @@ -0,0 +1,529 @@ +/* + * Copyright 2011 Daniel Drown + * + * 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. + * + * translate.c - CLAT functions / partial implementation of rfc6145 + */ +#include "translate.h" + +#include + +#include "checksum.h" +#include "clatd.h" +#include "common.h" +#include "config.h" +#include "debug.h" +#include "icmp.h" +#include "logging.h" + +/* function: packet_checksum + * calculates the checksum over all the packet components starting from pos + * checksum - checksum of packet components before pos + * packet - packet to calculate the checksum of + * pos - position to start counting from + * returns - the completed 16-bit checksum, ready to write into a checksum header field + */ +uint16_t packet_checksum(uint32_t checksum, clat_packet packet, clat_packet_index pos) { + int i; + for (i = pos; i < CLAT_POS_MAX; i++) { + if (packet[i].iov_len > 0) { + checksum = ip_checksum_add(checksum, packet[i].iov_base, packet[i].iov_len); + } + } + return ip_checksum_finish(checksum); +} + +/* function: packet_length + * returns the total length of all the packet components after pos + * packet - packet to calculate the length of + * pos - position to start counting after + * returns: the total length of the packet components after pos + */ +uint16_t packet_length(clat_packet packet, clat_packet_index pos) { + size_t len = 0; + int i; + for (i = pos + 1; i < CLAT_POS_MAX; i++) { + len += packet[i].iov_len; + } + return len; +} + +/* function: is_in_plat_subnet + * returns true iff the given IPv6 address is in the plat subnet. + * addr - IPv6 address + */ +int is_in_plat_subnet(const struct in6_addr *addr6) { + // Assumes a /96 plat subnet. + return (addr6 != NULL) && (memcmp(addr6, &Global_Clatd_Config.plat_subnet, 12) == 0); +} + +/* function: ipv6_addr_to_ipv4_addr + * return the corresponding ipv4 address for the given ipv6 address + * addr6 - ipv6 address + * returns: the IPv4 address + */ +uint32_t ipv6_addr_to_ipv4_addr(const struct in6_addr *addr6) { + if (is_in_plat_subnet(addr6)) { + // Assumes a /96 plat subnet. + return addr6->s6_addr32[3]; + } else if (IN6_ARE_ADDR_EQUAL(addr6, &Global_Clatd_Config.ipv6_local_subnet)) { + // Special-case our own address. + return Global_Clatd_Config.ipv4_local_subnet.s_addr; + } else { + // Third party packet. Let the caller deal with it. + return INADDR_NONE; + } +} + +/* function: ipv4_addr_to_ipv6_addr + * return the corresponding ipv6 address for the given ipv4 address + * addr4 - ipv4 address + */ +struct in6_addr ipv4_addr_to_ipv6_addr(uint32_t addr4) { + struct in6_addr addr6; + // Both addresses are in network byte order (addr4 comes from a network packet, and the config + // file entry is read using inet_ntop). + if (addr4 == Global_Clatd_Config.ipv4_local_subnet.s_addr) { + return Global_Clatd_Config.ipv6_local_subnet; + } else { + // Assumes a /96 plat subnet. + addr6 = Global_Clatd_Config.plat_subnet; + addr6.s6_addr32[3] = addr4; + return addr6; + } +} + +/* function: fill_tun_header + * fill in the header for the tun fd + * tun_header - tunnel header, already allocated + * proto - ethernet protocol id: ETH_P_IP(ipv4) or ETH_P_IPV6(ipv6) + */ +void fill_tun_header(struct tun_pi *tun_header, uint16_t proto) { + tun_header->flags = 0; + tun_header->proto = htons(proto); +} + +/* function: fill_ip_header + * generate an ipv4 header from an ipv6 header + * ip_targ - (ipv4) target packet header, source: original ipv4 addr, dest: local subnet addr + * payload_len - length of other data inside packet + * protocol - protocol number (tcp, udp, etc) + * old_header - (ipv6) source packet header, source: nat64 prefix, dest: local subnet prefix + */ +void fill_ip_header(struct iphdr *ip, uint16_t payload_len, uint8_t protocol, + const struct ip6_hdr *old_header) { + int ttl_guess; + memset(ip, 0, sizeof(struct iphdr)); + + ip->ihl = 5; + ip->version = 4; + ip->tos = 0; + ip->tot_len = htons(sizeof(struct iphdr) + payload_len); + ip->id = 0; + ip->frag_off = htons(IP_DF); + ip->ttl = old_header->ip6_hlim; + ip->protocol = protocol; + ip->check = 0; + + ip->saddr = ipv6_addr_to_ipv4_addr(&old_header->ip6_src); + ip->daddr = ipv6_addr_to_ipv4_addr(&old_header->ip6_dst); + + // Third-party ICMPv6 message. This may have been originated by an native IPv6 address. + // In that case, the source IPv6 address can't be translated and we need to make up an IPv4 + // source address. For now, use 255.0.0., which at least looks useful in traceroute. + if ((uint32_t)ip->saddr == INADDR_NONE) { + ttl_guess = icmp_guess_ttl(old_header->ip6_hlim); + ip->saddr = htonl((0xff << 24) + ttl_guess); + } +} + +/* function: fill_ip6_header + * generate an ipv6 header from an ipv4 header + * ip6 - (ipv6) target packet header, source: local subnet prefix, dest: nat64 prefix + * payload_len - length of other data inside packet + * protocol - protocol number (tcp, udp, etc) + * old_header - (ipv4) source packet header, source: local subnet addr, dest: internet's ipv4 addr + */ +void fill_ip6_header(struct ip6_hdr *ip6, uint16_t payload_len, uint8_t protocol, + const struct iphdr *old_header) { + memset(ip6, 0, sizeof(struct ip6_hdr)); + + ip6->ip6_vfc = 6 << 4; + ip6->ip6_plen = htons(payload_len); + ip6->ip6_nxt = protocol; + ip6->ip6_hlim = old_header->ttl; + + ip6->ip6_src = ipv4_addr_to_ipv6_addr(old_header->saddr); + ip6->ip6_dst = ipv4_addr_to_ipv6_addr(old_header->daddr); +} + +/* function: maybe_fill_frag_header + * fills a fragmentation header + * generate an ipv6 fragment header from an ipv4 header + * frag_hdr - target (ipv6) fragmentation header + * ip6_targ - target (ipv6) header + * old_header - (ipv4) source packet header + * returns: the length of the fragmentation header if present, or zero if not present + */ +size_t maybe_fill_frag_header(struct ip6_frag *frag_hdr, struct ip6_hdr *ip6_targ, + const struct iphdr *old_header) { + uint16_t frag_flags = ntohs(old_header->frag_off); + uint16_t frag_off = frag_flags & IP_OFFMASK; + if (frag_off == 0 && (frag_flags & IP_MF) == 0) { + // Not a fragment. + return 0; + } + + frag_hdr->ip6f_nxt = ip6_targ->ip6_nxt; + frag_hdr->ip6f_reserved = 0; + // In IPv4, the offset is the bottom 13 bits; in IPv6 it's the top 13 bits. + frag_hdr->ip6f_offlg = htons(frag_off << 3); + if (frag_flags & IP_MF) { + frag_hdr->ip6f_offlg |= IP6F_MORE_FRAG; + } + frag_hdr->ip6f_ident = htonl(ntohs(old_header->id)); + ip6_targ->ip6_nxt = IPPROTO_FRAGMENT; + + return sizeof(*frag_hdr); +} + +/* function: parse_frag_header + * return the length of the fragmentation header if present, or zero if not present + * generate an ipv6 fragment header from an ipv4 header + * frag_hdr - (ipv6) fragmentation header + * ip_targ - target (ipv4) header + * returns: the next header value + */ +uint8_t parse_frag_header(const struct ip6_frag *frag_hdr, struct iphdr *ip_targ) { + uint16_t frag_off = (ntohs(frag_hdr->ip6f_offlg & IP6F_OFF_MASK) >> 3); + if (frag_hdr->ip6f_offlg & IP6F_MORE_FRAG) { + frag_off |= IP_MF; + } + ip_targ->frag_off = htons(frag_off); + ip_targ->id = htons(ntohl(frag_hdr->ip6f_ident) & 0xffff); + ip_targ->protocol = frag_hdr->ip6f_nxt; + return frag_hdr->ip6f_nxt; +} + +/* function: icmp_to_icmp6 + * translate ipv4 icmp to ipv6 icmp + * out - output packet + * icmp - source packet icmp header + * checksum - pseudo-header checksum + * payload - icmp payload + * payload_size - size of payload + * returns: the highest position in the output clat_packet that's filled in + */ +int icmp_to_icmp6(clat_packet out, clat_packet_index pos, const struct icmphdr *icmp, + uint32_t checksum, const uint8_t *payload, size_t payload_size) { + struct icmp6_hdr *icmp6_targ = out[pos].iov_base; + uint8_t icmp6_type; + int clat_packet_len; + + memset(icmp6_targ, 0, sizeof(struct icmp6_hdr)); + + icmp6_type = icmp_to_icmp6_type(icmp->type, icmp->code); + icmp6_targ->icmp6_type = icmp6_type; + icmp6_targ->icmp6_code = icmp_to_icmp6_code(icmp->type, icmp->code); + + out[pos].iov_len = sizeof(struct icmp6_hdr); + + if (pos == CLAT_POS_TRANSPORTHDR && is_icmp_error(icmp->type) && icmp6_type != ICMP6_PARAM_PROB) { + // An ICMP error we understand, one level deep. + // Translate the nested packet (the one that caused the error). + clat_packet_len = ipv4_packet(out, pos + 1, payload, payload_size); + + // The pseudo-header checksum was calculated on the transport length of the original IPv4 + // packet that we were asked to translate. This transport length is 20 bytes smaller than it + // needs to be, because the ICMP error contains an IPv4 header, which we will be translating to + // an IPv6 header, which is 20 bytes longer. Fix it up here. + // We only need to do this for ICMP->ICMPv6, not ICMPv6->ICMP, because ICMP does not use the + // pseudo-header when calculating its checksum (as the IPv4 header has its own checksum). + checksum = checksum + htons(20); + } else if (icmp6_type == ICMP6_ECHO_REQUEST || icmp6_type == ICMP6_ECHO_REPLY) { + // Ping packet. + icmp6_targ->icmp6_id = icmp->un.echo.id; + icmp6_targ->icmp6_seq = icmp->un.echo.sequence; + out[CLAT_POS_PAYLOAD].iov_base = (uint8_t *)payload; + out[CLAT_POS_PAYLOAD].iov_len = payload_size; + clat_packet_len = CLAT_POS_PAYLOAD + 1; + } else { + // Unknown type/code. The type/code conversion functions have already logged an error. + return 0; + } + + icmp6_targ->icmp6_cksum = 0; // Checksum field must be 0 when calculating checksum. + icmp6_targ->icmp6_cksum = packet_checksum(checksum, out, pos); + + return clat_packet_len; +} + +/* function: icmp6_to_icmp + * translate ipv6 icmp to ipv4 icmp + * out - output packet + * icmp6 - source packet icmp6 header + * payload - icmp6 payload + * payload_size - size of payload + * returns: the highest position in the output clat_packet that's filled in + */ +int icmp6_to_icmp(clat_packet out, clat_packet_index pos, const struct icmp6_hdr *icmp6, + const uint8_t *payload, size_t payload_size) { + struct icmphdr *icmp_targ = out[pos].iov_base; + uint8_t icmp_type; + int clat_packet_len; + + memset(icmp_targ, 0, sizeof(struct icmphdr)); + + icmp_type = icmp6_to_icmp_type(icmp6->icmp6_type, icmp6->icmp6_code); + icmp_targ->type = icmp_type; + icmp_targ->code = icmp6_to_icmp_code(icmp6->icmp6_type, icmp6->icmp6_code); + + out[pos].iov_len = sizeof(struct icmphdr); + + if (pos == CLAT_POS_TRANSPORTHDR && is_icmp6_error(icmp6->icmp6_type) && + icmp_type != ICMP_PARAMETERPROB) { + // An ICMPv6 error we understand, one level deep. + // Translate the nested packet (the one that caused the error). + clat_packet_len = ipv6_packet(out, pos + 1, payload, payload_size); + } else if (icmp_type == ICMP_ECHO || icmp_type == ICMP_ECHOREPLY) { + // Ping packet. + icmp_targ->un.echo.id = icmp6->icmp6_id; + icmp_targ->un.echo.sequence = icmp6->icmp6_seq; + out[CLAT_POS_PAYLOAD].iov_base = (uint8_t *)payload; + out[CLAT_POS_PAYLOAD].iov_len = payload_size; + clat_packet_len = CLAT_POS_PAYLOAD + 1; + } else { + // Unknown type/code. The type/code conversion functions have already logged an error. + return 0; + } + + icmp_targ->checksum = 0; // Checksum field must be 0 when calculating checksum. + icmp_targ->checksum = packet_checksum(0, out, pos); + + return clat_packet_len; +} + +/* function: generic_packet + * takes a generic IP packet and sets it up for translation + * out - output packet + * pos - position in the output packet of the transport header + * payload - pointer to IP payload + * len - size of ip payload + * returns: the highest position in the output clat_packet that's filled in + */ +int generic_packet(clat_packet out, clat_packet_index pos, const uint8_t *payload, size_t len) { + out[pos].iov_len = 0; + out[CLAT_POS_PAYLOAD].iov_base = (uint8_t *)payload; + out[CLAT_POS_PAYLOAD].iov_len = len; + + return CLAT_POS_PAYLOAD + 1; +} + +/* function: udp_packet + * takes a udp packet and sets it up for translation + * out - output packet + * udp - pointer to udp header in packet + * old_sum - pseudo-header checksum of old header + * new_sum - pseudo-header checksum of new header + * len - size of ip payload + */ +int udp_packet(clat_packet out, clat_packet_index pos, const struct udphdr *udp, uint32_t old_sum, + uint32_t new_sum, size_t len) { + const uint8_t *payload; + size_t payload_size; + + if (len < sizeof(struct udphdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "udp_packet/(too small)"); + return 0; + } + + payload = (const uint8_t *)(udp + 1); + payload_size = len - sizeof(struct udphdr); + + return udp_translate(out, pos, udp, old_sum, new_sum, payload, payload_size); +} + +/* function: tcp_packet + * takes a tcp packet and sets it up for translation + * out - output packet + * tcp - pointer to tcp header in packet + * checksum - pseudo-header checksum + * len - size of ip payload + * returns: the highest position in the output clat_packet that's filled in + */ +int tcp_packet(clat_packet out, clat_packet_index pos, const struct tcphdr *tcp, uint32_t old_sum, + uint32_t new_sum, size_t len) { + const uint8_t *payload; + size_t payload_size, header_size; + + if (len < sizeof(struct tcphdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "tcp_packet/(too small)"); + return 0; + } + + if (tcp->doff < 5) { + logmsg_dbg(ANDROID_LOG_ERROR, "tcp_packet/tcp header length set to less than 5: %x", tcp->doff); + return 0; + } + + if ((size_t)tcp->doff * 4 > len) { + logmsg_dbg(ANDROID_LOG_ERROR, "tcp_packet/tcp header length set too large: %x", tcp->doff); + return 0; + } + + header_size = tcp->doff * 4; + payload = ((const uint8_t *)tcp) + header_size; + payload_size = len - header_size; + + return tcp_translate(out, pos, tcp, header_size, old_sum, new_sum, payload, payload_size); +} + +/* function: udp_translate + * common between ipv4/ipv6 - setup checksum and send udp packet + * out - output packet + * udp - udp header + * old_sum - pseudo-header checksum of old header + * new_sum - pseudo-header checksum of new header + * payload - tcp payload + * payload_size - size of payload + * returns: the highest position in the output clat_packet that's filled in + */ +int udp_translate(clat_packet out, clat_packet_index pos, const struct udphdr *udp, + uint32_t old_sum, uint32_t new_sum, const uint8_t *payload, size_t payload_size) { + struct udphdr *udp_targ = out[pos].iov_base; + + memcpy(udp_targ, udp, sizeof(struct udphdr)); + + out[pos].iov_len = sizeof(struct udphdr); + out[CLAT_POS_PAYLOAD].iov_base = (uint8_t *)payload; + out[CLAT_POS_PAYLOAD].iov_len = payload_size; + + if (udp_targ->check) { + udp_targ->check = ip_checksum_adjust(udp->check, old_sum, new_sum); + } else { + // Zero checksums are special. RFC 768 says, "An all zero transmitted checksum value means that + // the transmitter generated no checksum (for debugging or for higher level protocols that + // don't care)." However, in IPv6 zero UDP checksums were only permitted by RFC 6935 (2013). So + // for safety we recompute it. + udp_targ->check = 0; // Checksum field must be 0 when calculating checksum. + udp_targ->check = packet_checksum(new_sum, out, pos); + } + + // RFC 768: "If the computed checksum is zero, it is transmitted as all ones (the equivalent + // in one's complement arithmetic)." + if (!udp_targ->check) { + udp_targ->check = 0xffff; + } + + return CLAT_POS_PAYLOAD + 1; +} + +/* function: tcp_translate + * common between ipv4/ipv6 - setup checksum and send tcp packet + * out - output packet + * tcp - tcp header + * header_size - size of tcp header including options + * checksum - partial checksum covering ipv4/ipv6 header + * payload - tcp payload + * payload_size - size of payload + * returns: the highest position in the output clat_packet that's filled in + */ +int tcp_translate(clat_packet out, clat_packet_index pos, const struct tcphdr *tcp, + size_t header_size, uint32_t old_sum, uint32_t new_sum, const uint8_t *payload, + size_t payload_size) { + struct tcphdr *tcp_targ = out[pos].iov_base; + out[pos].iov_len = header_size; + + if (header_size > MAX_TCP_HDR) { + // A TCP header cannot be more than MAX_TCP_HDR bytes long because it's a 4-bit field that + // counts in 4-byte words. So this can never happen unless there is a bug in the caller. + logmsg(ANDROID_LOG_ERROR, "tcp_translate: header too long %d > %d, truncating", header_size, + MAX_TCP_HDR); + header_size = MAX_TCP_HDR; + } + + memcpy(tcp_targ, tcp, header_size); + + out[CLAT_POS_PAYLOAD].iov_base = (uint8_t *)payload; + out[CLAT_POS_PAYLOAD].iov_len = payload_size; + + tcp_targ->check = ip_checksum_adjust(tcp->check, old_sum, new_sum); + + return CLAT_POS_PAYLOAD + 1; +} + +// Weak symbol so we can override it in the unit test. +void send_rawv6(int fd, clat_packet out, int iov_len) __attribute__((weak)); + +void send_rawv6(int fd, clat_packet out, int iov_len) { + // A send on a raw socket requires a destination address to be specified even if the socket's + // protocol is IPPROTO_RAW. This is the address that will be used in routing lookups; the + // destination address in the packet header only affects what appears on the wire, not where the + // packet is sent to. + static struct sockaddr_in6 sin6 = { AF_INET6, 0, 0, { { { 0, 0, 0, 0 } } }, 0 }; + static struct msghdr msg = { + .msg_name = &sin6, + .msg_namelen = sizeof(sin6), + }; + + msg.msg_iov = out, msg.msg_iovlen = iov_len, + sin6.sin6_addr = ((struct ip6_hdr *)out[CLAT_POS_IPHDR].iov_base)->ip6_dst; + sendmsg(fd, &msg, 0); +} + +/* function: translate_packet + * takes a packet, translates it, and writes it to fd + * fd - fd to write translated packet to + * to_ipv6 - true if translating to ipv6, false if translating to ipv4 + * packet - packet + * packetsize - size of packet + */ +void translate_packet(int fd, int to_ipv6, const uint8_t *packet, size_t packetsize) { + int iov_len = 0; + + // Allocate buffers for all packet headers. + struct tun_pi tun_targ; + char iphdr[sizeof(struct ip6_hdr)]; + char fraghdr[sizeof(struct ip6_frag)]; + char transporthdr[MAX_TCP_HDR]; + char icmp_iphdr[sizeof(struct ip6_hdr)]; + char icmp_fraghdr[sizeof(struct ip6_frag)]; + char icmp_transporthdr[MAX_TCP_HDR]; + + // iovec of the packets we'll send. This gets passed down to the translation functions. + clat_packet out = { + { &tun_targ, 0 }, // Tunnel header. + { iphdr, 0 }, // IP header. + { fraghdr, 0 }, // Fragment header. + { transporthdr, 0 }, // Transport layer header. + { icmp_iphdr, 0 }, // ICMP error inner IP header. + { icmp_fraghdr, 0 }, // ICMP error fragmentation header. + { icmp_transporthdr, 0 }, // ICMP error transport layer header. + { NULL, 0 }, // Payload. No buffer, it's a pointer to the original payload. + }; + + if (to_ipv6) { + iov_len = ipv4_packet(out, CLAT_POS_IPHDR, packet, packetsize); + if (iov_len > 0) { + send_rawv6(fd, out, iov_len); + } + } else { + iov_len = ipv6_packet(out, CLAT_POS_IPHDR, packet, packetsize); + if (iov_len > 0) { + fill_tun_header(&tun_targ, ETH_P_IP); + out[CLAT_POS_TUNHDR].iov_len = sizeof(tun_targ); + writev(fd, out, iov_len); + } + } +} diff --git a/clatd/translate.h b/clatd/translate.h new file mode 100644 index 0000000000000000000000000000000000000000..0e520f710b73adff223b110298c7ef75067089d2 --- /dev/null +++ b/clatd/translate.h @@ -0,0 +1,90 @@ +/* + * Copyright 2011 Daniel Drown + * + * 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. + * + * translate.h - translate from one version of ip to another + */ +#ifndef __TRANSLATE_H__ +#define __TRANSLATE_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clatd.h" +#include "common.h" + +#define MAX_TCP_HDR (15 * 4) // Data offset field is 4 bits and counts in 32-bit words. + +// Calculates the checksum over all the packet components starting from pos. +uint16_t packet_checksum(uint32_t checksum, clat_packet packet, clat_packet_index pos); + +// Returns the total length of the packet components after pos. +uint16_t packet_length(clat_packet packet, clat_packet_index pos); + +// Returns true iff the given IPv6 address is in the plat subnet. +int is_in_plat_subnet(const struct in6_addr *addr6); + +// Functions to create tun, IPv4, and IPv6 headers. +void fill_tun_header(struct tun_pi *tun_header, uint16_t proto); +void fill_ip_header(struct iphdr *ip_targ, uint16_t payload_len, uint8_t protocol, + const struct ip6_hdr *old_header); +void fill_ip6_header(struct ip6_hdr *ip6, uint16_t payload_len, uint8_t protocol, + const struct iphdr *old_header); + +// Translate and send packets. +void translate_packet(int fd, int to_ipv6, const uint8_t *packet, size_t packetsize); + +// Translate IPv4 and IPv6 packets. +int ipv4_packet(clat_packet out, clat_packet_index pos, const uint8_t *packet, size_t len); +int ipv6_packet(clat_packet out, clat_packet_index pos, const uint8_t *packet, size_t len); + +// Deal with fragmented packets. +size_t maybe_fill_frag_header(struct ip6_frag *frag_hdr, struct ip6_hdr *ip6_targ, + const struct iphdr *old_header); +uint8_t parse_frag_header(const struct ip6_frag *frag_hdr, struct iphdr *ip_targ); + +// Deal with fragmented packets. +size_t maybe_fill_frag_header(struct ip6_frag *frag_hdr, struct ip6_hdr *ip6_targ, + const struct iphdr *old_header); +uint8_t parse_frag_header(const struct ip6_frag *frag_hdr, struct iphdr *ip_targ); + +// Translate ICMP packets. +int icmp_to_icmp6(clat_packet out, clat_packet_index pos, const struct icmphdr *icmp, + uint32_t checksum, const uint8_t *payload, size_t payload_size); +int icmp6_to_icmp(clat_packet out, clat_packet_index pos, const struct icmp6_hdr *icmp6, + const uint8_t *payload, size_t payload_size); + +// Translate generic IP packets. +int generic_packet(clat_packet out, clat_packet_index pos, const uint8_t *payload, size_t len); + +// Translate TCP and UDP packets. +int tcp_packet(clat_packet out, clat_packet_index pos, const struct tcphdr *tcp, uint32_t old_sum, + uint32_t new_sum, size_t len); +int udp_packet(clat_packet out, clat_packet_index pos, const struct udphdr *udp, uint32_t old_sum, + uint32_t new_sum, size_t len); + +int tcp_translate(clat_packet out, clat_packet_index pos, const struct tcphdr *tcp, + size_t header_size, uint32_t old_sum, uint32_t new_sum, const uint8_t *payload, + size_t payload_size); +int udp_translate(clat_packet out, clat_packet_index pos, const struct udphdr *udp, + uint32_t old_sum, uint32_t new_sum, const uint8_t *payload, size_t payload_size); + +#endif /* __TRANSLATE_H__ */ diff --git a/common/Android.bp b/common/Android.bp index 729ef3250f72e1b5d1f3e342dd9169f6d7f4a23a..1d73a46af585bc2d70713591a5028f13878c7f87 100644 --- a/common/Android.bp +++ b/common/Android.bp @@ -19,13 +19,20 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } +build = ["TrunkStable.bp"] + +// This is a placeholder comment to avoid merge conflicts +// as the above target may not exist +// depending on the branch + +// The library requires the final artifact to contain net-utils-device-common-struct. java_library { name: "connectivity-net-module-utils-bpf", srcs: [ "src/com/android/net/module/util/bpf/*.java", ], sdk_version: "module_current", - min_sdk_version: "29", + min_sdk_version: "30", visibility: [ // Do not add any lib. This library is only shared inside connectivity module // and its tests. @@ -34,8 +41,9 @@ java_library { libs: [ "androidx.annotation_annotation", "framework-connectivity.stubs.module_lib", - ], - static_libs: [ + // For libraries which are statically linked in framework-connectivity, do not + // statically link here because callers of this library might already have a static + // version linked. "net-utils-device-common-struct", ], apex_available: [ diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairManageAccountCallback.aidl b/common/TrunkStable.bp similarity index 64% rename from nearby/framework/java/android/nearby/aidl/IFastPairManageAccountCallback.aidl rename to common/TrunkStable.bp index 6b4aaee0b64b07209d6062700543c35fb84b44c3..59874c216deb142d3a41a09b43ee26052ecdeab2 100644 --- a/nearby/framework/java/android/nearby/aidl/IFastPairManageAccountCallback.aidl +++ b/common/TrunkStable.bp @@ -1,4 +1,5 @@ -// Copyright (C) 2021 The Android Open Source Project +// +// Copyright (C) 2023 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. @@ -11,15 +12,5 @@ // 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.nearby.aidl; - -/** - * Provides callback interface to send response for account management request. - * - * {@hide} - */ -interface IFastPairManageAccountCallback { - void onSuccess(); - void onError(int code, String message); -} \ No newline at end of file diff --git a/common/src/com/android/net/module/util/bpf/IngressDiscardKey.java b/common/src/com/android/net/module/util/bpf/IngressDiscardKey.java new file mode 100644 index 0000000000000000000000000000000000000000..9fefb521d90ce4294cb8abfb32b6ffee5d97c15e --- /dev/null +++ b/common/src/com/android/net/module/util/bpf/IngressDiscardKey.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 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.net.module.util.bpf; + +import com.android.net.module.util.InetAddressUtils; +import com.android.net.module.util.Struct; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; + +/** Key type for ingress discard map */ +public class IngressDiscardKey extends Struct { + // The destination ip of the incoming packet. IPv4 uses IPv4-mapped IPv6 address. + @Field(order = 0, type = Type.Ipv6Address) + public final Inet6Address dstAddr; + + public IngressDiscardKey(final Inet6Address dstAddr) { + this.dstAddr = dstAddr; + } + + private static Inet6Address getInet6Address(final InetAddress addr) { + return (addr instanceof Inet4Address) + ? InetAddressUtils.v4MappedV6Address((Inet4Address) addr) + : (Inet6Address) addr; + } + + public IngressDiscardKey(final InetAddress dstAddr) { + this(getInet6Address(dstAddr)); + } +} diff --git a/common/src/com/android/net/module/util/bpf/IngressDiscardValue.java b/common/src/com/android/net/module/util/bpf/IngressDiscardValue.java new file mode 100644 index 0000000000000000000000000000000000000000..7df3620f41d5cc2d2cfa1d62729e66e686e5eb1a --- /dev/null +++ b/common/src/com/android/net/module/util/bpf/IngressDiscardValue.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 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.net.module.util.bpf; + +import com.android.net.module.util.Struct; + +/** Value type for ingress discard map */ +public class IngressDiscardValue extends Struct { + // Allowed interface indexes. + // Use the same value for iif1 and iif2 if there is only a single allowed interface index. + @Field(order = 0, type = Type.S32) + public final int iif1; + @Field(order = 1, type = Type.S32) + public final int iif2; + + public IngressDiscardValue(final int iif1, final int iif2) { + this.iif1 = iif1; + this.iif2 = iif2; + } +} diff --git a/framework-t/Android.bp b/framework-t/Android.bp index ffa28573349a1e8f4f48420a51d3601e108e3f68..deda74e328d308f89f62d8c343edd6c1e7dd97de 100644 --- a/framework-t/Android.bp +++ b/framework-t/Android.bp @@ -19,6 +19,14 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } +framework_remoteauth_srcs = [":framework-remoteauth-java-sources"] +framework_remoteauth_api_srcs = [] + +java_defaults { + name: "enable-remoteauth-targets", + enabled: true, +} + // Include build rules from Sources.bp build = ["Sources.bp"] @@ -42,6 +50,7 @@ java_defaults { srcs: [ ":framework-connectivity-tiramisu-updatable-sources", ":framework-nearby-java-sources", + ":framework-thread-sources", ], libs: [ "unsupportedappusage", @@ -82,6 +91,20 @@ filegroup { visibility: ["//packages/modules/Connectivity:__subpackages__"], } +// The filegroup lists files that are necessary for verifying building mdns as a standalone, +// for use with service-connectivity-mdns-standalone-build-test +// This filegroup should never be included in anywhere in the module build. It is only used for +// building service-connectivity-mdns-standalone-build-test target. The files will be renamed by +// copybara to prevent them from being shadowed by the bootclasspath copies. +filegroup { + name: "framework-connectivity-t-mdns-standalone-build-sources", + srcs: [ + "src/android/net/nsd/OffloadEngine.java", + "src/android/net/nsd/OffloadServiceInfo.java", + ], + visibility: ["//packages/modules/Connectivity:__subpackages__"], +} + java_library { name: "framework-connectivity-t-pre-jarjar", defaults: ["framework-connectivity-t-defaults"], @@ -141,6 +164,7 @@ java_sdk_library { "//packages/modules/Connectivity/service", // For R8 only "//packages/modules/Connectivity/service-t", "//packages/modules/Connectivity/nearby:__subpackages__", + "//packages/modules/Connectivity/remoteauth:__subpackages__", "//frameworks/base", // Tests using hidden APIs @@ -151,11 +175,11 @@ java_sdk_library { "//frameworks/base/core/tests/benchmarks", "//frameworks/base/core/tests/utillib", "//frameworks/base/tests/vcn", - "//frameworks/libs/net/common/testutils", - "//frameworks/libs/net/common/tests:__subpackages__", "//frameworks/opt/net/ethernet/tests:__subpackages__", "//frameworks/opt/telephony/tests/telephonytests", "//packages/modules/CaptivePortalLogin/tests", + "//packages/modules/Connectivity/staticlibs/testutils", + "//packages/modules/Connectivity/staticlibs/tests:__subpackages__", "//packages/modules/Connectivity/Tethering/tests:__subpackages__", "//packages/modules/Connectivity/tests:__subpackages__", "//packages/modules/IPsec/tests/iketests", diff --git a/framework-t/api/OWNERS b/framework-t/api/OWNERS index de0f905e6865e79bc01041017a95f5e2ad1e19ea..8ef735c1985011d989ab540d78e4ae9d389d7659 100644 --- a/framework-t/api/OWNERS +++ b/framework-t/api/OWNERS @@ -1 +1,3 @@ -file:platform/packages/modules/Connectivity:master:/nearby/OWNERS +file:platform/packages/modules/Connectivity:main:/nearby/OWNERS +file:platform/packages/modules/Connectivity:main:/remoteauth/OWNERS +file:platform/packages/modules/Connectivity:main:/thread/OWNERS diff --git a/framework-t/api/current.txt b/framework-t/api/current.txt index 86745d401b9d58887c5a0b7f98827deed0f117a8..fb46ee7826abf996b9e591765f42f5579e75453b 100644 --- a/framework-t/api/current.txt +++ b/framework-t/api/current.txt @@ -127,6 +127,7 @@ package android.net { public final class IpSecTransform implements java.lang.AutoCloseable { method public void close(); + method @FlaggedApi("com.android.net.flags.ipsec_transform_state") public void getIpSecTransformState(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); } public static class IpSecTransform.Builder { @@ -138,6 +139,29 @@ package android.net { method @NonNull public android.net.IpSecTransform.Builder setIpv4Encapsulation(@NonNull android.net.IpSecManager.UdpEncapsulationSocket, int); } + @FlaggedApi("com.android.net.flags.ipsec_transform_state") public final class IpSecTransformState implements android.os.Parcelable { + method public int describeContents(); + method public long getByteCount(); + method public long getPacketCount(); + method @NonNull public byte[] getReplayBitmap(); + method public long getRxHighestSequenceNumber(); + method public long getTimestamp(); + method public long getTxHighestSequenceNumber(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + @FlaggedApi("com.android.net.flags.ipsec_transform_state") public static final class IpSecTransformState.Builder { + ctor public IpSecTransformState.Builder(); + method @NonNull public android.net.IpSecTransformState build(); + method @NonNull public android.net.IpSecTransformState.Builder setByteCount(long); + method @NonNull public android.net.IpSecTransformState.Builder setPacketCount(long); + method @NonNull public android.net.IpSecTransformState.Builder setReplayBitmap(@NonNull byte[]); + method @NonNull public android.net.IpSecTransformState.Builder setRxHighestSequenceNumber(long); + method @NonNull public android.net.IpSecTransformState.Builder setTimestamp(long); + method @NonNull public android.net.IpSecTransformState.Builder setTxHighestSequenceNumber(long); + } + public class TrafficStats { ctor public TrafficStats(); method public static void clearThreadStatsTag(); diff --git a/framework-t/api/module-lib-lint-baseline.txt b/framework-t/api/module-lib-lint-baseline.txt index 3158bd4596320d5f50a306edad31ba87da2310fd..6f954df640369f18b68d939ee4003770be3ae629 100644 --- a/framework-t/api/module-lib-lint-baseline.txt +++ b/framework-t/api/module-lib-lint-baseline.txt @@ -5,3 +5,17 @@ BannedThrow: android.app.usage.NetworkStatsManager#querySummary(android.net.Netw Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`) BannedThrow: android.app.usage.NetworkStatsManager#queryTaggedSummary(android.net.NetworkTemplate, long, long): Methods must not mention RuntimeException subclasses in throws clauses (was `java.lang.SecurityException`) + + +MissingPermission: android.net.IpSecManager#startTunnelModeTransformMigration(android.net.IpSecTransform, java.net.InetAddress, java.net.InetAddress): + Feature field FEATURE_IPSEC_TUNNEL_MIGRATION required by method android.net.IpSecManager.startTunnelModeTransformMigration(android.net.IpSecTransform, java.net.InetAddress, java.net.InetAddress) is hidden or removed + + +RequiresPermission: android.app.usage.NetworkStatsManager#registerUsageCallback(android.net.NetworkTemplate, long, java.util.concurrent.Executor, android.app.usage.NetworkStatsManager.UsageCallback): + Method 'registerUsageCallback' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.EthernetManager#disableInterface(String, java.util.concurrent.Executor, android.os.OutcomeReceiver): + Method 'disableInterface' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.EthernetManager#enableInterface(String, java.util.concurrent.Executor, android.os.OutcomeReceiver): + Method 'enableInterface' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.EthernetManager#updateConfiguration(String, android.net.EthernetNetworkUpdateRequest, java.util.concurrent.Executor, android.os.OutcomeReceiver): + Method 'updateConfiguration' documentation mentions permissions already declared by @RequiresPermission diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt index 6613ee6b3018684f811f08f4d1785c62df8c9001..35135738394b702cdf3163334025d456019603f3 100644 --- a/framework-t/api/system-current.txt +++ b/framework-t/api/system-current.txt @@ -305,6 +305,7 @@ package android.net { ctor public NetworkStats(long, int); method @NonNull public android.net.NetworkStats add(@NonNull android.net.NetworkStats); method @NonNull public android.net.NetworkStats addEntry(@NonNull android.net.NetworkStats.Entry); + method public android.net.NetworkStats clone(); method public int describeContents(); method @NonNull public java.util.Iterator iterator(); method @NonNull public android.net.NetworkStats subtract(@NonNull android.net.NetworkStats); @@ -374,3 +375,169 @@ package android.net.netstats.provider { } +package android.net.nsd { + + public final class NsdManager { + method @FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api") @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void registerOffloadEngine(@NonNull String, long, long, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.OffloadEngine); + method @FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api") @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void unregisterOffloadEngine(@NonNull android.net.nsd.OffloadEngine); + } + + @FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api") public interface OffloadEngine { + method public void onOffloadServiceRemoved(@NonNull android.net.nsd.OffloadServiceInfo); + method public void onOffloadServiceUpdated(@NonNull android.net.nsd.OffloadServiceInfo); + field public static final int OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK = 1; // 0x1 + field public static final int OFFLOAD_TYPE_FILTER_QUERIES = 2; // 0x2 + field public static final int OFFLOAD_TYPE_FILTER_REPLIES = 4; // 0x4 + field public static final int OFFLOAD_TYPE_REPLY = 1; // 0x1 + } + + @FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api") public final class OffloadServiceInfo implements android.os.Parcelable { + ctor public OffloadServiceInfo(@NonNull android.net.nsd.OffloadServiceInfo.Key, @NonNull java.util.List, @NonNull String, @Nullable byte[], @IntRange(from=0, to=java.lang.Integer.MAX_VALUE) int, long); + method public int describeContents(); + method @NonNull public String getHostname(); + method @NonNull public android.net.nsd.OffloadServiceInfo.Key getKey(); + method @Nullable public byte[] getOffloadPayload(); + method public long getOffloadType(); + method public int getPriority(); + method @NonNull public java.util.List getSubtypes(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class OffloadServiceInfo.Key implements android.os.Parcelable { + ctor public OffloadServiceInfo.Key(@NonNull String, @NonNull String); + method public int describeContents(); + method @NonNull public String getServiceName(); + method @NonNull public String getServiceType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + +} + +package android.net.thread { + + @FlaggedApi("com.android.net.thread.flags.thread_enabled") public final class ActiveOperationalDataset implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public static android.net.thread.ActiveOperationalDataset fromThreadTlvs(@NonNull byte[]); + method @NonNull public android.net.thread.OperationalDatasetTimestamp getActiveTimestamp(); + method @IntRange(from=0, to=65535) public int getChannel(); + method @NonNull @Size(min=1) public android.util.SparseArray getChannelMask(); + method @IntRange(from=0, to=255) public int getChannelPage(); + method @NonNull @Size(android.net.thread.ActiveOperationalDataset.LENGTH_EXTENDED_PAN_ID) public byte[] getExtendedPanId(); + method @NonNull public android.net.IpPrefix getMeshLocalPrefix(); + method @NonNull @Size(android.net.thread.ActiveOperationalDataset.LENGTH_NETWORK_KEY) public byte[] getNetworkKey(); + method @NonNull @Size(min=android.net.thread.ActiveOperationalDataset.LENGTH_MIN_NETWORK_NAME_BYTES, max=android.net.thread.ActiveOperationalDataset.LENGTH_MAX_NETWORK_NAME_BYTES) public String getNetworkName(); + method @IntRange(from=0, to=65534) public int getPanId(); + method @NonNull @Size(android.net.thread.ActiveOperationalDataset.LENGTH_PSKC) public byte[] getPskc(); + method @NonNull public android.net.thread.ActiveOperationalDataset.SecurityPolicy getSecurityPolicy(); + method @NonNull public byte[] toThreadTlvs(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final int CHANNEL_MAX_24_GHZ = 26; // 0x1a + field public static final int CHANNEL_MIN_24_GHZ = 11; // 0xb + field public static final int CHANNEL_PAGE_24_GHZ = 0; // 0x0 + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int LENGTH_EXTENDED_PAN_ID = 8; // 0x8 + field public static final int LENGTH_MAX_DATASET_TLVS = 254; // 0xfe + field public static final int LENGTH_MAX_NETWORK_NAME_BYTES = 16; // 0x10 + field public static final int LENGTH_MESH_LOCAL_PREFIX_BITS = 64; // 0x40 + field public static final int LENGTH_MIN_NETWORK_NAME_BYTES = 1; // 0x1 + field public static final int LENGTH_NETWORK_KEY = 16; // 0x10 + field public static final int LENGTH_PSKC = 16; // 0x10 + } + + public static final class ActiveOperationalDataset.Builder { + ctor public ActiveOperationalDataset.Builder(@NonNull android.net.thread.ActiveOperationalDataset); + ctor public ActiveOperationalDataset.Builder(); + method @NonNull public android.net.thread.ActiveOperationalDataset build(); + method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setActiveTimestamp(@NonNull android.net.thread.OperationalDatasetTimestamp); + method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setChannel(@IntRange(from=0, to=255) int, @IntRange(from=0, to=65535) int); + method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setChannelMask(@NonNull @Size(min=1) android.util.SparseArray); + method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setExtendedPanId(@NonNull @Size(android.net.thread.ActiveOperationalDataset.LENGTH_EXTENDED_PAN_ID) byte[]); + method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setMeshLocalPrefix(@NonNull android.net.IpPrefix); + method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setNetworkKey(@NonNull @Size(android.net.thread.ActiveOperationalDataset.LENGTH_NETWORK_KEY) byte[]); + method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setNetworkName(@NonNull @Size(min=android.net.thread.ActiveOperationalDataset.LENGTH_MIN_NETWORK_NAME_BYTES, max=android.net.thread.ActiveOperationalDataset.LENGTH_MAX_NETWORK_NAME_BYTES) String); + method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setPanId(@IntRange(from=0, to=65534) int); + method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setPskc(@NonNull @Size(android.net.thread.ActiveOperationalDataset.LENGTH_PSKC) byte[]); + method @NonNull public android.net.thread.ActiveOperationalDataset.Builder setSecurityPolicy(@NonNull android.net.thread.ActiveOperationalDataset.SecurityPolicy); + } + + public static final class ActiveOperationalDataset.SecurityPolicy { + ctor public ActiveOperationalDataset.SecurityPolicy(@IntRange(from=1, to=65535) int, @NonNull @Size(min=android.net.thread.ActiveOperationalDataset.SecurityPolicy.LENGTH_MIN_SECURITY_POLICY_FLAGS) byte[]); + method @NonNull @Size(min=android.net.thread.ActiveOperationalDataset.SecurityPolicy.LENGTH_MIN_SECURITY_POLICY_FLAGS) public byte[] getFlags(); + method @IntRange(from=1, to=65535) public int getRotationTimeHours(); + field public static final int DEFAULT_ROTATION_TIME_HOURS = 672; // 0x2a0 + field public static final int LENGTH_MIN_SECURITY_POLICY_FLAGS = 1; // 0x1 + } + + @FlaggedApi("com.android.net.thread.flags.thread_enabled") public final class OperationalDatasetTimestamp { + ctor public OperationalDatasetTimestamp(@IntRange(from=0, to=281474976710655L) long, @IntRange(from=0, to=32767) int, boolean); + method @NonNull public static android.net.thread.OperationalDatasetTimestamp fromInstant(@NonNull java.time.Instant); + method @IntRange(from=0, to=281474976710655L) public long getSeconds(); + method @IntRange(from=0, to=32767) public int getTicks(); + method public boolean isAuthoritativeSource(); + method @NonNull public java.time.Instant toInstant(); + } + + @FlaggedApi("com.android.net.thread.flags.thread_enabled") public final class PendingOperationalDataset implements android.os.Parcelable { + ctor public PendingOperationalDataset(@NonNull android.net.thread.ActiveOperationalDataset, @NonNull android.net.thread.OperationalDatasetTimestamp, @NonNull java.time.Duration); + method public int describeContents(); + method @NonNull public static android.net.thread.PendingOperationalDataset fromThreadTlvs(@NonNull byte[]); + method @NonNull public android.net.thread.ActiveOperationalDataset getActiveOperationalDataset(); + method @NonNull public java.time.Duration getDelayTimer(); + method @NonNull public android.net.thread.OperationalDatasetTimestamp getPendingTimestamp(); + method @NonNull public byte[] toThreadTlvs(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + @FlaggedApi("com.android.net.thread.flags.thread_enabled") public final class ThreadNetworkController { + method public void createRandomizedDataset(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); + method public int getThreadVersion(); + method public static boolean isAttached(int); + method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void join(@NonNull android.net.thread.ActiveOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); + method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void leave(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); + method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void registerOperationalDatasetCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.StateCallback); + method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void scheduleMigration(@NonNull android.net.thread.PendingOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); + method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void unregisterOperationalDatasetCallback(@NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback); + method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void unregisterStateCallback(@NonNull android.net.thread.ThreadNetworkController.StateCallback); + field public static final int DEVICE_ROLE_CHILD = 2; // 0x2 + field public static final int DEVICE_ROLE_DETACHED = 1; // 0x1 + field public static final int DEVICE_ROLE_LEADER = 4; // 0x4 + field public static final int DEVICE_ROLE_ROUTER = 3; // 0x3 + field public static final int DEVICE_ROLE_STOPPED = 0; // 0x0 + field public static final int THREAD_VERSION_1_3 = 4; // 0x4 + } + + public static interface ThreadNetworkController.OperationalDatasetCallback { + method public void onActiveOperationalDatasetChanged(@Nullable android.net.thread.ActiveOperationalDataset); + method public default void onPendingOperationalDatasetChanged(@Nullable android.net.thread.PendingOperationalDataset); + } + + public static interface ThreadNetworkController.StateCallback { + method public void onDeviceRoleChanged(int); + method public default void onPartitionIdChanged(long); + } + + @FlaggedApi("com.android.net.thread.flags.thread_enabled") public class ThreadNetworkException extends java.lang.Exception { + ctor public ThreadNetworkException(int, @NonNull String); + method public int getErrorCode(); + field public static final int ERROR_ABORTED = 2; // 0x2 + field public static final int ERROR_BUSY = 5; // 0x5 + field public static final int ERROR_FAILED_PRECONDITION = 6; // 0x6 + field public static final int ERROR_INTERNAL_ERROR = 1; // 0x1 + field public static final int ERROR_REJECTED_BY_PEER = 8; // 0x8 + field public static final int ERROR_RESOURCE_EXHAUSTED = 10; // 0xa + field public static final int ERROR_RESPONSE_BAD_FORMAT = 9; // 0x9 + field public static final int ERROR_TIMEOUT = 3; // 0x3 + field public static final int ERROR_UNAVAILABLE = 4; // 0x4 + field public static final int ERROR_UNSUPPORTED_CHANNEL = 7; // 0x7 + } + + @FlaggedApi("com.android.net.thread.flags.thread_enabled") public final class ThreadNetworkManager { + method @NonNull public java.util.List getAllThreadNetworkControllers(); + } + +} + diff --git a/framework-t/api/system-lint-baseline.txt b/framework-t/api/system-lint-baseline.txt index 9baf9919446eac3121712d16e8381451947b76f8..4f7af87c7f9eb9bd7250ccafc1b4dfae40c53991 100644 --- a/framework-t/api/system-lint-baseline.txt +++ b/framework-t/api/system-lint-baseline.txt @@ -5,3 +5,161 @@ BuilderSetStyle: android.net.IpSecTransform.Builder#buildTunnelModeTransform(jav GenericException: android.net.IpSecManager.IpSecTunnelInterface#finalize(): Methods must not throw generic exceptions (`java.lang.Throwable`) + + +MissingPermission: android.net.IpSecManager#startTunnelModeTransformMigration(android.net.IpSecTransform, java.net.InetAddress, java.net.InetAddress): + Feature field FEATURE_IPSEC_TUNNEL_MIGRATION required by method android.net.IpSecManager.startTunnelModeTransformMigration(android.net.IpSecTransform, java.net.InetAddress, java.net.InetAddress) is hidden or removed + + +RequiresPermission: android.net.EthernetManager#disableInterface(String, java.util.concurrent.Executor, android.os.OutcomeReceiver): + Method 'disableInterface' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.EthernetManager#enableInterface(String, java.util.concurrent.Executor, android.os.OutcomeReceiver): + Method 'enableInterface' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.EthernetManager#updateConfiguration(String, android.net.EthernetNetworkUpdateRequest, java.util.concurrent.Executor, android.os.OutcomeReceiver): + Method 'updateConfiguration' documentation mentions permissions already declared by @RequiresPermission + + +UnflaggedApi: android.nearby.CredentialElement#equals(Object): + New API must be flagged with @FlaggedApi: method android.nearby.CredentialElement.equals(Object) +UnflaggedApi: android.nearby.CredentialElement#hashCode(): + New API must be flagged with @FlaggedApi: method android.nearby.CredentialElement.hashCode() +UnflaggedApi: android.nearby.DataElement#equals(Object): + New API must be flagged with @FlaggedApi: method android.nearby.DataElement.equals(Object) +UnflaggedApi: android.nearby.DataElement#hashCode(): + New API must be flagged with @FlaggedApi: method android.nearby.DataElement.hashCode() +UnflaggedApi: android.nearby.NearbyDevice#equals(Object): + New API must be flagged with @FlaggedApi: method android.nearby.NearbyDevice.equals(Object) +UnflaggedApi: android.nearby.NearbyDevice#hashCode(): + New API must be flagged with @FlaggedApi: method android.nearby.NearbyDevice.hashCode() +UnflaggedApi: android.nearby.NearbyDevice#toString(): + New API must be flagged with @FlaggedApi: method android.nearby.NearbyDevice.toString() +UnflaggedApi: android.nearby.OffloadCapability#equals(Object): + New API must be flagged with @FlaggedApi: method android.nearby.OffloadCapability.equals(Object) +UnflaggedApi: android.nearby.OffloadCapability#hashCode(): + New API must be flagged with @FlaggedApi: method android.nearby.OffloadCapability.hashCode() +UnflaggedApi: android.nearby.OffloadCapability#toString(): + New API must be flagged with @FlaggedApi: method android.nearby.OffloadCapability.toString() +UnflaggedApi: android.nearby.PresenceCredential#equals(Object): + New API must be flagged with @FlaggedApi: method android.nearby.PresenceCredential.equals(Object) +UnflaggedApi: android.nearby.PresenceCredential#hashCode(): + New API must be flagged with @FlaggedApi: method android.nearby.PresenceCredential.hashCode() +UnflaggedApi: android.nearby.PublicCredential#equals(Object): + New API must be flagged with @FlaggedApi: method android.nearby.PublicCredential.equals(Object) +UnflaggedApi: android.nearby.PublicCredential#hashCode(): + New API must be flagged with @FlaggedApi: method android.nearby.PublicCredential.hashCode() +UnflaggedApi: android.nearby.ScanRequest#equals(Object): + New API must be flagged with @FlaggedApi: method android.nearby.ScanRequest.equals(Object) +UnflaggedApi: android.nearby.ScanRequest#hashCode(): + New API must be flagged with @FlaggedApi: method android.nearby.ScanRequest.hashCode() +UnflaggedApi: android.nearby.ScanRequest#toString(): + New API must be flagged with @FlaggedApi: method android.nearby.ScanRequest.toString() +UnflaggedApi: android.net.EthernetNetworkManagementException#equals(Object): + New API must be flagged with @FlaggedApi: method android.net.EthernetNetworkManagementException.equals(Object) +UnflaggedApi: android.net.EthernetNetworkManagementException#hashCode(): + New API must be flagged with @FlaggedApi: method android.net.EthernetNetworkManagementException.hashCode() +UnflaggedApi: android.net.EthernetNetworkUpdateRequest#equals(Object): + New API must be flagged with @FlaggedApi: method android.net.EthernetNetworkUpdateRequest.equals(Object) +UnflaggedApi: android.net.EthernetNetworkUpdateRequest#hashCode(): + New API must be flagged with @FlaggedApi: method android.net.EthernetNetworkUpdateRequest.hashCode() +UnflaggedApi: android.net.EthernetNetworkUpdateRequest#toString(): + New API must be flagged with @FlaggedApi: method android.net.EthernetNetworkUpdateRequest.toString() +UnflaggedApi: android.net.IpSecManager.IpSecTunnelInterface#finalize(): + New API must be flagged with @FlaggedApi: method android.net.IpSecManager.IpSecTunnelInterface.finalize() +UnflaggedApi: android.net.IpSecManager.IpSecTunnelInterface#toString(): + New API must be flagged with @FlaggedApi: method android.net.IpSecManager.IpSecTunnelInterface.toString() +UnflaggedApi: android.net.IpSecTransform.Builder#buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex): + New API must be flagged with @FlaggedApi: method android.net.IpSecTransform.Builder.buildTunnelModeTransform(java.net.InetAddress,android.net.IpSecManager.SecurityParameterIndex) +UnflaggedApi: android.net.NetworkStats.Entry#toString(): + New API must be flagged with @FlaggedApi: method android.net.NetworkStats.Entry.toString() +UnflaggedApi: android.net.nsd.NsdManager#registerOffloadEngine(String, long, long, java.util.concurrent.Executor, android.net.nsd.OffloadEngine): + New API must be flagged with @FlaggedApi: method android.net.nsd.NsdManager.registerOffloadEngine(String,long,long,java.util.concurrent.Executor,android.net.nsd.OffloadEngine) +UnflaggedApi: android.net.nsd.NsdManager#unregisterOffloadEngine(android.net.nsd.OffloadEngine): + New API must be flagged with @FlaggedApi: method android.net.nsd.NsdManager.unregisterOffloadEngine(android.net.nsd.OffloadEngine) +UnflaggedApi: android.net.nsd.OffloadEngine: + New API must be flagged with @FlaggedApi: class android.net.nsd.OffloadEngine +UnflaggedApi: android.net.nsd.OffloadEngine#OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK: + New API must be flagged with @FlaggedApi: field android.net.nsd.OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK +UnflaggedApi: android.net.nsd.OffloadEngine#OFFLOAD_TYPE_FILTER_QUERIES: + New API must be flagged with @FlaggedApi: field android.net.nsd.OffloadEngine.OFFLOAD_TYPE_FILTER_QUERIES +UnflaggedApi: android.net.nsd.OffloadEngine#OFFLOAD_TYPE_FILTER_REPLIES: + New API must be flagged with @FlaggedApi: field android.net.nsd.OffloadEngine.OFFLOAD_TYPE_FILTER_REPLIES +UnflaggedApi: android.net.nsd.OffloadEngine#OFFLOAD_TYPE_REPLY: + New API must be flagged with @FlaggedApi: field android.net.nsd.OffloadEngine.OFFLOAD_TYPE_REPLY +UnflaggedApi: android.net.nsd.OffloadEngine#onOffloadServiceRemoved(android.net.nsd.OffloadServiceInfo): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadEngine.onOffloadServiceRemoved(android.net.nsd.OffloadServiceInfo) +UnflaggedApi: android.net.nsd.OffloadEngine#onOffloadServiceUpdated(android.net.nsd.OffloadServiceInfo): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadEngine.onOffloadServiceUpdated(android.net.nsd.OffloadServiceInfo) +UnflaggedApi: android.net.nsd.OffloadServiceInfo: + New API must be flagged with @FlaggedApi: class android.net.nsd.OffloadServiceInfo +UnflaggedApi: android.net.nsd.OffloadServiceInfo#CONTENTS_FILE_DESCRIPTOR: + New API must be flagged with @FlaggedApi: field android.net.nsd.OffloadServiceInfo.CONTENTS_FILE_DESCRIPTOR +UnflaggedApi: android.net.nsd.OffloadServiceInfo#CREATOR: + New API must be flagged with @FlaggedApi: field android.net.nsd.OffloadServiceInfo.CREATOR +UnflaggedApi: android.net.nsd.OffloadServiceInfo#OffloadServiceInfo(android.net.nsd.OffloadServiceInfo.Key, java.util.List, String, byte[], int, long): + New API must be flagged with @FlaggedApi: constructor android.net.nsd.OffloadServiceInfo(android.net.nsd.OffloadServiceInfo.Key,java.util.List,String,byte[],int,long) +UnflaggedApi: android.net.nsd.OffloadServiceInfo#PARCELABLE_STABILITY_LOCAL: + New API must be flagged with @FlaggedApi: field android.net.nsd.OffloadServiceInfo.PARCELABLE_STABILITY_LOCAL +UnflaggedApi: android.net.nsd.OffloadServiceInfo#PARCELABLE_STABILITY_VINTF: + New API must be flagged with @FlaggedApi: field android.net.nsd.OffloadServiceInfo.PARCELABLE_STABILITY_VINTF +UnflaggedApi: android.net.nsd.OffloadServiceInfo#PARCELABLE_WRITE_RETURN_VALUE: + New API must be flagged with @FlaggedApi: field android.net.nsd.OffloadServiceInfo.PARCELABLE_WRITE_RETURN_VALUE +UnflaggedApi: android.net.nsd.OffloadServiceInfo#describeContents(): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.describeContents() +UnflaggedApi: android.net.nsd.OffloadServiceInfo#equals(Object): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.equals(Object) +UnflaggedApi: android.net.nsd.OffloadServiceInfo#getHostname(): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.getHostname() +UnflaggedApi: android.net.nsd.OffloadServiceInfo#getKey(): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.getKey() +UnflaggedApi: android.net.nsd.OffloadServiceInfo#getOffloadPayload(): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.getOffloadPayload() +UnflaggedApi: android.net.nsd.OffloadServiceInfo#getOffloadType(): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.getOffloadType() +UnflaggedApi: android.net.nsd.OffloadServiceInfo#getPriority(): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.getPriority() +UnflaggedApi: android.net.nsd.OffloadServiceInfo#getSubtypes(): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.getSubtypes() +UnflaggedApi: android.net.nsd.OffloadServiceInfo#hashCode(): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.hashCode() +UnflaggedApi: android.net.nsd.OffloadServiceInfo#toString(): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.toString() +UnflaggedApi: android.net.nsd.OffloadServiceInfo#writeToParcel(android.os.Parcel, int): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.writeToParcel(android.os.Parcel,int) +UnflaggedApi: android.net.nsd.OffloadServiceInfo.Key: + New API must be flagged with @FlaggedApi: class android.net.nsd.OffloadServiceInfo.Key +UnflaggedApi: android.net.nsd.OffloadServiceInfo.Key#CONTENTS_FILE_DESCRIPTOR: + New API must be flagged with @FlaggedApi: field android.net.nsd.OffloadServiceInfo.Key.CONTENTS_FILE_DESCRIPTOR +UnflaggedApi: android.net.nsd.OffloadServiceInfo.Key#CREATOR: + New API must be flagged with @FlaggedApi: field android.net.nsd.OffloadServiceInfo.Key.CREATOR +UnflaggedApi: android.net.nsd.OffloadServiceInfo.Key#Key(String, String): + New API must be flagged with @FlaggedApi: constructor android.net.nsd.OffloadServiceInfo.Key(String,String) +UnflaggedApi: android.net.nsd.OffloadServiceInfo.Key#PARCELABLE_STABILITY_LOCAL: + New API must be flagged with @FlaggedApi: field android.net.nsd.OffloadServiceInfo.Key.PARCELABLE_STABILITY_LOCAL +UnflaggedApi: android.net.nsd.OffloadServiceInfo.Key#PARCELABLE_STABILITY_VINTF: + New API must be flagged with @FlaggedApi: field android.net.nsd.OffloadServiceInfo.Key.PARCELABLE_STABILITY_VINTF +UnflaggedApi: android.net.nsd.OffloadServiceInfo.Key#PARCELABLE_WRITE_RETURN_VALUE: + New API must be flagged with @FlaggedApi: field android.net.nsd.OffloadServiceInfo.Key.PARCELABLE_WRITE_RETURN_VALUE +UnflaggedApi: android.net.nsd.OffloadServiceInfo.Key#describeContents(): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.Key.describeContents() +UnflaggedApi: android.net.nsd.OffloadServiceInfo.Key#equals(Object): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.Key.equals(Object) +UnflaggedApi: android.net.nsd.OffloadServiceInfo.Key#getServiceName(): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.Key.getServiceName() +UnflaggedApi: android.net.nsd.OffloadServiceInfo.Key#getServiceType(): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.Key.getServiceType() +UnflaggedApi: android.net.nsd.OffloadServiceInfo.Key#hashCode(): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.Key.hashCode() +UnflaggedApi: android.net.nsd.OffloadServiceInfo.Key#toString(): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.Key.toString() +UnflaggedApi: android.net.nsd.OffloadServiceInfo.Key#writeToParcel(android.os.Parcel, int): + New API must be flagged with @FlaggedApi: method android.net.nsd.OffloadServiceInfo.Key.writeToParcel(android.os.Parcel,int) +UnflaggedApi: android.net.thread.ThreadNetworkController: + New API must be flagged with @FlaggedApi: class android.net.thread.ThreadNetworkController +UnflaggedApi: android.net.thread.ThreadNetworkController#THREAD_VERSION_1_3: + New API must be flagged with @FlaggedApi: field android.net.thread.ThreadNetworkController.THREAD_VERSION_1_3 +UnflaggedApi: android.net.thread.ThreadNetworkController#getThreadVersion(): + New API must be flagged with @FlaggedApi: method android.net.thread.ThreadNetworkController.getThreadVersion() +UnflaggedApi: android.net.thread.ThreadNetworkManager: + New API must be flagged with @FlaggedApi: class android.net.thread.ThreadNetworkManager +UnflaggedApi: android.net.thread.ThreadNetworkManager#getAllThreadNetworkControllers(): + New API must be flagged with @FlaggedApi: method android.net.thread.ThreadNetworkManager.getAllThreadNetworkControllers() diff --git a/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java b/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java index d9c9d7403247fc5976f5715b82dc5e02521d4178..d89964d391e0fea5faab7b35188c2da4ab5eddc2 100644 --- a/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java +++ b/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java @@ -24,6 +24,8 @@ import android.net.mdns.aidl.IMDns; import android.net.nsd.INsdManager; import android.net.nsd.MDnsManager; import android.net.nsd.NsdManager; +import android.net.thread.IThreadNetworkManager; +import android.net.thread.ThreadNetworkManager; /** * Class for performing registration for Connectivity services which are exposed via updatable APIs @@ -89,5 +91,14 @@ public final class ConnectivityFrameworkInitializerTiramisu { return new MDnsManager(service); } ); + + SystemServiceRegistry.registerContextAwareService( + ThreadNetworkManager.SERVICE_NAME, + ThreadNetworkManager.class, + (context, serviceBinder) -> { + IThreadNetworkManager managerService = + IThreadNetworkManager.Stub.asInterface(serviceBinder); + return new ThreadNetworkManager(context, managerService); + }); } } diff --git a/framework-t/src/android/net/EthernetNetworkSpecifier.java b/framework-t/src/android/net/EthernetNetworkSpecifier.java index e4d6e248d48a37c34946ac86ef3d60851509887f..90c0361821e36c4b396b7f17d0a5dbe3133a795c 100644 --- a/framework-t/src/android/net/EthernetNetworkSpecifier.java +++ b/framework-t/src/android/net/EthernetNetworkSpecifier.java @@ -26,8 +26,6 @@ import java.util.Objects; /** * A {@link NetworkSpecifier} used to identify ethernet interfaces. - * - * @see EthernetManager */ public final class EthernetNetworkSpecifier extends NetworkSpecifier implements Parcelable { diff --git a/framework-t/src/android/net/IIpSecService.aidl b/framework-t/src/android/net/IIpSecService.aidl index 88ffd0ea9ec36f5a0b2315e669832495f379644c..f972ab9d2edcbd787bdddfff3f431228d7b3c041 100644 --- a/framework-t/src/android/net/IIpSecService.aidl +++ b/framework-t/src/android/net/IIpSecService.aidl @@ -22,6 +22,7 @@ import android.net.IpSecConfig; import android.net.IpSecUdpEncapResponse; import android.net.IpSecSpiResponse; import android.net.IpSecTransformResponse; +import android.net.IpSecTransformState; import android.net.IpSecTunnelInterfaceResponse; import android.os.Bundle; import android.os.IBinder; @@ -74,6 +75,8 @@ interface IIpSecService void deleteTransform(int transformId); + IpSecTransformState getTransformState(int transformId); + void applyTransportModeTransform( in ParcelFileDescriptor socket, int direction, int transformId); diff --git a/framework-t/src/android/net/IpSecManager.java b/framework-t/src/android/net/IpSecManager.java index 3afa6ef60f7d90d9888b34a552692d09bb8d2d75..3f74e1c3cba40187fe7f25ee6d1d99dcd03b3759 100644 --- a/framework-t/src/android/net/IpSecManager.java +++ b/framework-t/src/android/net/IpSecManager.java @@ -65,6 +65,13 @@ import java.util.Objects; public class IpSecManager { private static final String TAG = "IpSecManager"; + // TODO : remove this class when udc-mainline-prod is abandoned and android.net.flags.Flags is + // available here + /** @hide */ + public static class Flags { + static final String IPSEC_TRANSFORM_STATE = "com.android.net.flags.ipsec_transform_state"; + } + /** * Feature flag to declare the kernel support of updating IPsec SAs. * @@ -1084,6 +1091,12 @@ public class IpSecManager { } } + /** @hide */ + public IpSecTransformState getTransformState(int transformId) + throws IllegalStateException, RemoteException { + return mService.getTransformState(transformId); + } + /** * Construct an instance of IpSecManager within an application context. * diff --git a/framework-t/src/android/net/IpSecTransform.java b/framework-t/src/android/net/IpSecTransform.java index c236b6cb3aeda72461414fc05819ba3121b01042..246a2ddcf35cd84ff6ee987e7b87bd80dffb1697 100644 --- a/framework-t/src/android/net/IpSecTransform.java +++ b/framework-t/src/android/net/IpSecTransform.java @@ -15,8 +15,11 @@ */ package android.net; +import static android.net.IpSecManager.Flags.IPSEC_TRANSFORM_STATE; import static android.net.IpSecManager.INVALID_RESOURCE_ID; +import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,6 +29,8 @@ import android.annotation.SystemApi; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; +import android.os.OutcomeReceiver; +import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; @@ -38,6 +43,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; import java.util.Objects; +import java.util.concurrent.Executor; /** * This class represents a transform, which roughly corresponds to an IPsec Security Association. @@ -200,6 +206,43 @@ public final class IpSecTransform implements AutoCloseable { return mResourceId; } + /** + * Retrieve the current state of this IpSecTransform. + * + * @param executor The {@link Executor} on which to call the supplied callback. + * @param callback Callback that's called after the transform state is ready or when an error + * occurs. + * @see IpSecTransformState + */ + @FlaggedApi(IPSEC_TRANSFORM_STATE) + public void getIpSecTransformState( + @CallbackExecutor @NonNull Executor executor, + @NonNull OutcomeReceiver callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + // TODO: Consider adding check to prevent DDoS attack. + + try { + final IpSecTransformState ipSecTransformState = + getIpSecManager(mContext).getTransformState(mResourceId); + executor.execute( + () -> { + callback.onResult(ipSecTransformState); + }); + } catch (IllegalStateException e) { + executor.execute( + () -> { + callback.onError(e); + }); + } catch (RemoteException e) { + executor.execute( + () -> { + callback.onError(e.rethrowFromSystemServer()); + }); + } + } + /** * A callback class to provide status information regarding a NAT-T keepalive session * diff --git a/nearby/framework/java/android/nearby/aidl/ByteArrayParcel.aidl b/framework-t/src/android/net/IpSecTransformState.aidl similarity index 74% rename from nearby/framework/java/android/nearby/aidl/ByteArrayParcel.aidl rename to framework-t/src/android/net/IpSecTransformState.aidl index 53c73bdf6ee3947704f5cbf16ab4d79afa569d5b..69cce28ee7798f4ad5099027b0b84eb954177ee7 100644 --- a/nearby/framework/java/android/nearby/aidl/ByteArrayParcel.aidl +++ b/framework-t/src/android/net/IpSecTransformState.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2023 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. @@ -14,12 +14,7 @@ * limitations under the License. */ -package android.nearby.aidl; +package android.net; -/** - * This is to support 2D byte arrays. - * {@hide} - */ -parcelable ByteArrayParcel { - byte[] byteArray; -} \ No newline at end of file +/** @hide */ +parcelable IpSecTransformState; \ No newline at end of file diff --git a/framework-t/src/android/net/IpSecTransformState.java b/framework-t/src/android/net/IpSecTransformState.java new file mode 100644 index 0000000000000000000000000000000000000000..b575dd5818900fcf789e26969f684e639769bdea --- /dev/null +++ b/framework-t/src/android/net/IpSecTransformState.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2023 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.net; + +import static android.net.IpSecManager.Flags.IPSEC_TRANSFORM_STATE; + +import static com.android.internal.annotations.VisibleForTesting.Visibility; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.net.module.util.HexDump; + +import java.util.Objects; + +/** + * This class represents a snapshot of the state of an IpSecTransform + * + *

This class provides the current state of an IpSecTransform, enabling link metric analysis by + * the caller. Use cases include understanding transform usage, such as packet and byte counts, as + * well as observing out-of-order delivery by checking the bitmap. Additionally, callers can query + * IpSecTransformStates at two timestamps. By comparing the changes in packet counts and sequence + * numbers, callers can estimate IPsec data loss in the inbound direction. + */ +@FlaggedApi(IPSEC_TRANSFORM_STATE) +public final class IpSecTransformState implements Parcelable { + private final long mTimeStamp; + private final long mTxHighestSequenceNumber; + private final long mRxHighestSequenceNumber; + private final long mPacketCount; + private final long mByteCount; + private final byte[] mReplayBitmap; + + private IpSecTransformState( + long timestamp, + long txHighestSequenceNumber, + long rxHighestSequenceNumber, + long packetCount, + long byteCount, + byte[] replayBitmap) { + mTimeStamp = timestamp; + mTxHighestSequenceNumber = txHighestSequenceNumber; + mRxHighestSequenceNumber = rxHighestSequenceNumber; + mPacketCount = packetCount; + mByteCount = byteCount; + + Objects.requireNonNull(replayBitmap, "replayBitmap is null"); + mReplayBitmap = replayBitmap.clone(); + + validate(); + } + + private void validate() { + Objects.requireNonNull(mReplayBitmap, "mReplayBitmap is null"); + } + + /** + * Deserializes a IpSecTransformState from a PersistableBundle. + * + * @hide + */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public IpSecTransformState(@NonNull Parcel in) { + Objects.requireNonNull(in, "The input PersistableBundle is null"); + mTimeStamp = in.readLong(); + mTxHighestSequenceNumber = in.readLong(); + mRxHighestSequenceNumber = in.readLong(); + mPacketCount = in.readLong(); + mByteCount = in.readLong(); + mReplayBitmap = HexDump.hexStringToByteArray(in.readString()); + + validate(); + } + + // Parcelable methods + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeLong(mTimeStamp); + out.writeLong(mTxHighestSequenceNumber); + out.writeLong(mRxHighestSequenceNumber); + out.writeLong(mPacketCount); + out.writeLong(mByteCount); + out.writeString(HexDump.toHexString(mReplayBitmap)); + } + + @NonNull + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @NonNull + public IpSecTransformState createFromParcel(Parcel in) { + return new IpSecTransformState(in); + } + + @NonNull + public IpSecTransformState[] newArray(int size) { + return new IpSecTransformState[size]; + } + }; + + /** + * Retrieve the epoch timestamp (milliseconds) for when this state was created + * + * @see Builder#setTimestamp(long) + */ + public long getTimestamp() { + return mTimeStamp; + } + + /** + * Retrieve the highest sequence number sent so far + * + * @see Builder#setTxHighestSequenceNumber(long) + */ + public long getTxHighestSequenceNumber() { + return mTxHighestSequenceNumber; + } + + /** + * Retrieve the highest sequence number received so far + * + * @see Builder#setRxHighestSequenceNumber(long) + */ + public long getRxHighestSequenceNumber() { + return mRxHighestSequenceNumber; + } + + /** + * Retrieve the number of packets received AND sent so far + * + * @see Builder#setPacketCount(long) + */ + public long getPacketCount() { + return mPacketCount; + } + + /** + * Retrieve the number of bytes received AND sent so far + * + * @see Builder#setByteCount(long) + */ + public long getByteCount() { + return mByteCount; + } + + /** + * Retrieve the replay bitmap + * + *

This bitmap represents a replay window, allowing the caller to observe out-of-order + * delivery. The last bit represents the highest sequence number received so far and bits for + * the received packets will be marked as true. + * + *

The size of a replay bitmap will never change over the lifetime of an IpSecTransform + * + *

The replay bitmap is solely useful for inbound IpSecTransforms. For outbound + * IpSecTransforms, all bits will be unchecked. + * + * @see Builder#setReplayBitmap(byte[]) + */ + @NonNull + public byte[] getReplayBitmap() { + return mReplayBitmap.clone(); + } + + /** Builder class for testing purposes */ + @FlaggedApi(IPSEC_TRANSFORM_STATE) + public static final class Builder { + private long mTimeStamp; + private long mTxHighestSequenceNumber; + private long mRxHighestSequenceNumber; + private long mPacketCount; + private long mByteCount; + private byte[] mReplayBitmap; + + public Builder() { + mTimeStamp = System.currentTimeMillis(); + } + + /** + * Set the epoch timestamp (milliseconds) for when this state was created + * + * @see IpSecTransformState#getTimestamp() + */ + @NonNull + public Builder setTimestamp(long timeStamp) { + mTimeStamp = timeStamp; + return this; + } + + /** + * Set the highest sequence number sent so far + * + * @see IpSecTransformState#getTxHighestSequenceNumber() + */ + @NonNull + public Builder setTxHighestSequenceNumber(long seqNum) { + mTxHighestSequenceNumber = seqNum; + return this; + } + + /** + * Set the highest sequence number received so far + * + * @see IpSecTransformState#getRxHighestSequenceNumber() + */ + @NonNull + public Builder setRxHighestSequenceNumber(long seqNum) { + mRxHighestSequenceNumber = seqNum; + return this; + } + + /** + * Set the number of packets received AND sent so far + * + * @see IpSecTransformState#getPacketCount() + */ + @NonNull + public Builder setPacketCount(long packetCount) { + mPacketCount = packetCount; + return this; + } + + /** + * Set the number of bytes received AND sent so far + * + * @see IpSecTransformState#getByteCount() + */ + @NonNull + public Builder setByteCount(long byteCount) { + mByteCount = byteCount; + return this; + } + + /** + * Set the replay bitmap + * + * @see IpSecTransformState#getReplayBitmap() + */ + @NonNull + public Builder setReplayBitmap(@NonNull byte[] bitMap) { + mReplayBitmap = bitMap.clone(); + return this; + } + + /** + * Build and validate the IpSecTransformState + * + * @return an immutable IpSecTransformState instance + */ + @NonNull + public IpSecTransformState build() { + return new IpSecTransformState( + mTimeStamp, + mTxHighestSequenceNumber, + mRxHighestSequenceNumber, + mPacketCount, + mByteCount, + mReplayBitmap); + } + } +} diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java index 871996034ffaad915d13ed27bac1b296a718a617..e9a3f5839c3ad8888bbc36260a64d792c029fba7 100644 --- a/framework-t/src/android/net/NetworkStats.java +++ b/framework-t/src/android/net/NetworkStats.java @@ -32,6 +32,7 @@ import android.text.TextUtils; import android.util.SparseBooleanArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.CollectionUtils; import libcore.util.EmptyArray; @@ -46,6 +47,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Function; import java.util.function.Predicate; /** @@ -455,6 +457,41 @@ public final class NetworkStats implements Parcelable, Iterable entry.getTag() != TAG_NONE + ? null : temp.setKeys(entry.getIface(), UID_ALL, + SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL)); + + for (int i = 0; i < mappedStats.size; i++) { + mappedStats.operations[i] = 0L; + } + return mappedStats; } /** * Return total statistics grouped by {@link #uid}; doesn't mutate the * original structure. * @hide + * @deprecated Use {@link #mapKeysNotNull(Function)} instead. */ + @Deprecated public NetworkStats groupedByUid() { - final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); - - final Entry entry = new Entry(); - entry.iface = IFACE_ALL; - entry.set = SET_ALL; - entry.tag = TAG_NONE; - entry.metered = METERED_ALL; - entry.roaming = ROAMING_ALL; - entry.defaultNetwork = DEFAULT_NETWORK_ALL; - - for (int i = 0; i < size; i++) { - // skip specific tags, since already counted in TAG_NONE - if (tag[i] != TAG_NONE) continue; - - entry.uid = uid[i]; - entry.rxBytes = rxBytes[i]; - entry.rxPackets = rxPackets[i]; - entry.txBytes = txBytes[i]; - entry.txPackets = txPackets[i]; - entry.operations = operations[i]; - stats.combineValues(entry); + if (SdkLevel.isAtLeastV()) { + throw new UnsupportedOperationException("groupedByUid is not supported"); } - - return stats; + // Keep backward compatibility where the method filtered out tagged stats. The method used + // to deal with uid snapshot where tagged and non-tagged stats were mixed. And + // this method is also in Android O API list, so it is possible OEM can access it. + final Entry temp = new Entry(); + return this.mapKeysNotNull(entry -> entry.getTag() != TAG_NONE + ? null : temp.setKeys(IFACE_ALL, entry.getUid(), + SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL)); } /** @@ -1291,14 +1311,41 @@ public final class NetworkStats implements Parcelable, Iterable temp.setKeys(IFACE_ALL, entry.getUid(), entry.getSet(), + entry.getTag(), entry.getMetered(), entry.getRoaming(), entry.getDefaultNetwork())); + } + + /** + * Returns a new NetworkStats object where entries are transformed. + * + * Note that because NetworkStats is more akin to a map than to a list, + * the entries will be grouped after they are mapped by the key fields, + * e.g. uid, set, tag, defaultNetwork. + * Only the key returned by the function is used ; values will be forcefully + * copied from the original entry. Entries that map to the same set of keys + * will be added together. + */ + @NonNull + private NetworkStats mapKeysNotNull(@NonNull Function f) { + final NetworkStats ret = new NetworkStats(0, 1); + for (Entry e : this) { + final NetworkStats.Entry transformed = f.apply(e); + if (transformed == null) continue; + if (transformed == e) { + throw new IllegalStateException("A new entry must be created."); + } + transformed.setValues(e.getRxBytes(), e.getRxPackets(), e.getTxBytes(), + e.getTxPackets(), e.getOperations()); + ret.combineValues(transformed); } + return ret; } /** diff --git a/framework-t/src/android/net/NetworkStatsCollection.java b/framework-t/src/android/net/NetworkStatsCollection.java index e23faa4ba8fafaf7658e526b178b843230c58ffa..b6f6dbb1cb55e581c0daf1cd2655f66206cda67a 100644 --- a/framework-t/src/android/net/NetworkStatsCollection.java +++ b/framework-t/src/android/net/NetworkStatsCollection.java @@ -26,11 +26,13 @@ import static android.net.NetworkStats.ROAMING_NO; import static android.net.NetworkStats.ROAMING_YES; import static android.net.NetworkStats.SET_ALL; import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.SET_FOREGROUND; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.NetworkTemplate.MATCH_BLUETOOTH; import static android.net.NetworkTemplate.MATCH_ETHERNET; import static android.net.NetworkTemplate.MATCH_MOBILE; +import static android.net.NetworkTemplate.MATCH_PROXY; import static android.net.NetworkTemplate.MATCH_WIFI; import static android.net.TrafficStats.UID_REMOVED; import static android.text.format.DateUtils.WEEK_IN_MILLIS; @@ -49,6 +51,7 @@ import android.service.NetworkStatsCollectionStatsProto; import android.telephony.SubscriptionPlan; import android.text.format.DateUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.Log; @@ -784,6 +787,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W dumpCheckin(pw, start, end, new NetworkTemplate.Builder(MATCH_WIFI).build(), "wifi"); dumpCheckin(pw, start, end, new NetworkTemplate.Builder(MATCH_ETHERNET).build(), "eth"); dumpCheckin(pw, start, end, new NetworkTemplate.Builder(MATCH_BLUETOOTH).build(), "bt"); + dumpCheckin(pw, start, end, new NetworkTemplate.Builder(MATCH_PROXY).build(), "proxy"); } /** @@ -922,6 +926,100 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W } } + + private static String str(NetworkStatsCollection.Key key) { + StringBuilder sb = new StringBuilder() + .append(key.ident.toString()) + .append(" uid=").append(key.uid); + if (key.set != SET_FOREGROUND) { + sb.append(" set=").append(key.set); + } + if (key.tag != 0) { + sb.append(" tag=").append(key.tag); + } + return sb.toString(); + } + + // The importer will modify some keys when importing them. + // In order to keep the comparison code simple, add such special cases here and simply + // ignore them. This should not impact fidelity much because the start/end checks and the total + // bytes check still need to pass. + private static boolean couldKeyChangeOnImport(NetworkStatsCollection.Key key) { + if (key.ident.isEmpty()) return false; + final NetworkIdentity firstIdent = key.ident.iterator().next(); + + // Non-mobile network with non-empty RAT type. + // This combination is invalid and the NetworkIdentity.Builder will throw if it is passed + // in, but it looks like it was previously possible to persist it to disk. The importer sets + // the RAT type to NETWORK_TYPE_ALL. + if (firstIdent.getType() != ConnectivityManager.TYPE_MOBILE + && firstIdent.getRatType() != NetworkTemplate.NETWORK_TYPE_ALL) { + return true; + } + + return false; + } + + /** + * Compare two {@link NetworkStatsCollection} instances and returning a human-readable + * string description of difference for debugging purpose. + * + * @hide + */ + @Nullable + public static String compareStats( + NetworkStatsCollection migrated, NetworkStatsCollection legacy) { + final Map migEntries = + migrated.getEntries(); + final Map legEntries = legacy.getEntries(); + + final ArraySet unmatchedLegKeys = + new ArraySet<>(legEntries.keySet()); + + for (NetworkStatsCollection.Key legKey : legEntries.keySet()) { + final NetworkStatsHistory legHistory = legEntries.get(legKey); + final NetworkStatsHistory migHistory = migEntries.get(legKey); + + if (migHistory == null && couldKeyChangeOnImport(legKey)) { + unmatchedLegKeys.remove(legKey); + continue; + } + + if (migHistory == null) { + return "Missing migrated history for legacy key " + str(legKey) + + ", legacy history was " + legHistory; + } + if (!migHistory.isSameAs(legHistory)) { + return "Difference in history for key " + legKey + "; legacy history " + legHistory + + ", migrated history " + migHistory; + } + unmatchedLegKeys.remove(legKey); + } + + if (!unmatchedLegKeys.isEmpty()) { + final NetworkStatsHistory first = legEntries.get(unmatchedLegKeys.valueAt(0)); + return "Found unmatched legacy keys: count=" + unmatchedLegKeys.size() + + ", first unmatched collection " + first; + } + + if (migrated.getStartMillis() != legacy.getStartMillis() + || migrated.getEndMillis() != legacy.getEndMillis()) { + return "Start / end of the collections " + + migrated.getStartMillis() + "/" + legacy.getStartMillis() + " and " + + migrated.getEndMillis() + "/" + legacy.getEndMillis() + + " don't match"; + } + + if (migrated.getTotalBytes() != legacy.getTotalBytes()) { + return "Total bytes " + migrated.getTotalBytes() + " and " + legacy.getTotalBytes() + + " don't match for collections with start/end " + + migrated.getStartMillis() + + "/" + legacy.getStartMillis(); + } + + return null; + } + /** * the identifier that associate with the {@link NetworkStatsHistory} object to identify * a certain record in the {@link NetworkStatsCollection} object. diff --git a/framework-t/src/android/net/NetworkTemplate.java b/framework-t/src/android/net/NetworkTemplate.java index 33bd884931fc51b6584f438c82cf48efad452b9b..77b166cc3a74f193de25722cbaa42ebb773915c1 100644 --- a/framework-t/src/android/net/NetworkTemplate.java +++ b/framework-t/src/android/net/NetworkTemplate.java @@ -1170,7 +1170,7 @@ public final class NetworkTemplate implements Parcelable { * @param matchRule the target match rule to be checked. */ private static void assertRequestableMatchRule(final int matchRule) { - if (!isKnownMatchRule(matchRule) || matchRule == MATCH_PROXY) { + if (!isKnownMatchRule(matchRule)) { throw new IllegalArgumentException("Invalid match rule: " + getMatchRuleName(matchRule)); } diff --git a/framework-t/src/android/net/TrafficStats.java b/framework-t/src/android/net/TrafficStats.java index e408cbb42224b25f994da3fbd8806f36078a5bb3..d50908216464b69d5fc2cdb4a8c3f40f4e25bea9 100644 --- a/framework-t/src/android/net/TrafficStats.java +++ b/framework-t/src/android/net/TrafficStats.java @@ -682,33 +682,13 @@ public class TrafficStats { /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static long getMobileTcpRxPackets() { - long total = 0; - for (String iface : getMobileIfaces()) { - long stat = UNSUPPORTED; - try { - stat = getStatsService().getIfaceStats(iface, TYPE_TCP_RX_PACKETS); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - total += addIfSupported(stat); - } - return total; + return UNSUPPORTED; } /** {@hide} */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static long getMobileTcpTxPackets() { - long total = 0; - for (String iface : getMobileIfaces()) { - long stat = UNSUPPORTED; - try { - stat = getStatsService().getIfaceStats(iface, TYPE_TCP_TX_PACKETS); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - total += addIfSupported(stat); - } - return total; + return UNSUPPORTED; } /** @@ -1140,8 +1120,4 @@ public class TrafficStats { public static final int TYPE_TX_BYTES = 2; /** {@hide} */ public static final int TYPE_TX_PACKETS = 3; - /** {@hide} */ - public static final int TYPE_TCP_RX_PACKETS = 4; - /** {@hide} */ - public static final int TYPE_TCP_TX_PACKETS = 5; } diff --git a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl index 55331540c0fdf8ecefac5661b77e9de5fe6d4184..e671db15e84af343862640920b40fdea32706459 100644 --- a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl +++ b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl @@ -17,6 +17,7 @@ package android.net.nsd; import android.net.nsd.INsdManagerCallback; +import android.net.nsd.IOffloadEngine; import android.net.nsd.NsdServiceInfo; import android.os.Messenger; @@ -35,4 +36,6 @@ interface INsdServiceConnector { void stopResolution(int listenerKey); void registerServiceInfoCallback(int listenerKey, in NsdServiceInfo serviceInfo); void unregisterServiceInfoCallback(int listenerKey); + void registerOffloadEngine(String ifaceName, in IOffloadEngine cb, long offloadCapabilities, long offloadType); + void unregisterOffloadEngine(in IOffloadEngine cb); } \ No newline at end of file diff --git a/framework-t/src/android/net/nsd/IOffloadEngine.aidl b/framework-t/src/android/net/nsd/IOffloadEngine.aidl new file mode 100644 index 0000000000000000000000000000000000000000..2af733dd58b1ffe390046582098253cd717622f0 --- /dev/null +++ b/framework-t/src/android/net/nsd/IOffloadEngine.aidl @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2023, 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.net.nsd; + +import android.net.nsd.OffloadServiceInfo; + +/** + * Callbacks from NsdService to inform providers of packet offload. + * + * @hide + */ +oneway interface IOffloadEngine { + void onOffloadServiceUpdated(in OffloadServiceInfo info); + void onOffloadServiceRemoved(in OffloadServiceInfo info); +} diff --git a/framework-t/src/android/net/nsd/MDnsManager.java b/framework-t/src/android/net/nsd/MDnsManager.java index c11e60c49d90db0c0121517dbe94ece03f96d92e..c7ded25ff7c10a13984a2d313aa332bdd6586509 100644 --- a/framework-t/src/android/net/nsd/MDnsManager.java +++ b/framework-t/src/android/net/nsd/MDnsManager.java @@ -51,7 +51,7 @@ public class MDnsManager { public void startDaemon() { try { mMdns.startDaemon(); - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { Log.e(TAG, "Start mdns failed.", e); } } @@ -62,7 +62,7 @@ public class MDnsManager { public void stopDaemon() { try { mMdns.stopDaemon(); - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { Log.e(TAG, "Stop mdns failed.", e); } } @@ -85,7 +85,7 @@ public class MDnsManager { registrationType, port, txtRecord, interfaceIdx); try { mMdns.registerService(info); - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { Log.e(TAG, "Register service failed.", e); return false; } @@ -105,7 +105,7 @@ public class MDnsManager { registrationType, "" /* domainName */, interfaceIdx, NETID_UNSET); try { mMdns.discover(info); - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { Log.e(TAG, "Discover service failed.", e); return false; } @@ -129,7 +129,7 @@ public class MDnsManager { new byte[0] /* txtRecord */, interfaceIdx); try { mMdns.resolve(info); - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { Log.e(TAG, "Resolve service failed.", e); return false; } @@ -149,7 +149,7 @@ public class MDnsManager { "" /* address */, interfaceIdx, NETID_UNSET); try { mMdns.getServiceAddress(info); - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { Log.e(TAG, "Get service address failed.", e); return false; } @@ -165,7 +165,7 @@ public class MDnsManager { public boolean stopOperation(int id) { try { mMdns.stopOperation(id); - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { Log.e(TAG, "Stop operation failed.", e); return false; } @@ -180,7 +180,7 @@ public class MDnsManager { public void registerEventListener(@NonNull IMDnsEventListener listener) { try { mMdns.registerEventListener(listener); - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { Log.e(TAG, "Register listener failed.", e); } } @@ -193,7 +193,7 @@ public class MDnsManager { public void unregisterEventListener(@NonNull IMDnsEventListener listener) { try { mMdns.unregisterEventListener(listener); - } catch (RemoteException | ServiceSpecificException e) { + } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { Log.e(TAG, "Unregister listener failed.", e); } } diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java index 2930cbdf3c00c98d19b25b0a84d426f71a2f054e..bf01a9d633b7f0e7ceabeb33513d1b2a283ae9cc 100644 --- a/framework-t/src/android/net/nsd/NsdManager.java +++ b/framework-t/src/android/net/nsd/NsdManager.java @@ -16,24 +16,29 @@ package android.net.nsd; +import static android.Manifest.permission.NETWORK_SETTINGS; +import static android.Manifest.permission.NETWORK_STACK; +import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_PLATFORM_MDNS_BACKEND; import static android.net.connectivity.ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER; +import android.annotation.FlaggedApi; import android.annotation.IntDef; 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.annotation.SystemService; import android.app.compat.CompatChanges; import android.content.Context; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; +import android.net.ConnectivityThread; import android.net.Network; import android.net.NetworkRequest; import android.os.Handler; -import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.RemoteException; @@ -45,9 +50,11 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.net.module.util.CollectionUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Objects; import java.util.concurrent.Executor; @@ -137,6 +144,14 @@ public final class NsdManager { private static final String TAG = NsdManager.class.getSimpleName(); private static final boolean DBG = false; + // TODO : remove this class when udc-mainline-prod is abandoned and android.net.flags.Flags is + // available here + /** @hide */ + public static class Flags { + static final String REGISTER_NSD_OFFLOAD_ENGINE_API = + "com.android.net.flags.register_nsd_offload_engine_api"; + } + /** * Broadcast intent action to indicate whether network service discovery is * enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state @@ -246,6 +261,10 @@ public final class NsdManager { public static final int UNREGISTER_SERVICE_CALLBACK = 31; /** @hide */ public static final int UNREGISTER_SERVICE_CALLBACK_SUCCEEDED = 32; + /** @hide */ + public static final int REGISTER_OFFLOAD_ENGINE = 33; + /** @hide */ + public static final int UNREGISTER_OFFLOAD_ENGINE = 34; /** Dns based service discovery protocol */ public static final int PROTOCOL_DNS_SD = 0x0001; @@ -313,8 +332,109 @@ public final class NsdManager { private final ArrayMap mPerNetworkDiscoveryMap = new ArrayMap<>(); + @GuardedBy("mOffloadEngines") + private final ArrayList mOffloadEngines = new ArrayList<>(); private final ServiceHandler mHandler; + private static class OffloadEngineProxy extends IOffloadEngine.Stub { + private final Executor mExecutor; + private final OffloadEngine mEngine; + + private OffloadEngineProxy(@NonNull Executor executor, @NonNull OffloadEngine appCb) { + mExecutor = executor; + mEngine = appCb; + } + + @Override + public void onOffloadServiceUpdated(OffloadServiceInfo info) { + mExecutor.execute(() -> mEngine.onOffloadServiceUpdated(info)); + } + + @Override + public void onOffloadServiceRemoved(OffloadServiceInfo info) { + mExecutor.execute(() -> mEngine.onOffloadServiceRemoved(info)); + } + } + + /** + * Registers an OffloadEngine with NsdManager. + * + * A caller can register itself as an OffloadEngine if it supports mDns hardware offload. + * The caller must implement the {@link OffloadEngine} interface and update hardware offload + * state property when the {@link OffloadEngine#onOffloadServiceUpdated} and + * {@link OffloadEngine#onOffloadServiceRemoved} callback are called. Multiple engines may be + * registered for the same interface, and that the same engine cannot be registered twice. + * + * @param ifaceName indicates which network interface the hardware offload runs on + * @param offloadType the type of offload that the offload engine support + * @param offloadCapability the capabilities of the offload engine + * @param executor the executor on which to receive the offload callbacks + * @param engine the OffloadEngine that will receive the offload callbacks + * @throws IllegalStateException if the engine is already registered. + * + * @hide + */ + @FlaggedApi(NsdManager.Flags.REGISTER_NSD_OFFLOAD_ENGINE_API) + @SystemApi + @RequiresPermission(anyOf = {NETWORK_SETTINGS, PERMISSION_MAINLINE_NETWORK_STACK, + NETWORK_STACK}) + public void registerOffloadEngine(@NonNull String ifaceName, + @OffloadEngine.OffloadType long offloadType, + @OffloadEngine.OffloadCapability long offloadCapability, @NonNull Executor executor, + @NonNull OffloadEngine engine) { + Objects.requireNonNull(ifaceName); + Objects.requireNonNull(executor); + Objects.requireNonNull(engine); + final OffloadEngineProxy cbImpl = new OffloadEngineProxy(executor, engine); + synchronized (mOffloadEngines) { + if (CollectionUtils.contains(mOffloadEngines, impl -> impl.mEngine == engine)) { + throw new IllegalStateException("This engine is already registered"); + } + mOffloadEngines.add(cbImpl); + } + try { + mService.registerOffloadEngine(ifaceName, cbImpl, offloadCapability, offloadType); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + + /** + * Unregisters an OffloadEngine from NsdService. + * + * A caller can unregister itself as an OffloadEngine when it doesn't want to receive the + * callback anymore. The OffloadEngine must have been previously registered with the system + * using the {@link NsdManager#registerOffloadEngine} method. + * + * @param engine OffloadEngine object to be removed from NsdService + * @throws IllegalStateException if the engine is not registered. + * + * @hide + */ + @FlaggedApi(NsdManager.Flags.REGISTER_NSD_OFFLOAD_ENGINE_API) + @SystemApi + @RequiresPermission(anyOf = {NETWORK_SETTINGS, PERMISSION_MAINLINE_NETWORK_STACK, + NETWORK_STACK}) + public void unregisterOffloadEngine(@NonNull OffloadEngine engine) { + Objects.requireNonNull(engine); + final OffloadEngineProxy cbImpl; + synchronized (mOffloadEngines) { + final int index = CollectionUtils.indexOf(mOffloadEngines, + impl -> impl.mEngine == engine); + if (index < 0) { + throw new IllegalStateException("This engine is not registered"); + } + cbImpl = mOffloadEngines.remove(index); + } + + try { + mService.unregisterOffloadEngine(cbImpl); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + private class PerNetworkDiscoveryTracker { final String mServiceType; final int mProtocolType; @@ -523,10 +643,9 @@ public final class NsdManager { */ public NsdManager(Context context, INsdManager service) { mContext = context; - - HandlerThread t = new HandlerThread("NsdManager"); - t.start(); - mHandler = new ServiceHandler(t.getLooper()); + // Use a common singleton thread ConnectivityThread to be shared among all nsd tasks. + // Instead of launching separate threads to handle tasks from the various instances. + mHandler = new ServiceHandler(ConnectivityThread.getInstanceLooper()); try { mService = service.connect(new NsdCallbackImpl(mHandler), CompatChanges.isChangeEnabled( diff --git a/framework-t/src/android/net/nsd/OffloadEngine.java b/framework-t/src/android/net/nsd/OffloadEngine.java new file mode 100644 index 0000000000000000000000000000000000000000..901598585e8f291b401b15cd6336bec1e14ef569 --- /dev/null +++ b/framework-t/src/android/net/nsd/OffloadEngine.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 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.net.nsd; + +import android.annotation.FlaggedApi; +import android.annotation.LongDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * OffloadEngine is an interface for mDns hardware offloading. + * + * An offloading engine can interact with the firmware code to instruct the hardware to + * offload some of mDns network traffic before it reached android OS. This can improve the + * power consumption performance of the host system by not always waking up the OS to handle + * the mDns packet when the device is in low power mode. + * + * @hide + */ +@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api") +@SystemApi +public interface OffloadEngine { + /** + * Indicates that the OffloadEngine can generate replies to mDns queries. + * + * @see OffloadServiceInfo#getOffloadPayload() + */ + int OFFLOAD_TYPE_REPLY = 1; + /** + * Indicates that the OffloadEngine can filter and drop mDns queries. + */ + int OFFLOAD_TYPE_FILTER_QUERIES = 1 << 1; + /** + * Indicates that the OffloadEngine can filter and drop mDns replies. It can allow mDns packets + * to be received even when no app holds a {@link android.net.wifi.WifiManager.MulticastLock}. + */ + int OFFLOAD_TYPE_FILTER_REPLIES = 1 << 2; + + /** + * Indicates that the OffloadEngine can bypass multicast lock. + */ + int OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK = 1; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @LongDef(flag = true, prefix = {"OFFLOAD_TYPE"}, value = { + OFFLOAD_TYPE_REPLY, + OFFLOAD_TYPE_FILTER_QUERIES, + OFFLOAD_TYPE_FILTER_REPLIES, + }) + @interface OffloadType {} + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @LongDef(flag = true, prefix = {"OFFLOAD_CAPABILITY"}, value = { + OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK + }) + @interface OffloadCapability {} + + /** + * To be called when the OffloadServiceInfo is added or updated. + * + * @param info The OffloadServiceInfo to add or update. + */ + void onOffloadServiceUpdated(@NonNull OffloadServiceInfo info); + + /** + * To be called when the OffloadServiceInfo is removed. + * + * @param info The OffloadServiceInfo to remove. + */ + void onOffloadServiceRemoved(@NonNull OffloadServiceInfo info); +} diff --git a/framework-t/src/android/net/nsd/OffloadServiceInfo.java b/framework-t/src/android/net/nsd/OffloadServiceInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..98dc83a7db3d6aab3353b8c392ee43d862f973cb --- /dev/null +++ b/framework-t/src/android/net/nsd/OffloadServiceInfo.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2023 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.net.nsd; + +import android.annotation.FlaggedApi; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresApi; +import android.annotation.SystemApi; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.net.module.util.HexDump; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * The OffloadServiceInfo class contains all the necessary information the OffloadEngine needs to + * know about how to offload an mDns service. The OffloadServiceInfo is keyed on + * {@link OffloadServiceInfo.Key} which is a (serviceName, serviceType) pair. + * + * @hide + */ +@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api") +@SystemApi +@RequiresApi(Build.VERSION_CODES.TIRAMISU) +public final class OffloadServiceInfo implements Parcelable { + @NonNull + private final Key mKey; + @NonNull + private final String mHostname; + @NonNull final List mSubtypes; + @Nullable + private final byte[] mOffloadPayload; + private final int mPriority; + private final long mOffloadType; + + /** + * Creates a new OffloadServiceInfo object with the specified parameters. + * + * @param key The key of the service. + * @param subtypes The list of subTypes of the service. + * @param hostname The name of the host offering the service. It is meaningful only when + * offloadType contains OFFLOAD_REPLY. + * @param offloadPayload The raw udp payload for hardware offloading. + * @param priority The priority of the service, @see #getPriority. + * @param offloadType The type of the service offload, @see #getOffloadType. + */ + public OffloadServiceInfo(@NonNull Key key, + @NonNull List subtypes, @NonNull String hostname, + @Nullable byte[] offloadPayload, + @IntRange(from = 0, to = Integer.MAX_VALUE) int priority, + @OffloadEngine.OffloadType long offloadType) { + Objects.requireNonNull(key); + Objects.requireNonNull(subtypes); + Objects.requireNonNull(hostname); + mKey = key; + mSubtypes = subtypes; + mHostname = hostname; + mOffloadPayload = offloadPayload; + mPriority = priority; + mOffloadType = offloadType; + } + + /** + * Creates a new OffloadServiceInfo object from a Parcel. + * + * @param in The Parcel to read the object from. + * + * @hide + */ + public OffloadServiceInfo(@NonNull Parcel in) { + mKey = in.readParcelable(Key.class.getClassLoader(), + Key.class); + mSubtypes = in.createStringArrayList(); + mHostname = in.readString(); + mOffloadPayload = in.createByteArray(); + mPriority = in.readInt(); + mOffloadType = in.readLong(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mKey, flags); + dest.writeStringList(mSubtypes); + dest.writeString(mHostname); + dest.writeByteArray(mOffloadPayload); + dest.writeInt(mPriority); + dest.writeLong(mOffloadType); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + public static final Creator CREATOR = new Creator() { + @Override + public OffloadServiceInfo createFromParcel(Parcel in) { + return new OffloadServiceInfo(in); + } + + @Override + public OffloadServiceInfo[] newArray(int size) { + return new OffloadServiceInfo[size]; + } + }; + + /** + * Get the {@link Key}. + */ + @NonNull + public Key getKey() { + return mKey; + } + + /** + * Get the host name. (e.g. "Android.local" ) + */ + @NonNull + public String getHostname() { + return mHostname; + } + + /** + * Get the service subtypes. (e.g. ["_ann"] ) + */ + @NonNull + public List getSubtypes() { + return Collections.unmodifiableList(mSubtypes); + } + + /** + * Get the raw udp payload that the OffloadEngine can use to directly reply the incoming query. + *

+ * It is null if the OffloadEngine can not handle transmit. The packet must be sent as-is when + * replying to query. + */ + @Nullable + public byte[] getOffloadPayload() { + if (mOffloadPayload == null) { + return null; + } else { + return mOffloadPayload.clone(); + } + } + + /** + * Create a new OffloadServiceInfo with payload updated. + * + * @hide + */ + @NonNull + public OffloadServiceInfo withOffloadPayload(@NonNull byte[] offloadPayload) { + return new OffloadServiceInfo( + this.getKey(), + this.getSubtypes(), + this.getHostname(), + offloadPayload, + this.getPriority(), + this.getOffloadType() + ); + } + + /** + * Get the offloadType. + *

+ * For example, if the {@link com.android.server.NsdService} requests the OffloadEngine to both + * filter the mDNS queries and replies, the {@link #mOffloadType} = + * ({@link OffloadEngine#OFFLOAD_TYPE_FILTER_QUERIES} | + * {@link OffloadEngine#OFFLOAD_TYPE_FILTER_REPLIES}). + */ + @OffloadEngine.OffloadType public long getOffloadType() { + return mOffloadType; + } + + /** + * Get the priority for the OffloadServiceInfo. + *

+ * When OffloadEngine don't have enough resource + * (e.g. not enough memory) to offload all the OffloadServiceInfo. The OffloadServiceInfo + * having lower priority values should be handled by the OffloadEngine first. + */ + public int getPriority() { + return mPriority; + } + + /** + * Only for debug purpose, the string can be long as the raw packet is dump in the string. + */ + @Override + public String toString() { + return String.format( + "OffloadServiceInfo{ mOffloadServiceInfoKey=%s, mHostName=%s, " + + "mOffloadPayload=%s, mPriority=%d, mOffloadType=%d, mSubTypes=%s }", + mKey, + mHostname, HexDump.dumpHexString(mOffloadPayload), mPriority, + mOffloadType, mSubtypes.toString()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof OffloadServiceInfo)) return false; + OffloadServiceInfo that = (OffloadServiceInfo) o; + return mPriority == that.mPriority && mOffloadType == that.mOffloadType + && mKey.equals(that.mKey) + && mHostname.equals( + that.mHostname) && Arrays.equals(mOffloadPayload, + that.mOffloadPayload) + && mSubtypes.equals(that.mSubtypes); + } + + @Override + public int hashCode() { + int result = Objects.hash(mKey, mHostname, mPriority, + mOffloadType, mSubtypes); + result = 31 * result + Arrays.hashCode(mOffloadPayload); + return result; + } + + /** + * The {@link OffloadServiceInfo.Key} is the (serviceName, serviceType) pair. + */ + public static final class Key implements Parcelable { + @NonNull + private final String mServiceName; + @NonNull + private final String mServiceType; + + /** + * Creates a new OffloadServiceInfoKey object with the specified parameters. + * + * @param serviceName The name of the service. + * @param serviceType The type of the service. + */ + public Key(@NonNull String serviceName, @NonNull String serviceType) { + Objects.requireNonNull(serviceName); + Objects.requireNonNull(serviceType); + mServiceName = serviceName; + mServiceType = serviceType; + } + + /** + * Creates a new OffloadServiceInfoKey object from a Parcel. + * + * @param in The Parcel to read the object from. + * + * @hide + */ + public Key(@NonNull Parcel in) { + mServiceName = in.readString(); + mServiceType = in.readString(); + } + /** + * Get the service name. (e.g. "NsdChat") + */ + @NonNull + public String getServiceName() { + return mServiceName; + } + + /** + * Get the service type. (e.g. "_http._tcp.local" ) + */ + @NonNull + public String getServiceType() { + return mServiceType; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mServiceName); + dest.writeString(mServiceType); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + public static final Creator CREATOR = + new Creator() { + @Override + public Key createFromParcel(Parcel in) { + return new Key(in); + } + + @Override + public Key[] newArray(int size) { + return new Key[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Key)) return false; + Key that = (Key) o; + return Objects.equals(mServiceName, that.mServiceName) && Objects.equals( + mServiceType, that.mServiceType); + } + + @Override + public int hashCode() { + return Objects.hash(mServiceName, mServiceType); + } + + @Override + public String toString() { + return String.format("OffloadServiceInfoKey{ mServiceName=%s, mServiceType=%s }", + mServiceName, mServiceType); + } + } +} diff --git a/framework/Android.bp b/framework/Android.bp index d7eaf9b6a45d223bc1e3b8a6f22fe59bdcb2e896..1e6262d655b6a87bf4b4ceb58399796e38dbeb64 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -80,17 +80,22 @@ java_defaults { impl_only_libs: [ // TODO: figure out why just using "framework-tethering" uses the stubs, even though both // framework-connectivity and framework-tethering are in the same APEX. + "framework-location.stubs.module_lib", "framework-tethering.impl", "framework-wifi.stubs.module_lib", - "net-utils-device-common", ], static_libs: [ - "mdns_aidl_interface-lateststable-java", + // Not using the latest stable version because all functions in the latest version of + // mdns_aidl_interface are deprecated. + "mdns_aidl_interface-V1-java", "modules-utils-backgroundthread", "modules-utils-build", "modules-utils-preconditions", "framework-connectivity-javastream-protos", ], + impl_only_static_libs: [ + "net-utils-device-common-bpf", + ], libs: [ "androidx.annotation_annotation", "app-compat-annotations", @@ -107,19 +112,43 @@ java_library { name: "framework-connectivity-pre-jarjar", defaults: [ "framework-connectivity-defaults", - "CronetJavaPrejarjarDefaults", - ], + ], + static_libs: [ + "httpclient_api", + "httpclient_impl", + "http_client_logging", + // Framework-connectivity-pre-jarjar is identical to framework-connectivity + // implementation, but without the jarjar rules. However, framework-connectivity + // is not based on framework-connectivity-pre-jarjar, it's rebuilt from source + // to generate the SDK stubs. + // Even if the library is included in "impl_only_static_libs" of defaults. This is still + // needed because java_library which doesn't understand "impl_only_static_libs". + "net-utils-device-common-bpf", + ], libs: [ // This cannot be in the defaults clause above because if it were, it would be used // to generate the connectivity stubs. That would create a circular dependency // because the tethering impl depend on the connectivity stubs (e.g., // TetheringRequest depends on LinkAddress). + "framework-location.stubs.module_lib", "framework-tethering.impl", "framework-wifi.stubs.module_lib", ], visibility: ["//packages/modules/Connectivity:__subpackages__"] } +java_defaults { + name: "CronetJavaDefaults", + srcs: [":httpclient_api_sources"], + libs: [ + "androidx.annotation_annotation", + ], + impl_only_static_libs: [ + "httpclient_impl", + "http_client_logging", + ], +} + java_sdk_library { name: "framework-connectivity", defaults: [ @@ -148,11 +177,11 @@ java_sdk_library { "//frameworks/base/core/tests/benchmarks", "//frameworks/base/core/tests/utillib", "//frameworks/base/tests/vcn", - "//frameworks/libs/net/common/testutils", - "//frameworks/libs/net/common/tests:__subpackages__", "//frameworks/opt/net/ethernet/tests:__subpackages__", "//frameworks/opt/telephony/tests/telephonytests", "//packages/modules/CaptivePortalLogin/tests", + "//packages/modules/Connectivity/staticlibs/testutils", + "//packages/modules/Connectivity/staticlibs/tests:__subpackages__", "//packages/modules/Connectivity/Cronet/tests:__subpackages__", "//packages/modules/Connectivity/Tethering/tests:__subpackages__", "//packages/modules/Connectivity/tests:__subpackages__", @@ -229,6 +258,7 @@ java_genrule { ":framework-connectivity-t-pre-jarjar{.jar}", ":framework-connectivity.stubs.module_lib{.jar}", ":framework-connectivity-t.stubs.module_lib{.jar}", + ":framework-connectivity-module-api-stubs-including-flagged{.jar}", "jarjar-excludes.txt", ], tools: [ @@ -241,6 +271,7 @@ java_genrule { "--prefix android.net.connectivity " + "--apistubs $(location :framework-connectivity.stubs.module_lib{.jar}) " + "--apistubs $(location :framework-connectivity-t.stubs.module_lib{.jar}) " + + "--apistubs $(location :framework-connectivity-module-api-stubs-including-flagged{.jar}) " + // Make a ":"-separated list. There will be an extra ":" but empty items are ignored. "--unsupportedapi $$(printf ':%s' $(locations :connectivity-hiddenapi-files)) " + "--excludes $(location jarjar-excludes.txt) " + @@ -252,21 +283,51 @@ java_genrule { ], } +droidstubs { + name: "framework-connectivity-module-api-stubs-including-flagged-droidstubs", + srcs: [ + ":framework-connectivity-sources", + ":framework-connectivity-tiramisu-updatable-sources", + ":framework-nearby-java-sources", + ":framework-thread-sources", + ], + flags: [ + "--show-for-stub-purposes-annotation android.annotation.SystemApi" + + "\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)", + "--show-for-stub-purposes-annotation android.annotation.SystemApi" + + "\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES\\)", + ], + aidl: { + include_dirs: [ + "packages/modules/Connectivity/framework/aidl-export", + "frameworks/native/aidl/binder", // For PersistableBundle.aidl + ], + }, +} + +java_library { + name: "framework-connectivity-module-api-stubs-including-flagged", + srcs: [":framework-connectivity-module-api-stubs-including-flagged-droidstubs"], +} + // Library providing limited APIs within the connectivity module, so that R+ components like // Tethering have a controlled way to depend on newer components like framework-connectivity that // are not loaded on R. +// Note that this target needs to have access to hidden classes, and as such needs to list +// the full libraries instead of the .impl lib (which only expose API classes). java_library { name: "connectivity-internal-api-util", sdk_version: "module_current", libs: [ "androidx.annotation_annotation", - "framework-connectivity.impl", + "framework-connectivity-pre-jarjar", ], jarjar_rules: ":framework-connectivity-jarjar-rules", srcs: [ - // Files listed here MUST all be annotated with @RequiresApi(Build.VERSION_CODES.TIRAMISU), - // so that API checks are enforced for R+ users of this library - "src/android/net/connectivity/TiramisuConnectivityInternalApiUtil.java", + // Files listed here MUST all be annotated with @RequiresApi(Build.VERSION_CODES.S) + // or above as appropriate so that API checks are enforced for R+ users of this library + "src/android/net/RoutingCoordinatorManager.java", + "src/android/net/connectivity/ConnectivityInternalApiUtil.java", ], visibility: [ "//packages/modules/Connectivity/Tethering:__subpackages__", diff --git a/nearby/framework/java/android/nearby/PairStatusMetadata.aidl b/framework/aidl-export/android/net/LocalNetworkConfig.aidl similarity index 73% rename from nearby/framework/java/android/nearby/PairStatusMetadata.aidl rename to framework/aidl-export/android/net/LocalNetworkConfig.aidl index 911a30089f25188dcfb0fe58a3a5a9a989181544..e2829a5a41c61b5b6ee1be2c3f97edd8ea833cf2 100644 --- a/nearby/framework/java/android/nearby/PairStatusMetadata.aidl +++ b/framework/aidl-export/android/net/LocalNetworkConfig.aidl @@ -1,5 +1,6 @@ -/* - * Copyright (C) 2022, The Android Open Source Project +/** + * + * Copyright (C) 2023 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. @@ -14,11 +15,6 @@ * limitations under the License. */ -package android.nearby; +package android.net; -/** - * Metadata about an ongoing paring. Wraps transient data like status and progress. - * - * @hide - */ -parcelable PairStatusMetadata; +@JavaOnlyStableParcelable parcelable LocalNetworkConfig; diff --git a/framework/aidl-export/android/net/LocalNetworkInfo.aidl b/framework/aidl-export/android/net/LocalNetworkInfo.aidl new file mode 100644 index 0000000000000000000000000000000000000000..fa0bc41ed38789675521e85ad2a29f2a345715c0 --- /dev/null +++ b/framework/aidl-export/android/net/LocalNetworkInfo.aidl @@ -0,0 +1,20 @@ +/** + * + * Copyright (C) 2023 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.net; + +parcelable LocalNetworkInfo; diff --git a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothManagerCallback.java b/framework/aidl-export/android/net/nsd/OffloadServiceInfo.aidl similarity index 73% rename from nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothManagerCallback.java rename to framework/aidl-export/android/net/nsd/OffloadServiceInfo.aidl index f39b82f302af2e5fec7d22d27dd08830cf1aac78..aa7aef24fc467516e2379e593da2f4b856ab36e3 100644 --- a/nearby/tests/robotests/src/com/android/libraries/testing/deviceshadower/fakes/android/bluetooth/IBluetoothManagerCallback.java +++ b/framework/aidl-export/android/net/nsd/OffloadServiceInfo.aidl @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Android Open Source Project + * Copyright (C) 2023 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. @@ -14,12 +14,6 @@ * limitations under the License. */ -package android.bluetooth; - -/** - * Fake interface replacement for hidden IBluetoothManagerCallback class - */ -public interface IBluetoothManagerCallback { - -} +package android.net.nsd; +@JavaOnlyStableParcelable parcelable OffloadServiceInfo; \ No newline at end of file diff --git a/framework/api/lint-baseline.txt b/framework/api/lint-baseline.txt index 2f4004ab723d08cac3e5b930c936685927365181..4465bcbd6c7f864def4724074a1a1e620281afb8 100644 --- a/framework/api/lint-baseline.txt +++ b/framework/api/lint-baseline.txt @@ -1,4 +1,19 @@ // Baseline format: 1.0 +BroadcastBehavior: android.net.ConnectivityManager#ACTION_BACKGROUND_DATA_SETTING_CHANGED: + Field 'ACTION_BACKGROUND_DATA_SETTING_CHANGED' is missing @BroadcastBehavior + + +RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.app.PendingIntent): + Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback): + Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.NetworkCapabilities#getOwnerUid(): + Method 'getOwnerUid' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.http.BidirectionalStream.Builder#setTrafficStatsUid(int): + Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.http.UrlRequest.Builder#setTrafficStatsUid(int): + Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission + + VisiblySynchronized: android.net.NetworkInfo#toString(): Internal locks must not be exposed (synchronizing on this or class is still - externally observable): method android.net.NetworkInfo.toString() diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 193bd920d29717157b3d34e90a417684e357ddf8..026d8a951c0f1c68522779cac8dfae46b9559c28 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -14,6 +14,7 @@ package android.net { method @NonNull public static android.util.Range getIpSecNetIdRange(); method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.LinkProperties getRedactedLinkPropertiesForPackage(@NonNull android.net.LinkProperties, int, @NonNull String); method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.NetworkCapabilities getRedactedNetworkCapabilitiesForPackage(@NonNull android.net.NetworkCapabilities, int, @NonNull String); + method @FlaggedApi("com.android.net.flags.support_is_uid_networking_blocked") @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public boolean isUidNetworkingBlocked(int, boolean); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackForUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void removeUidFromMeteredNetworkAllowList(int); @@ -24,6 +25,7 @@ package android.net { method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network); + method @FlaggedApi("com.android.net.flags.set_data_saver_via_cm") @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setDataSaverEnabled(boolean); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setFirewallChainEnabled(int, boolean); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean); @@ -43,6 +45,7 @@ package android.net { field public static final int BLOCKED_METERED_REASON_DATA_SAVER = 65536; // 0x10000 field public static final int BLOCKED_METERED_REASON_MASK = -65536; // 0xffff0000 field public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 131072; // 0x20000 + field @FlaggedApi("com.android.net.flags.basic_background_restrictions_enabled") public static final int BLOCKED_REASON_APP_BACKGROUND = 64; // 0x40 field public static final int BLOCKED_REASON_APP_STANDBY = 4; // 0x4 field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1 field public static final int BLOCKED_REASON_DOZE = 2; // 0x2 @@ -50,6 +53,7 @@ package android.net { field public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 32; // 0x20 field public static final int BLOCKED_REASON_NONE = 0; // 0x0 field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8 + field @FlaggedApi("com.android.net.flags.basic_background_restrictions_enabled") public static final int FIREWALL_CHAIN_BACKGROUND = 6; // 0x6 field public static final int FIREWALL_CHAIN_DOZABLE = 1; // 0x1 field public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5; // 0x5 field public static final int FIREWALL_CHAIN_OEM_DENY_1 = 7; // 0x7 @@ -231,6 +235,7 @@ package android.net { public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo { ctor @Deprecated public VpnTransportInfo(int, @Nullable String); + method public long getApplicableRedactions(); method @Nullable public String getSessionId(); method @NonNull public android.net.VpnTransportInfo makeCopy(long); } diff --git a/framework/api/module-lib-lint-baseline.txt b/framework/api/module-lib-lint-baseline.txt new file mode 100644 index 0000000000000000000000000000000000000000..53a8c5e43bd17aa2f6aa8dea66f2c4d7089e72f1 --- /dev/null +++ b/framework/api/module-lib-lint-baseline.txt @@ -0,0 +1,33 @@ +// Baseline format: 1.0 +BroadcastBehavior: android.net.ConnectivityManager#ACTION_BACKGROUND_DATA_SETTING_CHANGED: + Field 'ACTION_BACKGROUND_DATA_SETTING_CHANGED' is missing @BroadcastBehavior + + +RequiresPermission: android.net.ConnectivityManager#isTetheringSupported(): + Method 'isTetheringSupported' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.app.PendingIntent): + Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback): + Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.ConnectivityManager#requestRouteToHostAddress(int, java.net.InetAddress): + Method 'requestRouteToHostAddress' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.LinkProperties#getCaptivePortalApiUrl(): + Method 'getCaptivePortalApiUrl' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.LinkProperties#getCaptivePortalData(): + Method 'getCaptivePortalData' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.NetworkCapabilities#getOwnerUid(): + Method 'getOwnerUid' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.NetworkCapabilities#getUnderlyingNetworks(): + Method 'getUnderlyingNetworks' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.NetworkCapabilities.Builder#setAllowedUids(java.util.Set): + Method 'setAllowedUids' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.NetworkCapabilities.Builder#setSignalStrength(int): + Method 'setSignalStrength' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.NetworkCapabilities.Builder#setUnderlyingNetworks(java.util.List): + Method 'setUnderlyingNetworks' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.NetworkRequest.Builder#setSignalStrength(int): + Method 'setSignalStrength' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.http.BidirectionalStream.Builder#setTrafficStatsUid(int): + Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.http.UrlRequest.Builder#setTrafficStatsUid(int): + Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 4a2ed8a2a87e58e366fa76570aff61ccd39f1867..e812024a70f716d1a819c02975881fcd629790db 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -94,6 +94,7 @@ package android.net { } public final class DscpPolicy implements android.os.Parcelable { + method public int describeContents(); method @Nullable public java.net.InetAddress getDestinationAddress(); method @Nullable public android.util.Range getDestinationPortRange(); method public int getDscpValue(); @@ -101,6 +102,7 @@ package android.net { method public int getProtocol(); method @Nullable public java.net.InetAddress getSourceAddress(); method public int getSourcePort(); + method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final int PROTOCOL_ANY = -1; // 0xffffffff field public static final int SOURCE_PORT_ANY = -1; // 0xffffffff diff --git a/framework/api/system-lint-baseline.txt b/framework/api/system-lint-baseline.txt index 9a97707763f1187a07e6f3e3aff9247b5a727146..3ac97c004dc1b97ae7174d92f18ea9c373895b46 100644 --- a/framework/api/system-lint-baseline.txt +++ b/framework/api/system-lint-baseline.txt @@ -1 +1,29 @@ // Baseline format: 1.0 +BroadcastBehavior: android.net.ConnectivityManager#ACTION_BACKGROUND_DATA_SETTING_CHANGED: + Field 'ACTION_BACKGROUND_DATA_SETTING_CHANGED' is missing @BroadcastBehavior + + +RequiresPermission: android.net.ConnectivityManager#isTetheringSupported(): + Method 'isTetheringSupported' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.app.PendingIntent): + Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback): + Method 'requestNetwork' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.LinkProperties#getCaptivePortalApiUrl(): + Method 'getCaptivePortalApiUrl' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.LinkProperties#getCaptivePortalData(): + Method 'getCaptivePortalData' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.NetworkCapabilities#getOwnerUid(): + Method 'getOwnerUid' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.NetworkCapabilities#getUnderlyingNetworks(): + Method 'getUnderlyingNetworks' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.NetworkCapabilities.Builder#setSignalStrength(int): + Method 'setSignalStrength' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.NetworkCapabilities.Builder#setUnderlyingNetworks(java.util.List): + Method 'setUnderlyingNetworks' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.NetworkRequest.Builder#setSignalStrength(int): + Method 'setSignalStrength' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.net.http.BidirectionalStream.Builder#setTrafficStatsUid(int): + Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.net.http.UrlRequest.Builder#setTrafficStatsUid(int): + Method 'setTrafficStatsUid' documentation mentions permissions without declaring @RequiresPermission diff --git a/framework/cronet_disabled/api/current.txt b/framework/cronet_disabled/api/current.txt deleted file mode 100644 index 672e3e264fbdd807db44b9b187fce6ae82c5c407..0000000000000000000000000000000000000000 --- a/framework/cronet_disabled/api/current.txt +++ /dev/null @@ -1,527 +0,0 @@ -// Signature format: 2.0 -package android.net { - - public class CaptivePortal implements android.os.Parcelable { - method public int describeContents(); - method public void ignoreNetwork(); - method public void reportCaptivePortalDismissed(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public class ConnectivityDiagnosticsManager { - method public void registerConnectivityDiagnosticsCallback(@NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback); - method public void unregisterConnectivityDiagnosticsCallback(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback); - } - - public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback { - ctor public ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback(); - method public void onConnectivityReportAvailable(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityReport); - method public void onDataStallSuspected(@NonNull android.net.ConnectivityDiagnosticsManager.DataStallReport); - method public void onNetworkConnectivityReported(@NonNull android.net.Network, boolean); - } - - public static final class ConnectivityDiagnosticsManager.ConnectivityReport implements android.os.Parcelable { - ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle); - method public int describeContents(); - method @NonNull public android.os.PersistableBundle getAdditionalInfo(); - method @NonNull public android.net.LinkProperties getLinkProperties(); - method @NonNull public android.net.Network getNetwork(); - method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); - method public long getReportTimestamp(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = "networkProbesAttempted"; - field public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = "networkProbesSucceeded"; - field public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult"; - field public static final int NETWORK_PROBE_DNS = 4; // 0x4 - field public static final int NETWORK_PROBE_FALLBACK = 32; // 0x20 - field public static final int NETWORK_PROBE_HTTP = 8; // 0x8 - field public static final int NETWORK_PROBE_HTTPS = 16; // 0x10 - field public static final int NETWORK_PROBE_PRIVATE_DNS = 64; // 0x40 - field public static final int NETWORK_VALIDATION_RESULT_INVALID = 0; // 0x0 - field public static final int NETWORK_VALIDATION_RESULT_PARTIALLY_VALID = 2; // 0x2 - field public static final int NETWORK_VALIDATION_RESULT_SKIPPED = 3; // 0x3 - field public static final int NETWORK_VALIDATION_RESULT_VALID = 1; // 0x1 - } - - public static final class ConnectivityDiagnosticsManager.DataStallReport implements android.os.Parcelable { - ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle); - method public int describeContents(); - method public int getDetectionMethod(); - method @NonNull public android.net.LinkProperties getLinkProperties(); - method @NonNull public android.net.Network getNetwork(); - method @NonNull public android.net.NetworkCapabilities getNetworkCapabilities(); - method public long getReportTimestamp(); - method @NonNull public android.os.PersistableBundle getStallDetails(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1 - field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2 - field public static final String KEY_DNS_CONSECUTIVE_TIMEOUTS = "dnsConsecutiveTimeouts"; - field public static final String KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS = "tcpMetricsCollectionPeriodMillis"; - field public static final String KEY_TCP_PACKET_FAIL_RATE = "tcpPacketFailRate"; - } - - public class ConnectivityManager { - method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener); - method public boolean bindProcessToNetwork(@Nullable android.net.Network); - method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); - method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork(); - method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo(); - method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo(); - method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks(); - method @Deprecated public boolean getBackgroundDataSetting(); - method @Nullable public android.net.Network getBoundNetworkForProcess(); - method public int getConnectionOwnerUid(int, @NonNull java.net.InetSocketAddress, @NonNull java.net.InetSocketAddress); - method @Nullable public android.net.ProxyInfo getDefaultProxy(); - method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.LinkProperties getLinkProperties(@Nullable android.net.Network); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getMultipathPreference(@Nullable android.net.Network); - method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkCapabilities getNetworkCapabilities(@Nullable android.net.Network); - method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(int); - method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getNetworkInfo(@Nullable android.net.Network); - method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public int getNetworkPreference(); - method @Nullable public byte[] getNetworkWatchlistConfigHash(); - method @Deprecated @Nullable public static android.net.Network getProcessDefaultNetwork(); - method public int getRestrictBackgroundStatus(); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public boolean isActiveNetworkMetered(); - method public boolean isDefaultNetworkActive(); - method @Deprecated public static boolean isNetworkTypeValid(int); - method public void registerBestMatchingNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerNetworkCallback(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent); - method public void releaseNetworkRequest(@NonNull android.app.PendingIntent); - method public void removeDefaultNetworkActiveListener(@NonNull android.net.ConnectivityManager.OnNetworkActiveListener); - method @Deprecated public void reportBadNetwork(@Nullable android.net.Network); - method public void reportNetworkConnectivity(@Nullable android.net.Network, boolean); - method public boolean requestBandwidthUpdate(@NonNull android.net.Network); - method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback); - method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int); - method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler, int); - method public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.app.PendingIntent); - method @Deprecated public void setNetworkPreference(int); - method @Deprecated public static boolean setProcessDefaultNetwork(@Nullable android.net.Network); - method public void unregisterNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback); - method public void unregisterNetworkCallback(@NonNull android.app.PendingIntent); - field @Deprecated public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; - field public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL"; - field public static final String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED"; - field @Deprecated public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; - field @Deprecated public static final int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1 - field public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL"; - field public static final String EXTRA_CAPTIVE_PORTAL_URL = "android.net.extra.CAPTIVE_PORTAL_URL"; - field @Deprecated public static final String EXTRA_EXTRA_INFO = "extraInfo"; - field @Deprecated public static final String EXTRA_IS_FAILOVER = "isFailover"; - field public static final String EXTRA_NETWORK = "android.net.extra.NETWORK"; - field @Deprecated public static final String EXTRA_NETWORK_INFO = "networkInfo"; - field public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST"; - field @Deprecated public static final String EXTRA_NETWORK_TYPE = "networkType"; - field public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity"; - field @Deprecated public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork"; - field public static final String EXTRA_REASON = "reason"; - field public static final int MULTIPATH_PREFERENCE_HANDOVER = 1; // 0x1 - field public static final int MULTIPATH_PREFERENCE_PERFORMANCE = 4; // 0x4 - field public static final int MULTIPATH_PREFERENCE_RELIABILITY = 2; // 0x2 - field public static final int RESTRICT_BACKGROUND_STATUS_DISABLED = 1; // 0x1 - field public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3; // 0x3 - field public static final int RESTRICT_BACKGROUND_STATUS_WHITELISTED = 2; // 0x2 - field @Deprecated public static final int TYPE_BLUETOOTH = 7; // 0x7 - field @Deprecated public static final int TYPE_DUMMY = 8; // 0x8 - field @Deprecated public static final int TYPE_ETHERNET = 9; // 0x9 - field @Deprecated public static final int TYPE_MOBILE = 0; // 0x0 - field @Deprecated public static final int TYPE_MOBILE_DUN = 4; // 0x4 - field @Deprecated public static final int TYPE_MOBILE_HIPRI = 5; // 0x5 - field @Deprecated public static final int TYPE_MOBILE_MMS = 2; // 0x2 - field @Deprecated public static final int TYPE_MOBILE_SUPL = 3; // 0x3 - field @Deprecated public static final int TYPE_VPN = 17; // 0x11 - field @Deprecated public static final int TYPE_WIFI = 1; // 0x1 - field @Deprecated public static final int TYPE_WIMAX = 6; // 0x6 - } - - public static class ConnectivityManager.NetworkCallback { - ctor public ConnectivityManager.NetworkCallback(); - ctor public ConnectivityManager.NetworkCallback(int); - method public void onAvailable(@NonNull android.net.Network); - method public void onBlockedStatusChanged(@NonNull android.net.Network, boolean); - method public void onCapabilitiesChanged(@NonNull android.net.Network, @NonNull android.net.NetworkCapabilities); - method public void onLinkPropertiesChanged(@NonNull android.net.Network, @NonNull android.net.LinkProperties); - method public void onLosing(@NonNull android.net.Network, int); - method public void onLost(@NonNull android.net.Network); - method public void onUnavailable(); - field public static final int FLAG_INCLUDE_LOCATION_INFO = 1; // 0x1 - } - - public static interface ConnectivityManager.OnNetworkActiveListener { - method public void onNetworkActive(); - } - - public class DhcpInfo implements android.os.Parcelable { - ctor public DhcpInfo(); - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field public int dns1; - field public int dns2; - field public int gateway; - field public int ipAddress; - field public int leaseDuration; - field public int netmask; - field public int serverAddress; - } - - public final class DnsResolver { - method @NonNull public static android.net.DnsResolver getInstance(); - method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback>); - method public void query(@Nullable android.net.Network, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback>); - method public void rawQuery(@Nullable android.net.Network, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback); - method public void rawQuery(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull java.util.concurrent.Executor, @Nullable android.os.CancellationSignal, @NonNull android.net.DnsResolver.Callback); - field public static final int CLASS_IN = 1; // 0x1 - field public static final int ERROR_PARSE = 0; // 0x0 - field public static final int ERROR_SYSTEM = 1; // 0x1 - field public static final int FLAG_EMPTY = 0; // 0x0 - field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4 - field public static final int FLAG_NO_CACHE_STORE = 2; // 0x2 - field public static final int FLAG_NO_RETRY = 1; // 0x1 - field public static final int TYPE_A = 1; // 0x1 - field public static final int TYPE_AAAA = 28; // 0x1c - } - - public static interface DnsResolver.Callback { - method public void onAnswer(@NonNull T, int); - method public void onError(@NonNull android.net.DnsResolver.DnsException); - } - - public static class DnsResolver.DnsException extends java.lang.Exception { - ctor public DnsResolver.DnsException(int, @Nullable Throwable); - field public final int code; - } - - public class InetAddresses { - method public static boolean isNumericAddress(@NonNull String); - method @NonNull public static java.net.InetAddress parseNumericAddress(@NonNull String); - } - - public final class IpConfiguration implements android.os.Parcelable { - method public int describeContents(); - method @Nullable public android.net.ProxyInfo getHttpProxy(); - method @Nullable public android.net.StaticIpConfiguration getStaticIpConfiguration(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public static final class IpConfiguration.Builder { - ctor public IpConfiguration.Builder(); - method @NonNull public android.net.IpConfiguration build(); - method @NonNull public android.net.IpConfiguration.Builder setHttpProxy(@Nullable android.net.ProxyInfo); - method @NonNull public android.net.IpConfiguration.Builder setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); - } - - public final class IpPrefix implements android.os.Parcelable { - ctor public IpPrefix(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); - method public boolean contains(@NonNull java.net.InetAddress); - method public int describeContents(); - method @NonNull public java.net.InetAddress getAddress(); - method @IntRange(from=0, to=128) public int getPrefixLength(); - method @NonNull public byte[] getRawAddress(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public class LinkAddress implements android.os.Parcelable { - method public int describeContents(); - method public java.net.InetAddress getAddress(); - method public int getFlags(); - method @IntRange(from=0, to=128) public int getPrefixLength(); - method public int getScope(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public final class LinkProperties implements android.os.Parcelable { - ctor public LinkProperties(); - method public boolean addRoute(@NonNull android.net.RouteInfo); - method public void clear(); - method public int describeContents(); - method @Nullable public java.net.Inet4Address getDhcpServerAddress(); - method @NonNull public java.util.List getDnsServers(); - method @Nullable public String getDomains(); - method @Nullable public android.net.ProxyInfo getHttpProxy(); - method @Nullable public String getInterfaceName(); - method @NonNull public java.util.List getLinkAddresses(); - method public int getMtu(); - method @Nullable public android.net.IpPrefix getNat64Prefix(); - method @Nullable public String getPrivateDnsServerName(); - method @NonNull public java.util.List getRoutes(); - method public boolean isPrivateDnsActive(); - method public boolean isWakeOnLanSupported(); - method public void setDhcpServerAddress(@Nullable java.net.Inet4Address); - method public void setDnsServers(@NonNull java.util.Collection); - method public void setDomains(@Nullable String); - method public void setHttpProxy(@Nullable android.net.ProxyInfo); - method public void setInterfaceName(@Nullable String); - method public void setLinkAddresses(@NonNull java.util.Collection); - method public void setMtu(int); - method public void setNat64Prefix(@Nullable android.net.IpPrefix); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public final class MacAddress implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public static android.net.MacAddress fromBytes(@NonNull byte[]); - method @NonNull public static android.net.MacAddress fromString(@NonNull String); - method public int getAddressType(); - method @Nullable public java.net.Inet6Address getLinkLocalIpv6FromEui48Mac(); - method public boolean isLocallyAssigned(); - method public boolean matches(@NonNull android.net.MacAddress, @NonNull android.net.MacAddress); - method @NonNull public byte[] toByteArray(); - method @NonNull public String toOuiString(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.net.MacAddress BROADCAST_ADDRESS; - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field public static final int TYPE_BROADCAST = 3; // 0x3 - field public static final int TYPE_MULTICAST = 2; // 0x2 - field public static final int TYPE_UNICAST = 1; // 0x1 - } - - public class Network implements android.os.Parcelable { - method public void bindSocket(java.net.DatagramSocket) throws java.io.IOException; - method public void bindSocket(java.net.Socket) throws java.io.IOException; - method public void bindSocket(java.io.FileDescriptor) throws java.io.IOException; - method public int describeContents(); - method public static android.net.Network fromNetworkHandle(long); - method public java.net.InetAddress[] getAllByName(String) throws java.net.UnknownHostException; - method public java.net.InetAddress getByName(String) throws java.net.UnknownHostException; - method public long getNetworkHandle(); - method public javax.net.SocketFactory getSocketFactory(); - method public java.net.URLConnection openConnection(java.net.URL) throws java.io.IOException; - method public java.net.URLConnection openConnection(java.net.URL, java.net.Proxy) throws java.io.IOException; - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public final class NetworkCapabilities implements android.os.Parcelable { - ctor public NetworkCapabilities(); - ctor public NetworkCapabilities(android.net.NetworkCapabilities); - method public int describeContents(); - method @NonNull public int[] getCapabilities(); - method @NonNull public int[] getEnterpriseIds(); - method public int getLinkDownstreamBandwidthKbps(); - method public int getLinkUpstreamBandwidthKbps(); - method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); - method public int getOwnerUid(); - method public int getSignalStrength(); - method @Nullable public android.net.TransportInfo getTransportInfo(); - method public boolean hasCapability(int); - method public boolean hasEnterpriseId(int); - method public boolean hasTransport(int); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17; // 0x11 - field public static final int NET_CAPABILITY_CBS = 5; // 0x5 - field public static final int NET_CAPABILITY_DUN = 2; // 0x2 - field public static final int NET_CAPABILITY_EIMS = 10; // 0xa - field public static final int NET_CAPABILITY_ENTERPRISE = 29; // 0x1d - field public static final int NET_CAPABILITY_FOREGROUND = 19; // 0x13 - field public static final int NET_CAPABILITY_FOTA = 3; // 0x3 - field public static final int NET_CAPABILITY_HEAD_UNIT = 32; // 0x20 - field public static final int NET_CAPABILITY_IA = 7; // 0x7 - field public static final int NET_CAPABILITY_IMS = 4; // 0x4 - field public static final int NET_CAPABILITY_INTERNET = 12; // 0xc - field public static final int NET_CAPABILITY_MCX = 23; // 0x17 - field public static final int NET_CAPABILITY_MMS = 0; // 0x0 - field public static final int NET_CAPABILITY_MMTEL = 33; // 0x21 - field public static final int NET_CAPABILITY_NOT_CONGESTED = 20; // 0x14 - field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb - field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd - field public static final int NET_CAPABILITY_NOT_ROAMING = 18; // 0x12 - field public static final int NET_CAPABILITY_NOT_SUSPENDED = 21; // 0x15 - field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf - field public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35; // 0x23 - field public static final int NET_CAPABILITY_PRIORITIZE_LATENCY = 34; // 0x22 - field public static final int NET_CAPABILITY_RCS = 8; // 0x8 - field public static final int NET_CAPABILITY_SUPL = 1; // 0x1 - field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19 - field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe - field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10 - field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6 - field public static final int NET_CAPABILITY_XCAP = 9; // 0x9 - field public static final int NET_ENTERPRISE_ID_1 = 1; // 0x1 - field public static final int NET_ENTERPRISE_ID_2 = 2; // 0x2 - field public static final int NET_ENTERPRISE_ID_3 = 3; // 0x3 - field public static final int NET_ENTERPRISE_ID_4 = 4; // 0x4 - field public static final int NET_ENTERPRISE_ID_5 = 5; // 0x5 - field public static final int SIGNAL_STRENGTH_UNSPECIFIED = -2147483648; // 0x80000000 - field public static final int TRANSPORT_BLUETOOTH = 2; // 0x2 - field public static final int TRANSPORT_CELLULAR = 0; // 0x0 - field public static final int TRANSPORT_ETHERNET = 3; // 0x3 - field public static final int TRANSPORT_LOWPAN = 6; // 0x6 - field public static final int TRANSPORT_THREAD = 9; // 0x9 - field public static final int TRANSPORT_USB = 8; // 0x8 - field public static final int TRANSPORT_VPN = 4; // 0x4 - field public static final int TRANSPORT_WIFI = 1; // 0x1 - field public static final int TRANSPORT_WIFI_AWARE = 5; // 0x5 - } - - @Deprecated public class NetworkInfo implements android.os.Parcelable { - ctor @Deprecated public NetworkInfo(int, int, @Nullable String, @Nullable String); - method @Deprecated public int describeContents(); - method @Deprecated @NonNull public android.net.NetworkInfo.DetailedState getDetailedState(); - method @Deprecated public String getExtraInfo(); - method @Deprecated public String getReason(); - method @Deprecated public android.net.NetworkInfo.State getState(); - method @Deprecated public int getSubtype(); - method @Deprecated public String getSubtypeName(); - method @Deprecated public int getType(); - method @Deprecated public String getTypeName(); - method @Deprecated public boolean isAvailable(); - method @Deprecated public boolean isConnected(); - method @Deprecated public boolean isConnectedOrConnecting(); - method @Deprecated public boolean isFailover(); - method @Deprecated public boolean isRoaming(); - method @Deprecated public void setDetailedState(@NonNull android.net.NetworkInfo.DetailedState, @Nullable String, @Nullable String); - method @Deprecated public void writeToParcel(android.os.Parcel, int); - field @Deprecated @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - @Deprecated public enum NetworkInfo.DetailedState { - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState AUTHENTICATING; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState BLOCKED; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CAPTIVE_PORTAL_CHECK; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTED; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState CONNECTING; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTED; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState DISCONNECTING; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState FAILED; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState IDLE; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState OBTAINING_IPADDR; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SCANNING; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState SUSPENDED; - enum_constant @Deprecated public static final android.net.NetworkInfo.DetailedState VERIFYING_POOR_LINK; - } - - @Deprecated public enum NetworkInfo.State { - enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTED; - enum_constant @Deprecated public static final android.net.NetworkInfo.State CONNECTING; - enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTED; - enum_constant @Deprecated public static final android.net.NetworkInfo.State DISCONNECTING; - enum_constant @Deprecated public static final android.net.NetworkInfo.State SUSPENDED; - enum_constant @Deprecated public static final android.net.NetworkInfo.State UNKNOWN; - } - - public class NetworkRequest implements android.os.Parcelable { - method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities); - method public int describeContents(); - method @NonNull public int[] getCapabilities(); - method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); - method @NonNull public int[] getTransportTypes(); - method public boolean hasCapability(int); - method public boolean hasTransport(int); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public static class NetworkRequest.Builder { - ctor public NetworkRequest.Builder(); - ctor public NetworkRequest.Builder(@NonNull android.net.NetworkRequest); - method public android.net.NetworkRequest.Builder addCapability(int); - method public android.net.NetworkRequest.Builder addTransportType(int); - method public android.net.NetworkRequest build(); - method @NonNull public android.net.NetworkRequest.Builder clearCapabilities(); - method public android.net.NetworkRequest.Builder removeCapability(int); - method public android.net.NetworkRequest.Builder removeTransportType(int); - method @NonNull public android.net.NetworkRequest.Builder setIncludeOtherUidNetworks(boolean); - method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String); - method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier); - } - - public class ParseException extends java.lang.RuntimeException { - ctor public ParseException(@NonNull String); - ctor public ParseException(@NonNull String, @NonNull Throwable); - field public String response; - } - - public class ProxyInfo implements android.os.Parcelable { - ctor public ProxyInfo(@Nullable android.net.ProxyInfo); - method public static android.net.ProxyInfo buildDirectProxy(String, int); - method public static android.net.ProxyInfo buildDirectProxy(String, int, java.util.List); - method public static android.net.ProxyInfo buildPacProxy(android.net.Uri); - method @NonNull public static android.net.ProxyInfo buildPacProxy(@NonNull android.net.Uri, int); - method public int describeContents(); - method public String[] getExclusionList(); - method public String getHost(); - method public android.net.Uri getPacFileUrl(); - method public int getPort(); - method public boolean isValid(); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public final class RouteInfo implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public android.net.IpPrefix getDestination(); - method @Nullable public java.net.InetAddress getGateway(); - method @Nullable public String getInterface(); - method public int getType(); - method public boolean hasGateway(); - method public boolean isDefaultRoute(); - method public boolean matches(java.net.InetAddress); - method public void writeToParcel(android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field public static final int RTN_THROW = 9; // 0x9 - field public static final int RTN_UNICAST = 1; // 0x1 - field public static final int RTN_UNREACHABLE = 7; // 0x7 - } - - public abstract class SocketKeepalive implements java.lang.AutoCloseable { - method public final void close(); - method public final void start(@IntRange(from=0xa, to=0xe10) int); - method public final void stop(); - field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1 - field public static final int ERROR_INSUFFICIENT_RESOURCES = -32; // 0xffffffe0 - field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8 - field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb - field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9 - field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec - field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea - field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7 - field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6 - field public static final int ERROR_UNSUPPORTED = -30; // 0xffffffe2 - } - - public static class SocketKeepalive.Callback { - ctor public SocketKeepalive.Callback(); - method public void onDataReceived(); - method public void onError(int); - method public void onStarted(); - method public void onStopped(); - } - - public final class StaticIpConfiguration implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public java.util.List getDnsServers(); - method @Nullable public String getDomains(); - method @Nullable public java.net.InetAddress getGateway(); - method @NonNull public android.net.LinkAddress getIpAddress(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public static final class StaticIpConfiguration.Builder { - ctor public StaticIpConfiguration.Builder(); - method @NonNull public android.net.StaticIpConfiguration build(); - method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable); - method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String); - method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress); - method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@NonNull android.net.LinkAddress); - } - - public interface TransportInfo { - } - -} - diff --git a/framework/cronet_disabled/api/lint-baseline.txt b/framework/cronet_disabled/api/lint-baseline.txt deleted file mode 100644 index 2f4004ab723d08cac3e5b930c936685927365181..0000000000000000000000000000000000000000 --- a/framework/cronet_disabled/api/lint-baseline.txt +++ /dev/null @@ -1,4 +0,0 @@ -// Baseline format: 1.0 -VisiblySynchronized: android.net.NetworkInfo#toString(): - Internal locks must not be exposed (synchronizing on this or class is still - externally observable): method android.net.NetworkInfo.toString() diff --git a/framework/cronet_disabled/api/module-lib-current.txt b/framework/cronet_disabled/api/module-lib-current.txt deleted file mode 100644 index 193bd920d29717157b3d34e90a417684e357ddf8..0000000000000000000000000000000000000000 --- a/framework/cronet_disabled/api/module-lib-current.txt +++ /dev/null @@ -1,239 +0,0 @@ -// Signature format: 2.0 -package android.net { - - public final class ConnectivityFrameworkInitializer { - method public static void registerServiceWrappers(); - } - - public class ConnectivityManager { - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void addUidToMeteredNetworkAllowList(int); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void addUidToMeteredNetworkDenyList(int); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void factoryReset(); - method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List getAllNetworkStateSnapshots(); - method @Nullable public android.net.ProxyInfo getGlobalProxy(); - method @NonNull public static android.util.Range getIpSecNetIdRange(); - method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.LinkProperties getRedactedLinkPropertiesForPackage(@NonNull android.net.LinkProperties, int, @NonNull String); - method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.NetworkCapabilities getRedactedNetworkCapabilitiesForPackage(@NonNull android.net.NetworkCapabilities, int, @NonNull String); - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackForUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void removeUidFromMeteredNetworkAllowList(int); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void removeUidFromMeteredNetworkDenyList(int); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void replaceFirewallChain(int, @NonNull int[]); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); - method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setFirewallChainEnabled(int, boolean); - method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo); - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean); - method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable); - method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreferences(@NonNull android.os.UserHandle, @NonNull java.util.List, @Nullable java.util.concurrent.Executor, @Nullable Runnable); - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection>); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setUidFirewallRule(int, int, int); - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setVpnDefaultForUids(@NonNull String, @NonNull java.util.Collection>); - method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void startCaptivePortalApp(@NonNull android.net.Network); - method public void systemReady(); - field public static final String ACTION_CLEAR_DNS_CACHE = "android.net.action.CLEAR_DNS_CACHE"; - field public static final String ACTION_PROMPT_LOST_VALIDATION = "android.net.action.PROMPT_LOST_VALIDATION"; - field public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = "android.net.action.PROMPT_PARTIAL_CONNECTIVITY"; - field public static final String ACTION_PROMPT_UNVALIDATED = "android.net.action.PROMPT_UNVALIDATED"; - field public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 262144; // 0x40000 - field public static final int BLOCKED_METERED_REASON_DATA_SAVER = 65536; // 0x10000 - field public static final int BLOCKED_METERED_REASON_MASK = -65536; // 0xffff0000 - field public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 131072; // 0x20000 - field public static final int BLOCKED_REASON_APP_STANDBY = 4; // 0x4 - field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1 - field public static final int BLOCKED_REASON_DOZE = 2; // 0x2 - field public static final int BLOCKED_REASON_LOCKDOWN_VPN = 16; // 0x10 - field public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 32; // 0x20 - field public static final int BLOCKED_REASON_NONE = 0; // 0x0 - field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8 - field public static final int FIREWALL_CHAIN_DOZABLE = 1; // 0x1 - field public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5; // 0x5 - field public static final int FIREWALL_CHAIN_OEM_DENY_1 = 7; // 0x7 - field public static final int FIREWALL_CHAIN_OEM_DENY_2 = 8; // 0x8 - field public static final int FIREWALL_CHAIN_OEM_DENY_3 = 9; // 0x9 - field public static final int FIREWALL_CHAIN_POWERSAVE = 3; // 0x3 - field public static final int FIREWALL_CHAIN_RESTRICTED = 4; // 0x4 - field public static final int FIREWALL_CHAIN_STANDBY = 2; // 0x2 - field public static final int FIREWALL_RULE_ALLOW = 1; // 0x1 - field public static final int FIREWALL_RULE_DEFAULT = 0; // 0x0 - field public static final int FIREWALL_RULE_DENY = 2; // 0x2 - field public static final int PROFILE_NETWORK_PREFERENCE_DEFAULT = 0; // 0x0 - field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1 - field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING = 3; // 0x3 - field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK = 2; // 0x2 - } - - public static class ConnectivityManager.NetworkCallback { - method public void onBlockedStatusChanged(@NonNull android.net.Network, int); - } - - public class ConnectivitySettingsManager { - method public static void clearGlobalProxy(@NonNull android.content.Context); - method @Nullable public static String getCaptivePortalHttpUrl(@NonNull android.content.Context); - method public static int getCaptivePortalMode(@NonNull android.content.Context, int); - method @NonNull public static java.time.Duration getConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration); - method @NonNull public static android.util.Range getDnsResolverSampleRanges(@NonNull android.content.Context); - method @NonNull public static java.time.Duration getDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration); - method public static int getDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, int); - method @Nullable public static android.net.ProxyInfo getGlobalProxy(@NonNull android.content.Context); - method public static long getIngressRateLimitInBytesPerSecond(@NonNull android.content.Context); - method @NonNull public static java.time.Duration getMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); - method public static boolean getMobileDataAlwaysOn(@NonNull android.content.Context, boolean); - method @NonNull public static java.util.Set getMobileDataPreferredUids(@NonNull android.content.Context); - method public static int getNetworkAvoidBadWifi(@NonNull android.content.Context); - method @Nullable public static String getNetworkMeteredMultipathPreference(@NonNull android.content.Context); - method public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, int); - method @NonNull public static java.time.Duration getNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration); - method @NonNull public static String getPrivateDnsDefaultMode(@NonNull android.content.Context); - method @Nullable public static String getPrivateDnsHostname(@NonNull android.content.Context); - method public static int getPrivateDnsMode(@NonNull android.content.Context); - method @NonNull public static java.util.Set getUidsAllowedOnRestrictedNetworks(@NonNull android.content.Context); - method public static boolean getWifiAlwaysRequested(@NonNull android.content.Context, boolean); - method @NonNull public static java.time.Duration getWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); - method public static void setCaptivePortalHttpUrl(@NonNull android.content.Context, @Nullable String); - method public static void setCaptivePortalMode(@NonNull android.content.Context, int); - method public static void setConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration); - method public static void setDnsResolverSampleRanges(@NonNull android.content.Context, @NonNull android.util.Range); - method public static void setDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration); - method public static void setDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, @IntRange(from=0, to=100) int); - method public static void setGlobalProxy(@NonNull android.content.Context, @NonNull android.net.ProxyInfo); - method public static void setIngressRateLimitInBytesPerSecond(@NonNull android.content.Context, @IntRange(from=-1L, to=4294967295L) long); - method public static void setMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); - method public static void setMobileDataAlwaysOn(@NonNull android.content.Context, boolean); - method public static void setMobileDataPreferredUids(@NonNull android.content.Context, @NonNull java.util.Set); - method public static void setNetworkAvoidBadWifi(@NonNull android.content.Context, int); - method public static void setNetworkMeteredMultipathPreference(@NonNull android.content.Context, @NonNull String); - method public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, @IntRange(from=0) int); - method public static void setNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration); - method public static void setPrivateDnsDefaultMode(@NonNull android.content.Context, @NonNull int); - method public static void setPrivateDnsHostname(@NonNull android.content.Context, @Nullable String); - method public static void setPrivateDnsMode(@NonNull android.content.Context, int); - method public static void setUidsAllowedOnRestrictedNetworks(@NonNull android.content.Context, @NonNull java.util.Set); - method public static void setWifiAlwaysRequested(@NonNull android.content.Context, boolean); - method public static void setWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration); - field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2 - field public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0; // 0x0 - field public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1; // 0x1 - field public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2; // 0x2 - field public static final int NETWORK_AVOID_BAD_WIFI_IGNORE = 0; // 0x0 - field public static final int NETWORK_AVOID_BAD_WIFI_PROMPT = 1; // 0x1 - field public static final int PRIVATE_DNS_MODE_OFF = 1; // 0x1 - field public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2; // 0x2 - field public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3; // 0x3 - } - - public final class DhcpOption implements android.os.Parcelable { - ctor public DhcpOption(byte, @Nullable byte[]); - method public int describeContents(); - method public byte getType(); - method @Nullable public byte[] getValue(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public final class NetworkAgentConfig implements android.os.Parcelable { - method @Nullable public String getSubscriberId(); - method public boolean isBypassableVpn(); - method public boolean isVpnValidationRequired(); - } - - public static final class NetworkAgentConfig.Builder { - method @NonNull public android.net.NetworkAgentConfig.Builder setBypassableVpn(boolean); - method @NonNull public android.net.NetworkAgentConfig.Builder setLocalRoutesExcludedForVpn(boolean); - method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String); - method @NonNull public android.net.NetworkAgentConfig.Builder setVpnRequiresValidation(boolean); - } - - public final class NetworkCapabilities implements android.os.Parcelable { - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public java.util.Set getAllowedUids(); - method @Nullable public java.util.Set> getUids(); - method public boolean hasForbiddenCapability(int); - field public static final long REDACT_ALL = -1L; // 0xffffffffffffffffL - field public static final long REDACT_FOR_ACCESS_FINE_LOCATION = 1L; // 0x1L - field public static final long REDACT_FOR_LOCAL_MAC_ADDRESS = 2L; // 0x2L - field public static final long REDACT_FOR_NETWORK_SETTINGS = 4L; // 0x4L - field public static final long REDACT_NONE = 0L; // 0x0L - field public static final int TRANSPORT_TEST = 7; // 0x7 - } - - public static final class NetworkCapabilities.Builder { - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAllowedUids(@NonNull java.util.Set); - method @NonNull public android.net.NetworkCapabilities.Builder setUids(@Nullable java.util.Set>); - } - - public class NetworkRequest implements android.os.Parcelable { - method @NonNull public int[] getEnterpriseIds(); - method @NonNull public int[] getForbiddenCapabilities(); - method public boolean hasEnterpriseId(int); - method public boolean hasForbiddenCapability(int); - } - - public static class NetworkRequest.Builder { - method @NonNull public android.net.NetworkRequest.Builder addForbiddenCapability(int); - method @NonNull public android.net.NetworkRequest.Builder removeForbiddenCapability(int); - method @NonNull public android.net.NetworkRequest.Builder setUids(@Nullable java.util.Set>); - } - - public final class ProfileNetworkPreference implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public int[] getExcludedUids(); - method @NonNull public int[] getIncludedUids(); - method public int getPreference(); - method public int getPreferenceEnterpriseId(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public static final class ProfileNetworkPreference.Builder { - ctor public ProfileNetworkPreference.Builder(); - method @NonNull public android.net.ProfileNetworkPreference build(); - method @NonNull public android.net.ProfileNetworkPreference.Builder setExcludedUids(@NonNull int[]); - method @NonNull public android.net.ProfileNetworkPreference.Builder setIncludedUids(@NonNull int[]); - method @NonNull public android.net.ProfileNetworkPreference.Builder setPreference(int); - method @NonNull public android.net.ProfileNetworkPreference.Builder setPreferenceEnterpriseId(int); - } - - public final class TestNetworkInterface implements android.os.Parcelable { - ctor public TestNetworkInterface(@NonNull android.os.ParcelFileDescriptor, @NonNull String); - method public int describeContents(); - method @NonNull public android.os.ParcelFileDescriptor getFileDescriptor(); - method @NonNull public String getInterfaceName(); - method @Nullable public android.net.MacAddress getMacAddress(); - method public int getMtu(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public class TestNetworkManager { - method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public android.net.TestNetworkInterface createTapInterface(); - method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public android.net.TestNetworkInterface createTunInterface(@NonNull java.util.Collection); - method @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public void setupTestNetwork(@NonNull String, @NonNull android.os.IBinder); - method @RequiresPermission(android.Manifest.permission.MANAGE_TEST_NETWORKS) public void teardownTestNetwork(@NonNull android.net.Network); - field public static final String TEST_TAP_PREFIX = "testtap"; - } - - public final class TestNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable { - ctor public TestNetworkSpecifier(@NonNull String); - method public int describeContents(); - method @Nullable public String getInterfaceName(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public interface TransportInfo { - method public default long getApplicableRedactions(); - method @NonNull public default android.net.TransportInfo makeCopy(long); - } - - public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo { - ctor @Deprecated public VpnTransportInfo(int, @Nullable String); - method @Nullable public String getSessionId(); - method @NonNull public android.net.VpnTransportInfo makeCopy(long); - } - -} - diff --git a/framework/cronet_disabled/api/module-lib-removed.txt b/framework/cronet_disabled/api/module-lib-removed.txt deleted file mode 100644 index d802177e249b3f97128699222e65c35e57ba7540..0000000000000000000000000000000000000000 --- a/framework/cronet_disabled/api/module-lib-removed.txt +++ /dev/null @@ -1 +0,0 @@ -// Signature format: 2.0 diff --git a/framework/cronet_disabled/api/removed.txt b/framework/cronet_disabled/api/removed.txt deleted file mode 100644 index 303a1e6173baf7a73d57b8b75e850eed386a161a..0000000000000000000000000000000000000000 --- a/framework/cronet_disabled/api/removed.txt +++ /dev/null @@ -1,11 +0,0 @@ -// Signature format: 2.0 -package android.net { - - public class ConnectivityManager { - method @Deprecated public boolean requestRouteToHost(int, int); - method @Deprecated public int startUsingNetworkFeature(int, String); - method @Deprecated public int stopUsingNetworkFeature(int, String); - } - -} - diff --git a/framework/cronet_disabled/api/system-current.txt b/framework/cronet_disabled/api/system-current.txt deleted file mode 100644 index 4a2ed8a2a87e58e366fa76570aff61ccd39f1867..0000000000000000000000000000000000000000 --- a/framework/cronet_disabled/api/system-current.txt +++ /dev/null @@ -1,544 +0,0 @@ -// Signature format: 2.0 -package android.net { - - public class CaptivePortal implements android.os.Parcelable { - method @Deprecated public void logEvent(int, @NonNull String); - method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void reevaluateNetwork(); - method public void useNetwork(); - field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64 - field public static final int APP_RETURN_DISMISSED = 0; // 0x0 - field public static final int APP_RETURN_UNWANTED = 1; // 0x1 - field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2 - } - - public final class CaptivePortalData implements android.os.Parcelable { - method public int describeContents(); - method public long getByteLimit(); - method public long getExpiryTimeMillis(); - method public long getRefreshTimeMillis(); - method @Nullable public android.net.Uri getUserPortalUrl(); - method public int getUserPortalUrlSource(); - method @Nullable public CharSequence getVenueFriendlyName(); - method @Nullable public android.net.Uri getVenueInfoUrl(); - method public int getVenueInfoUrlSource(); - method public boolean isCaptive(); - method public boolean isSessionExtendable(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field public static final int CAPTIVE_PORTAL_DATA_SOURCE_OTHER = 0; // 0x0 - field public static final int CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT = 1; // 0x1 - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public static class CaptivePortalData.Builder { - ctor public CaptivePortalData.Builder(); - ctor public CaptivePortalData.Builder(@Nullable android.net.CaptivePortalData); - method @NonNull public android.net.CaptivePortalData build(); - method @NonNull public android.net.CaptivePortalData.Builder setBytesRemaining(long); - method @NonNull public android.net.CaptivePortalData.Builder setCaptive(boolean); - method @NonNull public android.net.CaptivePortalData.Builder setExpiryTime(long); - method @NonNull public android.net.CaptivePortalData.Builder setRefreshTime(long); - method @NonNull public android.net.CaptivePortalData.Builder setSessionExtendable(boolean); - method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri); - method @NonNull public android.net.CaptivePortalData.Builder setUserPortalUrl(@Nullable android.net.Uri, int); - method @NonNull public android.net.CaptivePortalData.Builder setVenueFriendlyName(@Nullable CharSequence); - method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri); - method @NonNull public android.net.CaptivePortalData.Builder setVenueInfoUrl(@Nullable android.net.Uri, int); - } - - public class ConnectivityManager { - method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull android.os.ParcelFileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); - method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); - method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl(); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener); - method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public int registerNetworkProvider(@NonNull android.net.NetworkProvider); - method public void registerQosCallback(@NonNull android.net.QosSocketInfo, @NonNull java.util.concurrent.Executor, @NonNull android.net.QosCallback); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback); - method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void requestNetwork(@NonNull android.net.NetworkRequest, int, int, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback); - method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean); - method @RequiresPermission(android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE) public void setOemNetworkPreference(@NonNull android.net.OemNetworkPreferences, @Nullable java.util.concurrent.Executor, @Nullable Runnable); - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi(); - method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int); - method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider); - method public void unregisterQosCallback(@NonNull android.net.QosCallback); - method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback); - field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC"; - field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT"; - field public static final int TETHERING_BLUETOOTH = 2; // 0x2 - field public static final int TETHERING_USB = 1; // 0x1 - field public static final int TETHERING_WIFI = 0; // 0x0 - field @Deprecated public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd - field @Deprecated public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 - field @Deprecated public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb - field public static final int TYPE_NONE = -1; // 0xffffffff - field @Deprecated public static final int TYPE_PROXY = 16; // 0x10 - field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd - } - - @Deprecated public abstract static class ConnectivityManager.OnStartTetheringCallback { - ctor @Deprecated public ConnectivityManager.OnStartTetheringCallback(); - method @Deprecated public void onTetheringFailed(); - method @Deprecated public void onTetheringStarted(); - } - - @Deprecated public static interface ConnectivityManager.OnTetheringEntitlementResultListener { - method @Deprecated public void onTetheringEntitlementResult(int); - } - - @Deprecated public abstract static class ConnectivityManager.OnTetheringEventCallback { - ctor @Deprecated public ConnectivityManager.OnTetheringEventCallback(); - method @Deprecated public void onUpstreamChanged(@Nullable android.net.Network); - } - - public final class DscpPolicy implements android.os.Parcelable { - method @Nullable public java.net.InetAddress getDestinationAddress(); - method @Nullable public android.util.Range getDestinationPortRange(); - method public int getDscpValue(); - method public int getPolicyId(); - method public int getProtocol(); - method @Nullable public java.net.InetAddress getSourceAddress(); - method public int getSourcePort(); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field public static final int PROTOCOL_ANY = -1; // 0xffffffff - field public static final int SOURCE_PORT_ANY = -1; // 0xffffffff - } - - public static final class DscpPolicy.Builder { - ctor public DscpPolicy.Builder(int, int); - method @NonNull public android.net.DscpPolicy build(); - method @NonNull public android.net.DscpPolicy.Builder setDestinationAddress(@NonNull java.net.InetAddress); - method @NonNull public android.net.DscpPolicy.Builder setDestinationPortRange(@NonNull android.util.Range); - method @NonNull public android.net.DscpPolicy.Builder setProtocol(int); - method @NonNull public android.net.DscpPolicy.Builder setSourceAddress(@NonNull java.net.InetAddress); - method @NonNull public android.net.DscpPolicy.Builder setSourcePort(int); - } - - public final class InvalidPacketException extends java.lang.Exception { - ctor public InvalidPacketException(int); - method public int getError(); - field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb - field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9 - field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea - } - - public final class IpConfiguration implements android.os.Parcelable { - ctor public IpConfiguration(); - ctor public IpConfiguration(@NonNull android.net.IpConfiguration); - method @NonNull public android.net.IpConfiguration.IpAssignment getIpAssignment(); - method @NonNull public android.net.IpConfiguration.ProxySettings getProxySettings(); - method public void setHttpProxy(@Nullable android.net.ProxyInfo); - method public void setIpAssignment(@NonNull android.net.IpConfiguration.IpAssignment); - method public void setProxySettings(@NonNull android.net.IpConfiguration.ProxySettings); - method public void setStaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); - } - - public enum IpConfiguration.IpAssignment { - enum_constant public static final android.net.IpConfiguration.IpAssignment DHCP; - enum_constant public static final android.net.IpConfiguration.IpAssignment STATIC; - enum_constant public static final android.net.IpConfiguration.IpAssignment UNASSIGNED; - } - - public enum IpConfiguration.ProxySettings { - enum_constant public static final android.net.IpConfiguration.ProxySettings NONE; - enum_constant public static final android.net.IpConfiguration.ProxySettings PAC; - enum_constant public static final android.net.IpConfiguration.ProxySettings STATIC; - enum_constant public static final android.net.IpConfiguration.ProxySettings UNASSIGNED; - } - - public final class IpPrefix implements android.os.Parcelable { - ctor public IpPrefix(@NonNull String); - } - - public class KeepalivePacketData { - ctor protected KeepalivePacketData(@NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull java.net.InetAddress, @IntRange(from=0, to=65535) int, @NonNull byte[]) throws android.net.InvalidPacketException; - method @NonNull public java.net.InetAddress getDstAddress(); - method public int getDstPort(); - method @NonNull public byte[] getPacket(); - method @NonNull public java.net.InetAddress getSrcAddress(); - method public int getSrcPort(); - } - - public class LinkAddress implements android.os.Parcelable { - ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int); - ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int, int, int, long, long); - ctor public LinkAddress(@NonNull java.net.InetAddress, @IntRange(from=0, to=128) int); - ctor public LinkAddress(@NonNull String); - ctor public LinkAddress(@NonNull String, int, int); - method public long getDeprecationTime(); - method public long getExpirationTime(); - method public boolean isGlobalPreferred(); - method public boolean isIpv4(); - method public boolean isIpv6(); - method public boolean isSameAddressAs(@Nullable android.net.LinkAddress); - field public static final long LIFETIME_PERMANENT = 9223372036854775807L; // 0x7fffffffffffffffL - field public static final long LIFETIME_UNKNOWN = -1L; // 0xffffffffffffffffL - } - - public final class LinkProperties implements android.os.Parcelable { - ctor public LinkProperties(@Nullable android.net.LinkProperties); - ctor public LinkProperties(@Nullable android.net.LinkProperties, boolean); - method public boolean addDnsServer(@NonNull java.net.InetAddress); - method public boolean addLinkAddress(@NonNull android.net.LinkAddress); - method public boolean addPcscfServer(@NonNull java.net.InetAddress); - method @NonNull public java.util.List getAddresses(); - method @NonNull public java.util.List getAllInterfaceNames(); - method @NonNull public java.util.List getAllLinkAddresses(); - method @NonNull public java.util.List getAllRoutes(); - method @Nullable public android.net.Uri getCaptivePortalApiUrl(); - method @Nullable public android.net.CaptivePortalData getCaptivePortalData(); - method @NonNull public java.util.List getPcscfServers(); - method @Nullable public String getTcpBufferSizes(); - method @NonNull public java.util.List getValidatedPrivateDnsServers(); - method public boolean hasGlobalIpv6Address(); - method public boolean hasIpv4Address(); - method public boolean hasIpv4DefaultRoute(); - method public boolean hasIpv4DnsServer(); - method public boolean hasIpv6DefaultRoute(); - method public boolean hasIpv6DnsServer(); - method public boolean isIpv4Provisioned(); - method public boolean isIpv6Provisioned(); - method public boolean isProvisioned(); - method public boolean isReachable(@NonNull java.net.InetAddress); - method public boolean removeDnsServer(@NonNull java.net.InetAddress); - method public boolean removeLinkAddress(@NonNull android.net.LinkAddress); - method public boolean removeRoute(@NonNull android.net.RouteInfo); - method public void setCaptivePortalApiUrl(@Nullable android.net.Uri); - method public void setCaptivePortalData(@Nullable android.net.CaptivePortalData); - method public void setPcscfServers(@NonNull java.util.Collection); - method public void setPrivateDnsServerName(@Nullable String); - method public void setTcpBufferSizes(@Nullable String); - method public void setUsePrivateDns(boolean); - method public void setValidatedPrivateDnsServers(@NonNull java.util.Collection); - } - - public final class NattKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { - ctor public NattKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException; - method public int describeContents(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public class Network implements android.os.Parcelable { - ctor public Network(@NonNull android.net.Network); - method public int getNetId(); - method @NonNull public android.net.Network getPrivateDnsBypassingCopy(); - } - - public abstract class NetworkAgent { - ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, int, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider); - ctor public NetworkAgent(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String, @NonNull android.net.NetworkCapabilities, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkScore, @NonNull android.net.NetworkAgentConfig, @Nullable android.net.NetworkProvider); - method @Nullable public android.net.Network getNetwork(); - method public void markConnected(); - method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData); - method public void onAutomaticReconnectDisabled(); - method public void onBandwidthUpdateRequested(); - method public void onDscpPolicyStatusUpdated(int, int); - method public void onNetworkCreated(); - method public void onNetworkDestroyed(); - method public void onNetworkUnwanted(); - method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter); - method public void onQosCallbackUnregistered(int); - method public void onRemoveKeepalivePacketFilter(int); - method public void onSaveAcceptUnvalidated(boolean); - method public void onSignalStrengthThresholdsUpdated(@NonNull int[]); - method public void onStartSocketKeepalive(int, @NonNull java.time.Duration, @NonNull android.net.KeepalivePacketData); - method public void onStopSocketKeepalive(int); - method public void onValidationStatus(int, @Nullable android.net.Uri); - method @NonNull public android.net.Network register(); - method public void sendAddDscpPolicy(@NonNull android.net.DscpPolicy); - method public void sendLinkProperties(@NonNull android.net.LinkProperties); - method public void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities); - method public void sendNetworkScore(@NonNull android.net.NetworkScore); - method public void sendNetworkScore(@IntRange(from=0, to=99) int); - method public final void sendQosCallbackError(int, int); - method public final void sendQosSessionAvailable(int, int, @NonNull android.net.QosSessionAttributes); - method public final void sendQosSessionLost(int, int, int); - method public void sendRemoveAllDscpPolicies(); - method public void sendRemoveDscpPolicy(int); - method public final void sendSocketKeepaliveEvent(int, int); - method @Deprecated public void setLegacySubtype(int, @NonNull String); - method public void setLingerDuration(@NonNull java.time.Duration); - method public void setTeardownDelayMillis(@IntRange(from=0, to=0x1388) int); - method public void setUnderlyingNetworks(@Nullable java.util.List); - method public void unregister(); - method public void unregisterAfterReplacement(@IntRange(from=0, to=0x1388) int); - field public static final int DSCP_POLICY_STATUS_DELETED = 4; // 0x4 - field public static final int DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES = 3; // 0x3 - field public static final int DSCP_POLICY_STATUS_POLICY_NOT_FOUND = 5; // 0x5 - field public static final int DSCP_POLICY_STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED = 2; // 0x2 - field public static final int DSCP_POLICY_STATUS_REQUEST_DECLINED = 1; // 0x1 - field public static final int DSCP_POLICY_STATUS_SUCCESS = 0; // 0x0 - field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2 - field public static final int VALIDATION_STATUS_VALID = 1; // 0x1 - } - - public final class NetworkAgentConfig implements android.os.Parcelable { - method public int describeContents(); - method public int getLegacyType(); - method @NonNull public String getLegacyTypeName(); - method public boolean isExplicitlySelected(); - method public boolean isPartialConnectivityAcceptable(); - method public boolean isUnvalidatedConnectivityAcceptable(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public static final class NetworkAgentConfig.Builder { - ctor public NetworkAgentConfig.Builder(); - method @NonNull public android.net.NetworkAgentConfig build(); - method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean); - method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyExtraInfo(@NonNull String); - method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubType(int); - method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubTypeName(@NonNull String); - method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int); - method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String); - method @NonNull public android.net.NetworkAgentConfig.Builder setNat64DetectionEnabled(boolean); - method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean); - method @NonNull public android.net.NetworkAgentConfig.Builder setProvisioningNotificationEnabled(boolean); - method @NonNull public android.net.NetworkAgentConfig.Builder setUnvalidatedConnectivityAcceptable(boolean); - } - - public final class NetworkCapabilities implements android.os.Parcelable { - method @NonNull public int[] getAdministratorUids(); - method @Nullable public static String getCapabilityCarrierName(int); - method @Nullable public String getSsid(); - method @NonNull public java.util.Set getSubscriptionIds(); - method @NonNull public int[] getTransportTypes(); - method @Nullable public java.util.List getUnderlyingNetworks(); - method public boolean isPrivateDnsBroken(); - method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities); - field public static final int NET_CAPABILITY_BIP = 31; // 0x1f - field public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28; // 0x1c - field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16 - field public static final int NET_CAPABILITY_OEM_PRIVATE = 26; // 0x1a - field public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24; // 0x18 - field public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27; // 0x1b - field public static final int NET_CAPABILITY_VSIM = 30; // 0x1e - } - - public static final class NetworkCapabilities.Builder { - ctor public NetworkCapabilities.Builder(); - ctor public NetworkCapabilities.Builder(@NonNull android.net.NetworkCapabilities); - method @NonNull public android.net.NetworkCapabilities.Builder addCapability(int); - method @NonNull public android.net.NetworkCapabilities.Builder addEnterpriseId(int); - method @NonNull public android.net.NetworkCapabilities.Builder addTransportType(int); - method @NonNull public android.net.NetworkCapabilities build(); - method @NonNull public android.net.NetworkCapabilities.Builder removeCapability(int); - method @NonNull public android.net.NetworkCapabilities.Builder removeEnterpriseId(int); - method @NonNull public android.net.NetworkCapabilities.Builder removeTransportType(int); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setAdministratorUids(@NonNull int[]); - method @NonNull public android.net.NetworkCapabilities.Builder setLinkDownstreamBandwidthKbps(int); - method @NonNull public android.net.NetworkCapabilities.Builder setLinkUpstreamBandwidthKbps(int); - method @NonNull public android.net.NetworkCapabilities.Builder setNetworkSpecifier(@Nullable android.net.NetworkSpecifier); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setOwnerUid(int); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorPackageName(@Nullable String); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setRequestorUid(int); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkCapabilities.Builder setSignalStrength(int); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setSsid(@Nullable String); - method @NonNull public android.net.NetworkCapabilities.Builder setSubscriptionIds(@NonNull java.util.Set); - method @NonNull public android.net.NetworkCapabilities.Builder setTransportInfo(@Nullable android.net.TransportInfo); - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public android.net.NetworkCapabilities.Builder setUnderlyingNetworks(@Nullable java.util.List); - method @NonNull public static android.net.NetworkCapabilities.Builder withoutDefaultCapabilities(); - } - - public class NetworkProvider { - ctor public NetworkProvider(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String); - method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void declareNetworkRequestUnfulfillable(@NonNull android.net.NetworkRequest); - method public int getProviderId(); - method public void onNetworkRequestWithdrawn(@NonNull android.net.NetworkRequest); - method public void onNetworkRequested(@NonNull android.net.NetworkRequest, @IntRange(from=0, to=99) int, int); - method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void registerNetworkOffer(@NonNull android.net.NetworkScore, @NonNull android.net.NetworkCapabilities, @NonNull java.util.concurrent.Executor, @NonNull android.net.NetworkProvider.NetworkOfferCallback); - method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void unregisterNetworkOffer(@NonNull android.net.NetworkProvider.NetworkOfferCallback); - field public static final int ID_NONE = -1; // 0xffffffff - } - - public static interface NetworkProvider.NetworkOfferCallback { - method public void onNetworkNeeded(@NonNull android.net.NetworkRequest); - method public void onNetworkUnneeded(@NonNull android.net.NetworkRequest); - } - - public class NetworkReleasedException extends java.lang.Exception { - ctor public NetworkReleasedException(); - } - - public class NetworkRequest implements android.os.Parcelable { - method @Nullable public String getRequestorPackageName(); - method public int getRequestorUid(); - } - - public static class NetworkRequest.Builder { - method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int); - method @NonNull public android.net.NetworkRequest.Builder setSubscriptionIds(@NonNull java.util.Set); - } - - public final class NetworkScore implements android.os.Parcelable { - method public int describeContents(); - method public int getKeepConnectedReason(); - method public int getLegacyInt(); - method public boolean isExiting(); - method public boolean isTransportPrimary(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field public static final int KEEP_CONNECTED_FOR_HANDOVER = 1; // 0x1 - field public static final int KEEP_CONNECTED_NONE = 0; // 0x0 - } - - public static final class NetworkScore.Builder { - ctor public NetworkScore.Builder(); - method @NonNull public android.net.NetworkScore build(); - method @NonNull public android.net.NetworkScore.Builder setExiting(boolean); - method @NonNull public android.net.NetworkScore.Builder setKeepConnectedReason(int); - method @NonNull public android.net.NetworkScore.Builder setLegacyInt(int); - method @NonNull public android.net.NetworkScore.Builder setTransportPrimary(boolean); - } - - public final class OemNetworkPreferences implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public java.util.Map getNetworkPreferences(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID = 1; // 0x1 - field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2; // 0x2 - field public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY = 3; // 0x3 - field public static final int OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY = 4; // 0x4 - field public static final int OEM_NETWORK_PREFERENCE_UNINITIALIZED = 0; // 0x0 - } - - public static final class OemNetworkPreferences.Builder { - ctor public OemNetworkPreferences.Builder(); - ctor public OemNetworkPreferences.Builder(@NonNull android.net.OemNetworkPreferences); - method @NonNull public android.net.OemNetworkPreferences.Builder addNetworkPreference(@NonNull String, int); - method @NonNull public android.net.OemNetworkPreferences build(); - method @NonNull public android.net.OemNetworkPreferences.Builder clearNetworkPreference(@NonNull String); - } - - public abstract class QosCallback { - ctor public QosCallback(); - method public void onError(@NonNull android.net.QosCallbackException); - method public void onQosSessionAvailable(@NonNull android.net.QosSession, @NonNull android.net.QosSessionAttributes); - method public void onQosSessionLost(@NonNull android.net.QosSession); - } - - public static class QosCallback.QosCallbackRegistrationException extends java.lang.RuntimeException { - } - - public final class QosCallbackException extends java.lang.Exception { - ctor public QosCallbackException(@NonNull String); - ctor public QosCallbackException(@NonNull Throwable); - } - - public abstract class QosFilter { - method @NonNull public abstract android.net.Network getNetwork(); - method public abstract boolean matchesLocalAddress(@NonNull java.net.InetAddress, int, int); - method public boolean matchesProtocol(int); - method public abstract boolean matchesRemoteAddress(@NonNull java.net.InetAddress, int, int); - } - - public final class QosSession implements android.os.Parcelable { - ctor public QosSession(int, int); - method public int describeContents(); - method public int getSessionId(); - method public int getSessionType(); - method public long getUniqueId(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field public static final int TYPE_EPS_BEARER = 1; // 0x1 - field public static final int TYPE_NR_BEARER = 2; // 0x2 - } - - public interface QosSessionAttributes { - } - - public final class QosSocketInfo implements android.os.Parcelable { - ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.Socket) throws java.io.IOException; - ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.DatagramSocket) throws java.io.IOException; - method public int describeContents(); - method @NonNull public java.net.InetSocketAddress getLocalSocketAddress(); - method @NonNull public android.net.Network getNetwork(); - method @Nullable public java.net.InetSocketAddress getRemoteSocketAddress(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public final class RouteInfo implements android.os.Parcelable { - ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int); - ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int, int); - method public int getMtu(); - } - - public abstract class SocketKeepalive implements java.lang.AutoCloseable { - method public final void start(@IntRange(from=0xa, to=0xe10) int, int, @Nullable android.net.Network); - field public static final int ERROR_NO_SUCH_SLOT = -33; // 0xffffffdf - field public static final int FLAG_AUTOMATIC_ON_OFF = 1; // 0x1 - field public static final int SUCCESS = 0; // 0x0 - } - - public class SocketLocalAddressChangedException extends java.lang.Exception { - ctor public SocketLocalAddressChangedException(); - } - - public class SocketNotBoundException extends java.lang.Exception { - ctor public SocketNotBoundException(); - } - - public class SocketNotConnectedException extends java.lang.Exception { - ctor public SocketNotConnectedException(); - } - - public class SocketRemoteAddressChangedException extends java.lang.Exception { - ctor public SocketRemoteAddressChangedException(); - } - - public final class StaticIpConfiguration implements android.os.Parcelable { - ctor public StaticIpConfiguration(); - ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration); - method public void addDnsServer(@NonNull java.net.InetAddress); - method public void clear(); - method @NonNull public java.util.List getRoutes(@Nullable String); - } - - public final class TcpKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable { - ctor public TcpKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[], int, int, int, int, int, int) throws android.net.InvalidPacketException; - method public int describeContents(); - method public int getIpTos(); - method public int getIpTtl(); - method public int getTcpAck(); - method public int getTcpSeq(); - method public int getTcpWindow(); - method public int getTcpWindowScale(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - - public final class VpnTransportInfo implements android.os.Parcelable android.net.TransportInfo { - ctor public VpnTransportInfo(int, @Nullable String, boolean, boolean); - method public boolean areLongLivedTcpConnectionsExpensive(); - method public int describeContents(); - method public int getType(); - method public boolean isBypassable(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - } - -} - -package android.net.apf { - - public final class ApfCapabilities implements android.os.Parcelable { - ctor public ApfCapabilities(int, int, int); - method public int describeContents(); - method public static boolean getApfDrop8023Frames(); - method @NonNull public static int[] getApfEtherTypeBlackList(); - method public boolean hasDataAccess(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - field public final int apfPacketFormat; - field public final int apfVersionSupported; - field public final int maximumApfProgramSize; - } - -} - diff --git a/framework/cronet_disabled/api/system-lint-baseline.txt b/framework/cronet_disabled/api/system-lint-baseline.txt deleted file mode 100644 index 9a97707763f1187a07e6f3e3aff9247b5a727146..0000000000000000000000000000000000000000 --- a/framework/cronet_disabled/api/system-lint-baseline.txt +++ /dev/null @@ -1 +0,0 @@ -// Baseline format: 1.0 diff --git a/framework/cronet_disabled/api/system-removed.txt b/framework/cronet_disabled/api/system-removed.txt deleted file mode 100644 index d802177e249b3f97128699222e65c35e57ba7540..0000000000000000000000000000000000000000 --- a/framework/cronet_disabled/api/system-removed.txt +++ /dev/null @@ -1 +0,0 @@ -// Signature format: 2.0 diff --git a/framework/jarjar-excludes.txt b/framework/jarjar-excludes.txt index 9b48d57f389ee92d35091b4f300a15ff1a8ff365..09abd17950d997fea19e8a6fb540966e75b38416 100644 --- a/framework/jarjar-excludes.txt +++ b/framework/jarjar-excludes.txt @@ -14,6 +14,16 @@ android\.net\.INetworkAgentRegistry(\$.+)? # TODO: move files to android.net.connectivity.visiblefortesting android\.net\.IConnectivityDiagnosticsCallback(\$.+)? +# Classes used by tethering as a hidden API are compiled as a lib in target +# connectivity-internal-api-util. Because it's used by tethering, it can't +# be jarjared. Classes in android.net.connectivity are exempt from being +# listed here because they are already in the target package and as such +# are already not jarjared. +# Because Tethering can be installed on R without Connectivity, any use +# of these classes must be protected by a check for >= S SDK. +# It's unlikely anybody else declares a hidden class with this name ? +android\.net\.RoutingCoordinatorManager(\$.+)? +android\.net\.LocalNetworkInfo(\$.+)? # KeepaliveUtils is used by ConnectivityManager CTS # TODO: move into service-connectivity so framework-connectivity stops using @@ -27,4 +37,4 @@ android\.nearby\..+ # Don't touch anything that's already under android.net.http (cronet) # This is required since android.net.http contains api classes and hidden classes. # TODO: Remove this after hidden classes are moved to different package -android\.net\.http\..+ \ No newline at end of file +android\.net\.http\..+ diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp index ca297e5a8bb31bec128e478cc25637774c31d571..5403be79f0338d487eedf0856eff42086e4a160e 100644 --- a/framework/jni/android_net_NetworkUtils.cpp +++ b/framework/jni/android_net_NetworkUtils.cpp @@ -26,6 +26,7 @@ #include #include // NETID_USE_LOCAL_NAMESERVERS #include +#include #include #include "jni.h" @@ -240,6 +241,15 @@ static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jclass clazz, j trw.rcv_wnd, trw.rcv_wup, tcpinfo.tcpi_rcv_wscale); } +static void android_net_utils_setsockoptBytes(JNIEnv *env, jclass clazz, jobject javaFd, + jint level, jint option, jbyteArray javaBytes) { + int sock = AFileDescriptor_getFd(env, javaFd); + ScopedByteArrayRO value(env, javaBytes); + if (setsockopt(sock, level, option, value.get(), value.size()) != 0) { + jniThrowErrnoException(env, "setsockoptBytes", errno); + } +} + // ---------------------------------------------------------------------------- /* @@ -260,6 +270,8 @@ static const JNINativeMethod gNetworkUtilMethods[] = { { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult }, { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel }, { "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork }, + { "setsockoptBytes", "(Ljava/io/FileDescriptor;II[B)V", + (void*) android_net_utils_setsockoptBytes}, }; // clang-format on diff --git a/framework/src/android/net/BpfNetMapsConstants.java b/framework/src/android/net/BpfNetMapsConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..5d0fe73ca47e57fac066b492220e4f1426ba46b2 --- /dev/null +++ b/framework/src/android/net/BpfNetMapsConstants.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2023 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.net; + +import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; + +import android.util.Pair; + +import com.android.net.module.util.Struct; + +import java.util.Arrays; +import java.util.List; + +/** + * BpfNetMaps related constants that can be shared among modules. + * + * @hide + */ +// Note that this class should be put into bootclasspath instead of static libraries. +// Because modules could have different copies of this class if this is statically linked, +// which would be problematic if the definitions in these modules are not synchronized. +public class BpfNetMapsConstants { + // Prevent this class from being accidental instantiated. + private BpfNetMapsConstants() {} + + public static final String CONFIGURATION_MAP_PATH = + "/sys/fs/bpf/netd_shared/map_netd_configuration_map"; + public static final String UID_OWNER_MAP_PATH = + "/sys/fs/bpf/netd_shared/map_netd_uid_owner_map"; + public static final String UID_PERMISSION_MAP_PATH = + "/sys/fs/bpf/netd_shared/map_netd_uid_permission_map"; + public static final String COOKIE_TAG_MAP_PATH = + "/sys/fs/bpf/netd_shared/map_netd_cookie_tag_map"; + public static final String DATA_SAVER_ENABLED_MAP_PATH = + "/sys/fs/bpf/netd_shared/map_netd_data_saver_enabled_map"; + public static final String INGRESS_DISCARD_MAP_PATH = + "/sys/fs/bpf/netd_shared/map_netd_ingress_discard_map"; + public static final Struct.S32 UID_RULES_CONFIGURATION_KEY = new Struct.S32(0); + public static final Struct.S32 CURRENT_STATS_MAP_CONFIGURATION_KEY = new Struct.S32(1); + public static final Struct.S32 DATA_SAVER_ENABLED_KEY = new Struct.S32(0); + + public static final short DATA_SAVER_DISABLED = 0; + public static final short DATA_SAVER_ENABLED = 1; + + // LINT.IfChange(match_type) + public static final long NO_MATCH = 0; + public static final long HAPPY_BOX_MATCH = (1 << 0); + public static final long PENALTY_BOX_MATCH = (1 << 1); + public static final long DOZABLE_MATCH = (1 << 2); + public static final long STANDBY_MATCH = (1 << 3); + public static final long POWERSAVE_MATCH = (1 << 4); + public static final long RESTRICTED_MATCH = (1 << 5); + public static final long LOW_POWER_STANDBY_MATCH = (1 << 6); + public static final long IIF_MATCH = (1 << 7); + public static final long LOCKDOWN_VPN_MATCH = (1 << 8); + public static final long OEM_DENY_1_MATCH = (1 << 9); + public static final long OEM_DENY_2_MATCH = (1 << 10); + public static final long OEM_DENY_3_MATCH = (1 << 11); + public static final long BACKGROUND_MATCH = (1 << 12); + + public static final List> MATCH_LIST = Arrays.asList( + Pair.create(HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"), + Pair.create(PENALTY_BOX_MATCH, "PENALTY_BOX_MATCH"), + Pair.create(DOZABLE_MATCH, "DOZABLE_MATCH"), + Pair.create(STANDBY_MATCH, "STANDBY_MATCH"), + Pair.create(POWERSAVE_MATCH, "POWERSAVE_MATCH"), + Pair.create(RESTRICTED_MATCH, "RESTRICTED_MATCH"), + Pair.create(LOW_POWER_STANDBY_MATCH, "LOW_POWER_STANDBY_MATCH"), + Pair.create(IIF_MATCH, "IIF_MATCH"), + Pair.create(LOCKDOWN_VPN_MATCH, "LOCKDOWN_VPN_MATCH"), + Pair.create(OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH"), + Pair.create(OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH"), + Pair.create(OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH"), + Pair.create(BACKGROUND_MATCH, "BACKGROUND_MATCH") + ); + + /** + * List of all firewall allow chains. + * + * Allow chains mean the firewall denies all uids by default, uids must be explicitly allowed. + */ + public static final List ALLOW_CHAINS = List.of( + FIREWALL_CHAIN_DOZABLE, + FIREWALL_CHAIN_POWERSAVE, + FIREWALL_CHAIN_RESTRICTED, + FIREWALL_CHAIN_LOW_POWER_STANDBY, + FIREWALL_CHAIN_BACKGROUND + ); + + /** + * List of all firewall deny chains. + * + * Deny chains mean the firewall allows all uids by default, uids must be explicitly denied. + */ + public static final List DENY_CHAINS = List.of( + FIREWALL_CHAIN_STANDBY, + FIREWALL_CHAIN_OEM_DENY_1, + FIREWALL_CHAIN_OEM_DENY_2, + FIREWALL_CHAIN_OEM_DENY_3 + ); + // LINT.ThenChange(../../../../bpf_progs/netd.h) +} diff --git a/framework/src/android/net/BpfNetMapsReader.java b/framework/src/android/net/BpfNetMapsReader.java new file mode 100644 index 0000000000000000000000000000000000000000..4ab6d3e0b6a351f5f5271af25bd2123879dbbdc9 --- /dev/null +++ b/framework/src/android/net/BpfNetMapsReader.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2023 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.net; + +import static android.net.BpfNetMapsConstants.CONFIGURATION_MAP_PATH; +import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED; +import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_KEY; +import static android.net.BpfNetMapsConstants.DATA_SAVER_ENABLED_MAP_PATH; +import static android.net.BpfNetMapsConstants.HAPPY_BOX_MATCH; +import static android.net.BpfNetMapsConstants.PENALTY_BOX_MATCH; +import static android.net.BpfNetMapsConstants.UID_OWNER_MAP_PATH; +import static android.net.BpfNetMapsConstants.UID_RULES_CONFIGURATION_KEY; +import static android.net.BpfNetMapsUtils.getMatchByFirewallChain; +import static android.net.BpfNetMapsUtils.isFirewallAllowList; +import static android.net.BpfNetMapsUtils.throwIfPreT; +import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_RULE_DENY; + +import android.annotation.NonNull; +import android.annotation.RequiresApi; +import android.os.Build; +import android.os.ServiceSpecificException; +import android.system.ErrnoException; +import android.system.Os; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.build.SdkLevel; +import com.android.net.module.util.BpfMap; +import com.android.net.module.util.IBpfMap; +import com.android.net.module.util.Struct.S32; +import com.android.net.module.util.Struct.U32; +import com.android.net.module.util.Struct.U8; + +/** + * A helper class to *read* java BpfMaps. + * @hide + */ +@RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T +public class BpfNetMapsReader { + private static final String TAG = BpfNetMapsReader.class.getSimpleName(); + + // Locally store the handle of bpf maps. The FileDescriptors are statically cached inside the + // BpfMap implementation. + + // Bpf map to store various networking configurations, the format of the value is different + // for different keys. See BpfNetMapsConstants#*_CONFIGURATION_KEY for keys. + private final IBpfMap mConfigurationMap; + // Bpf map to store per uid traffic control configurations. + // See {@link UidOwnerValue} for more detail. + private final IBpfMap mUidOwnerMap; + private final IBpfMap mDataSaverEnabledMap; + private final Dependencies mDeps; + + // Bitmaps for calculating whether a given uid is blocked by firewall chains. + private static final long sMaskDropIfSet; + private static final long sMaskDropIfUnset; + + static { + long maskDropIfSet = 0L; + long maskDropIfUnset = 0L; + + for (int chain : BpfNetMapsConstants.ALLOW_CHAINS) { + final long match = getMatchByFirewallChain(chain); + maskDropIfUnset |= match; + } + for (int chain : BpfNetMapsConstants.DENY_CHAINS) { + final long match = getMatchByFirewallChain(chain); + maskDropIfSet |= match; + } + sMaskDropIfSet = maskDropIfSet; + sMaskDropIfUnset = maskDropIfUnset; + } + + private static class SingletonHolder { + static final BpfNetMapsReader sInstance = new BpfNetMapsReader(); + } + + @NonNull + public static BpfNetMapsReader getInstance() { + return SingletonHolder.sInstance; + } + + private BpfNetMapsReader() { + this(new Dependencies()); + } + + // While the production code uses the singleton to optimize for performance and deal with + // concurrent access, the test needs to use a non-static approach for dependency injection and + // mocking virtual bpf maps. + @VisibleForTesting + public BpfNetMapsReader(@NonNull Dependencies deps) { + if (!SdkLevel.isAtLeastT()) { + throw new UnsupportedOperationException( + BpfNetMapsReader.class.getSimpleName() + " is not supported below Android T"); + } + mDeps = deps; + mConfigurationMap = mDeps.getConfigurationMap(); + mUidOwnerMap = mDeps.getUidOwnerMap(); + mDataSaverEnabledMap = mDeps.getDataSaverEnabledMap(); + } + + /** + * Dependencies of BpfNetMapReader, for injection in tests. + */ + @VisibleForTesting + public static class Dependencies { + /** Get the configuration map. */ + public IBpfMap getConfigurationMap() { + try { + return new BpfMap<>(CONFIGURATION_MAP_PATH, BpfMap.BPF_F_RDONLY, + S32.class, U32.class); + } catch (ErrnoException e) { + throw new IllegalStateException("Cannot open configuration map", e); + } + } + + /** Get the uid owner map. */ + public IBpfMap getUidOwnerMap() { + try { + return new BpfMap<>(UID_OWNER_MAP_PATH, BpfMap.BPF_F_RDONLY, + S32.class, UidOwnerValue.class); + } catch (ErrnoException e) { + throw new IllegalStateException("Cannot open uid owner map", e); + } + } + + /** Get the data saver enabled map. */ + public IBpfMap getDataSaverEnabledMap() { + try { + return new BpfMap<>(DATA_SAVER_ENABLED_MAP_PATH, BpfMap.BPF_F_RDONLY, S32.class, + U8.class); + } catch (ErrnoException e) { + throw new IllegalStateException("Cannot open data saver enabled map", e); + } + } + } + + /** + * Get the specified firewall chain's status. + * + * @param chain target chain + * @return {@code true} if chain is enabled, {@code false} if chain is not enabled. + * @throws UnsupportedOperationException if called on pre-T devices. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + public boolean isChainEnabled(final int chain) { + return isChainEnabled(mConfigurationMap, chain); + } + + /** + * Get firewall rule of specified firewall chain on specified uid. + * + * @param chain target chain + * @param uid target uid + * @return either {@link ConnectivityManager#FIREWALL_RULE_ALLOW} or + * {@link ConnectivityManager#FIREWALL_RULE_DENY}. + * @throws UnsupportedOperationException if called on pre-T devices. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + public int getUidRule(final int chain, final int uid) { + return getUidRule(mUidOwnerMap, chain, uid); + } + + /** + * Get the specified firewall chain's status. + * + * @param configurationMap target configurationMap + * @param chain target chain + * @return {@code true} if chain is enabled, {@code false} if chain is not enabled. + * @throws UnsupportedOperationException if called on pre-T devices. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + public static boolean isChainEnabled( + final IBpfMap configurationMap, final int chain) { + throwIfPreT("isChainEnabled is not available on pre-T devices"); + + final long match = getMatchByFirewallChain(chain); + try { + final U32 config = configurationMap.getValue(UID_RULES_CONFIGURATION_KEY); + return (config.val & match) != 0; + } catch (ErrnoException e) { + throw new ServiceSpecificException(e.errno, + "Unable to get firewall chain status: " + Os.strerror(e.errno)); + } + } + + /** + * Get firewall rule of specified firewall chain on specified uid. + * + * @param uidOwnerMap target uidOwnerMap. + * @param chain target chain. + * @param uid target uid. + * @return either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY + * @throws UnsupportedOperationException if called on pre-T devices. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + public static int getUidRule(final IBpfMap uidOwnerMap, + final int chain, final int uid) { + throwIfPreT("getUidRule is not available on pre-T devices"); + + final long match = getMatchByFirewallChain(chain); + final boolean isAllowList = isFirewallAllowList(chain); + try { + final UidOwnerValue uidMatch = uidOwnerMap.getValue(new S32(uid)); + final boolean isMatchEnabled = uidMatch != null && (uidMatch.rule & match) != 0; + return isMatchEnabled == isAllowList ? FIREWALL_RULE_ALLOW : FIREWALL_RULE_DENY; + } catch (ErrnoException e) { + throw new ServiceSpecificException(e.errno, + "Unable to get uid rule status: " + Os.strerror(e.errno)); + } + } + + /** + * Return whether the network is blocked by firewall chains for the given uid. + * + * @param uid The target uid. + * @param isNetworkMetered Whether the target network is metered. + * @param isDataSaverEnabled Whether the data saver is enabled. + * + * @return True if the network is blocked. Otherwise, false. + * @throws ServiceSpecificException if the read fails. + * + * @hide + */ + public boolean isUidNetworkingBlocked(final int uid, boolean isNetworkMetered, + boolean isDataSaverEnabled) { + throwIfPreT("isUidBlockedByFirewallChains is not available on pre-T devices"); + + final long uidRuleConfig; + final long uidMatch; + try { + uidRuleConfig = mConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val; + final UidOwnerValue value = mUidOwnerMap.getValue(new S32(uid)); + uidMatch = (value != null) ? value.rule : 0L; + } catch (ErrnoException e) { + throw new ServiceSpecificException(e.errno, + "Unable to get firewall chain status: " + Os.strerror(e.errno)); + } + + final boolean blockedByAllowChains = 0 != (uidRuleConfig & ~uidMatch & sMaskDropIfUnset); + final boolean blockedByDenyChains = 0 != (uidRuleConfig & uidMatch & sMaskDropIfSet); + if (blockedByAllowChains || blockedByDenyChains) { + return true; + } + + if (!isNetworkMetered) return false; + if ((uidMatch & PENALTY_BOX_MATCH) != 0) return true; + if ((uidMatch & HAPPY_BOX_MATCH) != 0) return false; + return isDataSaverEnabled; + } + + /** + * Get Data Saver enabled or disabled + * + * @return whether Data Saver is enabled or disabled. + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + public boolean getDataSaverEnabled() { + throwIfPreT("getDataSaverEnabled is not available on pre-T devices"); + + // Note that this is not expected to be called until V given that it relies on the + // counterpart platform solution to set data saver status to bpf. + // See {@code NetworkManagementService#setDataSaverModeEnabled}. + if (!SdkLevel.isAtLeastV()) { + Log.wtf(TAG, "getDataSaverEnabled is not expected to be called on pre-V devices"); + } + + try { + return mDataSaverEnabledMap.getValue(DATA_SAVER_ENABLED_KEY).val == DATA_SAVER_ENABLED; + } catch (ErrnoException e) { + throw new ServiceSpecificException(e.errno, "Unable to get data saver: " + + Os.strerror(e.errno)); + } + } +} diff --git a/framework/src/android/net/BpfNetMapsUtils.java b/framework/src/android/net/BpfNetMapsUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..11d610cbfeee7c826e83f65972122d49c8a8b38b --- /dev/null +++ b/framework/src/android/net/BpfNetMapsUtils.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2023 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.net; + +import static android.net.BpfNetMapsConstants.ALLOW_CHAINS; +import static android.net.BpfNetMapsConstants.BACKGROUND_MATCH; +import static android.net.BpfNetMapsConstants.DENY_CHAINS; +import static android.net.BpfNetMapsConstants.DOZABLE_MATCH; +import static android.net.BpfNetMapsConstants.LOW_POWER_STANDBY_MATCH; +import static android.net.BpfNetMapsConstants.MATCH_LIST; +import static android.net.BpfNetMapsConstants.NO_MATCH; +import static android.net.BpfNetMapsConstants.OEM_DENY_1_MATCH; +import static android.net.BpfNetMapsConstants.OEM_DENY_2_MATCH; +import static android.net.BpfNetMapsConstants.OEM_DENY_3_MATCH; +import static android.net.BpfNetMapsConstants.POWERSAVE_MATCH; +import static android.net.BpfNetMapsConstants.RESTRICTED_MATCH; +import static android.net.BpfNetMapsConstants.STANDBY_MATCH; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; +import static android.system.OsConstants.EINVAL; + +import android.os.ServiceSpecificException; +import android.util.Pair; + +import com.android.modules.utils.build.SdkLevel; + +import java.util.StringJoiner; + +/** + * The classes and the methods for BpfNetMaps utilization. + * + * @hide + */ +// Note that this class should be put into bootclasspath instead of static libraries. +// Because modules could have different copies of this class if this is statically linked, +// which would be problematic if the definitions in these modules are not synchronized. +public class BpfNetMapsUtils { + // Prevent this class from being accidental instantiated. + private BpfNetMapsUtils() {} + + /** + * Get corresponding match from firewall chain. + */ + public static long getMatchByFirewallChain(final int chain) { + switch (chain) { + case FIREWALL_CHAIN_DOZABLE: + return DOZABLE_MATCH; + case FIREWALL_CHAIN_STANDBY: + return STANDBY_MATCH; + case FIREWALL_CHAIN_POWERSAVE: + return POWERSAVE_MATCH; + case FIREWALL_CHAIN_RESTRICTED: + return RESTRICTED_MATCH; + case FIREWALL_CHAIN_BACKGROUND: + return BACKGROUND_MATCH; + case FIREWALL_CHAIN_LOW_POWER_STANDBY: + return LOW_POWER_STANDBY_MATCH; + case FIREWALL_CHAIN_OEM_DENY_1: + return OEM_DENY_1_MATCH; + case FIREWALL_CHAIN_OEM_DENY_2: + return OEM_DENY_2_MATCH; + case FIREWALL_CHAIN_OEM_DENY_3: + return OEM_DENY_3_MATCH; + default: + throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain); + } + } + + /** + * Get whether the chain is an allow-list or a deny-list. + * + * ALLOWLIST means the firewall denies all by default, uids must be explicitly allowed + * DENYLIST means the firewall allows all by default, uids must be explicitly denied + */ + public static boolean isFirewallAllowList(final int chain) { + if (ALLOW_CHAINS.contains(chain)) { + return true; + } else if (DENY_CHAINS.contains(chain)) { + return false; + } + throw new ServiceSpecificException(EINVAL, "Invalid firewall chain: " + chain); + } + + /** + * Get match string representation from the given match bitmap. + */ + public static String matchToString(long matchMask) { + if (matchMask == NO_MATCH) { + return "NO_MATCH"; + } + + final StringJoiner sj = new StringJoiner(" "); + for (final Pair match : MATCH_LIST) { + final long matchFlag = match.first; + final String matchName = match.second; + if ((matchMask & matchFlag) != 0) { + sj.add(matchName); + matchMask &= ~matchFlag; + } + } + if (matchMask != 0) { + sj.add("UNKNOWN_MATCH(" + matchMask + ")"); + } + return sj.toString(); + } + + public static final boolean PRE_T = !SdkLevel.isAtLeastT(); + + /** + * Throw UnsupportedOperationException if SdkLevel is before T. + */ + public static void throwIfPreT(final String msg) { + if (PRE_T) { + throw new UnsupportedOperationException(msg); + } + } +} diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 23155210be5a77ac062f8ba0bed3fdb055ba88eb..fa27d0e010f8bfdb8d617ef3b0dc56b4b2a8ae65 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -26,9 +26,11 @@ import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT; import static android.net.QosCallback.QosCallbackRegistrationException; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresApi; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -115,6 +117,18 @@ public class ConnectivityManager { private static final String TAG = "ConnectivityManager"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + // TODO : remove this class when udc-mainline-prod is abandoned and android.net.flags.Flags is + // available here + /** @hide */ + public static class Flags { + static final String SET_DATA_SAVER_VIA_CM = + "com.android.net.flags.set_data_saver_via_cm"; + static final String SUPPORT_IS_UID_NETWORKING_BLOCKED = + "com.android.net.flags.support_is_uid_networking_blocked"; + static final String BASIC_BACKGROUND_RESTRICTIONS_ENABLED = + "com.android.net.flags.basic_background_restrictions_enabled"; + } + /** * A change in network connectivity has occurred. A default connection has either * been established or lost. The NetworkInfo for the affected network is @@ -885,6 +899,16 @@ public class ConnectivityManager { @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final int BLOCKED_REASON_LOW_POWER_STANDBY = 1 << 5; + /** + * Flag to indicate that an app is subject to default background restrictions that would + * result in its network access being blocked. + * + * @hide + */ + @FlaggedApi(Flags.BASIC_BACKGROUND_RESTRICTIONS_ENABLED) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int BLOCKED_REASON_APP_BACKGROUND = 1 << 6; + /** * Flag to indicate that an app is subject to Data saver restrictions that would * result in its metered network access being blocked. @@ -924,6 +948,7 @@ public class ConnectivityManager { BLOCKED_REASON_RESTRICTED_MODE, BLOCKED_REASON_LOCKDOWN_VPN, BLOCKED_REASON_LOW_POWER_STANDBY, + BLOCKED_REASON_APP_BACKGROUND, BLOCKED_METERED_REASON_DATA_SAVER, BLOCKED_METERED_REASON_USER_RESTRICTED, BLOCKED_METERED_REASON_ADMIN_DISABLED, @@ -941,7 +966,6 @@ public class ConnectivityManager { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) private final IConnectivityManager mService; - // LINT.IfChange(firewall_chain) /** * Firewall chain for device idle (doze mode). * Allowlist of apps that have network access in device idle. @@ -982,6 +1006,16 @@ public class ConnectivityManager { @SystemApi(client = MODULE_LIBRARIES) public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5; + /** + * Firewall chain used for always-on default background restrictions. + * Allowlist of apps that have access because either they are in the foreground or they are + * exempted for specific situations while in the background. + * @hide + */ + @FlaggedApi(Flags.BASIC_BACKGROUND_RESTRICTIONS_ENABLED) + @SystemApi(client = MODULE_LIBRARIES) + public static final int FIREWALL_CHAIN_BACKGROUND = 6; + /** * Firewall chain used for OEM-specific application restrictions. * @@ -1041,12 +1075,12 @@ public class ConnectivityManager { FIREWALL_CHAIN_POWERSAVE, FIREWALL_CHAIN_RESTRICTED, FIREWALL_CHAIN_LOW_POWER_STANDBY, + FIREWALL_CHAIN_BACKGROUND, FIREWALL_CHAIN_OEM_DENY_1, FIREWALL_CHAIN_OEM_DENY_2, FIREWALL_CHAIN_OEM_DENY_3 }) public @interface FirewallChain {} - // LINT.ThenChange(packages/modules/Connectivity/service/native/include/Common.h) /** * A firewall rule which allows or drops packets depending on existing policy. @@ -3811,11 +3845,28 @@ public class ConnectivityManager { @RequiresPermission(anyOf = { NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_FACTORY}) - public Network registerNetworkAgent(INetworkAgent na, NetworkInfo ni, LinkProperties lp, - NetworkCapabilities nc, @NonNull NetworkScore score, NetworkAgentConfig config, - int providerId) { + public Network registerNetworkAgent(@NonNull INetworkAgent na, @NonNull NetworkInfo ni, + @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, + @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId) { + return registerNetworkAgent(na, ni, lp, nc, null /* localNetworkConfig */, score, config, + providerId); + } + + /** + * @hide + * Register a NetworkAgent with ConnectivityService. + * @return Network corresponding to NetworkAgent. + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_FACTORY}) + public Network registerNetworkAgent(@NonNull INetworkAgent na, @NonNull NetworkInfo ni, + @NonNull LinkProperties lp, @NonNull NetworkCapabilities nc, + @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score, + @NonNull NetworkAgentConfig config, int providerId) { try { - return mService.registerNetworkAgent(na, ni, lp, nc, score, config, providerId); + return mService.registerNetworkAgent(na, ni, lp, nc, score, localNetworkConfig, config, + providerId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3924,16 +3975,21 @@ public class ConnectivityManager { * @param network The {@link Network} of the satisfying network. * @param networkCapabilities The {@link NetworkCapabilities} of the satisfying network. * @param linkProperties The {@link LinkProperties} of the satisfying network. + * @param localInfo The {@link LocalNetworkInfo} of the satisfying network, or null + * if this network is not a local network. * @param blocked Whether access to the {@link Network} is blocked due to system policy. * @hide */ public final void onAvailable(@NonNull Network network, @NonNull NetworkCapabilities networkCapabilities, - @NonNull LinkProperties linkProperties, @BlockedReason int blocked) { + @NonNull LinkProperties linkProperties, + @Nullable LocalNetworkInfo localInfo, + @BlockedReason int blocked) { // Internally only this method is called when a new network is available, and // it calls the callback in the same way and order that older versions used // to call so as not to change the behavior. onAvailable(network, networkCapabilities, linkProperties, blocked != 0); + if (null != localInfo) onLocalNetworkInfoChanged(network, localInfo); onBlockedStatusChanged(network, blocked); } @@ -3950,7 +4006,8 @@ public class ConnectivityManager { */ public void onAvailable(@NonNull Network network, @NonNull NetworkCapabilities networkCapabilities, - @NonNull LinkProperties linkProperties, boolean blocked) { + @NonNull LinkProperties linkProperties, + boolean blocked) { onAvailable(network); if (!networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)) { @@ -4076,6 +4133,19 @@ public class ConnectivityManager { public void onLinkPropertiesChanged(@NonNull Network network, @NonNull LinkProperties linkProperties) {} + /** + * Called when there is a change in the {@link LocalNetworkInfo} for this network. + * + * This is only called for local networks, that is those with the + * NET_CAPABILITY_LOCAL_NETWORK network capability. + * + * @param network the {@link Network} whose local network info has changed. + * @param localNetworkInfo the new {@link LocalNetworkInfo} for this network. + * @hide + */ + public void onLocalNetworkInfoChanged(@NonNull Network network, + @NonNull LocalNetworkInfo localNetworkInfo) {} + /** * Called when the network the framework connected to for this request suspends data * transmission temporarily. @@ -4170,27 +4240,29 @@ public class ConnectivityManager { } /** @hide */ - public static final int CALLBACK_PRECHECK = 1; + public static final int CALLBACK_PRECHECK = 1; /** @hide */ - public static final int CALLBACK_AVAILABLE = 2; + public static final int CALLBACK_AVAILABLE = 2; /** @hide arg1 = TTL */ - public static final int CALLBACK_LOSING = 3; + public static final int CALLBACK_LOSING = 3; /** @hide */ - public static final int CALLBACK_LOST = 4; + public static final int CALLBACK_LOST = 4; /** @hide */ - public static final int CALLBACK_UNAVAIL = 5; + public static final int CALLBACK_UNAVAIL = 5; /** @hide */ - public static final int CALLBACK_CAP_CHANGED = 6; + public static final int CALLBACK_CAP_CHANGED = 6; /** @hide */ - public static final int CALLBACK_IP_CHANGED = 7; + public static final int CALLBACK_IP_CHANGED = 7; /** @hide obj = NetworkCapabilities, arg1 = seq number */ - private static final int EXPIRE_LEGACY_REQUEST = 8; + private static final int EXPIRE_LEGACY_REQUEST = 8; /** @hide */ - public static final int CALLBACK_SUSPENDED = 9; + public static final int CALLBACK_SUSPENDED = 9; /** @hide */ - public static final int CALLBACK_RESUMED = 10; + public static final int CALLBACK_RESUMED = 10; /** @hide */ - public static final int CALLBACK_BLK_CHANGED = 11; + public static final int CALLBACK_BLK_CHANGED = 11; + /** @hide */ + public static final int CALLBACK_LOCAL_NETWORK_INFO_CHANGED = 12; /** @hide */ public static String getCallbackName(int whichCallback) { @@ -4206,6 +4278,7 @@ public class ConnectivityManager { case CALLBACK_SUSPENDED: return "CALLBACK_SUSPENDED"; case CALLBACK_RESUMED: return "CALLBACK_RESUMED"; case CALLBACK_BLK_CHANGED: return "CALLBACK_BLK_CHANGED"; + case CALLBACK_LOCAL_NETWORK_INFO_CHANGED: return "CALLBACK_LOCAL_NETWORK_INFO_CHANGED"; default: return Integer.toString(whichCallback); } @@ -4260,7 +4333,8 @@ public class ConnectivityManager { case CALLBACK_AVAILABLE: { NetworkCapabilities cap = getObject(message, NetworkCapabilities.class); LinkProperties lp = getObject(message, LinkProperties.class); - callback.onAvailable(network, cap, lp, message.arg1); + LocalNetworkInfo lni = getObject(message, LocalNetworkInfo.class); + callback.onAvailable(network, cap, lp, lni, message.arg1); break; } case CALLBACK_LOSING: { @@ -4285,6 +4359,11 @@ public class ConnectivityManager { callback.onLinkPropertiesChanged(network, lp); break; } + case CALLBACK_LOCAL_NETWORK_INFO_CHANGED: { + final LocalNetworkInfo info = getObject(message, LocalNetworkInfo.class); + callback.onLocalNetworkInfoChanged(network, info); + break; + } case CALLBACK_SUSPENDED: { callback.onNetworkSuspended(network); break; @@ -5940,6 +6019,28 @@ public class ConnectivityManager { return new Range(TUN_INTF_NETID_START, TUN_INTF_NETID_START + TUN_INTF_NETID_RANGE - 1); } + /** + * Sets data saver switch. + * + * @param enable True if enable. + * @throws IllegalStateException if failed. + * @hide + */ + @FlaggedApi(Flags.SET_DATA_SAVER_VIA_CM) + @SystemApi(client = MODULE_LIBRARIES) + @RequiresPermission(anyOf = { + android.Manifest.permission.NETWORK_SETTINGS, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK + }) + public void setDataSaverEnabled(final boolean enable) { + try { + mService.setDataSaverEnabled(enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Adds the specified UID to the list of UIds that are allowed to use data on metered networks * even when background data is restricted. The deny list takes precedence over the allow list. @@ -6149,6 +6250,39 @@ public class ConnectivityManager { } } + /** + * Return whether the network is blocked for the given uid and metered condition. + * + * Similar to {@link NetworkPolicyManager#isUidNetworkingBlocked}, but directly reads the BPF + * maps and therefore considerably faster. For use by the NetworkStack process only. + * + * @param uid The target uid. + * @param isNetworkMetered Whether the target network is metered. + * + * @return True if all networking with the given condition is blocked. Otherwise, false. + * @throws IllegalStateException if the map cannot be opened. + * @throws ServiceSpecificException if the read fails. + * @hide + */ + // This isn't protected by a standard Android permission since it can't + // afford to do IPC for performance reasons. Instead, the access control + // is provided by linux file group permission AID_NET_BW_ACCT and the + // selinux context fs_bpf_net*. + // Only the system server process and the network stack have access. + @FlaggedApi(Flags.SUPPORT_IS_UID_NETWORKING_BLOCKED) + @SystemApi(client = MODULE_LIBRARIES) + @RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T + @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) + public boolean isUidNetworkingBlocked(int uid, boolean isNetworkMetered) { + final BpfNetMapsReader reader = BpfNetMapsReader.getInstance(); + // Note that before V, the data saver status in bpf is written by ConnectivityService + // when receiving {@link #ACTION_RESTRICT_BACKGROUND_CHANGED}. Thus, + // the status is not synchronized. + // On V+, the data saver status is set by platform code when enabling/disabling + // data saver, which is synchronized. + return reader.isUidNetworkingBlocked(uid, isNetworkMetered, reader.getDataSaverEnabled()); + } + /** @hide */ public IBinder getCompanionDeviceManagerProxyService() { try { @@ -6157,4 +6291,24 @@ public class ConnectivityManager { throw e.rethrowFromSystemServer(); } } + + private static final Object sRoutingCoordinatorManagerLock = new Object(); + @GuardedBy("sRoutingCoordinatorManagerLock") + private static RoutingCoordinatorManager sRoutingCoordinatorManager = null; + /** @hide */ + @RequiresApi(Build.VERSION_CODES.S) + public RoutingCoordinatorManager getRoutingCoordinatorManager() { + try { + synchronized (sRoutingCoordinatorManagerLock) { + if (null == sRoutingCoordinatorManager) { + sRoutingCoordinatorManager = new RoutingCoordinatorManager(mContext, + IRoutingCoordinator.Stub.asInterface( + mService.getRoutingCoordinatorService())); + } + return sRoutingCoordinatorManager; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java index 822e67d339c5d3a1c76dc39ea696167a0295399a..ba7df7ff9b102624a2b2a5dc8fa05b43b8eef7f6 100644 --- a/framework/src/android/net/ConnectivitySettingsManager.java +++ b/framework/src/android/net/ConnectivitySettingsManager.java @@ -28,6 +28,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; +import android.content.pm.PackageManager; import android.net.ConnectivityManager.MultipathPreference; import android.os.Binder; import android.os.Build; @@ -36,6 +37,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; +import android.util.Log; import android.util.Range; import com.android.net.module.util.ConnectivitySettingsUtils; @@ -55,6 +57,7 @@ import java.util.StringJoiner; */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public class ConnectivitySettingsManager { + private static final String TAG = ConnectivitySettingsManager.class.getSimpleName(); private ConnectivitySettingsManager() {} @@ -173,7 +176,9 @@ public class ConnectivitySettingsManager { /** * When detecting a captive portal, immediately disconnect from the - * network and do not reconnect to that network in the future. + * network and do not reconnect to that network in the future; except + * on Wear platform companion proxy networks (transport BLUETOOTH) + * will stay behind captive portal. */ public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; @@ -696,10 +701,20 @@ public class ConnectivitySettingsManager { /** * Set global http proxy settings from given {@link ProxyInfo}. * + *

+ * While a {@link ProxyInfo} for a PAC proxy can be specified, not all devices support + * PAC proxies. In particular, smaller devices like watches often do not have the capabilities + * necessary to interpret the PAC file. In such cases, calling this API with a PAC proxy + * results in undefined behavior, including possibly breaking networking for applications. + * You can test for this by checking for the presence of {@link PackageManager.FEATURE_WEBVIEW}. + *

+ * * @param context The {@link Context} to set the setting. * @param proxyInfo The {@link ProxyInfo} for global http proxy settings which build from * {@link ProxyInfo#buildPacProxy(Uri)} or * {@link ProxyInfo#buildDirectProxy(String, int, List)} + * @throws UnsupportedOperationException if |proxyInfo| codes for a PAC proxy but the system + * does not support PAC proxies. */ public static void setGlobalProxy(@NonNull Context context, @NonNull ProxyInfo proxyInfo) { final String host = proxyInfo.getHost(); @@ -707,6 +722,14 @@ public class ConnectivitySettingsManager { final String exclusionList = proxyInfo.getExclusionListAsString(); final String pacFileUrl = proxyInfo.getPacFileUrl().toString(); + + if (!TextUtils.isEmpty(pacFileUrl)) { + final PackageManager pm = context.getPackageManager(); + if (null != pm && !pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) { + Log.wtf(TAG, "PAC proxy can't be installed on a device without FEATURE_WEBVIEW"); + } + } + if (TextUtils.isEmpty(pacFileUrl)) { Settings.Global.putString(context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, host); Settings.Global.putInt(context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, port); diff --git a/framework/src/android/net/DnsResolver.java b/framework/src/android/net/DnsResolver.java index c6034f1f6313301d8b382f55d22e696523ab45d6..5fefcd6770a1d2bd8cfd106ac83c9b4cd95d5981 100644 --- a/framework/src/android/net/DnsResolver.java +++ b/framework/src/android/net/DnsResolver.java @@ -77,6 +77,15 @@ public final class DnsResolver { @interface QueryType {} public static final int TYPE_A = 1; public static final int TYPE_AAAA = 28; + // TODO: add below constants as part of QueryType and the public API + /** @hide */ + public static final int TYPE_PTR = 12; + /** @hide */ + public static final int TYPE_TXT = 16; + /** @hide */ + public static final int TYPE_SRV = 33; + /** @hide */ + public static final int TYPE_ANY = 255; @IntDef(prefix = { "FLAG_" }, value = { FLAG_EMPTY, diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index ebe8bca77e3ed14192c15633146f6c6bdca02a66..d3a02b965241107728d1f4d551f4787651a8c313 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -27,6 +27,7 @@ import android.net.INetworkOfferCallback; import android.net.IQosCallback; import android.net.ISocketKeepaliveCallback; import android.net.LinkProperties; +import android.net.LocalNetworkConfig; import android.net.Network; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; @@ -146,7 +147,8 @@ interface IConnectivityManager void declareNetworkRequestUnfulfillable(in NetworkRequest request); Network registerNetworkAgent(in INetworkAgent na, in NetworkInfo ni, in LinkProperties lp, - in NetworkCapabilities nc, in NetworkScore score, in NetworkAgentConfig config, + in NetworkCapabilities nc, in NetworkScore score, + in LocalNetworkConfig localNetworkConfig, in NetworkAgentConfig config, in int factorySerialNumber); NetworkRequest requestNetwork(int uid, in NetworkCapabilities networkCapabilities, int reqType, @@ -238,6 +240,8 @@ interface IConnectivityManager void setTestAllowBadWifiUntil(long timeMs); + void setDataSaverEnabled(boolean enable); + void updateMeteredNetworkAllowList(int uid, boolean add); void updateMeteredNetworkDenyList(int uid, boolean add); @@ -257,4 +261,6 @@ interface IConnectivityManager void setVpnNetworkPreference(String session, in UidRange[] ranges); void setTestLowTcpPollingTimerForKeepalive(long timeMs); + + IBinder getRoutingCoordinatorService(); } diff --git a/framework/src/android/net/INetworkAgentRegistry.aidl b/framework/src/android/net/INetworkAgentRegistry.aidl index b375b7b64905127f8f039233e155389c26a419a1..61b27b5f0e64aa25784885ed95552bfb00fa08fe 100644 --- a/framework/src/android/net/INetworkAgentRegistry.aidl +++ b/framework/src/android/net/INetworkAgentRegistry.aidl @@ -17,6 +17,7 @@ package android.net; import android.net.DscpPolicy; import android.net.LinkProperties; +import android.net.LocalNetworkConfig; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; @@ -34,6 +35,7 @@ oneway interface INetworkAgentRegistry { void sendLinkProperties(in LinkProperties lp); // TODO: consider replacing this by "markConnected()" and removing void sendNetworkInfo(in NetworkInfo info); + void sendLocalNetworkConfig(in LocalNetworkConfig config); void sendScore(in NetworkScore score); void sendExplicitlySelected(boolean explicitlySelected, boolean acceptPartial); void sendSocketKeepaliveEvent(int slot, int reason); diff --git a/framework/src/android/net/IRoutingCoordinator.aidl b/framework/src/android/net/IRoutingCoordinator.aidl new file mode 100644 index 0000000000000000000000000000000000000000..cf02ec47aa42de9e8ed52ff7d94455fa8a333841 --- /dev/null +++ b/framework/src/android/net/IRoutingCoordinator.aidl @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 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.net; + +import android.net.RouteInfo; + +/** @hide */ +interface IRoutingCoordinator { + /** + * Add a route for specific network + * + * @param netId the network to add the route to + * @param route the route to add + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void addRoute(int netId, in RouteInfo route); + + /** + * Remove a route for specific network + * + * @param netId the network to remove the route from + * @param route the route to remove + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void removeRoute(int netId, in RouteInfo route); + + /** + * Update a route for specific network + * + * @param netId the network to update the route for + * @param route parcelable with route information + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void updateRoute(int netId, in RouteInfo route); + + /** + * Adds an interface to a network. The interface must not be assigned to any network, including + * the specified network. + * + * @param netId the network to add the interface to. + * @param iface the name of the interface to add. + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + void addInterfaceToNetwork(int netId, in String iface); + + /** + * Removes an interface from a network. The interface must be assigned to the specified network. + * + * @param netId the network to remove the interface from. + * @param iface the name of the interface to remove. + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + void removeInterfaceFromNetwork(int netId, in String iface); + + /** + * Add forwarding ip rule + * + * @param fromIface interface name to add forwarding ip rule + * @param toIface interface name to add forwarding ip rule + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void addInterfaceForward(in String fromIface, in String toIface); + + /** + * Remove forwarding ip rule + * + * @param fromIface interface name to remove forwarding ip rule + * @param toIface interface name to remove forwarding ip rule + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + void removeInterfaceForward(in String fromIface, in String toIface); +} diff --git a/framework/src/android/net/LinkAddress.java b/framework/src/android/net/LinkAddress.java index 90f55b3cd82f1fe0b10833ecd598e67535c396b7..8376963857bb3bd36925fd38cc554d33a027dd7a 100644 --- a/framework/src/android/net/LinkAddress.java +++ b/framework/src/android/net/LinkAddress.java @@ -37,6 +37,8 @@ import android.os.Parcelable; import android.os.SystemClock; import android.util.Pair; +import com.android.net.module.util.ConnectivityUtils; + import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -146,11 +148,7 @@ public class LinkAddress implements Parcelable { * Per RFC 4193 section 8, fc00::/7 identifies these addresses. */ private boolean isIpv6ULA() { - if (isIpv6()) { - byte[] bytes = address.getAddress(); - return ((bytes[0] & (byte)0xfe) == (byte)0xfc); - } - return false; + return ConnectivityUtils.isIPv6ULA(address); } /** diff --git a/framework/src/android/net/LocalNetworkConfig.java b/framework/src/android/net/LocalNetworkConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..17b1064f81dbb4721b73bd400ad1e0f501daefff --- /dev/null +++ b/framework/src/android/net/LocalNetworkConfig.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2023 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.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class to communicate configuration info about a local network through {@link NetworkAgent}. + * @hide + */ +// TODO : @SystemApi +public final class LocalNetworkConfig implements Parcelable { + @Nullable + private final NetworkRequest mUpstreamSelector; + + @NonNull + private final MulticastRoutingConfig mUpstreamMulticastRoutingConfig; + + @NonNull + private final MulticastRoutingConfig mDownstreamMulticastRoutingConfig; + + private LocalNetworkConfig(@Nullable final NetworkRequest upstreamSelector, + @Nullable final MulticastRoutingConfig upstreamConfig, + @Nullable final MulticastRoutingConfig downstreamConfig) { + mUpstreamSelector = upstreamSelector; + if (null != upstreamConfig) { + mUpstreamMulticastRoutingConfig = upstreamConfig; + } else { + mUpstreamMulticastRoutingConfig = MulticastRoutingConfig.CONFIG_FORWARD_NONE; + } + if (null != downstreamConfig) { + mDownstreamMulticastRoutingConfig = downstreamConfig; + } else { + mDownstreamMulticastRoutingConfig = MulticastRoutingConfig.CONFIG_FORWARD_NONE; + } + } + + /** + * Get the request choosing which network traffic from this network is forwarded to and from. + * + * This may be null if the local network doesn't forward the traffic anywhere. + */ + @Nullable + public NetworkRequest getUpstreamSelector() { + return mUpstreamSelector; + } + + /** + * Get the upstream multicast routing config + */ + @NonNull + public MulticastRoutingConfig getUpstreamMulticastRoutingConfig() { + return mUpstreamMulticastRoutingConfig; + } + + /** + * Get the downstream multicast routing config + */ + @NonNull + public MulticastRoutingConfig getDownstreamMulticastRoutingConfig() { + return mDownstreamMulticastRoutingConfig; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + dest.writeParcelable(mUpstreamSelector, flags); + dest.writeParcelable(mUpstreamMulticastRoutingConfig, flags); + dest.writeParcelable(mDownstreamMulticastRoutingConfig, flags); + } + + @Override + public String toString() { + return "LocalNetworkConfig{" + + "UpstreamSelector=" + mUpstreamSelector + + ", UpstreamMulticastConfig=" + mUpstreamMulticastRoutingConfig + + ", DownstreamMulticastConfig=" + mDownstreamMulticastRoutingConfig + + '}'; + } + + public static final @NonNull Creator CREATOR = new Creator<>() { + public LocalNetworkConfig createFromParcel(Parcel in) { + final NetworkRequest upstreamSelector = in.readParcelable(null); + final MulticastRoutingConfig upstreamConfig = in.readParcelable(null); + final MulticastRoutingConfig downstreamConfig = in.readParcelable(null); + return new LocalNetworkConfig( + upstreamSelector, upstreamConfig, downstreamConfig); + } + + @Override + public LocalNetworkConfig[] newArray(final int size) { + return new LocalNetworkConfig[size]; + } + }; + + + public static final class Builder { + @Nullable + private NetworkRequest mUpstreamSelector; + + @Nullable + private MulticastRoutingConfig mUpstreamMulticastRoutingConfig; + + @Nullable + private MulticastRoutingConfig mDownstreamMulticastRoutingConfig; + + /** + * Create a Builder + */ + public Builder() { + } + + /** + * Set to choose where this local network should forward its traffic to. + * + * The system will automatically choose the best network matching the request as an + * upstream, and set up forwarding between this local network and the chosen upstream. + * If no network matches the request, there is no upstream and the traffic is not forwarded. + * The caller can know when this changes by listening to link properties changes of + * this network with the {@link android.net.LinkProperties#getForwardedNetwork()} getter. + * + * Set this to null if the local network shouldn't be forwarded. Default is null. + */ + @NonNull + public Builder setUpstreamSelector(@Nullable NetworkRequest upstreamSelector) { + mUpstreamSelector = upstreamSelector; + return this; + } + + /** + * Set the upstream multicast routing config. + * + * If null, don't route multicast packets upstream. This is equivalent to a + * MulticastRoutingConfig in mode FORWARD_NONE. The default is null. + */ + @NonNull + public Builder setUpstreamMulticastRoutingConfig(@Nullable MulticastRoutingConfig cfg) { + mUpstreamMulticastRoutingConfig = cfg; + return this; + } + + /** + * Set the downstream multicast routing config. + * + * If null, don't route multicast packets downstream. This is equivalent to a + * MulticastRoutingConfig in mode FORWARD_NONE. The default is null. + */ + @NonNull + public Builder setDownstreamMulticastRoutingConfig(@Nullable MulticastRoutingConfig cfg) { + mDownstreamMulticastRoutingConfig = cfg; + return this; + } + + /** + * Build the LocalNetworkConfig object. + */ + @NonNull + public LocalNetworkConfig build() { + return new LocalNetworkConfig(mUpstreamSelector, + mUpstreamMulticastRoutingConfig, + mDownstreamMulticastRoutingConfig); + } + } +} diff --git a/framework/src/android/net/LocalNetworkInfo.java b/framework/src/android/net/LocalNetworkInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..f94513319af13a1a084b656e4c566d4f4b053be7 --- /dev/null +++ b/framework/src/android/net/LocalNetworkInfo.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2023 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.net; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + + +/** + * Information about a local network. + * + * This is sent to ConnectivityManager.NetworkCallback. + * @hide + */ +// TODO : make public +public final class LocalNetworkInfo implements Parcelable { + @Nullable private final Network mUpstreamNetwork; + + public LocalNetworkInfo(@Nullable final Network upstreamNetwork) { + this.mUpstreamNetwork = upstreamNetwork; + } + + /** + * Return the upstream network, or null if none. + */ + @Nullable + public Network getUpstreamNetwork() { + return mUpstreamNetwork; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull final Parcel dest, final int flags) { + dest.writeParcelable(mUpstreamNetwork, flags); + } + + @Override + public String toString() { + return "LocalNetworkInfo { upstream=" + mUpstreamNetwork + " }"; + } + + public static final @NonNull Creator CREATOR = new Creator<>() { + public LocalNetworkInfo createFromParcel(Parcel in) { + final Network upstreamNetwork = in.readParcelable(null); + return new LocalNetworkInfo(upstreamNetwork); + } + + @Override + public LocalNetworkInfo[] newArray(final int size) { + return new LocalNetworkInfo[size]; + } + }; + + /** + * Builder for LocalNetworkInfo + */ + public static final class Builder { + @Nullable private Network mUpstreamNetwork; + + /** + * Set the upstream network, or null if none. + * @return the builder + */ + @NonNull public Builder setUpstreamNetwork(@Nullable final Network network) { + mUpstreamNetwork = network; + return this; + } + + /** + * Build the LocalNetworkInfo + */ + @NonNull public LocalNetworkInfo build() { + return new LocalNetworkInfo(mUpstreamNetwork); + } + } +} diff --git a/framework/src/android/net/MacAddress.java b/framework/src/android/net/MacAddress.java index 26a504a29c1cf7cbeb4713def7a49154ed6d2c1f..049a425e1138a96fa8b826b1a4c6f278a07e2e32 100644 --- a/framework/src/android/net/MacAddress.java +++ b/framework/src/android/net/MacAddress.java @@ -127,7 +127,7 @@ public final class MacAddress implements Parcelable { /** * Convert this MacAddress to a byte array. * - * The returned array is in network order. For example, if this MacAddress is 1:2:3:4:5:6, + * The returned array is in network order. For example, if this MacAddress is 01:02:03:04:05:06, * the returned array is [1, 2, 3, 4, 5, 6]. * * @return a byte array representation of this MacAddress. diff --git a/framework/src/android/net/MulticastRoutingConfig.java b/framework/src/android/net/MulticastRoutingConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..4a3e1be8fa68aef9ed4400e6c35695a7e83bba23 --- /dev/null +++ b/framework/src/android/net/MulticastRoutingConfig.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2023 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.net; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.Inet6Address; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; +import java.util.StringJoiner; + +/** + * A class representing a configuration for multicast routing. + * + * Internal usage to Connectivity + * @hide + */ +// @SystemApi(client = MODULE_LIBRARIES) +public final class MulticastRoutingConfig implements Parcelable { + private static final String TAG = MulticastRoutingConfig.class.getSimpleName(); + + /** Do not forward any multicast packets. */ + public static final int FORWARD_NONE = 0; + /** + * Forward only multicast packets with destination in the list of listening addresses. + * Ignore the min scope. + */ + public static final int FORWARD_SELECTED = 1; + /** + * Forward all multicast packets with scope greater or equal than the min scope. + * Ignore the list of listening addresses. + */ + public static final int FORWARD_WITH_MIN_SCOPE = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "FORWARD_" }, value = { + FORWARD_NONE, + FORWARD_SELECTED, + FORWARD_WITH_MIN_SCOPE + }) + public @interface MulticastForwardingMode {} + + /** + * Not a multicast scope, for configurations that do not use the min scope. + */ + public static final int MULTICAST_SCOPE_NONE = -1; + + /** @hide */ + public static final MulticastRoutingConfig CONFIG_FORWARD_NONE = + new MulticastRoutingConfig(FORWARD_NONE, MULTICAST_SCOPE_NONE, null); + + @MulticastForwardingMode + private final int mForwardingMode; + + private final int mMinScope; + + @NonNull + private final Set mListeningAddresses; + + private MulticastRoutingConfig(@MulticastForwardingMode final int mode, final int scope, + @Nullable final Set addresses) { + mForwardingMode = mode; + mMinScope = scope; + if (null != addresses) { + mListeningAddresses = Collections.unmodifiableSet(new ArraySet<>(addresses)); + } else { + mListeningAddresses = Collections.emptySet(); + } + } + + /** + * Returns the forwarding mode. + */ + @MulticastForwardingMode + public int getForwardingMode() { + return mForwardingMode; + } + + /** + * Returns the minimal group address scope that is allowed for forwarding. + * If the forwarding mode is not FORWARD_WITH_MIN_SCOPE, will be MULTICAST_SCOPE_NONE. + */ + public int getMinimumScope() { + return mMinScope; + } + + /** + * Returns the list of group addresses listened by the outgoing interface. + * The list will be empty if the forwarding mode is not FORWARD_SELECTED. + */ + @NonNull + public Set getListeningAddresses() { + return mListeningAddresses; + } + + private MulticastRoutingConfig(Parcel in) { + mForwardingMode = in.readInt(); + mMinScope = in.readInt(); + final int count = in.readInt(); + final ArraySet listeningAddresses = new ArraySet<>(count); + final byte[] buffer = new byte[16]; // Size of an Inet6Address + for (int i = 0; i < count; ++i) { + in.readByteArray(buffer); + try { + listeningAddresses.add((Inet6Address) Inet6Address.getByAddress(buffer)); + } catch (UnknownHostException e) { + Log.wtf(TAG, "Can't read inet6address : " + Arrays.toString(buffer)); + } + } + mListeningAddresses = Collections.unmodifiableSet(listeningAddresses); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mForwardingMode); + dest.writeInt(mMinScope); + dest.writeInt(mListeningAddresses.size()); + for (final Inet6Address addr : mListeningAddresses) { + dest.writeByteArray(addr.getAddress()); + } + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + public static final Creator CREATOR = new Creator<>() { + @Override + public MulticastRoutingConfig createFromParcel(@NonNull Parcel in) { + return new MulticastRoutingConfig(in); + } + + @Override + public MulticastRoutingConfig[] newArray(int size) { + return new MulticastRoutingConfig[size]; + } + }; + + private static String forwardingModeToString(final int forwardingMode) { + switch (forwardingMode) { + case FORWARD_NONE: return "NONE"; + case FORWARD_SELECTED: return "SELECTED"; + case FORWARD_WITH_MIN_SCOPE: return "WITH_MIN_SCOPE"; + default: return "UNKNOWN"; + } + } + + public static final class Builder { + @MulticastForwardingMode + private final int mForwardingMode; + private int mMinScope; + private final ArraySet mListeningAddresses; + + // The two constructors with runtime checks for the mode and scope are arguably + // less convenient than three static factory methods, but API guidelines mandates + // that Builders are built with a constructor and not factory methods. + /** + * Create a new builder for forwarding mode FORWARD_NONE or FORWARD_SELECTED. + * + *

On a Builder for FORWARD_NONE, no properties can be set. + *

On a Builder for FORWARD_SELECTED, listening addresses can be added and removed + * but the minimum scope can't be set. + * + * @param mode {@link #FORWARD_NONE} or {@link #FORWARD_SELECTED}. Any other + * value will result in IllegalArgumentException. + * @see #Builder(int, int) + */ + public Builder(@MulticastForwardingMode final int mode) { + if (FORWARD_NONE != mode && FORWARD_SELECTED != mode) { + if (FORWARD_WITH_MIN_SCOPE == mode) { + throw new IllegalArgumentException("FORWARD_WITH_MIN_SCOPE requires " + + "passing the scope as a second argument"); + } else { + throw new IllegalArgumentException("Unknown forwarding mode : " + mode); + } + } + mForwardingMode = mode; + mMinScope = MULTICAST_SCOPE_NONE; + mListeningAddresses = new ArraySet<>(); + } + + /** + * Create a new builder for forwarding mode FORWARD_WITH_MIN_SCOPE. + * + *

On this Builder the scope can be set with {@link #setMinimumScope}, but + * listening addresses can't be added or removed. + * + * @param mode Must be {@link #FORWARD_WITH_MIN_SCOPE}. + * @param scope the minimum scope for this multicast routing config. + * @see Builder#Builder(int) + */ + public Builder(@MulticastForwardingMode final int mode, int scope) { + if (FORWARD_WITH_MIN_SCOPE != mode) { + throw new IllegalArgumentException("Forwarding with a min scope must " + + "use forward mode FORWARD_WITH_MIN_SCOPE"); + } + mForwardingMode = mode; + mMinScope = scope; + mListeningAddresses = new ArraySet<>(); + } + + /** + * Sets the minimum scope for this multicast routing config. + * This is only meaningful (indeed, allowed) for configs in FORWARD_WITH_MIN_SCOPE mode. + * @return this builder + */ + @NonNull + public Builder setMinimumScope(final int scope) { + if (FORWARD_WITH_MIN_SCOPE != mForwardingMode) { + throw new IllegalArgumentException("Can't set the scope on a builder in mode " + + modeToString(mForwardingMode)); + } + mMinScope = scope; + return this; + } + + /** + * Add an address to the set of listening addresses. + * + * This is only meaningful (indeed, allowed) for configs in FORWARD_SELECTED mode. + * If this address was already added, this is a no-op. + * @return this builder + */ + @NonNull + public Builder addListeningAddress(@NonNull final Inet6Address address) { + if (FORWARD_SELECTED != mForwardingMode) { + throw new IllegalArgumentException("Can't add an address on a builder in mode " + + modeToString(mForwardingMode)); + } + // TODO : should we check that this is a multicast address ? + mListeningAddresses.add(address); + return this; + } + + /** + * Remove an address from the set of listening addresses. + * + * This is only meaningful (indeed, allowed) for configs in FORWARD_SELECTED mode. + * If this address was not added, or was already removed, this is a no-op. + * @return this builder + */ + @NonNull + public Builder clearListeningAddress(@NonNull final Inet6Address address) { + if (FORWARD_SELECTED != mForwardingMode) { + throw new IllegalArgumentException("Can't remove an address on a builder in mode " + + modeToString(mForwardingMode)); + } + mListeningAddresses.remove(address); + return this; + } + + /** + * Build the config. + */ + @NonNull + public MulticastRoutingConfig build() { + return new MulticastRoutingConfig(mForwardingMode, mMinScope, mListeningAddresses); + } + } + + private static String modeToString(@MulticastForwardingMode final int mode) { + switch (mode) { + case FORWARD_NONE: return "FORWARD_NONE"; + case FORWARD_SELECTED: return "FORWARD_SELECTED"; + case FORWARD_WITH_MIN_SCOPE: return "FORWARD_WITH_MIN_SCOPE"; + default: return "unknown multicast routing mode " + mode; + } + } + + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (!(other instanceof MulticastRoutingConfig)) { + return false; + } else { + final MulticastRoutingConfig otherConfig = (MulticastRoutingConfig) other; + return mForwardingMode == otherConfig.mForwardingMode + && mMinScope == otherConfig.mMinScope + && mListeningAddresses.equals(otherConfig.mListeningAddresses); + } + } + + public int hashCode() { + return Objects.hash(mForwardingMode, mMinScope, mListeningAddresses); + } + + public String toString() { + final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}"); + + resultJoiner.add("ForwardingMode:"); + resultJoiner.add(modeToString(mForwardingMode)); + + if (mForwardingMode == FORWARD_WITH_MIN_SCOPE) { + resultJoiner.add("MinScope:"); + resultJoiner.add(Integer.toString(mMinScope)); + } + + if (mForwardingMode == FORWARD_SELECTED && !mListeningAddresses.isEmpty()) { + resultJoiner.add("ListeningAddresses: ["); + resultJoiner.add(TextUtils.join(",", mListeningAddresses)); + resultJoiner.add("]"); + } + + return resultJoiner.toString(); + } +} diff --git a/framework/src/android/net/NattKeepalivePacketData.java b/framework/src/android/net/NattKeepalivePacketData.java index c4f8fc281f25dc775cf9d3443f3054479e78164c..9e6d80d67ec714c45c79206d4656a527a02f5974 100644 --- a/framework/src/android/net/NattKeepalivePacketData.java +++ b/framework/src/android/net/NattKeepalivePacketData.java @@ -29,7 +29,9 @@ import android.system.OsConstants; import com.android.net.module.util.IpUtils; import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; +import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Objects; @@ -38,6 +40,7 @@ import java.util.Objects; @SystemApi public final class NattKeepalivePacketData extends KeepalivePacketData implements Parcelable { private static final int IPV4_HEADER_LENGTH = 20; + private static final int IPV6_HEADER_LENGTH = 40; private static final int UDP_HEADER_LENGTH = 8; // This should only be constructed via static factory methods, such as @@ -55,39 +58,85 @@ public final class NattKeepalivePacketData extends KeepalivePacketData implement public static NattKeepalivePacketData nattKeepalivePacket( InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort) throws InvalidPacketException { + if (dstPort != NattSocketKeepalive.NATT_PORT) { + throw new InvalidPacketException(ERROR_INVALID_PORT); + } - if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) { + // Convert IPv4 mapped v6 address to v4 if any. + final InetAddress srcAddr, dstAddr; + try { + srcAddr = InetAddress.getByAddress(srcAddress.getAddress()); + dstAddr = InetAddress.getByAddress(dstAddress.getAddress()); + } catch (UnknownHostException e) { throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); } - if (dstPort != NattSocketKeepalive.NATT_PORT) { - throw new InvalidPacketException(ERROR_INVALID_PORT); + if (srcAddr instanceof Inet4Address && dstAddr instanceof Inet4Address) { + return nattKeepalivePacketv4( + (Inet4Address) srcAddr, srcPort, (Inet4Address) dstAddr, dstPort); + } else if (srcAddr instanceof Inet6Address && dstAddr instanceof Inet6Address) { + return nattKeepalivePacketv6( + (Inet6Address) srcAddr, srcPort, (Inet6Address) dstAddr, dstPort); + } else { + // Destination address and source address should be the same IP family. + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); } + } + private static NattKeepalivePacketData nattKeepalivePacketv4( + Inet4Address srcAddress, int srcPort, Inet4Address dstAddress, int dstPort) + throws InvalidPacketException { int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1; - ByteBuffer buf = ByteBuffer.allocate(length); + final ByteBuffer buf = ByteBuffer.allocate(length); buf.order(ByteOrder.BIG_ENDIAN); - buf.putShort((short) 0x4500); // IP version and TOS + buf.putShort((short) 0x4500); // IP version and TOS buf.putShort((short) length); - buf.putInt(0); // ID, flags, offset - buf.put((byte) 64); // TTL + buf.putShort((short) 0); // ID + buf.putShort((short) 0x4000); // Flags(DF), offset + // Technically speaking, this should be reading/using the v4 sysctl + // /proc/sys/net/ipv4/ip_default_ttl. Use hard-coded 64 for simplicity. + buf.put((byte) 64); // TTL buf.put((byte) OsConstants.IPPROTO_UDP); - int ipChecksumOffset = buf.position(); - buf.putShort((short) 0); // IP checksum + final int ipChecksumOffset = buf.position(); + buf.putShort((short) 0); // IP checksum buf.put(srcAddress.getAddress()); buf.put(dstAddress.getAddress()); buf.putShort((short) srcPort); buf.putShort((short) dstPort); - buf.putShort((short) (length - 20)); // UDP length - int udpChecksumOffset = buf.position(); - buf.putShort((short) 0); // UDP checksum - buf.put((byte) 0xff); // NAT-T keepalive + buf.putShort((short) (UDP_HEADER_LENGTH + 1)); // UDP length + final int udpChecksumOffset = buf.position(); + buf.putShort((short) 0); // UDP checksum + buf.put((byte) 0xff); // NAT-T keepalive buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0)); buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH)); return new NattKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array()); } + private static NattKeepalivePacketData nattKeepalivePacketv6( + Inet6Address srcAddress, int srcPort, Inet6Address dstAddress, int dstPort) + throws InvalidPacketException { + final ByteBuffer buf = ByteBuffer.allocate(IPV6_HEADER_LENGTH + UDP_HEADER_LENGTH + 1); + buf.order(ByteOrder.BIG_ENDIAN); + buf.putInt(0x60000000); // IP version, traffic class and flow label + buf.putShort((short) (UDP_HEADER_LENGTH + 1)); // Payload length + buf.put((byte) OsConstants.IPPROTO_UDP); // Next header + // For native ipv6, this hop limit value should use the per interface v6 hoplimit sysctl. + // For 464xlat, this value should use the v4 ttl sysctl. + // Either way, for simplicity, just hard code 64. + buf.put((byte) 64); // Hop limit + buf.put(srcAddress.getAddress()); + buf.put(dstAddress.getAddress()); + // UDP + buf.putShort((short) srcPort); + buf.putShort((short) dstPort); + buf.putShort((short) (UDP_HEADER_LENGTH + 1)); // UDP length = Payload length + final int udpChecksumOffset = buf.position(); + buf.putShort((short) 0); // UDP checksum + buf.put((byte) 0xff); // NAT-T keepalive. 1 byte of data + buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV6_HEADER_LENGTH)); + return new NattKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array()); + } /** Parcelable Implementation */ public int describeContents() { return 0; diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java index 177f7e3d171ca903db083c2c1e8fdbb064fa801d..574ab2fe1b179fb820c2a70a2fd75f1c67618b0f 100644 --- a/framework/src/android/net/NetworkAgent.java +++ b/framework/src/android/net/NetworkAgent.java @@ -151,7 +151,7 @@ public abstract class NetworkAgent { /** * Sent by the NetworkAgent to ConnectivityService to pass the current - * NetworkCapabilties. + * NetworkCapabilities. * obj = NetworkCapabilities * @hide */ @@ -442,6 +442,14 @@ public abstract class NetworkAgent { */ public static final int EVENT_UNREGISTER_AFTER_REPLACEMENT = BASE + 29; + /** + * Sent by the NetworkAgent to ConnectivityService to pass the new value of the local + * network agent config. + * obj = {@code Pair} + * @hide + */ + public static final int EVENT_LOCAL_NETWORK_CONFIG_CHANGED = BASE + 30; + /** * DSCP policy was successfully added. */ @@ -517,20 +525,47 @@ public abstract class NetworkAgent { @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) { - this(looper, context, logTag, nc, lp, score, config, + this(context, looper, logTag, nc, lp, null /* localNetworkConfig */, score, config, + provider); + } + + /** + * Create a new network agent. + * @param context a {@link Context} to get system services from. + * @param looper the {@link Looper} on which to invoke the callbacks. + * @param logTag the tag for logs + * @param nc the initial {@link NetworkCapabilities} of this network. Update with + * sendNetworkCapabilities. + * @param lp the initial {@link LinkProperties} of this network. Update with sendLinkProperties. + * @param localNetworkConfig the initial {@link LocalNetworkConfig} of this + * network. Update with sendLocalNetworkConfig. Must be + * non-null iff the nc have NET_CAPABILITY_LOCAL_NETWORK. + * @param score the initial score of this network. Update with sendNetworkScore. + * @param config an immutable {@link NetworkAgentConfig} for this agent. + * @param provider the {@link NetworkProvider} managing this agent. + * @hide + */ + // TODO : expose + public NetworkAgent(@NonNull Context context, @NonNull Looper looper, @NonNull String logTag, + @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, + @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score, + @NonNull NetworkAgentConfig config, @Nullable NetworkProvider provider) { + this(looper, context, logTag, nc, lp, localNetworkConfig, score, config, provider == null ? NetworkProvider.ID_NONE : provider.getProviderId(), getLegacyNetworkInfo(config)); } private static class InitialConfiguration { - public final Context context; - public final NetworkCapabilities capabilities; - public final LinkProperties properties; - public final NetworkScore score; - public final NetworkAgentConfig config; - public final NetworkInfo info; + @NonNull public final Context context; + @NonNull public final NetworkCapabilities capabilities; + @NonNull public final LinkProperties properties; + @NonNull public final NetworkScore score; + @NonNull public final NetworkAgentConfig config; + @NonNull public final NetworkInfo info; + @Nullable public final LocalNetworkConfig localNetworkConfig; InitialConfiguration(@NonNull Context context, @NonNull NetworkCapabilities capabilities, - @NonNull LinkProperties properties, @NonNull NetworkScore score, + @NonNull LinkProperties properties, + @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, @NonNull NetworkInfo info) { this.context = context; this.capabilities = capabilities; @@ -538,14 +573,15 @@ public abstract class NetworkAgent { this.score = score; this.config = config; this.info = info; + this.localNetworkConfig = localNetworkConfig; } } private volatile InitialConfiguration mInitialConfiguration; private NetworkAgent(@NonNull Looper looper, @NonNull Context context, @NonNull String logTag, @NonNull NetworkCapabilities nc, @NonNull LinkProperties lp, - @NonNull NetworkScore score, @NonNull NetworkAgentConfig config, int providerId, - @NonNull NetworkInfo ni) { + @Nullable LocalNetworkConfig localNetworkConfig, @NonNull NetworkScore score, + @NonNull NetworkAgentConfig config, int providerId, @NonNull NetworkInfo ni) { mHandler = new NetworkAgentHandler(looper); LOG_TAG = logTag; mNetworkInfo = new NetworkInfo(ni); @@ -556,7 +592,7 @@ public abstract class NetworkAgent { mInitialConfiguration = new InitialConfiguration(context, new NetworkCapabilities(nc, NetworkCapabilities.REDACT_NONE), - new LinkProperties(lp), score, config, ni); + new LinkProperties(lp), localNetworkConfig, score, config, ni); } private class NetworkAgentHandler extends Handler { @@ -720,10 +756,20 @@ public abstract class NetworkAgent { } final ConnectivityManager cm = (ConnectivityManager) mInitialConfiguration.context .getSystemService(Context.CONNECTIVITY_SERVICE); - mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler), - new NetworkInfo(mInitialConfiguration.info), - mInitialConfiguration.properties, mInitialConfiguration.capabilities, - mInitialConfiguration.score, mInitialConfiguration.config, providerId); + if (mInitialConfiguration.localNetworkConfig == null) { + // Call registerNetworkAgent without localNetworkConfig argument to pass + // android.net.cts.NetworkAgentTest#testAgentStartsInConnecting in old cts + mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler), + new NetworkInfo(mInitialConfiguration.info), + mInitialConfiguration.properties, mInitialConfiguration.capabilities, + mInitialConfiguration.score, mInitialConfiguration.config, providerId); + } else { + mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler), + new NetworkInfo(mInitialConfiguration.info), + mInitialConfiguration.properties, mInitialConfiguration.capabilities, + mInitialConfiguration.localNetworkConfig, mInitialConfiguration.score, + mInitialConfiguration.config, providerId); + } mInitialConfiguration = null; // All this memory can now be GC'd } return mNetwork; @@ -1098,6 +1144,18 @@ public abstract class NetworkAgent { queueOrSendMessage(reg -> reg.sendNetworkCapabilities(nc)); } + /** + * Must be called by the agent when the network's {@link LocalNetworkConfig} changes. + * @param config the new LocalNetworkConfig + * @hide + */ + public void sendLocalNetworkConfig(@NonNull LocalNetworkConfig config) { + Objects.requireNonNull(config); + // If the agent doesn't have NET_CAPABILITY_LOCAL_NETWORK, this will be ignored by + // ConnectivityService with a Log.wtf. + queueOrSendMessage(reg -> reg.sendLocalNetworkConfig(config)); + } + /** * Must be called by the agent to update the score of this network. * diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 92e95995030a6b2aa3b8490cf041608c6e50491b..efae754caa5d20c8ca6629d8819ddae1b2cc3e9a 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -29,6 +29,9 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.net.ConnectivityManager.NetworkCallback; +// Can't be imported because aconfig tooling doesn't exist on udc-mainline-prod yet +// See inner class Flags which mimics this for the time being +// import android.net.flags.Flags; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -121,6 +124,14 @@ import java.util.StringJoiner; public final class NetworkCapabilities implements Parcelable { private static final String TAG = "NetworkCapabilities"; + // TODO : remove this class when udc-mainline-prod is abandoned and android.net.flags.Flags is + // available here + /** @hide */ + public static class Flags { + static final String FLAG_FORBIDDEN_CAPABILITY = + "com.android.net.flags.forbidden_capability"; + } + /** * Mechanism to support redaction of fields in NetworkCapabilities that are guarded by specific * app permissions. @@ -259,6 +270,19 @@ public final class NetworkCapabilities implements Parcelable { */ private int mEnterpriseId; + /** + * Gets the enterprise IDs as an int. Internal callers only. + * + * DO NOT USE THIS if not immediately collapsing back into a scalar. Instead, + * prefer getEnterpriseIds/hasEnterpriseId. + * + * @return the internal, version-dependent int representing enterprise ids + * @hide + */ + public int getEnterpriseIdsInternal() { + return mEnterpriseId; + } + /** * Get enteprise identifiers set. * @@ -429,6 +453,7 @@ public final class NetworkCapabilities implements Parcelable { NET_CAPABILITY_MMTEL, NET_CAPABILITY_PRIORITIZE_LATENCY, NET_CAPABILITY_PRIORITIZE_BANDWIDTH, + NET_CAPABILITY_LOCAL_NETWORK, }) public @interface NetCapability { } @@ -690,17 +715,24 @@ public final class NetworkCapabilities implements Parcelable { */ public static final int NET_CAPABILITY_PRIORITIZE_BANDWIDTH = 35; - private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS; - private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PRIORITIZE_BANDWIDTH; + /** + * This is a local network, e.g. a tethering downstream or a P2P direct network. + * + *

+ * Note that local networks are not sent to callbacks by default. To receive callbacks about + * them, the {@link NetworkRequest} instance must be prepared to see them, either by + * adding the capability with {@link NetworkRequest.Builder#addCapability}, by removing + * this forbidden capability with {@link NetworkRequest.Builder#removeForbiddenCapability}, + * or by clearing all capabilites with {@link NetworkRequest.Builder#clearCapabilities()}. + *

+ * @hide + */ + public static final int NET_CAPABILITY_LOCAL_NETWORK = 36; - private static final int ALL_VALID_CAPABILITIES; - static { - int caps = 0; - for (int i = MIN_NET_CAPABILITY; i <= MAX_NET_CAPABILITY; ++i) { - caps |= 1 << i; - } - ALL_VALID_CAPABILITIES = caps; - } + private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_LOCAL_NETWORK; + + // Set all bits up to the MAX_NET_CAPABILITY-th bit + private static final long ALL_VALID_CAPABILITIES = (2L << MAX_NET_CAPABILITY) - 1; /** * Network capabilities that are expected to be mutable, i.e., can change while a particular @@ -748,8 +780,10 @@ public final class NetworkCapabilities implements Parcelable { /** * Capabilities that are managed by ConnectivityService. + * @hide */ - private static final long CONNECTIVITY_MANAGED_CAPABILITIES = + @VisibleForTesting + public static final long CONNECTIVITY_MANAGED_CAPABILITIES = BitUtils.packBitList( NET_CAPABILITY_VALIDATED, NET_CAPABILITY_CAPTIVE_PORTAL, @@ -785,6 +819,10 @@ public final class NetworkCapabilities implements Parcelable { * Adds the given capability to this {@code NetworkCapability} instance. * Note that when searching for a network to satisfy a request, all capabilities * requested must be satisfied. + *

+ * If the capability was previously added to the list of forbidden capabilities (either + * by default or added using {@link #addForbiddenCapability(int)}), then it will be removed + * from the list of forbidden capabilities as well. * * @param capability the capability to be added. * @return This NetworkCapabilities instance, to facilitate chaining. @@ -793,8 +831,7 @@ public final class NetworkCapabilities implements Parcelable { public @NonNull NetworkCapabilities addCapability(@NetCapability int capability) { // If the given capability was previously added to the list of forbidden capabilities // then the capability will also be removed from the list of forbidden capabilities. - // TODO: Consider adding forbidden capabilities to the public API and mention this - // in the documentation. + // TODO: Add forbidden capabilities to the public API checkValidCapability(capability); mNetworkCapabilities |= 1L << capability; // remove from forbidden capability list @@ -837,7 +874,7 @@ public final class NetworkCapabilities implements Parcelable { } /** - * Removes (if found) the given forbidden capability from this {@code NetworkCapability} + * Removes (if found) the given forbidden capability from this {@link NetworkCapabilities} * instance that were added via addForbiddenCapability(int) or setCapabilities(int[], int[]). * * @param capability the capability to be removed. @@ -850,6 +887,16 @@ public final class NetworkCapabilities implements Parcelable { return this; } + /** + * Removes all forbidden capabilities from this {@link NetworkCapabilities} instance. + * @return This NetworkCapabilities instance, to facilitate chaining. + * @hide + */ + public @NonNull NetworkCapabilities removeAllForbiddenCapabilities() { + mForbiddenNetworkCapabilities = 0; + return this; + } + /** * Sets (or clears) the given capability on this {@link NetworkCapabilities} * instance. @@ -865,6 +912,19 @@ public final class NetworkCapabilities implements Parcelable { return this; } + /** + * Gets the capabilities as an int. Internal callers only. + * + * DO NOT USE THIS if not immediately collapsing back into a scalar. Instead, + * prefer getCapabilities/hasCapability. + * + * @return an internal, version-dependent int representing the capabilities + * @hide + */ + public long getCapabilitiesInternal() { + return mNetworkCapabilities; + } + /** * Gets all the capabilities set on this {@code NetworkCapability} instance. * @@ -880,6 +940,8 @@ public final class NetworkCapabilities implements Parcelable { * @return an array of forbidden capability values for this instance. * @hide */ + @NonNull + // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public public @NetCapability int[] getForbiddenCapabilities() { return BitUtils.unpackBits(mForbiddenNetworkCapabilities); } @@ -979,7 +1041,7 @@ public final class NetworkCapabilities implements Parcelable { /** * Tests for the presence of a capability on this instance. * - * @param capability the capabilities to be tested for. + * @param capability the capability to be tested for. * @return {@code true} if set on this instance. */ public boolean hasCapability(@NetCapability int capability) { @@ -987,19 +1049,27 @@ public final class NetworkCapabilities implements Parcelable { && ((mNetworkCapabilities & (1L << capability)) != 0); } - /** @hide */ + /** + * Tests for the presence of a forbidden capability on this instance. + * + * @param capability the capability to be tested for. + * @return {@code true} if this capability is set forbidden on this instance. + * @hide + */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public public boolean hasForbiddenCapability(@NetCapability int capability) { return isValidCapability(capability) && ((mForbiddenNetworkCapabilities & (1L << capability)) != 0); } /** - * Check if this NetworkCapabilities has system managed capabilities or not. + * Check if this NetworkCapabilities has connectivity-managed capabilities or not. * @hide */ public boolean hasConnectivityManagedCapability() { - return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0); + return (mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0 + || mForbiddenNetworkCapabilities != 0; } /** @@ -1387,6 +1457,18 @@ public final class NetworkCapabilities implements Parcelable { return mTransportTypes == (1 << transportType); } + /** + * Returns true iff this NC has the specified transport and no other, ignoring TRANSPORT_TEST. + * + * If this NC has the passed transport and no other, this method returns true. + * If this NC has the passed transport, TRANSPORT_TEST and no other, this method returns true. + * Otherwise, this method returns false. + * @hide + */ + public boolean hasSingleTransportBesidesTest(@Transport int transportType) { + return (mTransportTypes & ~(1 << TRANSPORT_TEST)) == (1 << transportType); + } + private boolean satisfiedByTransportTypes(NetworkCapabilities nc) { return ((this.mTransportTypes == 0) || ((this.mTransportTypes & nc.mTransportTypes) != 0)); @@ -2479,6 +2561,7 @@ public final class NetworkCapabilities implements Parcelable { case NET_CAPABILITY_MMTEL: return "MMTEL"; case NET_CAPABILITY_PRIORITIZE_LATENCY: return "PRIORITIZE_LATENCY"; case NET_CAPABILITY_PRIORITIZE_BANDWIDTH: return "PRIORITIZE_BANDWIDTH"; + case NET_CAPABILITY_LOCAL_NETWORK: return "LOCAL_NETWORK"; default: return Integer.toString(capability); } } @@ -2519,7 +2602,7 @@ public final class NetworkCapabilities implements Parcelable { } private static boolean isValidCapability(@NetworkCapabilities.NetCapability int capability) { - return capability >= MIN_NET_CAPABILITY && capability <= MAX_NET_CAPABILITY; + return capability >= 0 && capability <= MAX_NET_CAPABILITY; } private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) { @@ -2867,6 +2950,44 @@ public final class NetworkCapabilities implements Parcelable { return this; } + /** + * Adds the given capability to the list of forbidden capabilities. + * + * A network with a capability will not match a {@link NetworkCapabilities} or + * {@link NetworkRequest} which has said capability set as forbidden. For example, if + * a request has NET_CAPABILITY_INTERNET in the list of forbidden capabilities, networks + * with NET_CAPABILITY_INTERNET will not match the request. + * + * If the capability was previously added to the list of required capabilities (for + * example, it was there by default or added using {@link #addCapability(int)} method), then + * it will be removed from the list of required capabilities as well. + * + * @param capability the capability + * @return this builder + * @hide + */ + @NonNull + // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public + public Builder addForbiddenCapability(@NetCapability final int capability) { + mCaps.addForbiddenCapability(capability); + return this; + } + + /** + * Removes the given capability from the list of forbidden capabilities. + * + * @see #addForbiddenCapability(int) + * @param capability the capability + * @return this builder + * @hide + */ + @NonNull + // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public + public Builder removeForbiddenCapability(@NetCapability final int capability) { + mCaps.removeForbiddenCapability(capability); + return this; + } + /** * Adds the given enterprise capability identifier. * Note that when searching for a network to satisfy a request, all capabilities identifier @@ -3214,4 +3335,4 @@ public final class NetworkCapabilities implements Parcelable { return new NetworkCapabilities(mCaps); } } -} \ No newline at end of file +} diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 6c351d0548a22ff9390acb8d1417bb21d07788bb..653e41dc3b657400017d5c92fc5c3d1589361a1e 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -20,6 +20,7 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; @@ -39,6 +40,8 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; +// TODO : replace with android.net.flags.Flags when aconfig is supported on udc-mainline-prod +// import android.net.NetworkCapabilities.Flags; import android.net.NetworkCapabilities.NetCapability; import android.net.NetworkCapabilities.Transport; import android.os.Build; @@ -281,6 +284,18 @@ public class NetworkRequest implements Parcelable { NET_CAPABILITY_TRUSTED, NET_CAPABILITY_VALIDATED); + /** + * Capabilities that are forbidden by default. + * Forbidden capabilities only make sense in NetworkRequest, not for network agents. + * Therefore these capabilities are only in NetworkRequest. + */ + private static final int[] DEFAULT_FORBIDDEN_CAPABILITIES = new int[] { + // TODO(b/313030307): this should contain NET_CAPABILITY_LOCAL_NETWORK. + // We cannot currently add it because doing so would crash if the module rolls back, + // because JobScheduler persists NetworkRequests to disk, and existing production code + // does not consider LOCAL_NETWORK to be a valid capability. + }; + private final NetworkCapabilities mNetworkCapabilities; // A boolean that represents whether the NOT_VCN_MANAGED capability should be deduced when @@ -296,6 +311,16 @@ public class NetworkRequest implements Parcelable { // it for apps that do not have the NETWORK_SETTINGS permission. mNetworkCapabilities = new NetworkCapabilities(); mNetworkCapabilities.setSingleUid(Process.myUid()); + // Default forbidden capabilities are foremost meant to help with backward + // compatibility. When adding new types of network identified by a capability that + // might confuse older apps, a default forbidden capability will have apps not see + // these networks unless they explicitly ask for it. + // If the app called clearCapabilities() it will see everything, but then it + // can be argued that it's fair to send them too, since it asked for everything + // explicitly. + for (final int forbiddenCap : DEFAULT_FORBIDDEN_CAPABILITIES) { + mNetworkCapabilities.addForbiddenCapability(forbiddenCap); + } } /** @@ -408,6 +433,7 @@ public class NetworkRequest implements Parcelable { @NonNull @SuppressLint("MissingGetterMatchingBuilder") @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public public Builder addForbiddenCapability(@NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.addForbiddenCapability(capability); return this; @@ -424,6 +450,7 @@ public class NetworkRequest implements Parcelable { @NonNull @SuppressLint("BuilderSetStyle") @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public public Builder removeForbiddenCapability( @NetworkCapabilities.NetCapability int capability) { mNetworkCapabilities.removeForbiddenCapability(capability); @@ -433,6 +460,7 @@ public class NetworkRequest implements Parcelable { /** * Completely clears all the {@code NetworkCapabilities} from this builder instance, * removing even the capabilities that are set by default when the object is constructed. + * Also removes any set forbidden capabilities. * * @return The builder to facilitate chaining. */ @@ -721,6 +749,7 @@ public class NetworkRequest implements Parcelable { * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public instead of @SystemApi public boolean hasForbiddenCapability(@NetCapability int capability) { return networkCapabilities.hasForbiddenCapability(capability); } @@ -843,6 +872,7 @@ public class NetworkRequest implements Parcelable { */ @NonNull @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + // TODO : @FlaggedApi(Flags.FLAG_FORBIDDEN_CAPABILITY) and public instead of @SystemApi public @NetCapability int[] getForbiddenCapabilities() { // No need to make a defensive copy here as NC#getForbiddenCapabilities() already returns // a new array. diff --git a/framework/src/android/net/NetworkScore.java b/framework/src/android/net/NetworkScore.java index 815e2b08e888f8453055481451d3f09434c0018d..935dea134834da01c363aac6ee29b29263c9c522 100644 --- a/framework/src/android/net/NetworkScore.java +++ b/framework/src/android/net/NetworkScore.java @@ -44,7 +44,9 @@ public final class NetworkScore implements Parcelable { @Retention(RetentionPolicy.SOURCE) @IntDef(value = { KEEP_CONNECTED_NONE, - KEEP_CONNECTED_FOR_HANDOVER + KEEP_CONNECTED_FOR_HANDOVER, + KEEP_CONNECTED_FOR_TEST, + KEEP_CONNECTED_LOCAL_NETWORK }) public @interface KeepConnectedReason { } @@ -57,6 +59,18 @@ public final class NetworkScore implements Parcelable { * is being considered for handover. */ public static final int KEEP_CONNECTED_FOR_HANDOVER = 1; + /** + * Keep this network connected even if there is no outstanding request for it, because it + * is used in a test and it's not necessarily easy to file the right request for it. + * @hide + */ + public static final int KEEP_CONNECTED_FOR_TEST = 2; + /** + * Keep this network connected even if there is no outstanding request for it, because + * it is a local network. + * @hide + */ + public static final int KEEP_CONNECTED_LOCAL_NETWORK = 3; // Agent-managed policies // This network should lose to a wifi that has ever been validated diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java index 2679b6218c1975dd7e6d375ebf2c6f2246824dbf..fbdc024340994fb66cc909e4d62a84080d244909 100644 --- a/framework/src/android/net/NetworkUtils.java +++ b/framework/src/android/net/NetworkUtils.java @@ -426,4 +426,16 @@ public class NetworkUtils { return routedIPCount; } + /** + * Sets a socket option with byte array + * + * @param fd The socket file descriptor + * @param level The level at which the option is defined + * @param option The socket option for which the value is to be set + * @param value The option value to be set in byte array + * @throws ErrnoException if setsockopt fails + */ + public static native void setsockoptBytes(FileDescriptor fd, int level, int option, + byte[] value) throws ErrnoException; + } diff --git a/framework/src/android/net/QosSession.java b/framework/src/android/net/QosSession.java index 25f3965f1e06f2a24d055bcaf53348e256ece009..d1edae998ba8d0ea54773c90edfe78f3fcf6896c 100644 --- a/framework/src/android/net/QosSession.java +++ b/framework/src/android/net/QosSession.java @@ -22,6 +22,9 @@ import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Provides identifying information of a QoS session. Sent to an application through * {@link QosCallback}. @@ -107,6 +110,7 @@ public final class QosSession implements Parcelable { TYPE_EPS_BEARER, TYPE_NR_BEARER, }) + @Retention(RetentionPolicy.SOURCE) @interface QosSessionType {} private QosSession(final Parcel in) { diff --git a/framework/src/android/net/RouteInfo.java b/framework/src/android/net/RouteInfo.java index df5f151a3f78a874fd39630bc92fd350fba1e587..e8ebf8137ace2502067198b033a615833136e628 100644 --- a/framework/src/android/net/RouteInfo.java +++ b/framework/src/android/net/RouteInfo.java @@ -584,7 +584,7 @@ public final class RouteInfo implements Parcelable { } RouteKey p = (RouteKey) o; // No need to do anything special for scoped addresses. Inet6Address#equals does not - // consider the scope ID, but the netd route IPCs (e.g., INetd#networkAddRouteParcel) + // consider the scope ID, but the route IPCs (e.g., RoutingCoordinatorManager#addRoute) // and the kernel ignore scoped addresses both in the prefix and in the nexthop and only // look at RTA_OIF. return Objects.equals(p.mDestination, mDestination) diff --git a/framework/src/android/net/RoutingCoordinatorManager.java b/framework/src/android/net/RoutingCoordinatorManager.java new file mode 100644 index 0000000000000000000000000000000000000000..a9e7eef51e2c5381dea9cedb4eeffdc79fb4e633 --- /dev/null +++ b/framework/src/android/net/RoutingCoordinatorManager.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2023 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.net; + +import android.content.Context; +import android.os.Build; +import android.os.RemoteException; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +/** + * A manager class for talking to the routing coordinator service. + * + * This class should only be used by the connectivity and tethering module. This is enforced + * by the build rules. Do not change build rules to gain access to this class from elsewhere. + * @hide + */ +@RequiresApi(Build.VERSION_CODES.S) +public class RoutingCoordinatorManager { + @NonNull final Context mContext; + @NonNull final IRoutingCoordinator mService; + + public RoutingCoordinatorManager(@NonNull final Context context, + @NonNull final IRoutingCoordinator service) { + mContext = context; + mService = service; + } + + /** + * Add a route for specific network + * + * @param netId the network to add the route to + * @param route the route to add + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + public void addRoute(final int netId, final RouteInfo route) { + try { + mService.addRoute(netId, route); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove a route for specific network + * + * @param netId the network to remove the route from + * @param route the route to remove + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + public void removeRoute(final int netId, final RouteInfo route) { + try { + mService.removeRoute(netId, route); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Update a route for specific network + * + * @param netId the network to update the route for + * @param route parcelable with route information + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + public void updateRoute(final int netId, final RouteInfo route) { + try { + mService.updateRoute(netId, route); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Adds an interface to a network. The interface must not be assigned to any network, including + * the specified network. + * + * @param netId the network to add the interface to. + * @param iface the name of the interface to add. + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + public void addInterfaceToNetwork(final int netId, final String iface) { + try { + mService.addInterfaceToNetwork(netId, iface); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Removes an interface from a network. The interface must be assigned to the specified network. + * + * @param netId the network to remove the interface from. + * @param iface the name of the interface to remove. + * + * @throws ServiceSpecificException in case of failure, with an error code corresponding to the + * unix errno. + */ + public void removeInterfaceFromNetwork(final int netId, final String iface) { + try { + mService.removeInterfaceFromNetwork(netId, iface); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Add forwarding ip rule + * + * @param fromIface interface name to add forwarding ip rule + * @param toIface interface name to add forwarding ip rule + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + public void addInterfaceForward(final String fromIface, final String toIface) { + try { + mService.addInterfaceForward(fromIface, toIface); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove forwarding ip rule + * + * @param fromIface interface name to remove forwarding ip rule + * @param toIface interface name to remove forwarding ip rule + * @throws ServiceSpecificException in case of failure, with an error code indicating the + * cause of the failure. + */ + public void removeInterfaceForward(final String fromIface, final String toIface) { + try { + mService.removeInterfaceForward(fromIface, toIface); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/service/src/com/android/server/UidOwnerValue.java b/framework/src/android/net/UidOwnerValue.java similarity index 86% rename from service/src/com/android/server/UidOwnerValue.java rename to framework/src/android/net/UidOwnerValue.java index d6c0e0ddf465142df9c687725370ffaba61ac4b0..e8ae604b637ae78a348b284ce200bad265db4a24 100644 --- a/service/src/com/android/server/UidOwnerValue.java +++ b/framework/src/android/net/UidOwnerValue.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 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. @@ -14,11 +14,15 @@ * limitations under the License. */ -package com.android.server; +package android.net; import com.android.net.module.util.Struct; -/** Value type for per uid traffic control configuration map */ +/** + * Value type for per uid traffic control configuration map. + * + * @hide + */ public class UidOwnerValue extends Struct { // Allowed interface index. Only applicable if IIF_MATCH is set in the rule bitmask below. @Field(order = 0, type = Type.S32) diff --git a/framework/src/android/net/connectivity/TiramisuConnectivityInternalApiUtil.java b/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java similarity index 74% rename from framework/src/android/net/connectivity/TiramisuConnectivityInternalApiUtil.java rename to framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java index d65858f9ab7730b40e3389165a367cc4dba60220..79f1f6530374d3e5899bf7b11fe697c19dc61e2d 100644 --- a/framework/src/android/net/connectivity/TiramisuConnectivityInternalApiUtil.java +++ b/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java @@ -18,6 +18,7 @@ package android.net.connectivity; import android.content.Context; import android.net.ConnectivityManager; +import android.net.RoutingCoordinatorManager; import android.os.Build; import android.os.IBinder; @@ -34,15 +35,27 @@ import androidx.annotation.RequiresApi; * linter). * @hide */ -@RequiresApi(Build.VERSION_CODES.TIRAMISU) -public class TiramisuConnectivityInternalApiUtil { +@RequiresApi(Build.VERSION_CODES.S) +public class ConnectivityInternalApiUtil { /** * Get a service binder token for * {@link com.android.server.connectivity.wear.CompanionDeviceManagerProxyService}. */ + @RequiresApi(Build.VERSION_CODES.TIRAMISU) public static IBinder getCompanionDeviceManagerProxyService(Context ctx) { final ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class); return cm.getCompanionDeviceManagerProxyService(); } + + /** + * Obtain a routing coordinator manager from a context, possibly cross-module. + * @param ctx the context + * @return an instance of the coordinator manager + */ + @RequiresApi(Build.VERSION_CODES.S) + public static RoutingCoordinatorManager getRoutingCoordinatorManager(Context ctx) { + final ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class); + return cm.getRoutingCoordinatorManager(); + } } diff --git a/nearby/README.md b/nearby/README.md index 845188245b33588455bfd44bc4406d89bdb94e14..0d265638ba04e5a4fbec56bfc7722679390ed57d 100644 --- a/nearby/README.md +++ b/nearby/README.md @@ -47,12 +47,20 @@ Then, add the jar in IDE as below. ## Build and Install ```sh -$ source build/envsetup.sh && lunch -$ m com.google.android.tethering.next deapexer -$ $ANDROID_BUILD_TOP/out/host/linux-x86/bin/deapexer decompress --input \ - ${ANDROID_PRODUCT_OUT}/system/apex/com.google.android.tethering.next.capex \ - --output /tmp/tethering.apex -$ adb install -r /tmp/tethering.apex +Build unbundled module using banchan + +$ source build/envsetup.sh +$ banchan com.google.android.tethering mainline_modules_arm64 +$ m apps_only dist +$ adb install out/dist/com.google.android.tethering.apex +$ adb reboot +Ensure that the module you are installing is compatible with the module currently preloaded on the phone (in /system/apex/com.google.android.tethering.apex). Compatible means: + +1. Same package name +2. Same keys used to sign the apex and the payload +3. Higher version + +See go/mainline-local-build#build-install-local-module for more information ``` ## Build and Install from tm-mainline-prod branch @@ -63,7 +71,7 @@ When build and flash the APEX from tm-mainline-prod, you may see the error below This is because the device is flashed with AOSP built from master or other branches, which has prebuilt APEX with higher version. We can use root access to replace the prebuilt APEX with the APEX built from tm-mainline-prod as below. -1. adb root && adb remount; adb shell mount -orw,remount /system/apex +1. adb root && adb remount -R 2. cp tethering.next.apex com.google.android.tethering.apex 3. adb push com.google.android.tethering.apex /system/apex/ 4. adb reboot diff --git a/nearby/TEST_MAPPING b/nearby/TEST_MAPPING index dbaca33b812716350b2cea3d0da0b21fdb751da8..7e9a375c70abfad40a894e2d5baac2eda711b588 100644 --- a/nearby/TEST_MAPPING +++ b/nearby/TEST_MAPPING @@ -1,5 +1,10 @@ { "presubmit": [ + { + "name": "NearbyUnitTests" + } + ], + "postsubmit": [ { "name": "NearbyUnitTests" }, @@ -9,11 +14,6 @@ { "name": "NearbyIntegrationUntrustedTests" } - ], - "postsubmit": [ - { - "name": "NearbyUnitTests" - } ] // TODO(b/193602229): uncomment once it's supported. //"mainline-presubmit": [ diff --git a/nearby/framework/Android.bp b/nearby/framework/Android.bp index a8a6eaa9f51dcf938d378404ef682d2c67af49b5..4bb9efdb8114ef95684badf82154386c59a230d7 100644 --- a/nearby/framework/Android.bp +++ b/nearby/framework/Android.bp @@ -26,6 +26,7 @@ filegroup { ], path: "java", visibility: [ + "//packages/modules/Connectivity/framework:__subpackages__", "//packages/modules/Connectivity/framework-t:__subpackages__", ], } @@ -53,6 +54,6 @@ java_library { "modules-utils-preconditions", ], visibility: [ - "//packages/modules/Connectivity/nearby/tests:__subpackages__", - "//packages/modules/Connectivity/nearby/halfsheet:__subpackages__"], + "//packages/modules/Connectivity/nearby/tests:__subpackages__", + ], } diff --git a/nearby/framework/java/android/nearby/BroadcastRequest.java b/nearby/framework/java/android/nearby/BroadcastRequest.java index 90f4d0f14ffcb2e3e5abc3906685c27ec348b3d6..6d6357db34cc218ed3df084a6ad522b189fae107 100644 --- a/nearby/framework/java/android/nearby/BroadcastRequest.java +++ b/nearby/framework/java/android/nearby/BroadcastRequest.java @@ -88,6 +88,7 @@ public abstract class BroadcastRequest { * @hide */ @IntDef({MEDIUM_BLE}) + @Retention(RetentionPolicy.SOURCE) public @interface Medium {} /** diff --git a/nearby/framework/java/android/nearby/DataElement.java b/nearby/framework/java/android/nearby/DataElement.java index 10c11322ba0e23193b4ae130e2005ac032fbda88..8f032bf5b95ac896215284c5a2a9b533e2e43a71 100644 --- a/nearby/framework/java/android/nearby/DataElement.java +++ b/nearby/framework/java/android/nearby/DataElement.java @@ -39,10 +39,15 @@ public final class DataElement implements Parcelable { private final int mKey; private final byte[] mValue; - /** @hide */ + /** + * Note this interface is used for internal implementation only. + * We only keep those data element types used for encoding and decoding from the specs. + * Please read the nearby specs for learning more about each data type and use it as the only + * source. + * + * @hide + */ @IntDef({ - DataType.BLE_SERVICE_DATA, - DataType.BLE_ADDRESS, DataType.SALT, DataType.PRIVATE_IDENTITY, DataType.TRUSTED_IDENTITY, @@ -50,20 +55,17 @@ public final class DataElement implements Parcelable { DataType.PROVISIONED_IDENTITY, DataType.TX_POWER, DataType.ACTION, - DataType.MODEL_ID, - DataType.EDDYSTONE_EPHEMERAL_IDENTIFIER, DataType.ACCOUNT_KEY_DATA, DataType.CONNECTION_STATUS, DataType.BATTERY, + DataType.ENCRYPTION_INFO, + DataType.BLE_SERVICE_DATA, + DataType.BLE_ADDRESS, DataType.SCAN_MODE, DataType.TEST_DE_BEGIN, DataType.TEST_DE_END }) public @interface DataType { - int BLE_SERVICE_DATA = 100; - int BLE_ADDRESS = 101; - // This is to indicate if the scan is offload only - int SCAN_MODE = 102; int SALT = 0; int PRIVATE_IDENTITY = 1; int TRUSTED_IDENTITY = 2; @@ -71,11 +73,19 @@ public final class DataElement implements Parcelable { int PROVISIONED_IDENTITY = 4; int TX_POWER = 5; int ACTION = 6; - int MODEL_ID = 7; - int EDDYSTONE_EPHEMERAL_IDENTIFIER = 8; int ACCOUNT_KEY_DATA = 9; int CONNECTION_STATUS = 10; int BATTERY = 11; + + int ENCRYPTION_INFO = 16; + + // Not defined in the spec. Reserved for internal use only. + int BLE_SERVICE_DATA = 100; + int BLE_ADDRESS = 101; + // This is to indicate if the scan is offload only + int SCAN_MODE = 102; + + int DEVICE_TYPE = 22; // Reserves test DE ranges from {@link DataElement.DataType#TEST_DE_BEGIN} // to {@link DataElement.DataType#TEST_DE_END}, inclusive. // Reserves 128 Test DEs. @@ -83,27 +93,6 @@ public final class DataElement implements Parcelable { int TEST_DE_END = Integer.MAX_VALUE; // 2147483647 } - /** - * @hide - */ - public static boolean isValidType(int type) { - return type == DataType.BLE_SERVICE_DATA - || type == DataType.ACCOUNT_KEY_DATA - || type == DataType.BLE_ADDRESS - || type == DataType.SCAN_MODE - || type == DataType.SALT - || type == DataType.PRIVATE_IDENTITY - || type == DataType.TRUSTED_IDENTITY - || type == DataType.PUBLIC_IDENTITY - || type == DataType.PROVISIONED_IDENTITY - || type == DataType.TX_POWER - || type == DataType.ACTION - || type == DataType.MODEL_ID - || type == DataType.EDDYSTONE_EPHEMERAL_IDENTIFIER - || type == DataType.CONNECTION_STATUS - || type == DataType.BATTERY; - } - /** * @return {@code true} if this is identity type. * @hide diff --git a/nearby/framework/java/android/nearby/FastPairAccountKeyDeviceMetadata.java b/nearby/framework/java/android/nearby/FastPairAccountKeyDeviceMetadata.java deleted file mode 100644 index d42fbf4054104ed10e0efbbeea9479fb7b0ef53c..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/FastPairAccountKeyDeviceMetadata.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * 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 android.nearby; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel; - -/** - * Class for metadata of a Fast Pair device associated with an account. - * - * @hide - */ -public class FastPairAccountKeyDeviceMetadata { - - FastPairAccountKeyDeviceMetadataParcel mMetadataParcel; - - FastPairAccountKeyDeviceMetadata(FastPairAccountKeyDeviceMetadataParcel metadataParcel) { - this.mMetadataParcel = metadataParcel; - } - - /** - * Get Device Account Key, which uniquely identifies a Fast Pair device associated with an - * account. AccountKey is 16 bytes: first byte is 0x04. Other 15 bytes are randomly generated. - * - * @return 16-byte Account Key. - * @hide - */ - @Nullable - public byte[] getDeviceAccountKey() { - return mMetadataParcel.deviceAccountKey; - } - - /** - * Get a hash value of device's account key and public bluetooth address without revealing the - * public bluetooth address. Sha256 hash value is 32 bytes. - * - * @return 32-byte Sha256 hash value. - * @hide - */ - @Nullable - public byte[] getSha256DeviceAccountKeyPublicAddress() { - return mMetadataParcel.sha256DeviceAccountKeyPublicAddress; - } - - /** - * Get metadata of a Fast Pair device type. - * - * @hide - */ - @Nullable - public FastPairDeviceMetadata getFastPairDeviceMetadata() { - if (mMetadataParcel.metadata == null) { - return null; - } - return new FastPairDeviceMetadata(mMetadataParcel.metadata); - } - - /** - * Get Fast Pair discovery item, which is tied to both the device type and the account. - * - * @hide - */ - @Nullable - public FastPairDiscoveryItem getFastPairDiscoveryItem() { - if (mMetadataParcel.discoveryItem == null) { - return null; - } - return new FastPairDiscoveryItem(mMetadataParcel.discoveryItem); - } - - /** - * Builder used to create FastPairAccountKeyDeviceMetadata. - * - * @hide - */ - public static final class Builder { - - private final FastPairAccountKeyDeviceMetadataParcel mBuilderParcel; - - /** - * Default constructor of Builder. - * - * @hide - */ - public Builder() { - mBuilderParcel = new FastPairAccountKeyDeviceMetadataParcel(); - mBuilderParcel.deviceAccountKey = null; - mBuilderParcel.sha256DeviceAccountKeyPublicAddress = null; - mBuilderParcel.metadata = null; - mBuilderParcel.discoveryItem = null; - } - - /** - * Set Account Key. - * - * @param deviceAccountKey Fast Pair device account key, which is 16 bytes: first byte is - * 0x04. Next 15 bytes are randomly generated. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setDeviceAccountKey(@Nullable byte[] deviceAccountKey) { - mBuilderParcel.deviceAccountKey = deviceAccountKey; - return this; - } - - /** - * Set sha256 hash value of account key and public bluetooth address. - * - * @param sha256DeviceAccountKeyPublicAddress 32-byte sha256 hash value of account key and - * public bluetooth address. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setSha256DeviceAccountKeyPublicAddress( - @Nullable byte[] sha256DeviceAccountKeyPublicAddress) { - mBuilderParcel.sha256DeviceAccountKeyPublicAddress = - sha256DeviceAccountKeyPublicAddress; - return this; - } - - - /** - * Set Fast Pair metadata. - * - * @param metadata Fast Pair metadata. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setFastPairDeviceMetadata(@Nullable FastPairDeviceMetadata metadata) { - if (metadata == null) { - mBuilderParcel.metadata = null; - } else { - mBuilderParcel.metadata = metadata.mMetadataParcel; - } - return this; - } - - /** - * Set Fast Pair discovery item. - * - * @param discoveryItem Fast Pair discovery item. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setFastPairDiscoveryItem(@Nullable FastPairDiscoveryItem discoveryItem) { - if (discoveryItem == null) { - mBuilderParcel.discoveryItem = null; - } else { - mBuilderParcel.discoveryItem = discoveryItem.mMetadataParcel; - } - return this; - } - - /** - * Build {@link FastPairAccountKeyDeviceMetadata} with the currently set configuration. - * - * @hide - */ - @NonNull - public FastPairAccountKeyDeviceMetadata build() { - return new FastPairAccountKeyDeviceMetadata(mBuilderParcel); - } - } -} diff --git a/nearby/framework/java/android/nearby/FastPairAntispoofKeyDeviceMetadata.java b/nearby/framework/java/android/nearby/FastPairAntispoofKeyDeviceMetadata.java deleted file mode 100644 index 74831d5183e5c8c945eede014cdf382dbd230055..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/FastPairAntispoofKeyDeviceMetadata.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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 android.nearby; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel; - -/** - * Class for a type of registered Fast Pair device keyed by modelID, or antispoofKey. - * - * @hide - */ -public class FastPairAntispoofKeyDeviceMetadata { - - FastPairAntispoofKeyDeviceMetadataParcel mMetadataParcel; - FastPairAntispoofKeyDeviceMetadata( - FastPairAntispoofKeyDeviceMetadataParcel metadataParcel) { - this.mMetadataParcel = metadataParcel; - } - - /** - * Get Antispoof public key. - * - * @hide - */ - @Nullable - public byte[] getAntispoofPublicKey() { - return this.mMetadataParcel.antispoofPublicKey; - } - - /** - * Get metadata of a Fast Pair device type. - * - * @hide - */ - @Nullable - public FastPairDeviceMetadata getFastPairDeviceMetadata() { - if (this.mMetadataParcel.deviceMetadata == null) { - return null; - } - return new FastPairDeviceMetadata(this.mMetadataParcel.deviceMetadata); - } - - /** - * Builder used to create FastPairAntispoofkeyDeviceMetadata. - * - * @hide - */ - public static final class Builder { - - private final FastPairAntispoofKeyDeviceMetadataParcel mBuilderParcel; - - /** - * Default constructor of Builder. - * - * @hide - */ - public Builder() { - mBuilderParcel = new FastPairAntispoofKeyDeviceMetadataParcel(); - mBuilderParcel.antispoofPublicKey = null; - mBuilderParcel.deviceMetadata = null; - } - - /** - * Set AntiSpoof public key, which uniquely identify a Fast Pair device type. - * - * @param antispoofPublicKey is 64 bytes, see Data Format. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setAntispoofPublicKey(@Nullable byte[] antispoofPublicKey) { - mBuilderParcel.antispoofPublicKey = antispoofPublicKey; - return this; - } - - /** - * Set Fast Pair metadata, which is the property of a Fast Pair device type, including - * device images and strings. - * - * @param metadata Fast Pair device meta data. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setFastPairDeviceMetadata(@Nullable FastPairDeviceMetadata metadata) { - if (metadata != null) { - mBuilderParcel.deviceMetadata = metadata.mMetadataParcel; - } else { - mBuilderParcel.deviceMetadata = null; - } - return this; - } - - /** - * Build {@link FastPairAntispoofKeyDeviceMetadata} with the currently set configuration. - * - * @hide - */ - @NonNull - public FastPairAntispoofKeyDeviceMetadata build() { - return new FastPairAntispoofKeyDeviceMetadata(mBuilderParcel); - } - } -} diff --git a/nearby/framework/java/android/nearby/FastPairDataProviderService.java b/nearby/framework/java/android/nearby/FastPairDataProviderService.java deleted file mode 100644 index f1d507471fbab0a37699ff8f90f5e70cdc93f416..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/FastPairDataProviderService.java +++ /dev/null @@ -1,714 +0,0 @@ -/* - * 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 android.nearby; - -import android.accounts.Account; -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.Service; -import android.content.Intent; -import android.nearby.aidl.ByteArrayParcel; -import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel; -import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel; -import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel; -import android.nearby.aidl.FastPairEligibleAccountParcel; -import android.nearby.aidl.FastPairEligibleAccountsRequestParcel; -import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel; -import android.nearby.aidl.FastPairManageAccountRequestParcel; -import android.nearby.aidl.IFastPairAccountDevicesMetadataCallback; -import android.nearby.aidl.IFastPairAntispoofKeyDeviceMetadataCallback; -import android.nearby.aidl.IFastPairDataProvider; -import android.nearby.aidl.IFastPairEligibleAccountsCallback; -import android.nearby.aidl.IFastPairManageAccountCallback; -import android.nearby.aidl.IFastPairManageAccountDeviceCallback; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * A service class for fast pair data providers outside the system server. - * - * Fast pair providers should be wrapped in a non-exported service which returns the result of - * {@link #getBinder()} from the service's {@link android.app.Service#onBind(Intent)} method. The - * service should not be exported so that components other than the system server cannot bind to it. - * Alternatively, the service may be guarded by a permission that only system server can obtain. - * - *

Fast Pair providers are identified by their UID / package name. - * - * @hide - */ -public abstract class FastPairDataProviderService extends Service { - /** - * The action the wrapping service should have in its intent filter to implement the - * {@link android.nearby.FastPairDataProviderBase}. - * - * @hide - */ - public static final String ACTION_FAST_PAIR_DATA_PROVIDER = - "android.nearby.action.FAST_PAIR_DATA_PROVIDER"; - - /** - * Manage request type to add, or opt-in. - * - * @hide - */ - public static final int MANAGE_REQUEST_ADD = 0; - - /** - * Manage request type to remove, or opt-out. - * - * @hide - */ - public static final int MANAGE_REQUEST_REMOVE = 1; - - /** - * @hide - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - MANAGE_REQUEST_ADD, - MANAGE_REQUEST_REMOVE}) - @interface ManageRequestType {} - - /** - * Error code for bad request. - * - * @hide - */ - public static final int ERROR_CODE_BAD_REQUEST = 0; - - /** - * Error code for internal error. - * - * @hide - */ - public static final int ERROR_CODE_INTERNAL_ERROR = 1; - - /** - * @hide - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - ERROR_CODE_BAD_REQUEST, - ERROR_CODE_INTERNAL_ERROR}) - @interface ErrorCode {} - - private final IBinder mBinder; - private final String mTag; - - /** - * Constructor of FastPairDataProviderService. - * - * @param tag TAG for on device logging. - * @hide - */ - public FastPairDataProviderService(@NonNull String tag) { - mBinder = new Service(); - mTag = tag; - } - - @Override - @NonNull - public final IBinder onBind(@NonNull Intent intent) { - return mBinder; - } - - /** - * Callback to be invoked when an AntispoofKeyed device metadata is loaded. - * - * @hide - */ - public interface FastPairAntispoofKeyDeviceMetadataCallback { - - /** - * Invoked once the meta data is loaded. - * - * @hide - */ - void onFastPairAntispoofKeyDeviceMetadataReceived( - @NonNull FastPairAntispoofKeyDeviceMetadata metadata); - - /** Invoked in case of error. - * - * @hide - */ - void onError(@ErrorCode int code, @Nullable String message); - } - - /** - * Callback to be invoked when Fast Pair devices of a given account is loaded. - * - * @hide - */ - public interface FastPairAccountDevicesMetadataCallback { - - /** - * Should be invoked once the metadatas are loaded. - * - * @hide - */ - void onFastPairAccountDevicesMetadataReceived( - @NonNull Collection metadatas); - /** - * Invoked in case of error. - * - * @hide - */ - void onError(@ErrorCode int code, @Nullable String message); - } - - /** - * Callback to be invoked when FastPair eligible accounts are loaded. - * - * @hide - */ - public interface FastPairEligibleAccountsCallback { - - /** - * Should be invoked once the eligible accounts are loaded. - * - * @hide - */ - void onFastPairEligibleAccountsReceived( - @NonNull Collection accounts); - /** - * Invoked in case of error. - * - * @hide - */ - void onError(@ErrorCode int code, @Nullable String message); - } - - /** - * Callback to be invoked when a management action is finished. - * - * @hide - */ - public interface FastPairManageActionCallback { - - /** - * Should be invoked once the manage action is successful. - * - * @hide - */ - void onSuccess(); - /** - * Invoked in case of error. - * - * @hide - */ - void onError(@ErrorCode int code, @Nullable String message); - } - - /** - * Fulfills the Fast Pair device metadata request by using callback to send back the - * device meta data of a given modelId. - * - * @hide - */ - public abstract void onLoadFastPairAntispoofKeyDeviceMetadata( - @NonNull FastPairAntispoofKeyDeviceMetadataRequest request, - @NonNull FastPairAntispoofKeyDeviceMetadataCallback callback); - - /** - * Fulfills the account tied Fast Pair devices metadata request by using callback to send back - * all Fast Pair device's metadata of a given account. - * - * @hide - */ - public abstract void onLoadFastPairAccountDevicesMetadata( - @NonNull FastPairAccountDevicesMetadataRequest request, - @NonNull FastPairAccountDevicesMetadataCallback callback); - - /** - * Fulfills the Fast Pair eligible accounts request by using callback to send back Fast Pair - * eligible accounts. - * - * @hide - */ - public abstract void onLoadFastPairEligibleAccounts( - @NonNull FastPairEligibleAccountsRequest request, - @NonNull FastPairEligibleAccountsCallback callback); - - /** - * Fulfills the Fast Pair account management request by using callback to send back result. - * - * @hide - */ - public abstract void onManageFastPairAccount( - @NonNull FastPairManageAccountRequest request, - @NonNull FastPairManageActionCallback callback); - - /** - * Fulfills the request to manage device-account mapping by using callback to send back result. - * - * @hide - */ - public abstract void onManageFastPairAccountDevice( - @NonNull FastPairManageAccountDeviceRequest request, - @NonNull FastPairManageActionCallback callback); - - /** - * Class for reading FastPairAntispoofKeyDeviceMetadataRequest, which specifies the model ID of - * a Fast Pair device. To fulfill this request, corresponding - * {@link FastPairAntispoofKeyDeviceMetadata} should be fetched and returned. - * - * @hide - */ - public static class FastPairAntispoofKeyDeviceMetadataRequest { - - private final FastPairAntispoofKeyDeviceMetadataRequestParcel mMetadataRequestParcel; - - private FastPairAntispoofKeyDeviceMetadataRequest( - final FastPairAntispoofKeyDeviceMetadataRequestParcel metaDataRequestParcel) { - this.mMetadataRequestParcel = metaDataRequestParcel; - } - - /** - * Get modelId (24 bit), the key for FastPairAntispoofKeyDeviceMetadata in the same format - * returned by Google at device registration time. - * - * ModelId format is defined at device registration time, see - * Model ID. - * @return raw bytes of modelId in the same format returned by Google at device registration - * time. - * @hide - */ - public @NonNull byte[] getModelId() { - return this.mMetadataRequestParcel.modelId; - } - } - - /** - * Class for reading FastPairAccountDevicesMetadataRequest, which specifies the Fast Pair - * account and the allow list of the FastPair device keys saved to the account (i.e., FastPair - * accountKeys). - * - * A Fast Pair accountKey is created when a Fast Pair device is saved to an account. It is per - * Fast Pair device per account. - * - * To retrieve all Fast Pair accountKeys saved to an account, the caller needs to set - * account with an empty allow list. - * - * To retrieve metadata of a selected list of Fast Pair devices saved to an account, the caller - * needs to set account with a non-empty allow list. - * @hide - */ - public static class FastPairAccountDevicesMetadataRequest { - - private final FastPairAccountDevicesMetadataRequestParcel mMetadataRequestParcel; - - private FastPairAccountDevicesMetadataRequest( - final FastPairAccountDevicesMetadataRequestParcel metaDataRequestParcel) { - this.mMetadataRequestParcel = metaDataRequestParcel; - } - - /** - * Get FastPair account, whose Fast Pair devices' metadata is requested. - * - * @return a FastPair account. - * @hide - */ - public @NonNull Account getAccount() { - return this.mMetadataRequestParcel.account; - } - - /** - * Get allowlist of Fast Pair devices using a collection of deviceAccountKeys. - * Note that as a special case, empty list actually means all FastPair devices under the - * account instead of none. - * - * DeviceAccountKey is 16 bytes: first byte is 0x04. Other 15 bytes are randomly generated. - * - * @return allowlist of Fast Pair devices using a collection of deviceAccountKeys. - * @hide - */ - public @NonNull Collection getDeviceAccountKeys() { - if (this.mMetadataRequestParcel.deviceAccountKeys == null) { - return new ArrayList(0); - } - List deviceAccountKeys = - new ArrayList<>(this.mMetadataRequestParcel.deviceAccountKeys.length); - for (ByteArrayParcel deviceAccountKey : this.mMetadataRequestParcel.deviceAccountKeys) { - deviceAccountKeys.add(deviceAccountKey.byteArray); - } - return deviceAccountKeys; - } - } - - /** - * Class for reading FastPairEligibleAccountsRequest. Upon receiving this request, Fast Pair - * eligible accounts should be returned to bind Fast Pair devices. - * - * @hide - */ - public static class FastPairEligibleAccountsRequest { - @SuppressWarnings("UnusedVariable") - private final FastPairEligibleAccountsRequestParcel mAccountsRequestParcel; - - private FastPairEligibleAccountsRequest( - final FastPairEligibleAccountsRequestParcel accountsRequestParcel) { - this.mAccountsRequestParcel = accountsRequestParcel; - } - } - - /** - * Class for reading FastPairManageAccountRequest. If the request type is MANAGE_REQUEST_ADD, - * the account is enabled to bind Fast Pair devices; If the request type is - * MANAGE_REQUEST_REMOVE, the account is disabled to bind more Fast Pair devices. Furthermore, - * all existing bounded Fast Pair devices are unbounded. - * - * @hide - */ - public static class FastPairManageAccountRequest { - - private final FastPairManageAccountRequestParcel mAccountRequestParcel; - - private FastPairManageAccountRequest( - final FastPairManageAccountRequestParcel accountRequestParcel) { - this.mAccountRequestParcel = accountRequestParcel; - } - - /** - * Get request type: MANAGE_REQUEST_ADD, or MANAGE_REQUEST_REMOVE. - * - * @hide - */ - public @ManageRequestType int getRequestType() { - return this.mAccountRequestParcel.requestType; - } - /** - * Get account. - * - * @hide - */ - public @NonNull Account getAccount() { - return this.mAccountRequestParcel.account; - } - } - - /** - * Class for reading FastPairManageAccountDeviceRequest. If the request type is - * MANAGE_REQUEST_ADD, then a Fast Pair device is bounded to a Fast Pair account. If the - * request type is MANAGE_REQUEST_REMOVE, then a Fast Pair device is removed from a Fast Pair - * account. - * - * @hide - */ - public static class FastPairManageAccountDeviceRequest { - - private final FastPairManageAccountDeviceRequestParcel mRequestParcel; - - private FastPairManageAccountDeviceRequest( - final FastPairManageAccountDeviceRequestParcel requestParcel) { - this.mRequestParcel = requestParcel; - } - - /** - * Get request type: MANAGE_REQUEST_ADD, or MANAGE_REQUEST_REMOVE. - * - * @hide - */ - public @ManageRequestType int getRequestType() { - return this.mRequestParcel.requestType; - } - /** - * Get account. - * - * @hide - */ - public @NonNull Account getAccount() { - return this.mRequestParcel.account; - } - /** - * Get account key device metadata. - * - * @hide - */ - public @NonNull FastPairAccountKeyDeviceMetadata getAccountKeyDeviceMetadata() { - return new FastPairAccountKeyDeviceMetadata( - this.mRequestParcel.accountKeyDeviceMetadata); - } - } - - /** - * Callback class that sends back FastPairAntispoofKeyDeviceMetadata. - */ - private final class WrapperFastPairAntispoofKeyDeviceMetadataCallback implements - FastPairAntispoofKeyDeviceMetadataCallback { - - private IFastPairAntispoofKeyDeviceMetadataCallback mCallback; - - private WrapperFastPairAntispoofKeyDeviceMetadataCallback( - IFastPairAntispoofKeyDeviceMetadataCallback callback) { - mCallback = callback; - } - - /** - * Sends back FastPairAntispoofKeyDeviceMetadata. - */ - @Override - public void onFastPairAntispoofKeyDeviceMetadataReceived( - @NonNull FastPairAntispoofKeyDeviceMetadata metadata) { - try { - mCallback.onFastPairAntispoofKeyDeviceMetadataReceived(metadata.mMetadataParcel); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } catch (RuntimeException e) { - Log.w(mTag, e); - } - } - - @Override - public void onError(@ErrorCode int code, @Nullable String message) { - try { - mCallback.onError(code, message); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } catch (RuntimeException e) { - Log.w(mTag, e); - } - } - } - - /** - * Callback class that sends back collection of FastPairAccountKeyDeviceMetadata. - */ - private final class WrapperFastPairAccountDevicesMetadataCallback implements - FastPairAccountDevicesMetadataCallback { - - private IFastPairAccountDevicesMetadataCallback mCallback; - - private WrapperFastPairAccountDevicesMetadataCallback( - IFastPairAccountDevicesMetadataCallback callback) { - mCallback = callback; - } - - /** - * Sends back collection of FastPairAccountKeyDeviceMetadata. - */ - @Override - public void onFastPairAccountDevicesMetadataReceived( - @NonNull Collection metadatas) { - FastPairAccountKeyDeviceMetadataParcel[] metadataParcels = - new FastPairAccountKeyDeviceMetadataParcel[metadatas.size()]; - int i = 0; - for (FastPairAccountKeyDeviceMetadata metadata : metadatas) { - metadataParcels[i] = metadata.mMetadataParcel; - i = i + 1; - } - try { - mCallback.onFastPairAccountDevicesMetadataReceived(metadataParcels); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } catch (RuntimeException e) { - Log.w(mTag, e); - } - } - - @Override - public void onError(@ErrorCode int code, @Nullable String message) { - try { - mCallback.onError(code, message); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } catch (RuntimeException e) { - Log.w(mTag, e); - } - } - } - - /** - * Callback class that sends back eligible Fast Pair accounts. - */ - private final class WrapperFastPairEligibleAccountsCallback implements - FastPairEligibleAccountsCallback { - - private IFastPairEligibleAccountsCallback mCallback; - - private WrapperFastPairEligibleAccountsCallback( - IFastPairEligibleAccountsCallback callback) { - mCallback = callback; - } - - /** - * Sends back the eligible Fast Pair accounts. - */ - @Override - public void onFastPairEligibleAccountsReceived( - @NonNull Collection accounts) { - int i = 0; - FastPairEligibleAccountParcel[] accountParcels = - new FastPairEligibleAccountParcel[accounts.size()]; - for (FastPairEligibleAccount account: accounts) { - accountParcels[i] = account.mAccountParcel; - i = i + 1; - } - try { - mCallback.onFastPairEligibleAccountsReceived(accountParcels); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } catch (RuntimeException e) { - Log.w(mTag, e); - } - } - - @Override - public void onError(@ErrorCode int code, @Nullable String message) { - try { - mCallback.onError(code, message); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } catch (RuntimeException e) { - Log.w(mTag, e); - } - } - } - - /** - * Callback class that sends back Fast Pair account management result. - */ - private final class WrapperFastPairManageAccountCallback implements - FastPairManageActionCallback { - - private IFastPairManageAccountCallback mCallback; - - private WrapperFastPairManageAccountCallback( - IFastPairManageAccountCallback callback) { - mCallback = callback; - } - - /** - * Sends back Fast Pair account opt in result. - */ - @Override - public void onSuccess() { - try { - mCallback.onSuccess(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } catch (RuntimeException e) { - Log.w(mTag, e); - } - } - - @Override - public void onError(@ErrorCode int code, @Nullable String message) { - try { - mCallback.onError(code, message); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } catch (RuntimeException e) { - Log.w(mTag, e); - } - } - } - - /** - * Call back class that sends back account-device mapping management result. - */ - private final class WrapperFastPairManageAccountDeviceCallback implements - FastPairManageActionCallback { - - private IFastPairManageAccountDeviceCallback mCallback; - - private WrapperFastPairManageAccountDeviceCallback( - IFastPairManageAccountDeviceCallback callback) { - mCallback = callback; - } - - /** - * Sends back the account-device mapping management result. - */ - @Override - public void onSuccess() { - try { - mCallback.onSuccess(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } catch (RuntimeException e) { - Log.w(mTag, e); - } - } - - @Override - public void onError(@ErrorCode int code, @Nullable String message) { - try { - mCallback.onError(code, message); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } catch (RuntimeException e) { - Log.w(mTag, e); - } - } - } - - private final class Service extends IFastPairDataProvider.Stub { - - Service() { - } - - @Override - public void loadFastPairAntispoofKeyDeviceMetadata( - @NonNull FastPairAntispoofKeyDeviceMetadataRequestParcel requestParcel, - IFastPairAntispoofKeyDeviceMetadataCallback callback) { - onLoadFastPairAntispoofKeyDeviceMetadata( - new FastPairAntispoofKeyDeviceMetadataRequest(requestParcel), - new WrapperFastPairAntispoofKeyDeviceMetadataCallback(callback)); - } - - @Override - public void loadFastPairAccountDevicesMetadata( - @NonNull FastPairAccountDevicesMetadataRequestParcel requestParcel, - IFastPairAccountDevicesMetadataCallback callback) { - onLoadFastPairAccountDevicesMetadata( - new FastPairAccountDevicesMetadataRequest(requestParcel), - new WrapperFastPairAccountDevicesMetadataCallback(callback)); - } - - @Override - public void loadFastPairEligibleAccounts( - @NonNull FastPairEligibleAccountsRequestParcel requestParcel, - IFastPairEligibleAccountsCallback callback) { - onLoadFastPairEligibleAccounts(new FastPairEligibleAccountsRequest(requestParcel), - new WrapperFastPairEligibleAccountsCallback(callback)); - } - - @Override - public void manageFastPairAccount( - @NonNull FastPairManageAccountRequestParcel requestParcel, - IFastPairManageAccountCallback callback) { - onManageFastPairAccount(new FastPairManageAccountRequest(requestParcel), - new WrapperFastPairManageAccountCallback(callback)); - } - - @Override - public void manageFastPairAccountDevice( - @NonNull FastPairManageAccountDeviceRequestParcel requestParcel, - IFastPairManageAccountDeviceCallback callback) { - onManageFastPairAccountDevice(new FastPairManageAccountDeviceRequest(requestParcel), - new WrapperFastPairManageAccountDeviceCallback(callback)); - } - } -} diff --git a/nearby/framework/java/android/nearby/FastPairDeviceMetadata.java b/nearby/framework/java/android/nearby/FastPairDeviceMetadata.java deleted file mode 100644 index 0e2e79d8a67ea7d3d3f0394e143c862d7f1429c6..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/FastPairDeviceMetadata.java +++ /dev/null @@ -1,683 +0,0 @@ -/* - * 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 android.nearby; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.nearby.aidl.FastPairDeviceMetadataParcel; - -/** - * Class for the properties of a given type of Fast Pair device, including images and text. - * - * @hide - */ -public class FastPairDeviceMetadata { - - FastPairDeviceMetadataParcel mMetadataParcel; - - FastPairDeviceMetadata( - FastPairDeviceMetadataParcel metadataParcel) { - this.mMetadataParcel = metadataParcel; - } - - /** - * Get ImageUrl, which will be displayed in notification. - * - * @hide - */ - @Nullable - public String getImageUrl() { - return mMetadataParcel.imageUrl; - } - - /** - * Get IntentUri, which will be launched to install companion app. - * - * @hide - */ - @Nullable - public String getIntentUri() { - return mMetadataParcel.intentUri; - } - - /** - * Get BLE transmit power, as described in Fast Pair spec, see - * Transmit Power - * - * @hide - */ - public int getBleTxPower() { - return mMetadataParcel.bleTxPower; - } - - /** - * Get Fast Pair Half Sheet trigger distance in meters. - * - * @hide - */ - public float getTriggerDistance() { - return mMetadataParcel.triggerDistance; - } - - /** - * Get Fast Pair device image, which is submitted at device registration time to display on - * notification. It is a 32-bit PNG with dimensions of 512px by 512px. - * - * @return Fast Pair device image in 32-bit PNG with dimensions of 512px by 512px. - * @hide - */ - @Nullable - public byte[] getImage() { - return mMetadataParcel.image; - } - - /** - * Get Fast Pair device type. - * DEVICE_TYPE_UNSPECIFIED = 0; - * HEADPHONES = 1; - * TRUE_WIRELESS_HEADPHONES = 7; - * @hide - */ - public int getDeviceType() { - return mMetadataParcel.deviceType; - } - - /** - * Get Fast Pair device name. e.g., "Pixel Buds A-Series". - * - * @hide - */ - @Nullable - public String getName() { - return mMetadataParcel.name; - } - - /** - * Get true wireless image url for left bud. - * - * @hide - */ - @Nullable - public String getTrueWirelessImageUrlLeftBud() { - return mMetadataParcel.trueWirelessImageUrlLeftBud; - } - - /** - * Get true wireless image url for right bud. - * - * @hide - */ - @Nullable - public String getTrueWirelessImageUrlRightBud() { - return mMetadataParcel.trueWirelessImageUrlRightBud; - } - - /** - * Get true wireless image url for case. - * - * @hide - */ - @Nullable - public String getTrueWirelessImageUrlCase() { - return mMetadataParcel.trueWirelessImageUrlCase; - } - - /** - * Get InitialNotificationDescription, which is a translated string of - * "Tap to pair. Earbuds will be tied to %s" based on locale. - * - * @hide - */ - @Nullable - public String getInitialNotificationDescription() { - return mMetadataParcel.initialNotificationDescription; - } - - /** - * Get InitialNotificationDescriptionNoAccount, which is a translated string of - * "Tap to pair with this device" based on locale. - * - * @hide - */ - @Nullable - public String getInitialNotificationDescriptionNoAccount() { - return mMetadataParcel.initialNotificationDescriptionNoAccount; - } - - /** - * Get OpenCompanionAppDescription, which is a translated string of - * "Tap to finish setup" based on locale. - * - * @hide - */ - @Nullable - public String getOpenCompanionAppDescription() { - return mMetadataParcel.openCompanionAppDescription; - } - - /** - * Get UpdateCompanionAppDescription, which is a translated string of - * "Tap to update device settings and finish setup" based on locale. - * - * @hide - */ - @Nullable - public String getUpdateCompanionAppDescription() { - return mMetadataParcel.updateCompanionAppDescription; - } - - /** - * Get DownloadCompanionAppDescription, which is a translated string of - * "Tap to download device app on Google Play and see all features" based on locale. - * - * @hide - */ - @Nullable - public String getDownloadCompanionAppDescription() { - return mMetadataParcel.downloadCompanionAppDescription; - } - - /** - * Get UnableToConnectTitle, which is a translated string of - * "Unable to connect" based on locale. - */ - @Nullable - public String getUnableToConnectTitle() { - return mMetadataParcel.unableToConnectTitle; - } - - /** - * Get UnableToConnectDescription, which is a translated string of - * "Try manually pairing to the device" based on locale. - * - * @hide - */ - @Nullable - public String getUnableToConnectDescription() { - return mMetadataParcel.unableToConnectDescription; - } - - /** - * Get InitialPairingDescription, which is a translated string of - * "%s will appear on devices linked with %s" based on locale. - * - * @hide - */ - @Nullable - public String getInitialPairingDescription() { - return mMetadataParcel.initialPairingDescription; - } - - /** - * Get ConnectSuccessCompanionAppInstalled, which is a translated string of - * "Your device is ready to be set up" based on locale. - * - * @hide - */ - @Nullable - public String getConnectSuccessCompanionAppInstalled() { - return mMetadataParcel.connectSuccessCompanionAppInstalled; - } - - /** - * Get ConnectSuccessCompanionAppNotInstalled, which is a translated string of - * "Download the device app on Google Play to see all available features" based on locale. - * - * @hide - */ - @Nullable - public String getConnectSuccessCompanionAppNotInstalled() { - return mMetadataParcel.connectSuccessCompanionAppNotInstalled; - } - - /** - * Get SubsequentPairingDescription, which is a translated string of - * "Connect %s to this phone" based on locale. - * - * @hide - */ - @Nullable - public String getSubsequentPairingDescription() { - return mMetadataParcel.subsequentPairingDescription; - } - - /** - * Get RetroactivePairingDescription, which is a translated string of - * "Save device to %s for faster pairing to your other devices" based on locale. - * - * @hide - */ - @Nullable - public String getRetroactivePairingDescription() { - return mMetadataParcel.retroactivePairingDescription; - } - - /** - * Get WaitLaunchCompanionAppDescription, which is a translated string of - * "This will take a few moments" based on locale. - * - * @hide - */ - @Nullable - public String getWaitLaunchCompanionAppDescription() { - return mMetadataParcel.waitLaunchCompanionAppDescription; - } - - /** - * Get FailConnectGoToSettingsDescription, which is a translated string of - * "Try manually pairing to the device by going to Settings" based on locale. - * - * @hide - */ - @Nullable - public String getFailConnectGoToSettingsDescription() { - return mMetadataParcel.failConnectGoToSettingsDescription; - } - - /** - * Builder used to create FastPairDeviceMetadata. - * - * @hide - */ - public static final class Builder { - - private final FastPairDeviceMetadataParcel mBuilderParcel; - - /** - * Default constructor of Builder. - * - * @hide - */ - public Builder() { - mBuilderParcel = new FastPairDeviceMetadataParcel(); - mBuilderParcel.imageUrl = null; - mBuilderParcel.intentUri = null; - mBuilderParcel.name = null; - mBuilderParcel.bleTxPower = 0; - mBuilderParcel.triggerDistance = 0; - mBuilderParcel.image = null; - mBuilderParcel.deviceType = 0; // DEVICE_TYPE_UNSPECIFIED - mBuilderParcel.trueWirelessImageUrlLeftBud = null; - mBuilderParcel.trueWirelessImageUrlRightBud = null; - mBuilderParcel.trueWirelessImageUrlCase = null; - mBuilderParcel.initialNotificationDescription = null; - mBuilderParcel.initialNotificationDescriptionNoAccount = null; - mBuilderParcel.openCompanionAppDescription = null; - mBuilderParcel.updateCompanionAppDescription = null; - mBuilderParcel.downloadCompanionAppDescription = null; - mBuilderParcel.unableToConnectTitle = null; - mBuilderParcel.unableToConnectDescription = null; - mBuilderParcel.initialPairingDescription = null; - mBuilderParcel.connectSuccessCompanionAppInstalled = null; - mBuilderParcel.connectSuccessCompanionAppNotInstalled = null; - mBuilderParcel.subsequentPairingDescription = null; - mBuilderParcel.retroactivePairingDescription = null; - mBuilderParcel.waitLaunchCompanionAppDescription = null; - mBuilderParcel.failConnectGoToSettingsDescription = null; - } - - /** - * Set ImageUlr. - * - * @param imageUrl Image Ulr. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setImageUrl(@Nullable String imageUrl) { - mBuilderParcel.imageUrl = imageUrl; - return this; - } - - /** - * Set IntentUri. - * - * @param intentUri Intent uri. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setIntentUri(@Nullable String intentUri) { - mBuilderParcel.intentUri = intentUri; - return this; - } - - /** - * Set device name. - * - * @param name Device name. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setName(@Nullable String name) { - mBuilderParcel.name = name; - return this; - } - - /** - * Set ble transmission power. - * - * @param bleTxPower Ble transmission power. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setBleTxPower(int bleTxPower) { - mBuilderParcel.bleTxPower = bleTxPower; - return this; - } - - /** - * Set trigger distance. - * - * @param triggerDistance Fast Pair trigger distance. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setTriggerDistance(float triggerDistance) { - mBuilderParcel.triggerDistance = triggerDistance; - return this; - } - - /** - * Set image. - * - * @param image Fast Pair device image, which is submitted at device registration time to - * display on notification. It is a 32-bit PNG with dimensions of - * 512px by 512px. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setImage(@Nullable byte[] image) { - mBuilderParcel.image = image; - return this; - } - - /** - * Set device type. - * - * @param deviceType Fast Pair device type. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setDeviceType(int deviceType) { - mBuilderParcel.deviceType = deviceType; - return this; - } - - /** - * Set true wireless image url for left bud. - * - * @param trueWirelessImageUrlLeftBud True wireless image url for left bud. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setTrueWirelessImageUrlLeftBud( - @Nullable String trueWirelessImageUrlLeftBud) { - mBuilderParcel.trueWirelessImageUrlLeftBud = trueWirelessImageUrlLeftBud; - return this; - } - - /** - * Set true wireless image url for right bud. - * - * @param trueWirelessImageUrlRightBud True wireless image url for right bud. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setTrueWirelessImageUrlRightBud( - @Nullable String trueWirelessImageUrlRightBud) { - mBuilderParcel.trueWirelessImageUrlRightBud = trueWirelessImageUrlRightBud; - return this; - } - - /** - * Set true wireless image url for case. - * - * @param trueWirelessImageUrlCase True wireless image url for case. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setTrueWirelessImageUrlCase(@Nullable String trueWirelessImageUrlCase) { - mBuilderParcel.trueWirelessImageUrlCase = trueWirelessImageUrlCase; - return this; - } - - /** - * Set InitialNotificationDescription. - * - * @param initialNotificationDescription Initial notification description. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setInitialNotificationDescription( - @Nullable String initialNotificationDescription) { - mBuilderParcel.initialNotificationDescription = initialNotificationDescription; - return this; - } - - /** - * Set InitialNotificationDescriptionNoAccount. - * - * @param initialNotificationDescriptionNoAccount Initial notification description when - * account is not present. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setInitialNotificationDescriptionNoAccount( - @Nullable String initialNotificationDescriptionNoAccount) { - mBuilderParcel.initialNotificationDescriptionNoAccount = - initialNotificationDescriptionNoAccount; - return this; - } - - /** - * Set OpenCompanionAppDescription. - * - * @param openCompanionAppDescription Description for opening companion app. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setOpenCompanionAppDescription( - @Nullable String openCompanionAppDescription) { - mBuilderParcel.openCompanionAppDescription = openCompanionAppDescription; - return this; - } - - /** - * Set UpdateCompanionAppDescription. - * - * @param updateCompanionAppDescription Description for updating companion app. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setUpdateCompanionAppDescription( - @Nullable String updateCompanionAppDescription) { - mBuilderParcel.updateCompanionAppDescription = updateCompanionAppDescription; - return this; - } - - /** - * Set DownloadCompanionAppDescription. - * - * @param downloadCompanionAppDescription Description for downloading companion app. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setDownloadCompanionAppDescription( - @Nullable String downloadCompanionAppDescription) { - mBuilderParcel.downloadCompanionAppDescription = downloadCompanionAppDescription; - return this; - } - - /** - * Set UnableToConnectTitle. - * - * @param unableToConnectTitle Title when Fast Pair device is unable to be connected to. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setUnableToConnectTitle(@Nullable String unableToConnectTitle) { - mBuilderParcel.unableToConnectTitle = unableToConnectTitle; - return this; - } - - /** - * Set UnableToConnectDescription. - * - * @param unableToConnectDescription Description when Fast Pair device is unable to be - * connected to. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setUnableToConnectDescription( - @Nullable String unableToConnectDescription) { - mBuilderParcel.unableToConnectDescription = unableToConnectDescription; - return this; - } - - /** - * Set InitialPairingDescription. - * - * @param initialPairingDescription Description for Fast Pair initial pairing. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setInitialPairingDescription(@Nullable String initialPairingDescription) { - mBuilderParcel.initialPairingDescription = initialPairingDescription; - return this; - } - - /** - * Set ConnectSuccessCompanionAppInstalled. - * - * @param connectSuccessCompanionAppInstalled Description that let user open the companion - * app. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setConnectSuccessCompanionAppInstalled( - @Nullable String connectSuccessCompanionAppInstalled) { - mBuilderParcel.connectSuccessCompanionAppInstalled = - connectSuccessCompanionAppInstalled; - return this; - } - - /** - * Set ConnectSuccessCompanionAppNotInstalled. - * - * @param connectSuccessCompanionAppNotInstalled Description that let user download the - * companion app. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setConnectSuccessCompanionAppNotInstalled( - @Nullable String connectSuccessCompanionAppNotInstalled) { - mBuilderParcel.connectSuccessCompanionAppNotInstalled = - connectSuccessCompanionAppNotInstalled; - return this; - } - - /** - * Set SubsequentPairingDescription. - * - * @param subsequentPairingDescription Description that reminds user there is a paired - * device nearby. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setSubsequentPairingDescription( - @Nullable String subsequentPairingDescription) { - mBuilderParcel.subsequentPairingDescription = subsequentPairingDescription; - return this; - } - - /** - * Set RetroactivePairingDescription. - * - * @param retroactivePairingDescription Description that reminds users opt in their device. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setRetroactivePairingDescription( - @Nullable String retroactivePairingDescription) { - mBuilderParcel.retroactivePairingDescription = retroactivePairingDescription; - return this; - } - - /** - * Set WaitLaunchCompanionAppDescription. - * - * @param waitLaunchCompanionAppDescription Description that indicates companion app is - * about to launch. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setWaitLaunchCompanionAppDescription( - @Nullable String waitLaunchCompanionAppDescription) { - mBuilderParcel.waitLaunchCompanionAppDescription = - waitLaunchCompanionAppDescription; - return this; - } - - /** - * Set FailConnectGoToSettingsDescription. - * - * @param failConnectGoToSettingsDescription Description that indicates go to bluetooth - * settings when connection fail. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setFailConnectGoToSettingsDescription( - @Nullable String failConnectGoToSettingsDescription) { - mBuilderParcel.failConnectGoToSettingsDescription = - failConnectGoToSettingsDescription; - return this; - } - - /** - * Build {@link FastPairDeviceMetadata} with the currently set configuration. - * - * @hide - */ - @NonNull - public FastPairDeviceMetadata build() { - return new FastPairDeviceMetadata(mBuilderParcel); - } - } -} diff --git a/nearby/framework/java/android/nearby/FastPairDiscoveryItem.java b/nearby/framework/java/android/nearby/FastPairDiscoveryItem.java deleted file mode 100644 index d8dfe299688a48d62a17bc0fb3750555b0317a99..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/FastPairDiscoveryItem.java +++ /dev/null @@ -1,529 +0,0 @@ -/* - * 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 android.nearby; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.nearby.aidl.FastPairDiscoveryItemParcel; - -/** - * Class for FastPairDiscoveryItem and its builder. - * - * @hide - */ -public class FastPairDiscoveryItem { - - FastPairDiscoveryItemParcel mMetadataParcel; - - FastPairDiscoveryItem( - FastPairDiscoveryItemParcel metadataParcel) { - this.mMetadataParcel = metadataParcel; - } - - /** - * Get Id. - * - * @hide - */ - @Nullable - public String getId() { - return mMetadataParcel.id; - } - - /** - * Get MacAddress. - * - * @hide - */ - @Nullable - public String getMacAddress() { - return mMetadataParcel.macAddress; - } - - /** - * Get ActionUrl. - * - * @hide - */ - @Nullable - public String getActionUrl() { - return mMetadataParcel.actionUrl; - } - - /** - * Get DeviceName. - * - * @hide - */ - @Nullable - public String getDeviceName() { - return mMetadataParcel.deviceName; - } - - /** - * Get Title. - * - * @hide - */ - @Nullable - public String getTitle() { - return mMetadataParcel.title; - } - - /** - * Get Description. - * - * @hide - */ - @Nullable - public String getDescription() { - return mMetadataParcel.description; - } - - /** - * Get DisplayUrl. - * - * @hide - */ - @Nullable - public String getDisplayUrl() { - return mMetadataParcel.displayUrl; - } - - /** - * Get LastObservationTimestampMillis. - * - * @hide - */ - public long getLastObservationTimestampMillis() { - return mMetadataParcel.lastObservationTimestampMillis; - } - - /** - * Get FirstObservationTimestampMillis. - * - * @hide - */ - public long getFirstObservationTimestampMillis() { - return mMetadataParcel.firstObservationTimestampMillis; - } - - /** - * Get State. - * - * @hide - */ - public int getState() { - return mMetadataParcel.state; - } - - /** - * Get ActionUrlType. - * - * @hide - */ - public int getActionUrlType() { - return mMetadataParcel.actionUrlType; - } - - /** - * Get Rssi. - * - * @hide - */ - public int getRssi() { - return mMetadataParcel.rssi; - } - - /** - * Get PendingAppInstallTimestampMillis. - * - * @hide - */ - public long getPendingAppInstallTimestampMillis() { - return mMetadataParcel.pendingAppInstallTimestampMillis; - } - - /** - * Get TxPower. - * - * @hide - */ - public int getTxPower() { - return mMetadataParcel.txPower; - } - - /** - * Get AppName. - * - * @hide - */ - @Nullable - public String getAppName() { - return mMetadataParcel.appName; - } - - /** - * Get PackageName. - * - * @hide - */ - @Nullable - public String getPackageName() { - return mMetadataParcel.packageName; - } - - /** - * Get TriggerId. - * - * @hide - */ - @Nullable - public String getTriggerId() { - return mMetadataParcel.triggerId; - } - - /** - * Get IconPng, which is submitted at device registration time to display on notification. It is - * a 32-bit PNG with dimensions of 512px by 512px. - * - * @return IconPng in 32-bit PNG with dimensions of 512px by 512px. - * @hide - */ - @Nullable - public byte[] getIconPng() { - return mMetadataParcel.iconPng; - } - - /** - * Get IconFifeUrl. - * - * @hide - */ - @Nullable - public String getIconFfeUrl() { - return mMetadataParcel.iconFifeUrl; - } - - /** - * Get authenticationPublicKeySecp256r1, which is same as AntiSpoof public key, see - * Data Format. - * - * @return 64-byte authenticationPublicKeySecp256r1. - * @hide - */ - @Nullable - public byte[] getAuthenticationPublicKeySecp256r1() { - return mMetadataParcel.authenticationPublicKeySecp256r1; - } - - /** - * Builder used to create FastPairDiscoveryItem. - * - * @hide - */ - public static final class Builder { - - private final FastPairDiscoveryItemParcel mBuilderParcel; - - /** - * Default constructor of Builder. - * - * @hide - */ - public Builder() { - mBuilderParcel = new FastPairDiscoveryItemParcel(); - } - - /** - * Set Id. - * - * @param id Unique id. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * - * @hide - */ - @NonNull - public Builder setId(@Nullable String id) { - mBuilderParcel.id = id; - return this; - } - - /** - * Set MacAddress. - * - * @param macAddress Fast Pair device rotating mac address. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setMacAddress(@Nullable String macAddress) { - mBuilderParcel.macAddress = macAddress; - return this; - } - - /** - * Set ActionUrl. - * - * @param actionUrl Action Url of Fast Pair device. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setActionUrl(@Nullable String actionUrl) { - mBuilderParcel.actionUrl = actionUrl; - return this; - } - - /** - * Set DeviceName. - * @param deviceName Fast Pair device name. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setDeviceName(@Nullable String deviceName) { - mBuilderParcel.deviceName = deviceName; - return this; - } - - /** - * Set Title. - * - * @param title Title of Fast Pair device. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setTitle(@Nullable String title) { - mBuilderParcel.title = title; - return this; - } - - /** - * Set Description. - * - * @param description Description of Fast Pair device. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setDescription(@Nullable String description) { - mBuilderParcel.description = description; - return this; - } - - /** - * Set DisplayUrl. - * - * @param displayUrl Display Url of Fast Pair device. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setDisplayUrl(@Nullable String displayUrl) { - mBuilderParcel.displayUrl = displayUrl; - return this; - } - - /** - * Set LastObservationTimestampMillis. - * - * @param lastObservationTimestampMillis Last observed timestamp of Fast Pair device, keyed - * by a rotating id. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setLastObservationTimestampMillis( - long lastObservationTimestampMillis) { - mBuilderParcel.lastObservationTimestampMillis = lastObservationTimestampMillis; - return this; - } - - /** - * Set FirstObservationTimestampMillis. - * - * @param firstObservationTimestampMillis First observed timestamp of Fast Pair device, - * keyed by a rotating id. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setFirstObservationTimestampMillis( - long firstObservationTimestampMillis) { - mBuilderParcel.firstObservationTimestampMillis = firstObservationTimestampMillis; - return this; - } - - /** - * Set State. - * - * @param state Item's current state. e.g. if the item is blocked. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setState(int state) { - mBuilderParcel.state = state; - return this; - } - - /** - * Set ActionUrlType. - * - * @param actionUrlType The resolved url type for the action_url. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setActionUrlType(int actionUrlType) { - mBuilderParcel.actionUrlType = actionUrlType; - return this; - } - - /** - * Set Rssi. - * - * @param rssi Beacon's RSSI value. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setRssi(int rssi) { - mBuilderParcel.rssi = rssi; - return this; - } - - /** - * Set PendingAppInstallTimestampMillis. - * - * @param pendingAppInstallTimestampMillis The timestamp when the user is redirected to App - * Store after clicking on the item. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setPendingAppInstallTimestampMillis(long pendingAppInstallTimestampMillis) { - mBuilderParcel.pendingAppInstallTimestampMillis = pendingAppInstallTimestampMillis; - return this; - } - - /** - * Set TxPower. - * - * @param txPower Beacon's tx power. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setTxPower(int txPower) { - mBuilderParcel.txPower = txPower; - return this; - } - - /** - * Set AppName. - * - * @param appName Human readable name of the app designated to open the uri. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setAppName(@Nullable String appName) { - mBuilderParcel.appName = appName; - return this; - } - - /** - * Set PackageName. - * - * @param packageName Package name of the App that owns this item. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setPackageName(@Nullable String packageName) { - mBuilderParcel.packageName = packageName; - return this; - } - - /** - * Set TriggerId. - * - * @param triggerId TriggerId identifies the trigger/beacon that is attached with a message. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setTriggerId(@Nullable String triggerId) { - mBuilderParcel.triggerId = triggerId; - return this; - } - - /** - * Set IconPng. - * - * @param iconPng Bytes of item icon in PNG format displayed in Discovery item list. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setIconPng(@Nullable byte[] iconPng) { - mBuilderParcel.iconPng = iconPng; - return this; - } - - /** - * Set IconFifeUrl. - * - * @param iconFifeUrl A FIFE URL of the item icon displayed in Discovery item list. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setIconFfeUrl(@Nullable String iconFifeUrl) { - mBuilderParcel.iconFifeUrl = iconFifeUrl; - return this; - } - - /** - * Set authenticationPublicKeySecp256r1, which is same as AntiSpoof public key, see - * Data Format - * - * @param authenticationPublicKeySecp256r1 64-byte Fast Pair device public key. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setAuthenticationPublicKeySecp256r1( - @Nullable byte[] authenticationPublicKeySecp256r1) { - mBuilderParcel.authenticationPublicKeySecp256r1 = authenticationPublicKeySecp256r1; - return this; - } - - /** - * Build {@link FastPairDiscoveryItem} with the currently set configuration. - * - * @hide - */ - @NonNull - public FastPairDiscoveryItem build() { - return new FastPairDiscoveryItem(mBuilderParcel); - } - } -} diff --git a/nearby/framework/java/android/nearby/FastPairEligibleAccount.java b/nearby/framework/java/android/nearby/FastPairEligibleAccount.java deleted file mode 100644 index 8be4cca8e8993e40ca5464a0af9df4c6214dd88d..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/FastPairEligibleAccount.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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 android.nearby; - -import android.accounts.Account; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.nearby.aidl.FastPairEligibleAccountParcel; - -/** - * Class for FastPairEligibleAccount and its builder. - * - * @hide - */ -public class FastPairEligibleAccount { - - FastPairEligibleAccountParcel mAccountParcel; - - FastPairEligibleAccount(FastPairEligibleAccountParcel accountParcel) { - this.mAccountParcel = accountParcel; - } - - /** - * Get Account. - * - * @hide - */ - @Nullable - public Account getAccount() { - return this.mAccountParcel.account; - } - - /** - * Get OptIn Status. - * - * @hide - */ - public boolean isOptIn() { - return this.mAccountParcel.optIn; - } - - /** - * Builder used to create FastPairEligibleAccount. - * - * @hide - */ - public static final class Builder { - - private final FastPairEligibleAccountParcel mBuilderParcel; - - /** - * Default constructor of Builder. - * - * @hide - */ - public Builder() { - mBuilderParcel = new FastPairEligibleAccountParcel(); - mBuilderParcel.account = null; - mBuilderParcel.optIn = false; - } - - /** - * Set Account. - * - * @param account Fast Pair eligible account. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setAccount(@Nullable Account account) { - mBuilderParcel.account = account; - return this; - } - - /** - * Set whether the account is opt into Fast Pair. - * - * @param optIn Whether the Fast Pair eligible account opts into Fast Pair. - * @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}. - * @hide - */ - @NonNull - public Builder setOptIn(boolean optIn) { - mBuilderParcel.optIn = optIn; - return this; - } - - /** - * Build {@link FastPairEligibleAccount} with the currently set configuration. - * - * @hide - */ - @NonNull - public FastPairEligibleAccount build() { - return new FastPairEligibleAccount(mBuilderParcel); - } - } -} diff --git a/nearby/framework/java/android/nearby/NearbyDevice.java b/nearby/framework/java/android/nearby/NearbyDevice.java index e8fcc28d89666c5e792c477434724e51476ea7cd..e7db0c54af543fd8580f59b43d5961ffdd4292d1 100644 --- a/nearby/framework/java/android/nearby/NearbyDevice.java +++ b/nearby/framework/java/android/nearby/NearbyDevice.java @@ -25,6 +25,8 @@ import android.util.ArraySet; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.Objects; import java.util.Set; @@ -149,6 +151,7 @@ public abstract class NearbyDevice { * @hide */ @IntDef({Medium.BLE, Medium.BLUETOOTH}) + @Retention(RetentionPolicy.SOURCE) public @interface Medium { int BLE = 1; int BLUETOOTH = 2; diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java index a70b303dca3a1d059ff882b2f74134330330a095..00f1c389231d0c19255e7702c37ace9d6c15d263 100644 --- a/nearby/framework/java/android/nearby/NearbyManager.java +++ b/nearby/framework/java/android/nearby/NearbyManager.java @@ -34,6 +34,8 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.Objects; import java.util.WeakHashMap; @@ -63,6 +65,7 @@ public class NearbyManager { ScanStatus.SUCCESS, ScanStatus.ERROR, }) + @Retention(RetentionPolicy.SOURCE) public @interface ScanStatus { // The undetermined status, some modules may be initializing. Retry is suggested. int UNKNOWN = 0; @@ -281,6 +284,8 @@ public class NearbyManager { */ public void queryOffloadCapability(@NonNull @CallbackExecutor Executor executor, @NonNull Consumer callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); try { mService.queryOffloadCapability(new OffloadTransport(executor, callback)); } catch (RemoteException e) { diff --git a/nearby/framework/java/android/nearby/PairStatusMetadata.java b/nearby/framework/java/android/nearby/PairStatusMetadata.java deleted file mode 100644 index a4cd1348e46c61d5636eee2ac2d1b039d7a25092..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/PairStatusMetadata.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.Objects; - -/** - * Metadata about an ongoing paring. Wraps transient data like status and progress. - * - * @hide - */ -public final class PairStatusMetadata implements Parcelable { - - @Status - private final int mStatus; - - /** The status of the pairing. */ - @IntDef({ - Status.UNKNOWN, - Status.SUCCESS, - Status.FAIL, - Status.DISMISS - }) - public @interface Status { - int UNKNOWN = 1000; - int SUCCESS = 1001; - int FAIL = 1002; - int DISMISS = 1003; - } - - /** Converts the status to readable string. */ - public static String statusToString(@Status int status) { - switch (status) { - case Status.SUCCESS: - return "SUCCESS"; - case Status.FAIL: - return "FAIL"; - case Status.DISMISS: - return "DISMISS"; - case Status.UNKNOWN: - default: - return "UNKNOWN"; - } - } - - public int getStatus() { - return mStatus; - } - - @Override - public String toString() { - return "PairStatusMetadata[ status=" + statusToString(mStatus) + "]"; - } - - @Override - public boolean equals(Object other) { - if (other instanceof PairStatusMetadata) { - return mStatus == ((PairStatusMetadata) other).mStatus; - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(mStatus); - } - - public PairStatusMetadata(@Status int status) { - mStatus = status; - } - - public static final Creator CREATOR = new Creator() { - @Override - public PairStatusMetadata createFromParcel(Parcel in) { - return new PairStatusMetadata(in.readInt()); - } - - @Override - public PairStatusMetadata[] newArray(int size) { - return new PairStatusMetadata[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeInt(mStatus); - } -} diff --git a/nearby/framework/java/android/nearby/aidl/FastPairAccountKeyDeviceMetadataParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairAccountKeyDeviceMetadataParcel.aidl deleted file mode 100644 index 80143232af215eceedcf06439d91b36463515a9d..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/aidl/FastPairAccountKeyDeviceMetadataParcel.aidl +++ /dev/null @@ -1,35 +0,0 @@ -// 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 android.nearby.aidl; - -import android.nearby.aidl.FastPairDeviceMetadataParcel; -import android.nearby.aidl.FastPairDiscoveryItemParcel; - -/** - * Metadata of a Fast Pair device associated with an account. - * {@hide} - */ - // TODO(b/204780849): remove unnecessary fields and polish comments. -parcelable FastPairAccountKeyDeviceMetadataParcel { - // Key of the Fast Pair device associated with the account. - byte[] deviceAccountKey; - // Hash function of device account key and public bluetooth address. - byte[] sha256DeviceAccountKeyPublicAddress; - // Fast Pair device metadata for the Fast Pair device. - FastPairDeviceMetadataParcel metadata; - // Fast Pair discovery item tied to both the Fast Pair device and the - // account. - FastPairDiscoveryItemParcel discoveryItem; -} \ No newline at end of file diff --git a/nearby/framework/java/android/nearby/aidl/FastPairAntispoofKeyDeviceMetadataParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairAntispoofKeyDeviceMetadataParcel.aidl deleted file mode 100644 index 4fd4d4b83ffd58dabea40617bec559b06ec0d629..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/aidl/FastPairAntispoofKeyDeviceMetadataParcel.aidl +++ /dev/null @@ -1,31 +0,0 @@ -// 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 android.nearby.aidl; - -import android.nearby.aidl.FastPairDeviceMetadataParcel; - -/** - * Metadata of a Fast Pair device keyed by AntispoofKey, - * Used by initial pairing without account association. - * - * {@hide} - */ -parcelable FastPairAntispoofKeyDeviceMetadataParcel { - // Anti-spoof public key. - byte[] antispoofPublicKey; - - // Fast Pair device metadata for the Fast Pair device. - FastPairDeviceMetadataParcel deviceMetadata; -} \ No newline at end of file diff --git a/nearby/framework/java/android/nearby/aidl/FastPairDeviceMetadataParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairDeviceMetadataParcel.aidl deleted file mode 100644 index d90f6a1d8b5e219e580f8308731bc6e4c65aa96b..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/aidl/FastPairDeviceMetadataParcel.aidl +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 android.nearby.aidl; - -/** - * Fast Pair Device Metadata for a given device model ID. - * @hide - */ -// TODO(b/204780849): remove unnecessary fields and polish comments. -parcelable FastPairDeviceMetadataParcel { - // The image to show on the notification. - String imageUrl; - - // The intent that will be launched via the notification. - String intentUri; - - // The transmit power of the device's BLE chip. - int bleTxPower; - - // The distance that the device must be within to show a notification. - // If no distance is set, we default to 0.6 meters. Only Nearby admins can - // change this. - float triggerDistance; - - // The image icon that shows in the notification. - byte[] image; - - // The name of the device. - String name; - - int deviceType; - - // The image urls for device with device type "true wireless". - String trueWirelessImageUrlLeftBud; - String trueWirelessImageUrlRightBud; - String trueWirelessImageUrlCase; - - // The notification description for when the device is initially discovered. - String initialNotificationDescription; - - // The notification description for when the device is initially discovered - // and no account is logged in. - String initialNotificationDescriptionNoAccount; - - // The notification description for once we have finished pairing and the - // companion app has been opened. For Bisto devices, this String will point - // users to setting up the assistant. - String openCompanionAppDescription; - - // The notification description for once we have finished pairing and the - // companion app needs to be updated before use. - String updateCompanionAppDescription; - - // The notification description for once we have finished pairing and the - // companion app needs to be installed. - String downloadCompanionAppDescription; - - // The notification title when a pairing fails. - String unableToConnectTitle; - - // The notification summary when a pairing fails. - String unableToConnectDescription; - - // The description that helps user initially paired with device. - String initialPairingDescription; - - // The description that let user open the companion app. - String connectSuccessCompanionAppInstalled; - - // The description that let user download the companion app. - String connectSuccessCompanionAppNotInstalled; - - // The description that reminds user there is a paired device nearby. - String subsequentPairingDescription; - - // The description that reminds users opt in their device. - String retroactivePairingDescription; - - // The description that indicates companion app is about to launch. - String waitLaunchCompanionAppDescription; - - // The description that indicates go to bluetooth settings when connection - // fail. - String failConnectGoToSettingsDescription; -} \ No newline at end of file diff --git a/nearby/framework/java/android/nearby/aidl/FastPairDiscoveryItemParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairDiscoveryItemParcel.aidl deleted file mode 100644 index 2cc2daad00a68a86d5190c619b55aaaa21f9a528..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/aidl/FastPairDiscoveryItemParcel.aidl +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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 android.nearby.aidl; - -/** - * Fast Pair Discovery Item. - * @hide - */ -// TODO(b/204780849): remove unnecessary fields and polish comments. -parcelable FastPairDiscoveryItemParcel { - // Offline item: unique ID generated on client. - // Online item: unique ID generated on server. - String id; - - // The most recent all upper case mac associated with this item. - // (Mac-to-DiscoveryItem is a many-to-many relationship) - String macAddress; - - String actionUrl; - - // The bluetooth device name from advertisement - String deviceName; - - // Item's title - String title; - - // Item's description. - String description; - - // The URL for display - String displayUrl; - - // Client timestamp when the beacon was last observed in BLE scan. - long lastObservationTimestampMillis; - - // Client timestamp when the beacon was first observed in BLE scan. - long firstObservationTimestampMillis; - - // Item's current state. e.g. if the item is blocked. - int state; - - // The resolved url type for the action_url. - int actionUrlType; - - // The timestamp when the user is redirected to Play Store after clicking on - // the item. - long pendingAppInstallTimestampMillis; - - // Beacon's RSSI value - int rssi; - - // Beacon's tx power - int txPower; - - // Human readable name of the app designated to open the uri - // Used in the second line of the notification, "Open in {} app" - String appName; - - // Package name of the App that owns this item. - String packageName; - - // TriggerId identifies the trigger/beacon that is attached with a message. - // It's generated from server for online messages to synchronize formatting - // across client versions. - // Example: - // * BLE_UID: 3||deadbeef - // * BLE_URL: http://trigger.id - // See go/discovery-store-message-and-trigger-id for more details. - String triggerId; - - // Bytes of item icon in PNG format displayed in Discovery item list. - byte[] iconPng; - - // A FIFE URL of the item icon displayed in Discovery item list. - String iconFifeUrl; - - // Fast Pair antispoof key. - byte[] authenticationPublicKeySecp256r1; -} \ No newline at end of file diff --git a/nearby/framework/java/android/nearby/aidl/FastPairManageAccountDeviceRequestParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairManageAccountDeviceRequestParcel.aidl deleted file mode 100644 index 59834b2839fa1b167d4bf040241e7b4371fb2eb9..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/aidl/FastPairManageAccountDeviceRequestParcel.aidl +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 android.nearby.aidl; - -import android.accounts.Account; -import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel; - -/** - * Request details for managing Fast Pair device-account mapping. - * {@hide} - */ - // TODO(b/204780849): remove unnecessary fields and polish comments. -parcelable FastPairManageAccountDeviceRequestParcel { - Account account; - // MANAGE_ACCOUNT_DEVICE_ADD: add Fast Pair device to the account. - // MANAGE_ACCOUNT_DEVICE_REMOVE: remove Fast Pair device from the account. - int requestType; - // Fast Pair account key-ed device metadata. - FastPairAccountKeyDeviceMetadataParcel accountKeyDeviceMetadata; -} \ No newline at end of file diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairAccountDevicesMetadataCallback.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairAccountDevicesMetadataCallback.aidl deleted file mode 100644 index 7db18d00106503c2fd656e80f4434c89cad2b676..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/aidl/IFastPairAccountDevicesMetadataCallback.aidl +++ /dev/null @@ -1,28 +0,0 @@ -// 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 android.nearby.aidl; - -import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel; - -/** - * Provides callback interface for OEMs to send back metadata of FastPair - * devices associated with an account. - * - * {@hide} - */ -interface IFastPairAccountDevicesMetadataCallback { - void onFastPairAccountDevicesMetadataReceived(in FastPairAccountKeyDeviceMetadataParcel[] accountDevicesMetadata); - void onError(int code, String message); -} \ No newline at end of file diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairAntispoofKeyDeviceMetadataCallback.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairAntispoofKeyDeviceMetadataCallback.aidl deleted file mode 100644 index 38abba4fb1baccfc66a899e01d42b0f19792984b..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/aidl/IFastPairAntispoofKeyDeviceMetadataCallback.aidl +++ /dev/null @@ -1,27 +0,0 @@ -// 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 android.nearby.aidl; - -import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel; - -/** - * Provides callback interface for OEMs to send FastPair AntispoofKey Device metadata back. - * - * {@hide} - */ -interface IFastPairAntispoofKeyDeviceMetadataCallback { - void onFastPairAntispoofKeyDeviceMetadataReceived(in FastPairAntispoofKeyDeviceMetadataParcel metadata); - void onError(int code, String message); -} \ No newline at end of file diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairDataProvider.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairDataProvider.aidl deleted file mode 100644 index 295621188c15337d7d871e5847c45df91c6b9592..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/aidl/IFastPairDataProvider.aidl +++ /dev/null @@ -1,44 +0,0 @@ -// 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 android.nearby.aidl; - -import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel; -import android.nearby.aidl.IFastPairAntispoofKeyDeviceMetadataCallback; -import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel; -import android.nearby.aidl.IFastPairAccountDevicesMetadataCallback; -import android.nearby.aidl.FastPairEligibleAccountsRequestParcel; -import android.nearby.aidl.IFastPairEligibleAccountsCallback; -import android.nearby.aidl.FastPairManageAccountRequestParcel; -import android.nearby.aidl.IFastPairManageAccountCallback; -import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel; -import android.nearby.aidl.IFastPairManageAccountDeviceCallback; - -/** - * Interface for communicating with the fast pair providers. - * - * {@hide} - */ -oneway interface IFastPairDataProvider { - void loadFastPairAntispoofKeyDeviceMetadata(in FastPairAntispoofKeyDeviceMetadataRequestParcel request, - in IFastPairAntispoofKeyDeviceMetadataCallback callback); - void loadFastPairAccountDevicesMetadata(in FastPairAccountDevicesMetadataRequestParcel request, - in IFastPairAccountDevicesMetadataCallback callback); - void loadFastPairEligibleAccounts(in FastPairEligibleAccountsRequestParcel request, - in IFastPairEligibleAccountsCallback callback); - void manageFastPairAccount(in FastPairManageAccountRequestParcel request, - in IFastPairManageAccountCallback callback); - void manageFastPairAccountDevice(in FastPairManageAccountDeviceRequestParcel request, - in IFastPairManageAccountDeviceCallback callback); -} diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairEligibleAccountsCallback.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairEligibleAccountsCallback.aidl deleted file mode 100644 index 9990014d405af74e72e2c144fc184bda986d3df3..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/aidl/IFastPairEligibleAccountsCallback.aidl +++ /dev/null @@ -1,28 +0,0 @@ -// 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 android.nearby.aidl; - -import android.accounts.Account; -import android.nearby.aidl.FastPairEligibleAccountParcel; - -/** - * Provides callback interface for OEMs to return FastPair Eligible accounts. - * - * {@hide} - */ -interface IFastPairEligibleAccountsCallback { - void onFastPairEligibleAccountsReceived(in FastPairEligibleAccountParcel[] accounts); - void onError(int code, String message); - } \ No newline at end of file diff --git a/nearby/framework/java/android/nearby/aidl/IFastPairUiService.aidl b/nearby/framework/java/android/nearby/aidl/IFastPairUiService.aidl deleted file mode 100644 index 9200a9d25440e1e2e90e0bb78f5f5e54c5a6259c..0000000000000000000000000000000000000000 --- a/nearby/framework/java/android/nearby/aidl/IFastPairUiService.aidl +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2022, 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.nearby.aidl; - -import android.nearby.aidl.IFastPairStatusCallback; -import android.nearby.FastPairDevice; - -/** - * 0p API for controlling Fast Pair. Used to talk between foreground activities - * and background services. - * - * {@hide} - */ -interface IFastPairUiService { - - void registerCallback(in IFastPairStatusCallback fastPairStatusCallback); - - void unregisterCallback(in IFastPairStatusCallback fastPairStatusCallback); - - void connect(in FastPairDevice fastPairDevice); - - void cancel(in FastPairDevice fastPairDevice); -} \ No newline at end of file diff --git a/nearby/halfsheet/Android.bp b/nearby/halfsheet/Android.bp deleted file mode 100644 index 6190d45975c1e1dfdc6a1cdd4292cacd15844440..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/Android.bp +++ /dev/null @@ -1,56 +0,0 @@ -// 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 { - default_applicable_licenses: ["Android-Apache-2.0"], -} - -android_app { - name: "HalfSheetUX", - defaults: ["platform_app_defaults"], - srcs: ["src/**/*.java"], - resource_dirs: ["res"], - sdk_version: "module_current", - // This is included in tethering apex, which uses min SDK 30 - min_sdk_version: "30", - updatable: true, - certificate: ":com.android.nearby.halfsheetcertificate", - libs: [ - "framework-bluetooth", - "framework-connectivity-t.impl", - "framework-nearby-static", - ], - static_libs: [ - "androidx.annotation_annotation", - "androidx.fragment_fragment", - "androidx-constraintlayout_constraintlayout", - "androidx.localbroadcastmanager_localbroadcastmanager", - "androidx.core_core", - "androidx.appcompat_appcompat", - "androidx.recyclerview_recyclerview", - "androidx.lifecycle_lifecycle-runtime", - "androidx.lifecycle_lifecycle-extensions", - "com.google.android.material_material", - "fast-pair-lite-protos", - ], - manifest: "AndroidManifest.xml", - jarjar_rules: ":nearby-jarjar-rules", - apex_available: ["com.android.tethering",], - lint: { strict_updatability_linting: true } -} - -android_app_certificate { - name: "com.android.nearby.halfsheetcertificate", - certificate: "apk-certs/com.android.nearby.halfsheet" -} diff --git a/nearby/halfsheet/AndroidManifest.xml b/nearby/halfsheet/AndroidManifest.xml deleted file mode 100644 index 22987fb2a0e0533d1fbe11fe629b34d3896abb8d..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/AndroidManifest.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - diff --git a/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.pk8 b/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.pk8 deleted file mode 100644 index 187d51e6754399d45cb90c805151ef495a92f30d..0000000000000000000000000000000000000000 Binary files a/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.pk8 and /dev/null differ diff --git a/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.x509.pem b/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.x509.pem deleted file mode 100644 index 440c524a7f90a9dc8e317badfb0cc34fa2861870..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/apk-certs/com.android.nearby.halfsheet.x509.pem +++ /dev/null @@ -1,34 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIF6zCCA9OgAwIBAgIUU5ATKevcNA5ZSurwgwGenwrr4c4wDQYJKoZIhvcNAQEL -BQAwgYMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMQwwCgYDVQQH -DANNVFYxDzANBgNVBAoMBkdvb2dsZTEPMA0GA1UECwwGbmVhcmJ5MQswCQYDVQQD -DAJ3czEiMCAGCSqGSIb3DQEJARYTd2VpY2VzdW5AZ29vZ2xlLmNvbTAgFw0yMTEy -MDgwMTMxMzFaGA80NzU5MTEwNDAxMzEzMVowgYMxCzAJBgNVBAYTAlVTMRMwEQYD -VQQIDApDYWxpZm9ybmlhMQwwCgYDVQQHDANNVFYxDzANBgNVBAoMBkdvb2dsZTEP -MA0GA1UECwwGbmVhcmJ5MQswCQYDVQQDDAJ3czEiMCAGCSqGSIb3DQEJARYTd2Vp -Y2VzdW5AZ29vZ2xlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB -AO0JW1YZ5bKHZG5B9eputz3kGREmXcWZ97dg/ODDs3+op4ulBmgaYeo5yeCy29GI -Sjgxo4G+9fNZ7Fejrk5/LLWovAoRvVxnkRxCkTfp15jZpKNnZjT2iTRLXzNz2O04 -cC0jB81mu5vJ9a8pt+EQkuSwjDMiUi6q4Sf6IRxtTCd5a1yn9eHf1y2BbCmU+Eys -bs97HJl9PgMCp7hP+dYDxEtNTAESg5IpJ1i7uINgPNl8d0tvJ9rOEdy0IcdeGwt/ -t0L9fIoRCePttH+idKIyDjcNyp9WtX2/wZKlsGap83rGzLdL2PI4DYJ2Ytmy8W3a -9qFJNrhl3Q3BYgPlcCg9qQOIKq6ZJgFFH3snVDKvtSFd8b9ofK7UzD5g2SllTqDA -4YvrdK4GETQunSjG7AC/2PpvN/FdhHm7pBi0fkgwykMh35gv0h8mmb6pBISYgr85 -+GMBilNiNJ4G6j3cdOa72pvfDW5qn5dn5ks8cIgW2X1uF/GT8rR6Mb2rwhjY9eXk -TaP0RykyzheMY/7dWeA/PdN3uMCEJEt72ZakDIswgQVPCIw8KQPIf6pl0d5hcLSV -QzhqBaXudseVg0QlZ86iaobpZvCrW0KqQmMU5GVhEtDc2sPe5e+TCmUC/H+vo8F8 -1UYu3MJaBcpePFlgIsLhW0niUTfCq2FiNrPykOJT7U9NAgMBAAGjUzBRMB0GA1Ud -DgQWBBQKSepRcKTv9hr8mmKjYCL7NeG2izAfBgNVHSMEGDAWgBQKSepRcKTv9hr8 -mmKjYCL7NeG2izAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQC/ -BoItafzvjYPzENY16BIkgRqJVU7IosWxGLczzg19NFu6HPa54alqkawp7RI1ZNVH -bJjQma5ap0L+Y06/peIU9rvEtfbCkkYJwvIaSRlTlzrNwNEcj3yJMmGTr/wfIzq8 -PN1t0hihnqI8ZguOPC+sV6ARoC+ygkwaLU1oPbVvOGz9WplvSokE1mvtqKAyuDoL -LZfWwbhxRAgwgCIEz6cPfEcgg3Xzc+L4OzmNhTTc7GNOAtvvW7Zqc2Lohb8nQMNw -uY65yiHPNmjmc+xLHZk3jQg82tKv792JJRkVXPsIfQV087IzxFFjjvKy82rVfeaN -F9g2EpUvdjtm8zx7K5tiDv9Es/Up7oOnoB5baLgnMAEVMTZY+4k/6BfVM5CVUu+H -AO1yh2yeNWbzY8B+zxRef3C2Ax68lJHFyz8J1pfrGpWxML3rDmWiVDMtEk73t3g+ -lcyLYo7OW+iBn6BODRcINO4R640oyMjFz2wPSPAsU0Zj/MbgC6iaS+goS3QnyPQS -O3hKWfwqQuA7BZ0la1n+plKH5PKxQESAbd37arzCsgQuktl33ONiwYOt6eUyHl/S -E3ZdldkmGm9z0mcBYG9NczDBSYmtuZOGjEzIRqI5GFD2WixE+dqTzVP/kyBd4BLc -OTmBynN/8D/qdUZNrT+tgs+mH/I2SsKYW9Zymwf7Qw== ------END CERTIFICATE----- diff --git a/nearby/halfsheet/apk-certs/key.pem b/nearby/halfsheet/apk-certs/key.pem deleted file mode 100644 index e9f4288aa08836c944505ea3486a7b198854a674..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/apk-certs/key.pem +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDtCVtWGeWyh2Ru -QfXqbrc95BkRJl3Fmfe3YPzgw7N/qKeLpQZoGmHqOcngstvRiEo4MaOBvvXzWexX -o65Ofyy1qLwKEb1cZ5EcQpE36deY2aSjZ2Y09ok0S18zc9jtOHAtIwfNZrubyfWv -KbfhEJLksIwzIlIuquEn+iEcbUwneWtcp/Xh39ctgWwplPhMrG7PexyZfT4DAqe4 -T/nWA8RLTUwBEoOSKSdYu7iDYDzZfHdLbyfazhHctCHHXhsLf7dC/XyKEQnj7bR/ -onSiMg43DcqfVrV9v8GSpbBmqfN6xsy3S9jyOA2CdmLZsvFt2vahSTa4Zd0NwWID -5XAoPakDiCqumSYBRR97J1Qyr7UhXfG/aHyu1Mw+YNkpZU6gwOGL63SuBhE0Lp0o -xuwAv9j6bzfxXYR5u6QYtH5IMMpDId+YL9IfJpm+qQSEmIK/OfhjAYpTYjSeBuo9 -3HTmu9qb3w1uap+XZ+ZLPHCIFtl9bhfxk/K0ejG9q8IY2PXl5E2j9EcpMs4XjGP+ -3VngPz3Td7jAhCRLe9mWpAyLMIEFTwiMPCkDyH+qZdHeYXC0lUM4agWl7nbHlYNE -JWfOomqG6Wbwq1tCqkJjFORlYRLQ3NrD3uXvkwplAvx/r6PBfNVGLtzCWgXKXjxZ -YCLC4VtJ4lE3wqthYjaz8pDiU+1PTQIDAQABAoICAQCt4R5CM+8enlka1IIbvann -2cpVnUpOaNqhh6EZFBY5gDOfqafgd/H5yvh/P1UnCI5BWJBz3ew33nAT/fsglAPt -ImEGFetNvJ9jFqXGWWCRPJ6cS35bPbp6RQwKB2JK6grH4ZmYoFLhPi5elwDPNcQ7 -xBKkc/nLSAiwtbjSTI7/qf8K0h752aTUOctpWWEnhZon00ywf4Ic3TbBatF/n/W/ -s20coEMp1cyKN/JrVQ5uD/LGwDyBModB2lWpFSxLrB14I9DWyxbxP28X7ckXLhbl -ZdWMOyQZoa/S7n5PYT49g1Wq5BW54UpvuH5c6fpWtrgSqk1cyUR2EbTf3NAAhPLU -PgPK8wbFMcMB3TpQDXl7USA7QX5wSv22OfhivPsHQ9szGM0f84mK0PhXYPWBiNUY -Y8rrIjOijB4eFGDFnTIMTofAb07NxRThci710BYUqgBVTBG5N+avIesjwkikMjOI -PwYukKSQSw/Tqxy5Z9l22xksGynBZFjEFs/WT5pDczPAktA4xW3CGxjkMsIYaOBs -OCEujqc5+mHSywYvy8aN+nA+yPucJP5e5pLZ1qaU0tqyakCx8XeeOyP6Wfm3UAAV -AYelBRcWcJxM51w4o5UnUnpBD+Uxiz1sRVlqa9bLJjP4M+wJNL+WaIn9D6WhPOvl -+naDC+p29ou2JzyKFDsOQQKCAQEA+Jalm+xAAPc+t/gCdAqEDo0NMA2/NG8m9BRc -CVZRRaWVyGPeg5ziT/7caGwy2jpOZEjK0OOTCAqF+sJRDj6DDIw7nDrlxNyaXnCF -gguQHFIYaHcjKGTs5l0vgL3H7pMFHN2qVynf4xrTuBXyT1GJ4vdWKAJbooa02c8W -XI2fjwZ7Y8wSWrm1tn3oTTBR3N6o1GyPY6/TrL0mhpWwgx5eJeLl3GuUxOhXY5R9 -y48ziS97Dqdq75MxUOHickofCNcm7p+jA8Hg+SxLMR/kUFsXOxawmvsBqdL1XzU5 -LTS7xAEY9iMuBcO6yIxcxqBx96idjsPXx1lgARo1CpaZYCzgPQKCAQEA9BqKMN/Y -o+T+ac99St8x3TYkk5lkvLVqlPw+EQhEqrm9EEBPntxWM5FEIpPVmFm7taGTgPfN -KKaaNxX5XyK9B2v1QqN7XrX0nF4+6x7ao64fdpRUParIuBVctqzQWWthme66eHrf -L86T/tkt3o/7p+Hd4Z9UT3FaAew1ggWr00xz5PJ/4b3f3mRmtNmgeTYskWMxOpSj -bEenom4Row7sfLNeXNSWDGlzJ/lf6svvbVM2X5h2uFsxlt/Frq9ooTA3wwhnbd1i -cFifDQ6cxF5mBpz/V/hnlHVfuXlknEZa9EQXHNo/aC9y+bR+ai05FJyK/WgqleW8 -5PBmoTReWA2MUQKCAQAnnnLkh+GnhcBEN83ESszDOO3KI9a+d5yguAH3Jv+q9voJ -Rwl2tnFHSJo+NkhgiXxm9UcFxc9wL6Us0v1yJLpkLJFvk9984Z/kv1A36rncGaV0 -ONCspnEvQdjJTvXnax0cfaOhYrYhDuyBYVYOGDO+rabYl4+dNpTqRdwNgjDU7baK -sEKYnRJ99FEqxDG33vDPckHkJGi7FiZmusK4EwX0SdZSq/6450LORyNJZxhSm/Oj -4UDkz/PDLU0W5ANQOGInE+A6QBMoA0w0lx2fRPVN4I7jFHAubcXXl7b2InpugbJF -wFOcbZZ+UgiTS4z+aKw7zbC9P9xSMKgVeO0W6/ANAoIBABe0LA8q7YKczgfAWk5W -9iShCVQ75QheJYdqJyzIPMLHXpChbhnjE4vWY2NoL6mnrQ6qLgSsC4QTCY6n15th -aDG8Tgi2j1hXGvXEQR/b0ydp1SxSowuJ9gvKJ0Kl7WWBg+zKvdjNNbcSvFRXCpk+ -KhXXXRB3xFwiibb+FQQXQOQ33FkzIy/snDygS0jsiSS8Gf/UPgeOP4BYRPME9Tl8 -TYKeeF9TVW7HHqOXF7VZMFrRZcpKp9ynHl2kRTH9Xo+oewG5YzHL+a8nK+q8rIR1 -Fjs2K6WDPauw6ia8nwR94H8vzX7Dwrx/Pw74c/4jfhN+UBDjeJ8tu/YPUif9SdwL -FMECggEALdCGKfQ4vPmqI6UdfVB5hdCPoM6tUsI2yrXFvlHjSGVanC/IG9x2mpRb -4odamLYx4G4NjP1IJSY08LFT9VhLZtRM1W3fGeboW12LTEVNrI3lRBU84rAQ1ced -l6/DvTKJjhfwTxb/W7sqmZY5hF3QuNxs67Z8x0pe4b58musa0qFCs4Sa8qTNZKRW -fIbxIKuvu1HSNOKkZLu6Gq8km+XIlVAaSVA03Tt+EK74MFL6+pcd7/VkS00MAYUC -gS4ic+QFzCl5P8zl/GoX8iUFsRZQCSJkZ75VwO13pEupVwCAW8WWJO83U4jBsnJs -ayrX7pbsnW6jsNYBUlck+RYVYkVkxA== ------END PRIVATE KEY----- diff --git a/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_enter.xml b/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_enter.xml deleted file mode 100644 index 098dccbf503d2ecd4092be40adabff3bad328e0f..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_enter.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_exit.xml b/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_exit.xml deleted file mode 100644 index 1cf7401f76a33f027b8c2c5cbb82ed50bd95cde6..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/anim/fast_pair_bottom_sheet_exit.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_in.xml b/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_in.xml deleted file mode 100644 index 9a51ddbe40130f417348607e8099875f89c33796..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_in.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - diff --git a/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_out.xml b/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_out.xml deleted file mode 100644 index c589482c33aae451b3e5a99cf7ac58da40dd5f5d..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/anim/fast_pair_half_sheet_slide_out.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - diff --git a/nearby/halfsheet/res/drawable/fast_pair_ic_info.xml b/nearby/halfsheet/res/drawable/fast_pair_ic_info.xml deleted file mode 100644 index 7d61d1c970c0dbeddd49081ae8be03f2844e37fc..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/drawable/fast_pair_ic_info.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - \ No newline at end of file diff --git a/nearby/halfsheet/res/drawable/fastpair_outline.xml b/nearby/halfsheet/res/drawable/fastpair_outline.xml deleted file mode 100644 index 6765e1194c97fae256e48b4ae8e5eeb67eee19db..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/drawable/fastpair_outline.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/nearby/halfsheet/res/drawable/half_sheet_bg.xml b/nearby/halfsheet/res/drawable/half_sheet_bg.xml deleted file mode 100644 index 7e7d8ddaeb33d026f7f02984f3d47151a0b02179..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/drawable/half_sheet_bg.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/nearby/halfsheet/res/drawable/quantum_ic_devices_other_vd_theme_24.xml b/nearby/halfsheet/res/drawable/quantum_ic_devices_other_vd_theme_24.xml deleted file mode 100644 index 3dcfdee413d2758c4db10f9df0a1f3d5aafe4bca..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/drawable/quantum_ic_devices_other_vd_theme_24.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - diff --git a/nearby/halfsheet/res/layout-land/fast_pair_device_pairing_fragment.xml b/nearby/halfsheet/res/layout-land/fast_pair_device_pairing_fragment.xml deleted file mode 100644 index 545f7fa47239630921197c7f8fd5f9d46e89548b..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/layout-land/fast_pair_device_pairing_fragment.xml +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/nearby/halfsheet/res/layout-land/fast_pair_half_sheet.xml b/nearby/halfsheet/res/layout-land/fast_pair_half_sheet.xml deleted file mode 100644 index e993536fa052047ad70bc967e554ffd5b1d87469..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/layout-land/fast_pair_half_sheet.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml b/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml deleted file mode 100644 index 77cd1ea56cd59ab8a6b9d26df01ac0cdd985c223..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/nearby/halfsheet/res/layout/fast_pair_half_sheet.xml b/nearby/halfsheet/res/layout/fast_pair_half_sheet.xml deleted file mode 100644 index 705aa1b15804a16b2145f9a0480e29958e8e9df5..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/layout/fast_pair_half_sheet.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/nearby/halfsheet/res/layout/fast_pair_heads_up_notification.xml b/nearby/halfsheet/res/layout/fast_pair_heads_up_notification.xml deleted file mode 100644 index 11b83434013b4f5e562a61d685eeaa8ed3671343..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/layout/fast_pair_heads_up_notification.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_large_image.xml b/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_large_image.xml deleted file mode 100644 index dd289477eab92ce82271cf78ab57573cc3270c16..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_large_image.xml +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_small_image.xml b/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_small_image.xml deleted file mode 100644 index ee1d89f59bdf374cdd45de222be6d734e7432263..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/layout/fast_pair_heads_up_notification_small_image.xml +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/nearby/halfsheet/res/values-af/strings.xml b/nearby/halfsheet/res/values-af/strings.xml deleted file mode 100644 index b0f56316da7e4048b9e8e398066b789a76bb14f1..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-af/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Begin tans opstelling …" - "Stel toestel op" - "Toestel is gekoppel" - "Gekoppel aan “%s”" - "Kon nie koppel nie" - "Kan nie koppel nie" - "Probeer die toestel self saambind" - "Probeer om die toestel in saambindmodus te sit" - "Toestelle binne bereik" - "Toestelle met jou rekening" - "Jou gestoorde toestel is reg" - "Naby" - "Toestelle" - "Koppel tans …" - "Battery: %d%%" - "Klaar" - "Stoor" - "Koppel" - "Stel op" - "Instellings" - diff --git a/nearby/halfsheet/res/values-am/strings.xml b/nearby/halfsheet/res/values-am/strings.xml deleted file mode 100644 index 7c0aed47d8fc902b921dcc4e441f262c78807203..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-am/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "ማዋቀርን በመጀመር ላይ…" - "መሣሪያ አዋቅር" - "መሣሪያ ተገናኝቷል" - "ከ«%s» ጋር ተገናኝቷል" - "መገናኘት አልተቻለም" - "መገናኘት አልተቻለም" - "በእጅ ከመሣሪያው ጋር ለማጣመር ይሞክሩ" - "መሣሪያውን ወደ ማጣመር ሁነታ ለማስገባት ይሞክሩ" - "በቅርብ ያሉ መሣሪያዎች" - "ከመለያዎ ጋር መሣሪያዎች" - "የተቀመጠው መሣሪያዎ ይገኛል" - "በአቅራቢያ" - "መሣሪያዎች" - "በማገናኘት ላይ…" - "ባትሪ፦ %d%%" - "ተጠናቅቋል" - "አስቀምጥ" - "አገናኝ" - "አዋቅር" - "ቅንብሮች" - diff --git a/nearby/halfsheet/res/values-ar/strings.xml b/nearby/halfsheet/res/values-ar/strings.xml deleted file mode 100644 index 089faaa95b9fb36981fa88637737706c8e451dc6..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-ar/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "جارٍ الإعداد…" - "إعداد جهاز" - "تمّ إقران الجهاز" - "‏تم الربط بـ \"%s\"" - "تعذّر الربط" - "يتعذَّر الاتصال" - "جرِّب الإقران يدويًا بالجهاز." - "جرِّب تشغيل الجهاز في وضع الإقران." - "الأجهزة القريبة" - "الأجهزة المرتبطة بحسابك" - "جهازك المحفوظ متاح" - "مشاركة عن قرب" - "الأجهزة" - "جارٍ الاتصال…" - "‏البطارية: %d%%" - "تم" - "حفظ" - "ربط" - "إعداد" - "الإعدادات" - diff --git a/nearby/halfsheet/res/values-as/strings.xml b/nearby/halfsheet/res/values-as/strings.xml deleted file mode 100644 index bb9dfccc09e5bd45516a59ce755b657532ac748b..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-as/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "ছেটআপ আৰম্ভ কৰি থকা হৈছে…" - "ডিভাইচ ছেট আপ কৰক" - "ডিভাইচ সংযোগ কৰা হ’ল" - "“%s”ৰ সৈতে সংযোগ কৰা হ’ল" - "সংযোগ কৰিব পৰা নগ’ল" - "সংযোগ কৰিব পৰা নাই" - "ডিভাইচটোৰ সৈতে মেনুৱেলী পেয়াৰ কৰক" - "ডিভাইচক পেয়াৰ কৰা ম’ডত ৰাখি চাওক" - "সংযোগ সীমাত থকা ডিভাইচসমূহ" - "আপোনাৰ একাউণ্টত সংযোগ হোৱা ডিভাইচবোৰ" - "আপুনি ছেভ কৰা ডিভাইচ উপলব্ধ" - "নিকটৱৰ্তী" - "ডিভাইচ" - "সংযোগ কৰি থকা হৈছে…" - "বেটাৰী: %d%%" - "হ’ল" - "ছেভ কৰক" - "সংযোগ কৰক" - "ছেট আপ কৰক" - "ছেটিং" - diff --git a/nearby/halfsheet/res/values-az/strings.xml b/nearby/halfsheet/res/values-az/strings.xml deleted file mode 100644 index 844963b46db71a311bedf84b3d2878d07a544902..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-az/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Ayarlama başladılır…" - "Cihazı quraşdırın" - "Cihaz qoşulub" - "“%s” şəbəkəsinə qoşulub" - "Qoşulmaq mümkün olmadı" - "Qoşulmaq olmur" - "Cihazı manual olaraq birləşdirin" - "Cihazı qoşalaşdırma rejiminə qoymağa çalışın" - "Əl altında olan cihazlar" - "Hesabınızdakı cihazlar" - "Yadda saxlanmış cihazınız əlçatandır" - "Yaxınlıqda" - "Cihazlar" - "Qoşulur…" - "Batareya: %d%%" - "Oldu" - "Saxlayın" - "Qoşun" - "Ayarlayın" - "Ayarlar" - diff --git a/nearby/halfsheet/res/values-b+sr+Latn/strings.xml b/nearby/halfsheet/res/values-b+sr+Latn/strings.xml deleted file mode 100644 index fcd1dc613b871b19654a7a6f3f56a589962c3462..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-b+sr+Latn/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Podešavanje se pokreće…" - "Podesite uređaj" - "Uređaj je povezan" - "Povezani ste sa uređajem %s" - "Povezivanje nije uspelo" - "Povezivanje nije uspelo" - "Probajte da uparite ručno sa uređajem" - "Probajte da prebacite uređaj u režim uparivanja" - "Uređaji u dometu" - "Uređaji povezani sa nalogom" - "Sačuvani uređaj je dostupan" - "U blizini" - "Uređaji" - "Povezuje se…" - "Baterija: %d%%" - "Gotovo" - "Sačuvaj" - "Poveži" - "Podesi" - "Podešavanja" - diff --git a/nearby/halfsheet/res/values-be/strings.xml b/nearby/halfsheet/res/values-be/strings.xml deleted file mode 100644 index f4699224863c8f9645638904d30a29b5d151ec1b..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-be/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Пачынаецца наладжванне…" - "Наладзьце прыладу" - "Прылада падключана" - "Падключана да прылады \"%s\"" - "Не ўдалося падключыцца" - "Не ўдалося падключыцца" - "Паспрабуйце спалучыць прыладу ўручную" - "Перавядзіце прыладу ў рэжым спалучэння" - "Прылады ў межах дасягальнасці" - "Прылады з вашым уліковым запісам" - "Ёсць захаваная вамі прылада" - "Паблізу" - "Прылады" - "Падключэнне…" - "Узровень зараду: %d%%" - "Гатова" - "Захаваць" - "Падключыць" - "Наладзіць" - "Налады" - diff --git a/nearby/halfsheet/res/values-bg/strings.xml b/nearby/halfsheet/res/values-bg/strings.xml deleted file mode 100644 index a0c5103cd871488f0afbf5111c00695ac7baf43c..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-bg/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Настройването се стартира…" - "Настройване на устройството" - "Устройството е свързано" - "Установена е връзка с(ъс) „%s“" - "Свързването не бе успешно" - "Не може да се установи връзка" - "Опитайте да сдвоите устройството ръчно" - "Опитайте да зададете режим на сдвояване за устройството" - "Устройства в обхват" - "Устройства с профила ви" - "Запазеното ви у-во е налице" - "В близост" - "Устройства" - "Свързва се…" - "Батерия: %d%%" - "Готово" - "Запазване" - "Свързване" - "Настройване" - "Настройки" - diff --git a/nearby/halfsheet/res/values-bn/strings.xml b/nearby/halfsheet/res/values-bn/strings.xml deleted file mode 100644 index 4d6afc015b2eca081317d5523c3496bb4ffbe2a7..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-bn/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "সেট-আপ করা শুরু হচ্ছে…" - "ডিভাইস সেট-আপ করুন" - "ডিভাইস কানেক্ট হয়েছে" - "“%s”-এ কানেক্ট করা হয়েছে" - "কানেক্ট করা যায়নি" - "কানেক্ট করা যায়নি" - "ডিভাইসে ম্যানুয়ালি পেয়ার করার চেষ্টা করুন" - "ডিভাইস \'যোগ করার\' মোডে রাখার চেষ্টা করুন" - "কাছে রয়েছে এমন ডিভাইস" - "আপনার অ্যাকাউন্টের সাথে কানেক্ট থাকা ডিভাইস" - "আপনার সেভ করা ডিভাইস উপলভ্য আছে" - "নিয়ারবাই" - "ডিভাইস" - "কানেক্ট করা হচ্ছে…" - "ব্যাটারি: %d%%" - "হয়ে গেছে" - "সেভ করুন" - "কানেক্ট করুন" - "সেট-আপ করুন" - "সেটিংস" - diff --git a/nearby/halfsheet/res/values-bs/strings.xml b/nearby/halfsheet/res/values-bs/strings.xml deleted file mode 100644 index 47f13c3ac9fd67bb52ac52f2ade624d5d99a7e8d..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-bs/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Pokretanje postavljanja…" - "Postavi uređaj" - "Uređaj je povezan" - "Povezano s uređajem “%s”" - "Povezivanje nije uspjelo" - "Nije moguće povezati" - "Pokušajte ručno upariti uređaj" - "Pokušajte staviti uređaj u način rada za uparivanje" - "Uređaji u dometu" - "Uređaji s vašim računom" - "Sačuvani uređaj je dostupan" - "U blizini" - "Uređaji" - "Povezivanje…" - "Baterija: %d%%" - "Gotovo" - "Sačuvaj" - "Poveži" - "Postavi" - "Postavke" - diff --git a/nearby/halfsheet/res/values-ca/strings.xml b/nearby/halfsheet/res/values-ca/strings.xml deleted file mode 100644 index 44ebc3e0051c4311c2b20bbbc89b5646c52c4888..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-ca/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Iniciant la configuració…" - "Configura el dispositiu" - "El dispositiu s\'ha connectat" - "Connectat a %s" - "No s\'ha pogut connectar" - "No es pot establir la connexió" - "Prova de vincular el dispositiu manualment" - "Prova d\'activar el mode de vinculació al dispositiu" - "Dispositius a l\'abast" - "Dispositius amb el teu compte" - "Dispositiu desat disponible" - "A prop" - "Dispositius" - "S\'està connectant…" - "Bateria: %d%%" - "Fet" - "Desa" - "Connecta" - "Configura" - "Configuració" - diff --git a/nearby/halfsheet/res/values-cs/strings.xml b/nearby/halfsheet/res/values-cs/strings.xml deleted file mode 100644 index 872eef5c86e4bc17e0243d47a4d5cadded106b3f..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-cs/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Zahajování nastavení…" - "Nastavení zařízení" - "Zařízení připojeno" - "Připojeno k zařízení %s" - "Nelze se připojit" - "Nepodařilo se připojit" - "Zkuste zařízení spárovat ručně" - "Přepněte zařízení do režimu párování" - "Zařízení v dosahu" - "Zařízení s vaším účtem" - "Je dostupné uložené zařízení" - "V okolí" - "Zařízení" - "Připojování…" - "Baterie: %d %%" - "Hotovo" - "Uložit" - "Připojit" - "Nastavit" - "Nastavení" - diff --git a/nearby/halfsheet/res/values-da/strings.xml b/nearby/halfsheet/res/values-da/strings.xml deleted file mode 100644 index 89b221f8f940810542c69bc2d1eb20c2ba1d8a65..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-da/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Begynder konfiguration…" - "Konfigurer enhed" - "Enheden er forbundet" - "Der er oprettet forbindelse til \"%s\"" - "Forbindelsen kan ikke oprettes" - "Kunne ikke forbindes" - "Prøv at parre med enheden manuelt" - "Prøv at sætte enheden i parringstilstand" - "Enheder inden for rækkevidde" - "Enheder med din konto" - "Din gemte enhed er tilgængelig" - "Tæt på" - "Enheder" - "Forbinder…" - "Batteri: %d %%" - "Luk" - "Gem" - "Opret forbindelse" - "Konfigurer" - "Indstillinger" - diff --git a/nearby/halfsheet/res/values-de/strings.xml b/nearby/halfsheet/res/values-de/strings.xml deleted file mode 100644 index de54114e2309019bf4f1981b1e24caa5ddbcaf80..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-de/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Einrichtung wird gestartet..." - "Gerät einrichten" - "Gerät verbunden" - "Mit „%s“ verbunden" - "Verbindung nicht möglich" - "Kein Verbindungsaufbau möglich" - "Versuche, das Gerät manuell zu koppeln" - "Versuche, das Gerät in den Kopplungsmodus zu versetzen" - "Geräte in Reichweite" - "Geräte mit deinem Konto" - "Gespeichertes Gerät verfügbar" - "Nearby" - "Geräte" - "Wird verbunden…" - "Akkustand: %d %%" - "Fertig" - "Speichern" - "Verbinden" - "Einrichten" - "Einstellungen" - diff --git a/nearby/halfsheet/res/values-el/strings.xml b/nearby/halfsheet/res/values-el/strings.xml deleted file mode 100644 index 1ea467a51e38a9097ecb416429c30e76393fb3ae..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-el/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Έναρξη ρύθμισης…" - "Ρύθμιση συσκευής" - "Η συσκευή συνδέθηκε" - "Συνδέθηκε με τη συσκευή %s" - "Αδυναμία σύνδεσης" - "Δεν είναι δυνατή η σύνδεση" - "Δοκιμάστε να κάνετε μη αυτόματη σύζευξη στη συσκευή" - "Δοκιμάστε να θέσετε τη συσκευή σε λειτουργία σύζευξης" - "Συσκευές εντός εμβέλειας" - "Συσκευές με τον λογαριασμό σας" - "Η αποθ. συσκ. είναι διαθέσιμη" - "Κοντά" - "Συσκευές" - "Σύνδεση…" - "Μπαταρία: %d%%" - "Τέλος" - "Αποθήκευση" - "Σύνδεση" - "Ρύθμιση" - "Ρυθμίσεις" - diff --git a/nearby/halfsheet/res/values-en-rAU/strings.xml b/nearby/halfsheet/res/values-en-rAU/strings.xml deleted file mode 100644 index b7039a13cb12fb8f8f7f3660618aef22510ab8c2..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-en-rAU/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Starting setup…" - "Set up device" - "Device connected" - "Connected to \'%s\'" - "Couldn\'t connect" - "Unable to connect" - "Try pairing to the device manually" - "Try putting the device into pairing mode" - "Devices within reach" - "Devices with your account" - "Your saved device is available" - "Nearby" - "Devices" - "Connecting…" - "Battery: %d%%" - "Done" - "Save" - "Connect" - "Set up" - "Settings" - diff --git a/nearby/halfsheet/res/values-en-rCA/strings.xml b/nearby/halfsheet/res/values-en-rCA/strings.xml deleted file mode 100644 index 06b3a5eff267e3853d6c8ebd909d75e694b74529..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-en-rCA/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Starting Setup…" - "Set up device" - "Device connected" - "Connected to “%s”" - "Couldn\'t connect" - "Unable to connect" - "Try manually pairing to the device" - "Try putting the device into pairing mode" - "Devices within reach" - "Devices with your account" - "Your saved device is available" - "Nearby" - "Devices" - "Connecting…" - "Battery: %d%%" - "Done" - "Save" - "Connect" - "Set up" - "Settings" - diff --git a/nearby/halfsheet/res/values-en-rGB/strings.xml b/nearby/halfsheet/res/values-en-rGB/strings.xml deleted file mode 100644 index b7039a13cb12fb8f8f7f3660618aef22510ab8c2..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-en-rGB/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Starting setup…" - "Set up device" - "Device connected" - "Connected to \'%s\'" - "Couldn\'t connect" - "Unable to connect" - "Try pairing to the device manually" - "Try putting the device into pairing mode" - "Devices within reach" - "Devices with your account" - "Your saved device is available" - "Nearby" - "Devices" - "Connecting…" - "Battery: %d%%" - "Done" - "Save" - "Connect" - "Set up" - "Settings" - diff --git a/nearby/halfsheet/res/values-en-rIN/strings.xml b/nearby/halfsheet/res/values-en-rIN/strings.xml deleted file mode 100644 index b7039a13cb12fb8f8f7f3660618aef22510ab8c2..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-en-rIN/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Starting setup…" - "Set up device" - "Device connected" - "Connected to \'%s\'" - "Couldn\'t connect" - "Unable to connect" - "Try pairing to the device manually" - "Try putting the device into pairing mode" - "Devices within reach" - "Devices with your account" - "Your saved device is available" - "Nearby" - "Devices" - "Connecting…" - "Battery: %d%%" - "Done" - "Save" - "Connect" - "Set up" - "Settings" - diff --git a/nearby/halfsheet/res/values-en-rXC/strings.xml b/nearby/halfsheet/res/values-en-rXC/strings.xml deleted file mode 100644 index c71272ef30a68da1cad9f4f57cb049ee67a0eb8c..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-en-rXC/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‏‏‏‎‎‎‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‏‏‏‎Starting Setup…‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‎‎Set up device‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‎‏‎‎‏‎‏‏‎‎‏‏‎‏‎Device connected‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‏‎‏‏‏‏‏‎‏‏‎‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‎‎‎‎‏‏‎‏‏‏‏‎‎Connected to “%s”‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎Couldn\'t connect‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‏‎‏‎‎‏‎Unable to connect‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‎‏‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‏‏‎Try manually pairing to the device‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‎‏‎‏‎‏‏‏‎‏‏‏‎‏‎‏‏‏‎‎Try putting the device into pairing mode‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‎‏‎‎‏‎‏‏‎‎‏‎‎‎‎‏‎‎‏‏‎‏‎‎‎‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‏‏‎‏‎‎‎‎Devices within reach‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎‏‏‏‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‏‏‎‎Devices with your account‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‏‏‎‏‏‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‎‎Your saved device is available‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎‏‏‎‎‏‏‏‎Nearby‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‎‎‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‎Devices‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‎‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‎‎‎‏‎‎‏‎‏‏‎‎‎‏‏‏‏‎‎‏‏‎‎‏‎‎‎‏‏‎‏‏‏‎Connecting…‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‎‎‏‏‏‎‎‏‏‎‏‎‎‏‏‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‎‎‎‎‏‎‏‎Battery: %d%%‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‏‏‎‏‎‏‎‏‎‏‏‏‎‎‏‎‎‏‏‎‏‏‎‏‎‏‏‏‎‎‎‏‎‎‏‎‏‏‎Done‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‎‎‎‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‏‎‎‏‎‎‎‎Save‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‏‏‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‏‏‎‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‏‎Connect‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‎‎‎‏‎‎‎‎‏‏‏‎‏‏‎‏‎‏‏‎‏‏‏‎‎‏‎‏‏‎‎‏‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎Set up‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‎‏‎‏‏‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‏‎‎Settings‎‏‎‎‏‎" - diff --git a/nearby/halfsheet/res/values-es-rUS/strings.xml b/nearby/halfsheet/res/values-es-rUS/strings.xml deleted file mode 100644 index 05eb75dc7bb225db838873fd2cda0ca31f5caa91..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-es-rUS/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Iniciando la configuración…" - "Configuración del dispositivo" - "Se conectó el dispositivo" - "Se estableció conexión con \"%s\"" - "No se pudo establecer conexión" - "No se pudo establecer conexión" - "Intenta vincular el dispositivo manualmente" - "Prueba poner el dispositivo en el modo de vinculación" - "Dispositivos al alcance" - "Dispositivos con tu cuenta" - "El dispositivo está disponible" - "Nearby" - "Dispositivos" - "Conectando…" - "Batería: %d%%" - "Listo" - "Guardar" - "Conectar" - "Configurar" - "Configuración" - diff --git a/nearby/halfsheet/res/values-es/strings.xml b/nearby/halfsheet/res/values-es/strings.xml deleted file mode 100644 index 7142a1a65c3cc341e4dc4bf5aabc8689cba2beaf..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-es/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Iniciando configuración…" - "Configurar el dispositivo" - "Dispositivo conectado" - "Conectado a \"%s\"" - "No se ha podido conectar" - "No se ha podido conectar" - "Prueba a emparejar el dispositivo manualmente" - "Prueba a poner el dispositivo en modo Emparejamiento" - "Dispositivos al alcance" - "Dispositivos conectados con tu cuenta" - "Dispositivo guardado disponible" - "Nearby" - "Dispositivos" - "Conectando…" - "Batería: %d %%" - "Hecho" - "Guardar" - "Conectar" - "Configurar" - "Ajustes" - diff --git a/nearby/halfsheet/res/values-et/strings.xml b/nearby/halfsheet/res/values-et/strings.xml deleted file mode 100644 index 20a46a5fd432a8e1f36289b9d583b072f6a52e75..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-et/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Seadistuse käivitamine …" - "Seadistage seade" - "Seade on ühendatud" - "Ühendatud seadmega „%s“" - "Ühendamine ebaõnnestus" - "Ühendust ei õnnestu luua" - "Proovige seadmega käsitsi siduda" - "Lülitage seade sidumisrežiimi" - "Ühendusulatuses olevad seadmed" - "Teie kontoga ühendatud seadmed" - "Salvestatud seade on saadaval" - "Läheduses" - "Seadmed" - "Ühendamine …" - "Aku: %d%%" - "Valmis" - "Salvesta" - "Ühenda" - "Seadistamine" - "Seaded" - diff --git a/nearby/halfsheet/res/values-eu/strings.xml b/nearby/halfsheet/res/values-eu/strings.xml deleted file mode 100644 index cd6eb34be18234779a1a14fbecbc3db34858cd77..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-eu/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Konfigurazio-prozesua abiarazten…" - "Konfiguratu gailua" - "Konektatu da gailua" - "%s gailura konektatuta" - "Ezin izan da konektatu" - "Ezin da konektatu" - "Saiatu gailua eskuz parekatzen" - "Jarri gailua parekatzeko moduan" - "Estaldura-eremuko gailuak" - "Kontura konektatutako gailuak" - "Gordetako gailua erabilgarri dago" - "Nearby" - "Gailuak" - "Konektatzen…" - "Bateria: %% %d" - "Eginda" - "Gorde" - "Konektatu" - "Konfiguratu" - "Ezarpenak" - diff --git a/nearby/halfsheet/res/values-fa/strings.xml b/nearby/halfsheet/res/values-fa/strings.xml deleted file mode 100644 index 7490e0f961ed67e3d3d1708870f433be4b6ffa3c..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-fa/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "درحال شروع راه‌اندازی…" - "راه‌اندازی دستگاه" - "دستگاه متصل شد" - "‏به «%s» متصل شد" - "متصل نشد" - "اتصال برقرار نشد" - "مرتبط‌سازی با دستگاه را به‌صورت دستی امتحان کنید" - "دستگاه را در حالت مرتبط‌سازی قرار دهید" - "دستگاه‌های دردسترس" - "دستگاه‌های متصل به حسابتان" - "دستگاه ذخیره‌شده‌تان دردسترس است" - "اطراف" - "دستگاه‌ها" - "درحال اتصال…" - "‏باتری: %d%%" - "تمام" - "ذخیره" - "متصل کردن" - "راه‌اندازی" - "تنظیمات" - diff --git a/nearby/halfsheet/res/values-fi/strings.xml b/nearby/halfsheet/res/values-fi/strings.xml deleted file mode 100644 index b488b3e2c3e080f6aa4d69e5804c0a619e032c27..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-fi/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Aloitetaan käyttöönottoa…" - "Määritä laite" - "Laite on yhdistetty" - "%s yhdistetty" - "Ei yhteyttä" - "Yhdistäminen epäonnistui" - "Yritä yhdistää laitteet manuaalisesti" - "Kokeile asettaa laite parinmuodostustilaan" - "Kantoalueella olevat laitteet" - "Tiliisi liitetyt laitteet" - "Laitteesi on käytettävissä" - "Lähellä" - "Laitteet" - "Yhdistetään…" - "Akku: %d %%" - "Valmis" - "Tallenna" - "Yhdistä" - "Ota käyttöön" - "Asetukset" - diff --git a/nearby/halfsheet/res/values-fr-rCA/strings.xml b/nearby/halfsheet/res/values-fr-rCA/strings.xml deleted file mode 100644 index 9a48890861521c4a2906dd6fec9817098d4906f8..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-fr-rCA/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Démarrage de la configuration…" - "Configurer l\'appareil" - "Appareil associé" - "Connecté à l\'appareil suivant : « %s »" - "Impossible d\'associer" - "Échec de la connexion" - "Essayez d\'associer manuellement l\'appareil" - "Essayez de mettre l\'appareil en mode d\'association" - "Appareils à portée" - "Appareils connectés à votre compte" - "Appareil enregistré accessible" - "À proximité" - "Appareils" - "Connexion en cours…" - "Pile : %d%%" - "OK" - "Enregistrer" - "Associer" - "Configurer" - "Paramètres" - diff --git a/nearby/halfsheet/res/values-fr/strings.xml b/nearby/halfsheet/res/values-fr/strings.xml deleted file mode 100644 index f1263ab22da36445a94bd8a7eb42b2e171af23d6..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-fr/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Début de la configuration…" - "Configurer un appareil" - "Appareil associé" - "Connecté à \"%s\"" - "Impossible de se connecter" - "Impossible de se connecter" - "Essayez d\'associer manuellement l\'appareil" - "Essayez de mettre l\'appareil en mode association" - "Appareils à portée de main" - "Appareils connectés à votre compte" - "Appareil enregistré disponible" - "À proximité" - "Appareils" - "Connexion…" - "Batterie : %d %%" - "OK" - "Enregistrer" - "Connecter" - "Configurer" - "Paramètres" - diff --git a/nearby/halfsheet/res/values-gl/strings.xml b/nearby/halfsheet/res/values-gl/strings.xml deleted file mode 100644 index 91eac4f89286d37d0662cbe3cd239aedc0748a07..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-gl/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Iniciando configuración…" - "Configura o dispositivo" - "Conectouse o dispositivo" - "O dispositivo conectouse a %s" - "Non se puido conectar" - "Non se puido conectar" - "Proba a vincular o dispositivo manualmente" - "Proba a poñer o dispositivo no modo de vinculación" - "Dispositivos dentro do alcance" - "Dispositivos conectados á túa conta" - "Dispositivo gardado dispoñible" - "Nearby" - "Dispositivos" - "Conectando…" - "Batería: %d %%" - "Feito" - "Gardar" - "Conectar" - "Configurar" - "Configuración" - diff --git a/nearby/halfsheet/res/values-gu/strings.xml b/nearby/halfsheet/res/values-gu/strings.xml deleted file mode 100644 index a7a7a2bb0cc43c084a166c5d5c1ac0838bdaf2f0..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-gu/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "સેટઅપ શરૂ કરી રહ્યાં છીએ…" - "ડિવાઇસનું સેટઅપ કરો" - "ડિવાઇસ કનેક્ટ કર્યું" - "“%s” સાથે કનેક્ટ કરેલું" - "કનેક્ટ કરી શક્યા નથી" - "કનેક્ટ કરી શકાયું નથી" - "ડિવાઇસથી મેન્યૂઅલી જોડાણ બનાવવાનો પ્રયાસ કરો" - "ડિવાઇસને જોડાણ બનાવવાના મોડમાં રાખવાનો પ્રયાસ કરો" - "પહોંચની અંદરના ડિવાઇસ" - "તમારા એકાઉન્ટ સાથેના ડિવાઇસ" - "તમારું સાચવેલું ડિવાઇસ ઉપલબ્ધ છે" - "શેરિંગ" - "ડિવાઇસ" - "કનેક્ટ કરી રહ્યાં છીએ…" - "બૅટરી: %d%%" - "થઈ ગયું" - "સાચવો" - "કનેક્ટ કરો" - "સેટઅપ કરો" - "સેટિંગ" - diff --git a/nearby/halfsheet/res/values-hi/strings.xml b/nearby/halfsheet/res/values-hi/strings.xml deleted file mode 100644 index dff9496c12f86877f50b634c1ed7b6ef4a499f15..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-hi/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "सेट अप शुरू किया जा रहा है…" - "डिवाइस सेट अप करें" - "डिवाइस कनेक्ट हो गया" - "“%s” से कनेक्ट हो गया है" - "कनेक्ट नहीं किया जा सका" - "कनेक्ट नहीं किया जा सका" - "डिवाइस को मैन्युअल तरीके से दूसरे डिवाइस से जोड़ने की कोशिश करें" - "डिवाइस को दूसरे डिवाइस से जोड़ने वाले मोड में रखें" - "ऐसे डिवाइस जो रेंज में हैं" - "आपके खाते से जुड़े डिवाइस" - "सेव किया गया डिवाइस उपलब्ध है" - "आस-पास शेयरिंग" - "डिवाइस" - "कनेक्ट हो रहा है…" - "बैटरी: %d%%" - "हो गया" - "सेव करें" - "कनेक्ट करें" - "सेट अप करें" - "सेटिंग" - diff --git a/nearby/halfsheet/res/values-hr/strings.xml b/nearby/halfsheet/res/values-hr/strings.xml deleted file mode 100644 index 13952b895cabcb370371e182c7fe5aa7a0e57b23..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-hr/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Pokretanje postavljanja…" - "Postavi uređaj" - "Uređaj je povezan" - "Povezano s uređajem %s" - "Povezivanje nije uspjelo" - "Povezivanje nije uspjelo" - "Pokušajte ručno upariti s uređajem" - "Pokušajte staviti uređaj u način uparivanja" - "Uređaji u dometu" - "Uređaji s vašim računom" - "Spremljeni je uređaj dostupan" - "U blizini" - "Uređaji" - "Povezivanje…" - "Baterija: %d%%" - "Gotovo" - "Spremi" - "Poveži" - "Postavi" - "Postavke" - diff --git a/nearby/halfsheet/res/values-hu/strings.xml b/nearby/halfsheet/res/values-hu/strings.xml deleted file mode 100644 index 3d810d46a00fbacaae41747ebf51709985af798a..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-hu/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Beállítás megkezdése…" - "Eszköz beállítása" - "Eszköz csatlakoztatva" - "Csatlakoztatva a következőhöz: %s" - "Nem sikerült csatlakozni" - "Nem sikerült csatlakozni" - "Próbálkozzon az eszköz kézi párosításával" - "Próbálja meg bekapcsolni az eszközön a párosítási módot" - "Elérhető eszközök" - "A fiókjával társított eszközök" - "A mentett eszköze elérhető" - "Közeli" - "Eszközök" - "Csatlakozás…" - "Akkumulátor: %d%%" - "Kész" - "Mentés" - "Csatlakozás" - "Beállítás" - "Beállítások" - diff --git a/nearby/halfsheet/res/values-hy/strings.xml b/nearby/halfsheet/res/values-hy/strings.xml deleted file mode 100644 index 57b925609ffdc55c7a6df200fbfd407156ac62e5..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-hy/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Կարգավորում…" - "Կարգավորեք սարքը" - "Սարքը զուգակցվեց" - "«%s» սարքի հետ կապը հաստատվեց" - "Չհաջողվեց միանալ" - "Չհաջողվեց կապ հաստատել" - "Փորձեք ձեռքով զուգակցել սարքը" - "Փորձեք սարքում միացնել զուգակցման ռեժիմը" - "Սարքեր հասանելիության տիրույթում" - "Ձեր հաշվի հետ կապված սարքեր" - "Պահված սարքը հասանելի է" - "Մոտակայքում" - "Սարքեր" - "Միացում…" - "Մարտկոցի լիցքը՝ %d%%" - "Պատրաստ է" - "Պահել" - "Միանալ" - "Կարգավորել" - "Կարգավորումներ" - diff --git a/nearby/halfsheet/res/values-in/strings.xml b/nearby/halfsheet/res/values-in/strings.xml deleted file mode 100644 index c665572005554cbf967f0b9fae885cced164c514..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-in/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Memulai Penyiapan …" - "Siapkan perangkat" - "Perangkat terhubung" - "Terhubung ke “%s”" - "Tidak dapat terhubung" - "Tidak dapat terhubung" - "Coba sambungkan ke perangkat secara manual" - "Coba masukkan perangkat ke dalam mode penyambungan" - "Perangkat dalam jangkauan" - "Perangkat dengan akun Anda" - "Perangkat tersimpan tersedia" - "Berbagi Langsung" - "Perangkat" - "Menghubungkan …" - "Daya baterai: %d%%" - "Selesai" - "Simpan" - "Hubungkan" - "Siapkan" - "Setelan" - diff --git a/nearby/halfsheet/res/values-is/strings.xml b/nearby/halfsheet/res/values-is/strings.xml deleted file mode 100644 index 04a4de4d4e978058de9b6fbfd078bcb24a0387fd..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-is/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Ræsir uppsetningu…" - "Uppsetning tækis" - "Tækið er tengt" - "Tengt við „%s“" - "Tenging mistókst" - "Tenging tókst ekki" - "Prófaðu að para tækið handvirkt" - "Prófaðu að kveikja á pörunarstillingu í tækinu" - "Nálæg tæki" - "Tæki tengd reikningnum þínum" - "Vistaða tækið er tiltækt" - "Nálægt" - "Tæki" - "Tengist…" - "Rafhlaða: %d%%" - "Lokið" - "Vista" - "Tengja" - "Setja upp" - "Stillingar" - diff --git a/nearby/halfsheet/res/values-it/strings.xml b/nearby/halfsheet/res/values-it/strings.xml deleted file mode 100644 index 2ffe2680139bde0a5fd2e1ecf87280333bbf9ab1..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-it/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Avvio della configurazione…" - "Configura dispositivo" - "Dispositivo connesso" - "Connesso a \"%s\"" - "Impossibile connettere" - "Impossibile connettersi" - "Prova a eseguire l\'accoppiamento manuale con il dispositivo" - "Prova a impostare il dispositivo sulla modalità di accoppiamento" - "Dispositivi nelle vicinanze" - "Dispositivi collegati all\'account" - "Disposit. salvato disponibile" - "Nelle vicinanze" - "Dispositivi" - "Connessione in corso" - "Batteria: %d%%" - "Fine" - "Salva" - "Connetti" - "Configura" - "Impostazioni" - diff --git a/nearby/halfsheet/res/values-iw/strings.xml b/nearby/halfsheet/res/values-iw/strings.xml deleted file mode 100644 index 61724f098cd977ba7eedea61305022f1ca0ea190..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-iw/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "ההגדרה מתבצעת…" - "הגדרת המכשיר" - "המכשיר מחובר" - "‏יש חיבור אל \'%s\'" - "לא ניתן להתחבר" - "לא ניתן להתחבר" - "כדאי לנסות לבצע התאמה ידנית למכשיר" - "כדאי לנסות להעביר את המכשיר למצב התאמה" - "מכשירים בהישג יד" - "מכשירים המחוברים לחשבון שלך" - "המכשיר ששמרת זמין" - "בקרבת מקום" - "מכשירים" - "מתבצעת התחברות…" - "‏טעינת הסוללה: %d%%" - "סיום" - "שמירה" - "התחברות" - "הגדרה" - "הגדרות" - diff --git a/nearby/halfsheet/res/values-ja/strings.xml b/nearby/halfsheet/res/values-ja/strings.xml deleted file mode 100644 index 3168bdad5de5cd02ff9bfa378b7619bd07938a2b..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-ja/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "セットアップを開始中…" - "デバイスのセットアップ" - "デバイス接続完了" - "「%s」に接続しました" - "接続エラー" - "接続できません" - "手動でデバイスとペア設定してみてください" - "デバイスをペア設定モードにしてください" - "近接するデバイス" - "アカウントと接続済みのデバイス" - "保存済みのデバイスがあります" - "周辺ユーザーとの共有" - "デバイス" - "接続しています…" - "バッテリー: %d%%" - "完了" - "保存" - "接続" - "セットアップ" - "設定" - diff --git a/nearby/halfsheet/res/values-ka/strings.xml b/nearby/halfsheet/res/values-ka/strings.xml deleted file mode 100644 index a9ee648f6c728c6be101089c65a341be1121a3d1..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-ka/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "დაყენება იწყება…" - "მოწყობილობის დაყენება" - "მოწყობილობა დაკავშირებულია" - "დაკავშირებულია „%s“-თან" - "დაკავშირება ვერ მოხერხდა" - "დაკავშირება ვერ ხერხდება" - "ცადეთ მოწყობილობასთან ხელით დაწყვილება" - "ცადეთ მოწყობილობის გადაყვანა დაწყვილების რეჟიმზე" - "ხელმისაწვდომი მოწყობილობები" - "მოწყობილობები თქვენი ანგარიშით" - "შენახული მოწყობილობა ხელმისაწვდომია" - "ახლომახლო" - "მოწყობილობები" - "მიმდინარეობს დაკავშირება…" - "ბატარეა: %d%%" - "მზადაა" - "შენახვა" - "დაკავშირება" - "დაყენება" - "პარამეტრები" - diff --git a/nearby/halfsheet/res/values-kk/strings.xml b/nearby/halfsheet/res/values-kk/strings.xml deleted file mode 100644 index 6e1a0bd63f12b0564f0dc275860143fab289aad2..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-kk/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Реттеу басталуда…" - "Құрылғыны реттеу" - "Құрылғы байланыстырылды" - "\"%s\" құрылғысымен байланыстырылды" - "Қосылмады" - "Қосылу мүмкін емес" - "Құрылғыны қолмен жұптап көріңіз." - "Құрылғыны жұптау режиміне қойып көріңіз." - "Қолжетімді құрылғылар" - "Аккаунтпен байланыстырылған құрылғылар" - "Сақталған құрылғы қолжетімді" - "Маңайдағы" - "Құрылғылар" - "Жалғанып жатыр…" - "Батарея: %d%%" - "Дайын" - "Сақтау" - "Қосу" - "Реттеу" - "Параметрлер" - diff --git a/nearby/halfsheet/res/values-km/strings.xml b/nearby/halfsheet/res/values-km/strings.xml deleted file mode 100644 index deb65045fbefcd0a68b43e5a8fa630d4bc5ad367..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-km/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "កំពុងចាប់ផ្ដើម​រៀបចំ…" - "រៀបចំ​ឧបករណ៍" - "បានភ្ជាប់ឧបករណ៍" - "បានភ្ជាប់ជាមួយ “%s”" - "មិន​អាចភ្ជាប់​បានទេ" - "មិនអាច​ភ្ជាប់​បានទេ" - "សាកល្បង​ផ្គូផ្គង​ដោយ​ផ្ទាល់​ទៅឧបករណ៍" - "សាកល្បង​កំណត់​ឧបករណ៍​ឱ្យ​ប្រើ​មុខងារ​​ផ្គូផ្គង" - "ឧបករណ៍​នៅ​ក្បែរដៃ" - "ឧបករណ៍​ភ្ជាប់​ជាមួយ​គណនី​របស់អ្នក​" - "ឧបករណ៍ដែលអ្នកបានរក្សាទុកអាចប្រើបានហើយ" - "នៅ​ជិត" - "ឧបករណ៍" - "កំពុងភ្ជាប់…" - "ថ្ម៖ %d%%" - "រួចរាល់" - "រក្សាទុក" - "ភ្ជាប់" - "រៀបចំ" - "ការកំណត់" - diff --git a/nearby/halfsheet/res/values-kn/strings.xml b/nearby/halfsheet/res/values-kn/strings.xml deleted file mode 100644 index 87b4fe33f761bfcf7092a05a8d46731fa3413673..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-kn/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "ಸೆಟಪ್ ಪ್ರಾರಂಭಿಸಲಾಗುತ್ತಿದೆ…" - "ಸಾಧನವನ್ನು ಸೆಟಪ್ ಮಾಡಿ" - "ಸಾಧನವನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ" - "“%s” ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ" - "ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ" - "ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ" - "ಸಾಧನಕ್ಕೆ ಹಸ್ತಚಾಲಿತವಾಗಿ ಜೋಡಿಸಲು ಪ್ರಯತ್ನಿಸಿ" - "ಜೋಡಿಸುವಿಕೆ ಮೋಡ್‌ನಲ್ಲಿ ಸಾಧನವನ್ನು ಇರಿಸಲು ಪ್ರಯತ್ನಿಸಿ" - "ವ್ಯಾಪ್ತಿಯಲ್ಲಿರುವ ಸಾಧನಗಳು" - "ನಿಮ್ಮ ಖಾತೆಗೆ ಸಂಪರ್ಕಿತವಾಗಿರುವ ಸಾಧನಗಳು" - "ಉಳಿಸಲಾದ ನಿಮ್ಮ ಸಾಧನವು ಲಭ್ಯವಿದೆ" - "ಸಮೀಪದಲ್ಲಿರುವುದು" - "ಸಾಧನಗಳು" - "ಕನೆಕ್ಟ್ ಆಗುತ್ತಿದೆ…" - "ಬ್ಯಾಟರಿ: %d%%" - "ಮುಗಿದಿದೆ" - "ಉಳಿಸಿ" - "ಕನೆಕ್ಟ್ ಮಾಡಿ" - "ಸೆಟಪ್ ಮಾಡಿ" - "ಸೆಟ್ಟಿಂಗ್‌ಗಳು" - diff --git a/nearby/halfsheet/res/values-ko/strings.xml b/nearby/halfsheet/res/values-ko/strings.xml deleted file mode 100644 index 37d50da4eca63d6057a9fd6dc51a9cfe29160ac4..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-ko/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "설정을 시작하는 중…" - "기기 설정" - "기기 연결됨" - "\'%s\'에 연결됨" - "연결할 수 없음" - "연결할 수 없음" - "기기에 수동으로 페어링을 시도해 보세요." - "기기를 페어링 모드로 전환하세요." - "연결 가능 기기" - "내 계정에 연결된 기기" - "저장된 기기 사용 가능" - "Nearby" - "기기" - "연결 중…" - "배터리: %d%%" - "완료" - "저장" - "연결" - "설정" - "설정" - diff --git a/nearby/halfsheet/res/values-ky/strings.xml b/nearby/halfsheet/res/values-ky/strings.xml deleted file mode 100644 index b6aa4098615e369e989fde821970175d57ec140b..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-ky/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Жөндөлүп баштады…" - "Түзмөктү жөндөө" - "Түзмөк туташты" - "“%s” түзмөгүнө туташты" - "Туташпай койду" - "Туташпай жатат" - "Түзмөккө кол менен жупташтырып көрүңүз" - "Түзмөктү Байланыштыруу режимине коюп көрүңүз" - "Жеткиликтүү түзмөктөр" - "Аккаунтуңузга кирип турган түзмөктөр" - "Сакталган түзмөгүңүз жеткиликтүү" - "Жакын жерде" - "Түзмөктөр" - "Туташууда…" - "Батарея: %d%%" - "Бүттү" - "Сактоо" - "Туташуу" - "Жөндөө" - "Параметрлер" - diff --git a/nearby/halfsheet/res/values-lo/strings.xml b/nearby/halfsheet/res/values-lo/strings.xml deleted file mode 100644 index cbc7601898b9d9bd75a7c46e03a4feed3d61b6dd..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-lo/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "ກຳລັງເລີ່ມການຕັ້ງຄ່າ…" - "ຕັ້ງຄ່າອຸປະກອນ" - "ເຊື່ອມຕໍ່ອຸປະກອນແລ້ວ" - "ເຊື່ອມຕໍ່ກັບ “%s” ແລ້ວ" - "ບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້" - "ບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້" - "ລອງຈັບຄູ່ອຸປະກອນດ້ວຍຕົນເອງ" - "ໃຫ້ລອງຕັ້ງອຸປະກອນເປັນໂໝດຈັບຄູ່" - "ອຸປະກອນພ້ອມໃຫ້ເຂົ້າເຖິງ" - "ອຸປະກອນທີ່ມີບັນຊີຂອງທ່ານ" - "ອຸປະກອນທີ່ບັນທຶກໄວ້ຂອງທ່ານສາມາດໃຊ້ໄດ້" - "ໃກ້ຄຽງ" - "ອຸປະກອນ" - "ກຳລັງເຊື່ອມຕໍ່…" - "ແບັດເຕີຣີ: %d%%" - "ແລ້ວໆ" - "ບັນທຶກ" - "ເຊື່ອມຕໍ່" - "ຕັ້ງຄ່າ" - "ການຕັ້ງຄ່າ" - diff --git a/nearby/halfsheet/res/values-lt/strings.xml b/nearby/halfsheet/res/values-lt/strings.xml deleted file mode 100644 index 29a9bc5ab8ccb90d18b152316a27db2898e91c2e..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-lt/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Pradedama sąranka…" - "Įrenginio nustatymas" - "Įrenginys prijungtas" - "Prisijungta prie „%s“" - "Prisijungti nepavyko" - "Nepavyko prisijungti" - "Pabandykite neautomatiškai susieti įrenginį" - "Pabandykite vėl įgalinti įrenginio susiejimo režimą" - "Lengvai pasiekiami įrenginiai" - "Įrenginiai su jūsų paskyra" - "Išsaugotas įrenginys pasiekiamas" - "Netoliese" - "Įrenginiai" - "Prisijungiama…" - "Akumuliatorius: %d %%" - "Atlikta" - "Išsaugoti" - "Prisijungti" - "Nustatyti" - "Nustatymai" - diff --git a/nearby/halfsheet/res/values-lv/strings.xml b/nearby/halfsheet/res/values-lv/strings.xml deleted file mode 100644 index 9573357cd954f54dc5f5db6e4060d7e5999d95e1..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-lv/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Tiek sākta iestatīšana…" - "Iestatiet ierīci" - "Ierīce ir pievienota" - "Izveidots savienojums ar ierīci “%s”" - "Nevarēja izveidot savienojumu" - "Nevar izveidot savienojumu." - "Mēģiniet manuāli izveidot savienojumu pārī ar ierīci." - "Ieslēdziet ierīcē režīmu savienošanai pārī" - "Sasniedzamas ierīces" - "Ierīces ar jūsu kontu" - "Jūsu saglabātā ierīce pieejama" - "Tuvumā" - "Ierīces" - "Savienojuma izveide…" - "Akumulatora uzlādes līmenis: %d%%" - "Gatavs" - "Saglabāt" - "Izveidot savienojumu" - "Iestatīt" - "Iestatījumi" - diff --git a/nearby/halfsheet/res/values-mk/strings.xml b/nearby/halfsheet/res/values-mk/strings.xml deleted file mode 100644 index 693f1123ce917062dd21e7685b07c89a3199e0a6..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-mk/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Се започнува со поставување…" - "Поставете го уредот" - "Уредот е поврзан" - "Поврзан со „%s“" - "Не може да се поврзе" - "Не може да се поврзе" - "Обидете се рачно да се спарите со уредот" - "Пробајте да го ставите уредот во режим на спарување" - "Уреди на дофат" - "Уреди поврзани со вашата сметка" - "Вашиот зачуван уред е достапен" - "Во близина" - "Уреди" - "Се поврзува…" - "Батерија: %d %%" - "Готово" - "Зачувај" - "Поврзи" - "Поставете" - "Поставки" - diff --git a/nearby/halfsheet/res/values-ml/strings.xml b/nearby/halfsheet/res/values-ml/strings.xml deleted file mode 100644 index 56a2db2a75c351335b5037d2dad99018a0a1022f..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-ml/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "സജ്ജീകരിക്കൽ ആരംഭിക്കുന്നു…" - "ഉപകരണം സജ്ജീകരിക്കുക" - "ഉപകരണം കണക്റ്റ് ചെയ്‌തു" - "“%s” എന്നതിലേക്ക് കണക്റ്റ് ചെയ്‌തു" - "കണക്റ്റ് ചെയ്യാനായില്ല" - "കണക്റ്റ് ചെയ്യാനാകുന്നില്ല" - "ഉപകരണം ജോടിയാക്കാൻ നേരിട്ട് ശ്രമിക്കുക" - "ഉപകരണം ജോഡിയാക്കൽ മോഡിലേക്ക് മാറ്റിയ ശേഷം ശ്രമിക്കുക" - "പരിധിയിലുള്ള ഉപകരണങ്ങൾ" - "അക്കൗണ്ട് ഉപയോഗിക്കുന്ന ഉപകരണങ്ങൾ" - "സംരക്ഷിച്ച ഉപകരണം ലഭ്യമാണ്" - "സമീപമുള്ളവ" - "ഉപകരണങ്ങൾ" - "കണക്റ്റ് ചെയ്യുന്നു…" - "ബാറ്ററി: %d%%" - "പൂർത്തിയായി" - "സംരക്ഷിക്കുക" - "കണക്റ്റ് ചെയ്യുക" - "സജ്ജീകരിക്കുക" - "ക്രമീകരണം" - diff --git a/nearby/halfsheet/res/values-mn/strings.xml b/nearby/halfsheet/res/values-mn/strings.xml deleted file mode 100644 index 5a72ce3846bf637fb8c135ef9f91c4f8a6526eb7..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-mn/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Тохируулгыг эхлүүлж байна…" - "Төхөөрөмж тохируулах" - "Төхөөрөмж холбогдсон" - "“%s”-д холбогдсон" - "Холбогдож чадсангүй" - "Холбогдох боломжгүй байна" - "Төхөөрөмжийг гар аргаар хослуулна уу" - "Төхөөрөмжийг хослуулах горимд оруулахаар оролдоно уу" - "Хүрээн дэх төхөөрөмжүүд" - "Таны бүртгэлтэй холбогдсон төхөөрөмж" - "Таны хадгалсан төхөөрөмж бэлэн байна" - "Ойролцоо" - "Төхөөрөмж" - "Холбогдож байна…" - "Батарей: %d%%" - "Болсон" - "Хадгалах" - "Холбох" - "Тохируулах" - "Тохиргоо" - diff --git a/nearby/halfsheet/res/values-mr/strings.xml b/nearby/halfsheet/res/values-mr/strings.xml deleted file mode 100644 index 3eeb0ec587397a6966e23900007a1f6bf2cfdaeb..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-mr/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "सेटअप सुरू करत आहे…" - "डिव्हाइस सेट करा" - "डिव्हाइस कनेक्ट केले आहे" - "\"%s\" शी कनेक्ट केले आहे" - "कनेक्ट करता आले नाही" - "कनेक्ट करता आले नाही" - "डिव्हाइसशी मॅन्युअली पेअर करण्याचा प्रयत्न करा" - "डिव्हाइसला पेअरिंग मोडमध्ये ठेवण्याचा प्रयत्न करा" - "पुरेसे जवळ असलेले डिव्हाइस" - "तुमच्या खात्‍यासह असलेली डिव्‍हाइस" - "सेव्ह केलेले डिव्हाइस उपलब्ध" - "जवळपास" - "डिव्हाइस" - "कनेक्ट करत आहे…" - "बॅटरी: %d%%" - "पूर्ण झाले" - "सेव्ह करा" - "कनेक्ट करा" - "सेट करा" - "सेटिंग्ज" - diff --git a/nearby/halfsheet/res/values-ms/strings.xml b/nearby/halfsheet/res/values-ms/strings.xml deleted file mode 100644 index 0af903db13e5afb81737aeb00847f0536190f449..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-ms/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Memulakan Persediaan…" - "Sediakan peranti" - "Peranti disambungkan" - "Disambungkan kepada “%s”" - "Tidak dapat menyambung" - "Tidak dapat menyambung" - "Cuba gandingkan dengan peranti secara manual" - "Cuba letakkan peranti dalam mod penggandingan" - "Peranti dalam capaian" - "Peranti dengan akaun anda" - "Peranti disimpan anda tersedia" - "Berdekatan" - "Peranti" - "Menyambung…" - "Bateri: %d%%" - "Selesai" - "Simpan" - "Sambung" - "Sediakan" - "Tetapan" - diff --git a/nearby/halfsheet/res/values-my/strings.xml b/nearby/halfsheet/res/values-my/strings.xml deleted file mode 100644 index 306538f59761d44fa5f2dbc9a489e49b61685a80..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-my/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "စနစ်ထည့်သွင်းခြင်း စတင်နေသည်…" - "စက်ကို စနစ်ထည့်သွင်းရန်" - "စက်ကို ချိတ်ဆက်လိုက်ပြီ" - "“%s” သို့ ချိတ်ဆက်ထားသည်" - "ချိတ်ဆက်၍မရပါ" - "ချိတ်ဆက်၍ မရပါ" - "စက်ကို ကိုယ်တိုင်တွဲချိတ်ကြည့်ပါ" - "စက်ကို တွဲချိတ်ခြင်းမုဒ်သို့ ထားကြည့်ပါ" - "လက်လှမ်းမီသည့် စက်များ" - "သင့်အကောင့်ရှိသည့် စက်များ" - "သင်သိမ်းထားသောစက် ရပါပြီ" - "အနီးတစ်ဝိုက်" - "စက်များ" - "ချိတ်ဆက်နေသည်…" - "ဘက်ထရီ− %d%%" - "ပြီးပြီ" - "သိမ်းရန်" - "ချိတ်ဆက်ရန်" - "စနစ်ထည့်သွင်းရန်" - "ဆက်တင်များ" - diff --git a/nearby/halfsheet/res/values-nb/strings.xml b/nearby/halfsheet/res/values-nb/strings.xml deleted file mode 100644 index 72a2ab756584f93e224e9d21aa1e90fbbe5b17cd..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-nb/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Starter konfigureringen …" - "Konfigurer enheten" - "Enheten er tilkoblet" - "Koblet til «%s»" - "Kunne ikke koble til" - "Kan ikke koble til" - "Prøv manuell tilkobling til enheten" - "Prøv å sette enheten i tilkoblingsmodus" - "Enheter innen rekkevidde" - "Enheter med kontoen din" - "Lagret enhet tilgjengelig" - "I nærheten" - "Enheter" - "Kobler til …" - "Batteri: %d %%" - "Ferdig" - "Lagre" - "Koble til" - "Konfigurer" - "Innstillinger" - diff --git a/nearby/halfsheet/res/values-ne/strings.xml b/nearby/halfsheet/res/values-ne/strings.xml deleted file mode 100644 index 2956768f43207ef61a4507aeddccb81f85859db7..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-ne/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "सेटअप प्रक्रिया सुरु गरिँदै छ…" - "डिभाइस सेटअप गर्नुहोस्" - "डिभाइस कनेक्ट गरियो" - "\"%s\" मा कनेक्ट गरिएको छ" - "कनेक्ट गर्न सकिएन" - "कनेक्ट गर्न सकिएन" - "म्यानुअल तरिकाले डिभाइस कनेक्ट गरी हेर्नुहोस्" - "डिभाइस कनेक्ट गर्ने मोडमा राखी हेर्नुहोस्" - "कनेक्ट गर्न सकिने नजिकैका डिभाइसहरू" - "तपाईंको खातामा कनेक्ट गरिएका डिभाइस" - "तपाईंले सेभ गरेको डिभाइस उपलब्ध छ" - "नजिकै" - "डिभाइसहरू" - "कनेक्ट गरिँदै छ…" - "ब्याट्री: %d%%" - "सम्पन्न भयो" - "सेभ गर्नुहोस्" - "कनेक्ट गर्नुहोस्" - "सेटअप गर्नुहोस्" - "सेटिङ" - diff --git a/nearby/halfsheet/res/values-night/colors.xml b/nearby/halfsheet/res/values-night/colors.xml deleted file mode 100644 index 69b832a605938ed28564e0ddad9b8482a9f77cc0..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-night/colors.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - #00000000 - - - @android:color/system_accent1_100 - - @android:color/system_neutral1_50 - - @android:color/system_neutral1_900 - - @android:color/system_accent1_600 - - @android:color/system_neutral2_200 - - @android:color/system_neutral1_50 - - @android:color/system_neutral1_800 - - - #FFFFFF - #24FFFFFF - #F6AEA9 - - - diff --git a/nearby/halfsheet/res/values-nl/strings.xml b/nearby/halfsheet/res/values-nl/strings.xml deleted file mode 100644 index e956116a2e71ff4b105023e5ac9fd8a95305575d..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-nl/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Instellen starten…" - "Apparaat instellen" - "Apparaat verbonden" - "Verbonden met %s" - "Kan geen verbinding maken" - "Kan geen verbinding maken" - "Probeer handmatig met het apparaat te koppelen" - "Activeer de koppelingsstand van het apparaat" - "Apparaten binnen bereik" - "Apparaten met je account" - "Opgeslagen apparaat beschikbaar" - "In de buurt" - "Apparaten" - "Verbinden…" - "Batterij: %d%%" - "Klaar" - "Opslaan" - "Verbinden" - "Instellen" - "Instellingen" - diff --git a/nearby/halfsheet/res/values-or/strings.xml b/nearby/halfsheet/res/values-or/strings.xml deleted file mode 100644 index 0ec472ca31f90faa8ab1fd57211d262a5e22fa95..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-or/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "ସେଟଅପ ଆରମ୍ଭ କରାଯାଉଛି…" - "ଡିଭାଇସ ସେଟ ଅପ କରନ୍ତୁ" - "ଡିଭାଇସ ସଂଯୁକ୍ତ ହୋଇଛି" - "“%s” ସହ କନେକ୍ଟ କରାଯାଇଛି" - "ସଂଯୋଗ କରାଯାଇପାରିଲା ନାହିଁ" - "କନେକ୍ଟ କରିବାରେ ଅସମର୍ଥ" - "ଡିଭାଇସ ସହ ମାନୁଆଲୀ ପେୟାର କରିବା ପାଇଁ ଚେଷ୍ଟା କରନ୍ତୁ" - "ଡିଭାଇସକୁ ପେୟାରିଂ ମୋଡରେ ରଖିବାକୁ ଚେଷ୍ଟା କରନ୍ତୁ" - "ପହଞ୍ଚ ଭିତରେ ଥିବା ଡିଭାଇସଗୁଡ଼ିକ" - "ଆପଣଙ୍କ ଆକାଉଣ୍ଟ ସହ ଥିବା ଡିଭାଇସଗୁଡ଼ିକ" - "ସେଭ ଥିବା ଆପଣଙ୍କ ଡିଭାଇସ ଉପଲବ୍ଧ" - "ଆଖପାଖର" - "ଡିଭାଇସଗୁଡ଼ିକ" - "କନେକ୍ଟ କରାଯାଉଛି…" - "ବେଟେରୀ: %d%%" - "ହୋଇଗଲା" - "ସେଭ କରନ୍ତୁ" - "ସଂଯୋଗ କରନ୍ତୁ" - "ସେଟ ଅପ କରନ୍ତୁ" - "ସେଟିଂସ" - diff --git a/nearby/halfsheet/res/values-pa/strings.xml b/nearby/halfsheet/res/values-pa/strings.xml deleted file mode 100644 index 4eb0553ea1c2d5bbafd3077f178eb0022430ec00..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-pa/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "ਸੈੱਟਅੱਪ ਸ਼ੁਰੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…" - "ਡੀਵਾਈਸ ਸੈੱਟਅੱਪ ਕਰੋ" - "ਡੀਵਾਈਸ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ" - "“%s” ਨਾਲ ਕਨੈਕਟ ਹੈ" - "ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ" - "ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ" - "ਡੀਵਾਈਸ ਨਾਲ ਹੱਥੀਂ ਜੋੜਾਬੱਧ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ" - "ਡੀਵਾਈਸ ਨੂੰ ਜੋੜਾਬੱਧਕਰਨ ਮੋਡ ਵਿੱਚ ਰੱਖਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ" - "ਡੀਵਾਈਸ ਜੋ ਪਹੁੰਚ ਅੰਦਰ ਹਨ" - "ਤੁਹਾਡੇ ਖਾਤੇ ਵਾਲੇ ਡੀਵਾਈਸ" - "ਰੱਖਿਅਤ ਕੀਤਾ ਡੀਵਾਈਸ ਉਪਲਬਧ ਹੈ" - "ਨਜ਼ਦੀਕੀ" - "ਡੀਵਾਈਸ" - "ਕਨੈਕਟ ਹੋ ਰਿਹਾ ਹੈ…" - "ਬੈਟਰੀ: %d%%" - "ਹੋ ਗਿਆ" - "ਰੱਖਿਅਤ ਕਰੋ" - "ਕਨੈਕਟ ਕਰੋ" - "ਸੈੱਟਅੱਪ ਕਰੋ" - "ਸੈਟਿੰਗਾਂ" - diff --git a/nearby/halfsheet/res/values-pl/strings.xml b/nearby/halfsheet/res/values-pl/strings.xml deleted file mode 100644 index 5082e18159f3fcb85ee6f50ca2b830d04d092ff3..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-pl/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Rozpoczynam konfigurowanie…" - "Skonfiguruj urządzenie" - "Urządzenie połączone" - "Połączono z: „%s”" - "Nie udało się połączyć" - "Nie udało się połączyć" - "Spróbuj ręcznie sparować urządzenie" - "Włącz na urządzeniu tryb parowania" - "Urządzenia w zasięgu" - "Urządzenia z Twoim kontem" - "Zapisane urządzenie dostępne" - "W pobliżu" - "Urządzenia" - "Łączę…" - "Bateria: %d%%" - "Gotowe" - "Zapisz" - "Połącz" - "Skonfiguruj" - "Ustawienia" - diff --git a/nearby/halfsheet/res/values-pt-rBR/strings.xml b/nearby/halfsheet/res/values-pt-rBR/strings.xml deleted file mode 100644 index 15d29d2cc51ce6ab840d8e62dfa5f89df9752ec6..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-pt-rBR/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Iniciando a configuração…" - "Configurar dispositivo" - "Dispositivo conectado" - "Conectado ao dispositivo \"%s\"" - "Erro ao conectar" - "Não foi possível conectar" - "Tente parear o dispositivo manualmente" - "Coloque o dispositivo no modo de pareamento" - "Dispositivos ao alcance" - "Dispositivos conectados à sua conta" - "Dispositivo salvo disponível" - "Por perto" - "Dispositivos" - "Conectando…" - "Bateria: %d%%" - "Concluído" - "Salvar" - "Conectar" - "Configurar" - "Configurações" - diff --git a/nearby/halfsheet/res/values-pt-rPT/strings.xml b/nearby/halfsheet/res/values-pt-rPT/strings.xml deleted file mode 100644 index ab8decf29922becdb9573dcebd5965413d8cb2c1..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-pt-rPT/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "A iniciar a configuração…" - "Configure o dispositivo" - "Dispositivo ligado" - "Ligado a \"%s\"" - "Não foi possível ligar" - "Impossível ligar" - "Experimente sincronizar manualmente com o dispositivo" - "Experimente ativar o modo de sincronização no dispositivo" - "Dispositivos ao alcance" - "Dispositivos com a sua conta" - "Disposit. guardado disponível" - "Partilha" - "Dispositivos" - "A ligar…" - "Bateria: %d%%" - "Concluir" - "Guardar" - "Ligar" - "Configurar" - "Definições" - diff --git a/nearby/halfsheet/res/values-pt/strings.xml b/nearby/halfsheet/res/values-pt/strings.xml deleted file mode 100644 index 15d29d2cc51ce6ab840d8e62dfa5f89df9752ec6..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-pt/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Iniciando a configuração…" - "Configurar dispositivo" - "Dispositivo conectado" - "Conectado ao dispositivo \"%s\"" - "Erro ao conectar" - "Não foi possível conectar" - "Tente parear o dispositivo manualmente" - "Coloque o dispositivo no modo de pareamento" - "Dispositivos ao alcance" - "Dispositivos conectados à sua conta" - "Dispositivo salvo disponível" - "Por perto" - "Dispositivos" - "Conectando…" - "Bateria: %d%%" - "Concluído" - "Salvar" - "Conectar" - "Configurar" - "Configurações" - diff --git a/nearby/halfsheet/res/values-ro/strings.xml b/nearby/halfsheet/res/values-ro/strings.xml deleted file mode 100644 index 0335d01ef3dbd820629005d1e5152e63f5e9bcc9..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-ro/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Începe configurarea…" - "Configurează dispozitivul" - "Dispozitivul s-a conectat" - "Conectat la %s" - "Nu s-a putut conecta" - "Nu se poate conecta" - "Încearcă asocierea manuală cu dispozitivul" - "Încearcă să pui dispozitivul în modul de asociere" - "Dispozitive în vecinătate" - "Dispozitive conectate cu contul" - "Dispozitivul este disponibil" - "În apropiere" - "Dispozitive" - "Se conectează…" - "Baterie: %d%%" - "Gata" - "Salvează" - "Conectează" - "Configurează" - "Setări" - diff --git a/nearby/halfsheet/res/values-ru/strings.xml b/nearby/halfsheet/res/values-ru/strings.xml deleted file mode 100644 index d90b6447e534ae7f2b0e6765149193408582aed7..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-ru/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Начинаем настройку…" - "Настройка устройства" - "Устройство подключено" - "Подключено к устройству \"%s\"" - "Ошибка подключения" - "Не удалось подключиться" - "Попробуйте подключиться к устройству вручную." - "Переведите устройство в режим подключения." - "Устройства в зоне охвата" - "Устройства с вашим аккаунтом" - "Доступно сохранен. устройство" - "Мое окружение" - "Устройства" - "Подключение…" - "Батарея: %d %%" - "Готово" - "Сохранить" - "Подключить" - "Настроить" - "Открыть настройки" - diff --git a/nearby/halfsheet/res/values-si/strings.xml b/nearby/halfsheet/res/values-si/strings.xml deleted file mode 100644 index c9b96bb23f15238dd7af5a678a0b4a7818bf84ed..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-si/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "පිහිටුවීම ආරම්භ කරමින්…" - "උපාංගය පිහිටුවන්න" - "උපාංගය සම්බන්ධිතයි" - "“%s” වෙත සම්බන්ධ විය" - "සම්බන්ධ කළ නොහැකි විය" - "සම්බන්ධ වීමට නොහැකි වේ" - "උපාංගය වෙත හස්තීයව යුගල කිරීමට උත්සාහ කරන්න" - "උපාංගය යුගල කිරීමේ ප්‍රකාරයට දැමීමට උත්සාහ කරන්න" - "ළඟා විය හැකි උපාංග" - "ඔබේ ගිණුම සමග උපාංග" - "ඔබේ සුරැකි උපාංගය ලබා ගත හැක" - "අවට" - "උපාංග" - "සම්බන්ධ වෙමින්…" - "බැටරිය: %d%%" - "නිමයි" - "සුරකින්න" - "සම්බන්ධ කරන්න" - "පිහිටුවන්න" - "සැකසීම්" - diff --git a/nearby/halfsheet/res/values-sk/strings.xml b/nearby/halfsheet/res/values-sk/strings.xml deleted file mode 100644 index 793821134ef9b63392bb5bb8d1ee93fec692ccf3..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-sk/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Spúšťa sa nastavenie…" - "Nastavte zariadenie" - "Zariadenie je pripojené" - "Pripojené k zariadeniu %s" - "Nepodarilo sa pripojiť" - "Nepodarilo sa pripojiť" - "Skúste zariadenie spárovať ručne" - "Prepnite zariadenie do párovacieho režimu" - "Zariadenia v dosahu" - "Zariadenia s vaším účtom" - "Uložené zariadenie je dostupné" - "Nablízku" - "Zariadenia" - "Pripája sa…" - "Batéria: %d %%" - "Hotovo" - "Uložiť" - "Pripojiť" - "Nastaviť" - "Nastavenia" - diff --git a/nearby/halfsheet/res/values-sl/strings.xml b/nearby/halfsheet/res/values-sl/strings.xml deleted file mode 100644 index 9e9357c75f279b49d3e37e717cf1b19b1bc6407d..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-sl/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Začetek nastavitve …" - "Nastavitev naprave" - "Naprava je povezana" - "Povezano z napravo »%s«" - "Povezava ni mogoča" - "Povezave ni mogoče vzpostaviti" - "Poskusite ročno seznaniti napravo." - "Poskusite napravo preklopiti v način za seznanjanje." - "Naprave znotraj dosega" - "Naprave z vašim računom" - "Shranjena naprava je na voljo" - "Bližina" - "Naprave" - "Povezovanje …" - "Baterija: %d %%" - "Končano" - "Shrani" - "Poveži" - "Nastavi" - "Nastavitve" - diff --git a/nearby/halfsheet/res/values-sq/strings.xml b/nearby/halfsheet/res/values-sq/strings.xml deleted file mode 100644 index 538e9d6ec0b6dc294f232480648ff38c8a708eb6..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-sq/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Po nis konfigurimin…" - "Konfiguro pajisjen" - "Pajisja u lidh" - "U lidh me “%s”" - "Nuk mund të lidhej" - "Nuk mund të lidhet" - "Provo të çiftosh me pajisjen manualisht" - "Provo ta vendosësh pajisjen në modalitetin e çiftimit" - "Pajisjet që mund të arrish" - "Pajisjet me llogarinë tënde" - "Pajisja jote e ruajtur ofrohet" - "Në afërsi" - "Pajisjet" - "Po lidhet…" - "Bateria: %d%%" - "U krye" - "Ruaj" - "Lidh" - "Konfiguro" - "Cilësimet" - diff --git a/nearby/halfsheet/res/values-sr/strings.xml b/nearby/halfsheet/res/values-sr/strings.xml deleted file mode 100644 index c4bcd19d9333c81003054159f4ba0c1abb5f747b..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-sr/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Подешавање се покреће…" - "Подесите уређај" - "Уређај је повезан" - "Повезани сте са уређајем %s" - "Повезивање није успело" - "Повезивање није успело" - "Пробајте да упарите ручно са уређајем" - "Пробајте да пребаците уређај у режим упаривања" - "Уређаји у домету" - "Уређаји повезани са налогом" - "Сачувани уређај је доступан" - "У близини" - "Уређаји" - "Повезује се…" - "Батерија: %d%%" - "Готово" - "Сачувај" - "Повежи" - "Подеси" - "Подешавања" - diff --git a/nearby/halfsheet/res/values-sv/strings.xml b/nearby/halfsheet/res/values-sv/strings.xml deleted file mode 100644 index b00091ccb03cccd651b574555491c88d3a128e1c..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-sv/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Konfigureringen startas …" - "Konfigurera enheten" - "Enheten är ansluten" - "Ansluten till %s" - "Det gick inte att ansluta" - "Det går inte att ansluta" - "Testa att parkoppla enheten manuellt" - "Testa att aktivera parkopplingsläget på enheten" - "Enheter inom räckvidd" - "Enheter med ditt konto" - "En sparad enhet är tillgänglig" - "Närdelning" - "Enheter" - "Ansluter …" - "Batteri: %d %%" - "Klar" - "Spara" - "Anslut" - "Konfigurera" - "Inställningar" - diff --git a/nearby/halfsheet/res/values-sw/strings.xml b/nearby/halfsheet/res/values-sw/strings.xml deleted file mode 100644 index 238a288963cb9777fd74e4075c9df3f14f446ee1..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-sw/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Inaanza Kuweka Mipangilio…" - "Weka mipangilio ya kifaa" - "Kifaa kimeunganishwa" - "Imeunganishwa kwenye “%s”" - "Imeshindwa kuunganisha" - "Imeshindwa kuunganisha" - "Jaribu kuoanisha mwenyewe kwenye kifaa" - "Jaribu kuweka kifaa katika hali ya kuoanisha" - "Vifaa vilivyo karibu nawe" - "Vifaa vilivyounganishwa kwenye akaunti yako" - "Kifaa ulichohifadhi kinapatikana" - "Uhamishaji wa Karibu" - "Vifaa" - "Inaunganisha…" - "Betri: %d%%" - "Imemaliza" - "Hifadhi" - "Unganisha" - "Weka mipangilio" - "Mipangilio" - diff --git a/nearby/halfsheet/res/values-ta/strings.xml b/nearby/halfsheet/res/values-ta/strings.xml deleted file mode 100644 index baadcc2d5925aef710b5a002b359c0d997962136..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-ta/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "அமைவைத் தொடங்குகிறது…" - "சாதனத்தை அமையுங்கள்" - "சாதனம் இணைக்கப்பட்டது" - "“%s” உடன் இணைக்கப்பட்டது" - "இணைக்க முடியவில்லை" - "இணைக்க முடியவில்லை" - "சாதனத்துடன் நீங்களாகவே இணைக்க முயலவும்" - "சாதனத்தை \'இணைத்தல் பயன்முறையில்\' வைக்கவும்" - "தொடர்பு வரம்பிலுள்ள சாதனங்கள்" - "உங்கள் கணக்குடன் இணைந்துள்ள சாதனங்கள்" - "நீங்கள் சேமித்த சாதனம் உள்ளது" - "அருகில் பகிர்தல்" - "சாதனங்கள்" - "இணைக்கிறது…" - "பேட்டரி: %d%%" - "முடிந்தது" - "சேமி" - "இணை" - "அமை" - "அமைப்புகள்" - diff --git a/nearby/halfsheet/res/values-te/strings.xml b/nearby/halfsheet/res/values-te/strings.xml deleted file mode 100644 index cb8f91b6dbea02bceed748f72b3bdf1e9d626244..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-te/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "సెటప్ ప్రారంభమవుతోంది…" - "పరికరాన్ని సెటప్ చేయండి" - "పరికరం కనెక్ట్ చేయబడింది" - "“%s”కు కనెక్ట్ చేయబడింది" - "కనెక్ట్ చేయడం సాధ్యపడలేదు" - "కనెక్ట్ చేయలేకపోయింది" - "పరికరానికి మాన్యువల్‌గా పెయిరింగ్ చేయడానికి ట్రై చేయండి" - "పరికరాన్ని పెయిరింగ్ మోడ్‌లో ఉంచడానికి ట్రై చేయండి" - "అందుబాటులో ఉన్న పరికరాలు" - "మీ ఖాతా ఉన్న పరికరాలు" - "మీ సేవ్ చేసిన పరికరం అందుబాటులో ఉంది" - "సమీపం" - "పరికరాలు" - "కనెక్ట్ అవుతోంది…" - "బ్యాటరీ: %d%%" - "పూర్తయింది" - "సేవ్ చేయండి" - "కనెక్ట్ చేయండి" - "సెటప్ చేయండి" - "సెట్టింగ్‌లు" - diff --git a/nearby/halfsheet/res/values-th/strings.xml b/nearby/halfsheet/res/values-th/strings.xml deleted file mode 100644 index f5c5c2e2efa157b3448303ae06804656f1a36207..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-th/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "กำลังเริ่มการตั้งค่า…" - "ตั้งค่าอุปกรณ์" - "เชื่อมต่ออุปกรณ์แล้ว" - "เชื่อมต่อกับ \"%s\" แล้ว" - "เชื่อมต่อไม่ได้" - "เชื่อมต่อไม่ได้" - "ลองจับคู่อุปกรณ์ด้วยตนเอง" - "พยายามนำอุปกรณ์เข้าสู่โหมดการจับคู่" - "อุปกรณ์ที่อยู่ติดกัน" - "อุปกรณ์ที่มีบัญชีของคุณ" - "อุปกรณ์ที่บันทึกพร้อมใช้แล้ว" - "ใกล้เคียง" - "อุปกรณ์" - "กำลังเชื่อมต่อ…" - "แบตเตอรี่: %d%%" - "เสร็จสิ้น" - "บันทึก" - "เชื่อมต่อ" - "ตั้งค่า" - "การตั้งค่า" - diff --git a/nearby/halfsheet/res/values-tl/strings.xml b/nearby/halfsheet/res/values-tl/strings.xml deleted file mode 100644 index a546da67c282e2ddd99246a0d4e7dc638e839850..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-tl/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Sinisimulan ang Pag-set Up…" - "I-set up ang device" - "Naikonekta na ang device" - "Nakakonekta sa “%s”" - "Hindi makakonekta" - "Hindi makakonekta" - "Subukang manual na magpares sa device" - "Subukang ilagay sa pairing mode ang device" - "Mga naaabot na device" - "Mga device sa iyong account" - "Available ang iyong na-save na device" - "Kalapit" - "Mga Device" - "Kumokonekta…" - "Baterya: %d%%" - "Tapos na" - "I-save" - "Kumonekta" - "I-set up" - "Mga Setting" - diff --git a/nearby/halfsheet/res/values-tr/strings.xml b/nearby/halfsheet/res/values-tr/strings.xml deleted file mode 100644 index a54c5e76589cf47f0f8b81e7a1d580ce60cda7ba..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-tr/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Kurulum Başlatılıyor…" - "Cihazı kur" - "Cihaz bağlandı" - "\"%s\" cihazına bağlanıldı" - "Bağlanamadı" - "Bağlantı kurulamadı" - "Cihazla manuel olarak eşlemeyi deneyin" - "Cihazı eşleme moduna geçirmeyi deneyin" - "Erişilebilecek cihazlar" - "Hesabınıza bağlı cihazlar" - "Kayıtlı cihazınız kullanılabilir" - "Yakındaki" - "Cihazlar" - "Bağlanıyor…" - "Pil seviyesi: %%%d" - "Bitti" - "Kaydet" - "Bağlan" - "Kur" - "Ayarlar" - diff --git a/nearby/halfsheet/res/values-uk/strings.xml b/nearby/halfsheet/res/values-uk/strings.xml deleted file mode 100644 index ab73c1fb5bcd731efc7b139030003769173b816c..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-uk/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Запуск налаштування…" - "Налаштуйте пристрій" - "Пристрій підключено" - "Підключено до пристрою \"%s\"" - "Не вдалося підключити" - "Не вдалося підключитися" - "Спробуйте підключитися до пристрою вручну" - "Спробуйте ввімкнути на пристрої режим підключення" - "Пристрої в радіусі дії" - "Пристрої з вашим обліковим записом" - "Збережений пристрій доступний" - "Поблизу" - "Пристрої" - "Підключення…" - "Акумулятор: %d%%" - "Готово" - "Зберегти" - "Підключити" - "Налаштувати" - "Налаштування" - diff --git a/nearby/halfsheet/res/values-ur/strings.xml b/nearby/halfsheet/res/values-ur/strings.xml deleted file mode 100644 index a2b2038edf2b71fd03dbb8e71fbccab6603824ef..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-ur/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "سیٹ اپ شروع ہو رہا ہے…" - "آلہ سیٹ اپ کریں" - "آلہ منسلک ہے" - "‏\"‎\"%s سے منسلک ہے" - "منسلک نہیں ہو سکا" - "منسلک ہونے سے قاصر" - "دستی طور پر آلے کے ساتھ جوڑا بنانا آزمائیں" - "آلے کو جوڑا بنانے والے موڈ میں رکھ کر آزمائیں" - "رسائی کے اندر آلات" - "آپ کے اکاؤنٹ سے منسلک آلات" - "آپ کا محفوظ کردہ آلہ دستیاب ہے" - "قریبی" - "آلات" - "منسلک ہو رہا ہے…" - "‏بیٹری: ‎‎%d%%‎" - "ہو گیا" - "محفوظ کریں" - "منسلک کریں" - "سیٹ اپ کریں" - "ترتیبات" - diff --git a/nearby/halfsheet/res/values-uz/strings.xml b/nearby/halfsheet/res/values-uz/strings.xml deleted file mode 100644 index 70c190ae7c5b88dc30d9d8af6d8fbb5f59a23e9a..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-uz/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Sozlash boshlandi…" - "Qurilmani sozlash" - "Qurilma ulandi" - "Bunga ulandi: “%s”" - "Ulanmadi" - "Ulanmadi" - "Qurilmangizga odatiy usulda ulaning" - "Qurilmada ulanish rejimini yoqing" - "Atrofdagi qurilmalar" - "Hisobingizga ulangan qurilmalar" - "Saqlangan qurilmangiz mavjud" - "Yaqin-atrofda" - "Qurilmalar" - "Ulanmoqda…" - "Batareya: %d%%" - "Tayyor" - "Saqlash" - "Ulanish" - "Sozlash" - "Sozlamalar" - diff --git a/nearby/halfsheet/res/values-vi/strings.xml b/nearby/halfsheet/res/values-vi/strings.xml deleted file mode 100644 index e2ea4673b55cdcaf11b0b027174b4ddbb8df2aef..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-vi/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Đang bắt đầu thiết lập…" - "Thiết lập thiết bị" - "Đã kết nối thiết bị" - "Đã kết nối với “%s”" - "Không kết nối được" - "Không thể kết nối" - "Hãy thử ghép nối thủ công với thiết bị" - "Hãy thử đưa thiết bị này vào chế độ ghép nối" - "Thiết bị trong tầm tay" - "Các thiết bị có tài khoản của bạn" - "Thiết bị đã lưu của bạn có sẵn" - "Lân cận" - "Thiết bị" - "Đang kết nối…" - "Pin: %d%%" - "Xong" - "Lưu" - "Kết nối" - "Thiết lập" - "Cài đặt" - diff --git a/nearby/halfsheet/res/values-zh-rCN/strings.xml b/nearby/halfsheet/res/values-zh-rCN/strings.xml deleted file mode 100644 index 55dd8b0c73711cdb677fe2c5f59aa0c46a3ba811..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-zh-rCN/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "正在启动设置…" - "设置设备" - "设备已连接" - "已连接到“%s”" - "无法连接" - "无法连接" - "请尝试手动与该设备配对" - "请尝试让设备进入配对模式" - "附近的设备" - "与您的账号相关联的设备" - "您保存的设备已可供使用" - "附近" - "设备" - "正在连接…" - "电量:%d%%" - "完成" - "保存" - "连接" - "设置" - "设置" - diff --git a/nearby/halfsheet/res/values-zh-rHK/strings.xml b/nearby/halfsheet/res/values-zh-rHK/strings.xml deleted file mode 100644 index 4dac931c1652de70988553521c57f5df39937b2c..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-zh-rHK/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "開始設定…" - "設定裝置" - "已連接裝置" - "已連線至「%s」" - "無法連接" - "無法連線" - "嘗試手動配對裝置" - "嘗試讓裝置進入配對模式" - "附近的裝置" - "已連結你帳戶的裝置" - "你儲存的裝置已可使用" - "咫尺共享" - "裝置" - "正在連接…" - "電量:%d%%" - "完成" - "儲存" - "連接" - "設定" - "設定" - diff --git a/nearby/halfsheet/res/values-zh-rTW/strings.xml b/nearby/halfsheet/res/values-zh-rTW/strings.xml deleted file mode 100644 index 0c90ebb8aa67637f27b3b6c03d9201aa55e91922..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-zh-rTW/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "正在啟動設定程序…" - "設定裝置" - "裝置已連線" - "已連線到「%s」" - "無法連線" - "無法連線" - "嘗試手動配對裝置" - "嘗試讓裝置進入配對模式" - "鄰近裝置" - "與你帳戶連結的裝置" - "你儲存的裝置已可使用" - "鄰近分享" - "裝置" - "連線中…" - "電池電量:%d%%" - "完成" - "儲存" - "連線" - "設定" - "設定" - diff --git a/nearby/halfsheet/res/values-zu/strings.xml b/nearby/halfsheet/res/values-zu/strings.xml deleted file mode 100644 index 3f26469d78851c41817d627950afe6d8a5fedb6d..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values-zu/strings.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - "Iqalisa Ukusetha…" - "Setha idivayisi" - "Idivayisi ixhunyiwe" - "Ixhunywe ku-\"%s\"" - "Ayikwazanga ukuxhuma" - "Ayikwazanga ukuxhuma" - "Zama ukubhangqa kule divayisi" - "Zama ukubeka le divayisi kumodi yokubhangqa" - "Amadivayisi afinyelelekayo" - "Amadivayisi ane-akhawunti yakho" - "Idivayisi yakho elondoloziwe ikhona" - "Eduze" - "Amadivayisi" - "Iyaxhuma…" - "Ibhethri: %d%%" - "Kwenziwe" - "Londoloza" - "Xhuma" - "Setha" - "Amasethingi" - diff --git a/nearby/halfsheet/res/values/colors.xml b/nearby/halfsheet/res/values/colors.xml deleted file mode 100644 index b0666655040d72f1f514de6934c80e2eef7ae1cf..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values/colors.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - #00000000 - - @android:color/system_accent1_100 - @android:color/system_neutral1_900 - @android:color/system_neutral1_900 - @android:color/system_accent1_600 - @android:color/system_neutral2_700 - @android:color/system_neutral1_900 - - - #4285F4 - - - #DE000000 - #24000000 - #D93025 - #80868B - #FFFFFF - #1A73E8 - #F44336 - #24000000 - diff --git a/nearby/halfsheet/res/values/dimens.xml b/nearby/halfsheet/res/values/dimens.xml deleted file mode 100644 index f843042adb64848b0e6355b48272ddc8665fb35f..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values/dimens.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - 160dp - 14sp - 11sp - 4dp - 8dp - 8dp - 40dp - 64dp - 32dp - 3dp - 350dp - 215dp - 136dp - 36dp - 48dp - 152dp - 100dp - 156 - 182 - - - 360dp - - 16dp - 70dp - 4dp - - 4dp - 32dp - 32dp - - 0dp - 0dp - 0dp - 0dp - 0dp - - 48dp - diff --git a/nearby/halfsheet/res/values/ints.xml b/nearby/halfsheet/res/values/ints.xml deleted file mode 100644 index 07bf9d2347c1b8c16653be075c51d2df3094a951..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values/ints.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - 250 - 250 - diff --git a/nearby/halfsheet/res/values/overlayable.xml b/nearby/halfsheet/res/values/overlayable.xml deleted file mode 100644 index fffa2e357e4cbc101b0b11f1e122ef5a6b52296a..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values/overlayable.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/nearby/halfsheet/res/values/strings.xml b/nearby/halfsheet/res/values/strings.xml deleted file mode 100644 index c1f53d439cef240ba15baf00ac1e48f3823be9dd..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values/strings.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - Starting Setup… - - Set up device - - Device connected - Connected to \u201c%s\u201d - - Couldn\'t connect - Unable to connect - Try manually pairing to the device - Try putting the device into pairing mode - - - - Devices within reach - Devices with your account - Your saved device is available - - - - - Nearby - - Devices - - Connecting… - - Battery: %d%% - - Done - - Save - - Connect - - Set up - - Settings - \ No newline at end of file diff --git a/nearby/halfsheet/res/values/styles.xml b/nearby/halfsheet/res/values/styles.xml deleted file mode 100644 index 917bb63cfd6fa508c9370c3854d5ea87858b0244..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/res/values/styles.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/FastPairUiServiceClient.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/FastPairUiServiceClient.java deleted file mode 100644 index bec0c0aff783743af9a8c9a8ca66032c26a65394..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/src/com/android/nearby/halfsheet/FastPairUiServiceClient.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.halfsheet; - -import android.content.Context; -import android.nearby.FastPairDevice; -import android.nearby.FastPairStatusCallback; -import android.nearby.PairStatusMetadata; -import android.nearby.aidl.IFastPairStatusCallback; -import android.nearby.aidl.IFastPairUiService; -import android.os.Handler; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import androidx.annotation.BinderThread; -import androidx.annotation.UiThread; - -import java.lang.ref.WeakReference; - -/** - * A utility class for connecting to the {@link IFastPairUiService} and receive callbacks. - * - * @hide - */ -@UiThread -public class FastPairUiServiceClient { - - private static final String TAG = "FastPairHalfSheet"; - - private final IBinder mBinder; - private final WeakReference mWeakContext; - IFastPairUiService mFastPairUiService; - PairStatusCallbackIBinder mPairStatusCallbackIBinder; - - /** - * The Ibinder instance should be from - * {@link com.android.server.nearby.fastpair.halfsheet.FastPairUiServiceImpl} so that the client can - * talk with the service. - */ - public FastPairUiServiceClient(Context context, IBinder binder) { - mBinder = binder; - mFastPairUiService = IFastPairUiService.Stub.asInterface(mBinder); - mWeakContext = new WeakReference<>(context); - } - - /** - * Registers a callback at service to get UI updates. - */ - public void registerHalfSheetStateCallBack(FastPairStatusCallback fastPairStatusCallback) { - if (mPairStatusCallbackIBinder != null) { - return; - } - mPairStatusCallbackIBinder = new PairStatusCallbackIBinder(fastPairStatusCallback); - try { - mFastPairUiService.registerCallback(mPairStatusCallbackIBinder); - } catch (RemoteException e) { - Log.w(TAG, "Failed to register fastPairStatusCallback", e); - } - } - - /** - * Pairs the device at service. - */ - public void connect(FastPairDevice fastPairDevice) { - try { - mFastPairUiService.connect(fastPairDevice); - } catch (RemoteException e) { - Log.w(TAG, "Failed to connect Fast Pair device" + fastPairDevice, e); - } - } - - /** - * Cancels Fast Pair connection and dismisses half sheet. - */ - public void cancel(FastPairDevice fastPairDevice) { - try { - mFastPairUiService.cancel(fastPairDevice); - } catch (RemoteException e) { - Log.w(TAG, "Failed to connect Fast Pair device" + fastPairDevice, e); - } - } - - private class PairStatusCallbackIBinder extends IFastPairStatusCallback.Stub { - private final FastPairStatusCallback mStatusCallback; - - private PairStatusCallbackIBinder(FastPairStatusCallback fastPairStatusCallback) { - mStatusCallback = fastPairStatusCallback; - } - - @BinderThread - @Override - public synchronized void onPairUpdate(FastPairDevice fastPairDevice, - PairStatusMetadata pairStatusMetadata) { - Context context = mWeakContext.get(); - if (context != null) { - Handler handler = new Handler(context.getMainLooper()); - handler.post(() -> - mStatusCallback.onPairUpdate(fastPairDevice, pairStatusMetadata)); - } - } - } -} diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java deleted file mode 100644 index 94f4ef436ce32f45b942bffeae3eb046315ffe63..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * 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.nearby.halfsheet; - -import static android.Manifest.permission.ACCESS_FINE_LOCATION; - -import static com.android.nearby.halfsheet.constants.Constant.ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET; -import static com.android.nearby.halfsheet.constants.Constant.ACTION_FAST_PAIR_HALF_SHEET_CANCEL; -import static com.android.nearby.halfsheet.constants.Constant.ACTION_HALF_SHEET_FOREGROUND_STATE; -import static com.android.nearby.halfsheet.constants.Constant.DEVICE_PAIRING_FRAGMENT_TYPE; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_FOREGROUND; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_INFO; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_IS_RETROACTIVE; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_TYPE; -import static com.android.nearby.halfsheet.constants.Constant.TAG; -import static com.android.nearby.halfsheet.constants.FastPairConstants.EXTRA_MODEL_ID; -import static com.android.nearby.halfsheet.constants.UserActionHandlerBase.EXTRA_MAC_ADDRESS; -import static com.android.nearby.halfsheet.fragment.DevicePairingFragment.APP_LAUNCH_FRAGMENT_TYPE; - -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentActivity; - -import com.android.nearby.halfsheet.fragment.DevicePairingFragment; -import com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment; -import com.android.nearby.halfsheet.utils.BroadcastUtils; - -import com.google.protobuf.InvalidProtocolBufferException; - -import java.util.Locale; - -import service.proto.Cache; - -/** - * A class show Fast Pair related information in Half sheet format. - */ -public class HalfSheetActivity extends FragmentActivity { - @Nullable - private HalfSheetModuleFragment mHalfSheetModuleFragment; - @Nullable - private Cache.ScanFastPairStoreItem mScanFastPairStoreItem; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - byte[] infoArray = getIntent().getByteArrayExtra(EXTRA_HALF_SHEET_INFO); - String fragmentType = getIntent().getStringExtra(EXTRA_HALF_SHEET_TYPE); - if (infoArray == null || fragmentType == null) { - Log.d( - "HalfSheetActivity", - "exit flag off or do not have enough half sheet information."); - finish(); - return; - } - - switch (fragmentType) { - case DEVICE_PAIRING_FRAGMENT_TYPE: - mHalfSheetModuleFragment = DevicePairingFragment.newInstance(getIntent(), - savedInstanceState); - if (mHalfSheetModuleFragment == null) { - Log.d(TAG, "device pairing fragment has error."); - finish(); - return; - } - break; - case APP_LAUNCH_FRAGMENT_TYPE: - // currentFragment = AppLaunchFragment.newInstance(getIntent()); - if (mHalfSheetModuleFragment == null) { - Log.v(TAG, "app launch fragment has error."); - finish(); - return; - } - break; - default: - Log.w(TAG, "there is no valid type for half sheet"); - finish(); - return; - } - if (mHalfSheetModuleFragment != null) { - getSupportFragmentManager() - .beginTransaction() - .replace(R.id.fragment_container, mHalfSheetModuleFragment) - .commit(); - } - setContentView(R.layout.fast_pair_half_sheet); - - // If the user taps on the background, then close the activity. - // Unless they tap on the card itself, then ignore the tap. - findViewById(R.id.background).setOnClickListener(v -> onCancelClicked()); - findViewById(R.id.card) - .setOnClickListener( - v -> Log.v(TAG, "card view is clicked noop")); - try { - mScanFastPairStoreItem = - Cache.ScanFastPairStoreItem.parseFrom(infoArray); - } catch (InvalidProtocolBufferException e) { - Log.w( - TAG, "error happens when pass info to half sheet"); - } - } - - @Override - protected void onStart() { - super.onStart(); - BroadcastUtils.sendBroadcast( - this, - new Intent(ACTION_HALF_SHEET_FOREGROUND_STATE) - .putExtra(EXTRA_HALF_SHEET_FOREGROUND, true)); - } - - @Override - protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) { - super.onSaveInstanceState(savedInstanceState); - if (mHalfSheetModuleFragment != null) { - mHalfSheetModuleFragment.onSaveInstanceState(savedInstanceState); - } - } - - @Override - public void onBackPressed() { - super.onBackPressed(); - sendHalfSheetCancelBroadcast(); - } - - @Override - protected void onUserLeaveHint() { - super.onUserLeaveHint(); - sendHalfSheetCancelBroadcast(); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - String fragmentType = getIntent().getStringExtra(EXTRA_HALF_SHEET_TYPE); - if (fragmentType == null) { - return; - } - if (fragmentType.equals(DEVICE_PAIRING_FRAGMENT_TYPE) - && intent.getExtras() != null - && intent.getByteArrayExtra(EXTRA_HALF_SHEET_INFO) != null) { - try { - Cache.ScanFastPairStoreItem testScanFastPairStoreItem = - Cache.ScanFastPairStoreItem.parseFrom( - intent.getByteArrayExtra(EXTRA_HALF_SHEET_INFO)); - if (mScanFastPairStoreItem != null - && !testScanFastPairStoreItem.getAddress().equals( - mScanFastPairStoreItem.getAddress()) - && testScanFastPairStoreItem.getModelId().equals( - mScanFastPairStoreItem.getModelId())) { - Log.d(TAG, "possible factory reset happens"); - halfSheetStateChange(); - } - } catch (InvalidProtocolBufferException | NullPointerException e) { - Log.w(TAG, "error happens when pass info to half sheet"); - } - } - } - - /** This function should be called when user click empty area and cancel button. */ - public void onCancelClicked() { - Log.d(TAG, "Cancels the half sheet and paring."); - sendHalfSheetCancelBroadcast(); - finish(); - } - - /** Changes the half sheet foreground state to false. */ - public void halfSheetStateChange() { - BroadcastUtils.sendBroadcast( - this, - new Intent(ACTION_HALF_SHEET_FOREGROUND_STATE) - .putExtra(EXTRA_HALF_SHEET_FOREGROUND, false)); - finish(); - } - - - /** - * Changes the half sheet ban state to active. - * Sometimes users leave half sheet to go to fast pair info page, - * we do not want the behavior to be counted as dismiss. - */ - public void sendBanStateResetBroadcast() { - if (mScanFastPairStoreItem == null) { - return; - } - BroadcastUtils.sendBroadcast( - this, - new Intent(ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET) - .putExtra(EXTRA_MODEL_ID, mScanFastPairStoreItem.getModelId() - .toLowerCase(Locale.ROOT))); - } - - private void sendHalfSheetCancelBroadcast() { - BroadcastUtils.sendBroadcast( - this, - new Intent(ACTION_HALF_SHEET_FOREGROUND_STATE) - .putExtra(EXTRA_HALF_SHEET_FOREGROUND, false)); - if (mScanFastPairStoreItem == null) { - return; - } - BroadcastUtils.sendBroadcast( - this, - new Intent(ACTION_FAST_PAIR_HALF_SHEET_CANCEL) - .putExtra(EXTRA_MODEL_ID, - mScanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT)) - .putExtra(EXTRA_HALF_SHEET_TYPE, - getIntent().getStringExtra(EXTRA_HALF_SHEET_TYPE)) - .putExtra( - EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR, - getIntent().getBooleanExtra(EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR, - false)) - .putExtra( - EXTRA_HALF_SHEET_IS_RETROACTIVE, - getIntent().getBooleanExtra(EXTRA_HALF_SHEET_IS_RETROACTIVE, - false)) - .putExtra(EXTRA_MAC_ADDRESS, mScanFastPairStoreItem.getAddress()), - ACCESS_FINE_LOCATION); - } - - @Override - public void setTitle(CharSequence title) { - super.setTitle(title); - TextView toolbarTitle = findViewById(R.id.toolbar_title); - toolbarTitle.setText(title); - } -} diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/constants/Constant.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/constants/Constant.java deleted file mode 100644 index 65c76d18b50c0c1abb50d363de0821664ad42f5e..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/src/com/android/nearby/halfsheet/constants/Constant.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.halfsheet.constants; - -/** - * String constant for half sheet. - */ -public class Constant { - public static final String TAG = "FastPairHalfSheet"; - private static final String PREFIX = "com.android.nearby.halfsheet."; - - // Intent extra - public static final String EXTRA_BINDER = "com.android.server.nearby.fastpair.BINDER"; - public static final String EXTRA_BUNDLE = "com.android.server.nearby.fastpair.BUNDLE_EXTRA"; - - public static final String EXTRA_TITLE = PREFIX + "HALF_SHEET_TITLE"; - public static final String EXTRA_DESCRIPTION = PREFIX + "HALF_SHEET_DESCRIPTION"; - public static final String EXTRA_HALF_SHEET_ID = PREFIX + "HALF_SHEET_ID"; - public static final String EXTRA_HALF_SHEET_INFO = PREFIX + "HALF_SHEET"; - public static final String EXTRA_HALF_SHEET_TYPE = PREFIX + "HALF_SHEET_TYPE"; - public static final String EXTRA_HALF_SHEET_ACCOUNT_NAME = PREFIX + "HALF_SHEET_ACCOUNT_NAME"; - public static final String EXTRA_HALF_SHEET_CONTENT = PREFIX + "HALF_SHEET_CONTENT"; - public static final String EXTRA_HALF_SHEET_FOREGROUND = - PREFIX + "EXTRA_HALF_SHEET_FOREGROUND"; - public static final String EXTRA_HALF_SHEET_IS_RETROACTIVE = - PREFIX + "HALF_SHEET_IS_RETROACTIVE"; - public static final String EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR = - PREFIX + "HALF_SHEET_IS_SUBSEQUENT_PAIR"; - public static final String EXTRA_HALF_SHEET_PAIRING_RESURFACE = - PREFIX + "EXTRA_HALF_SHEET_PAIRING_RESURFACE"; - - // Intent Actions - public static final String ACTION_HALF_SHEET_FOREGROUND_STATE = - PREFIX + "ACTION_HALF_SHEET_FOREGROUND_STATE"; - public static final String ACTION_FAST_PAIR_HALF_SHEET_CANCEL = - "com.android.nearby.ACTION_FAST_PAIR_HALF_SHEET_CANCEL"; - public static final String ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET = - "com.android.nearby.ACTION_FAST_PAIR_BAN_STATE_RESET"; - public static final String ACTION_RESOURCES_APK = - "android.nearby.SHOW_HALFSHEET"; - public static final String ACTION_FAST_PAIR = PREFIX + "ACTION_MAGIC_PAIR"; - - public static final String RESULT_FAIL = "RESULT_FAIL"; - public static final String ARG_FRAGMENT_STATE = "ARG_FRAGMENT_STATE"; - public static final String DEVICE_PAIRING_FRAGMENT_TYPE = "DEVICE_PAIRING"; - - // Content url for help page about Fast Pair in half sheet. - // Todo(b/246007000): Add a flag to set up content url of the help page. - public static final String FAST_PAIR_HALF_SHEET_HELP_URL = - "https://support.google.com/android/answer/9075925"; -} diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/constants/UserActionHandlerBase.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/constants/UserActionHandlerBase.java deleted file mode 100644 index 767c6d686c261ee42e9d461f813a5eff031cfeba..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/src/com/android/nearby/halfsheet/constants/UserActionHandlerBase.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.halfsheet.constants; - -/** Handles intents to {@link com.android.server.nearby.fastpair.FastPairManager}. */ -public class UserActionHandlerBase { - public static final String PREFIX = "com.android.server.nearby.fastpair."; - public static final String ACTION_PREFIX = "com.android.server.nearby:"; - - public static final String EXTRA_ITEM_ID = PREFIX + "EXTRA_ITEM_ID"; - public static final String EXTRA_COMPANION_APP = ACTION_PREFIX + "EXTRA_COMPANION_APP"; - public static final String EXTRA_MAC_ADDRESS = PREFIX + "EXTRA_MAC_ADDRESS"; - - public static final String ACTION_FAST_PAIR = ACTION_PREFIX + "ACTION_FAST_PAIR"; -} diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java deleted file mode 100644 index 9f5c9154b91db87df9d8da35ac9c3d875bf32466..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java +++ /dev/null @@ -1,507 +0,0 @@ -/* - * 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.nearby.halfsheet.fragment; - -import static android.text.TextUtils.isEmpty; - -import static com.android.nearby.halfsheet.constants.Constant.ARG_FRAGMENT_STATE; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_BINDER; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_BUNDLE; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_DESCRIPTION; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_ACCOUNT_NAME; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_CONTENT; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_ID; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_INFO; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_TITLE; -import static com.android.nearby.halfsheet.constants.Constant.FAST_PAIR_HALF_SHEET_HELP_URL; -import static com.android.nearby.halfsheet.constants.Constant.RESULT_FAIL; -import static com.android.nearby.halfsheet.constants.Constant.TAG; -import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.FAILED; -import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.FOUND_DEVICE; -import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.NOT_STARTED; -import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRED_LAUNCHABLE; -import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRED_UNLAUNCHABLE; -import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRING; - -import android.bluetooth.BluetoothDevice; -import android.content.Intent; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.nearby.FastPairDevice; -import android.nearby.FastPairStatusCallback; -import android.nearby.NearbyDevice; -import android.nearby.PairStatusMetadata; -import android.os.Bundle; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import androidx.annotation.Nullable; - -import com.android.nearby.halfsheet.FastPairUiServiceClient; -import com.android.nearby.halfsheet.HalfSheetActivity; -import com.android.nearby.halfsheet.R; -import com.android.nearby.halfsheet.utils.FastPairUtils; -import com.android.nearby.halfsheet.utils.HelpUtils; -import com.android.nearby.halfsheet.utils.IconUtils; - -import com.google.protobuf.InvalidProtocolBufferException; - -import java.util.Objects; - -import service.proto.Cache.ScanFastPairStoreItem; - -/** - * Modularize half sheet for fast pair this fragment will show when half sheet does device pairing. - * - *

This fragment will handle initial pairing subsequent pairing and retroactive pairing. - */ -@SuppressWarnings("nullness") -public class DevicePairingFragment extends HalfSheetModuleFragment implements - FastPairStatusCallback { - private TextView mTitleView; - private TextView mSubTitleView; - private ImageView mImage; - - private Button mConnectButton; - private Button mSetupButton; - private Button mCancelButton; - // Opens Bluetooth Settings. - private Button mSettingsButton; - private ImageView mInfoIconButton; - private ProgressBar mConnectProgressBar; - - private Bundle mBundle; - - private ScanFastPairStoreItem mScanFastPairStoreItem; - private FastPairUiServiceClient mFastPairUiServiceClient; - - private @PairStatusMetadata.Status int mPairStatus = PairStatusMetadata.Status.UNKNOWN; - // True when there is a companion app to open. - private boolean mIsLaunchable; - private boolean mIsConnecting; - // Indicates that the setup button is clicked before. - private boolean mSetupButtonClicked = false; - - // Holds the new text while we transition between the two. - private static final int TAG_PENDING_TEXT = R.id.toolbar_title; - public static final String APP_LAUNCH_FRAGMENT_TYPE = "APP_LAUNCH"; - - private static final String ARG_SETUP_BUTTON_CLICKED = "SETUP_BUTTON_CLICKED"; - private static final String ARG_PAIRING_RESULT = "PAIRING_RESULT"; - - /** - * Create certain fragment according to the intent. - */ - @Nullable - public static HalfSheetModuleFragment newInstance( - Intent intent, @Nullable Bundle saveInstanceStates) { - Bundle args = new Bundle(); - byte[] infoArray = intent.getByteArrayExtra(EXTRA_HALF_SHEET_INFO); - - Bundle bundle = intent.getBundleExtra(EXTRA_BUNDLE); - String title = intent.getStringExtra(EXTRA_TITLE); - String description = intent.getStringExtra(EXTRA_DESCRIPTION); - String accountName = intent.getStringExtra(EXTRA_HALF_SHEET_ACCOUNT_NAME); - String result = intent.getStringExtra(EXTRA_HALF_SHEET_CONTENT); - int halfSheetId = intent.getIntExtra(EXTRA_HALF_SHEET_ID, 0); - - args.putByteArray(EXTRA_HALF_SHEET_INFO, infoArray); - args.putString(EXTRA_HALF_SHEET_ACCOUNT_NAME, accountName); - args.putString(EXTRA_TITLE, title); - args.putString(EXTRA_DESCRIPTION, description); - args.putInt(EXTRA_HALF_SHEET_ID, halfSheetId); - args.putString(EXTRA_HALF_SHEET_CONTENT, result == null ? "" : result); - args.putBundle(EXTRA_BUNDLE, bundle); - if (saveInstanceStates != null) { - if (saveInstanceStates.containsKey(ARG_FRAGMENT_STATE)) { - args.putSerializable( - ARG_FRAGMENT_STATE, saveInstanceStates.getSerializable(ARG_FRAGMENT_STATE)); - } - if (saveInstanceStates.containsKey(BluetoothDevice.EXTRA_DEVICE)) { - args.putParcelable( - BluetoothDevice.EXTRA_DEVICE, - saveInstanceStates.getParcelable(BluetoothDevice.EXTRA_DEVICE)); - } - if (saveInstanceStates.containsKey(BluetoothDevice.EXTRA_PAIRING_KEY)) { - args.putInt( - BluetoothDevice.EXTRA_PAIRING_KEY, - saveInstanceStates.getInt(BluetoothDevice.EXTRA_PAIRING_KEY)); - } - if (saveInstanceStates.containsKey(ARG_SETUP_BUTTON_CLICKED)) { - args.putBoolean( - ARG_SETUP_BUTTON_CLICKED, - saveInstanceStates.getBoolean(ARG_SETUP_BUTTON_CLICKED)); - } - if (saveInstanceStates.containsKey(ARG_PAIRING_RESULT)) { - args.putBoolean(ARG_PAIRING_RESULT, - saveInstanceStates.getBoolean(ARG_PAIRING_RESULT)); - } - } - DevicePairingFragment fragment = new DevicePairingFragment(); - fragment.setArguments(args); - return fragment; - } - - @Nullable - @Override - public View onCreateView( - LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - /* attachToRoot= */ - View rootView = inflater.inflate( - R.layout.fast_pair_device_pairing_fragment, container, /* attachToRoot= */ - false); - if (getContext() == null) { - Log.d(TAG, "can't find the attached activity"); - return rootView; - } - - Bundle args = getArguments(); - byte[] storeFastPairItemBytesArray = args.getByteArray(EXTRA_HALF_SHEET_INFO); - mBundle = args.getBundle(EXTRA_BUNDLE); - if (mBundle != null) { - mFastPairUiServiceClient = - new FastPairUiServiceClient(getContext(), mBundle.getBinder(EXTRA_BINDER)); - mFastPairUiServiceClient.registerHalfSheetStateCallBack(this); - } - if (args.containsKey(EXTRA_HALF_SHEET_CONTENT)) { - if (RESULT_FAIL.equals(args.getString(EXTRA_HALF_SHEET_CONTENT))) { - mPairStatus = PairStatusMetadata.Status.FAIL; - } - } - if (args.containsKey(ARG_FRAGMENT_STATE)) { - mFragmentState = (HalfSheetFragmentState) args.getSerializable(ARG_FRAGMENT_STATE); - } - if (args.containsKey(ARG_SETUP_BUTTON_CLICKED)) { - mSetupButtonClicked = args.getBoolean(ARG_SETUP_BUTTON_CLICKED); - } - if (args.containsKey(ARG_PAIRING_RESULT)) { - mPairStatus = args.getInt(ARG_PAIRING_RESULT); - } - - // Initiate views. - mTitleView = Objects.requireNonNull(getActivity()).findViewById(R.id.toolbar_title); - mSubTitleView = rootView.findViewById(R.id.header_subtitle); - mImage = rootView.findViewById(R.id.pairing_pic); - mConnectProgressBar = rootView.findViewById(R.id.connect_progressbar); - mConnectButton = rootView.findViewById(R.id.connect_btn); - mCancelButton = rootView.findViewById(R.id.cancel_btn); - mSettingsButton = rootView.findViewById(R.id.settings_btn); - mSetupButton = rootView.findViewById(R.id.setup_btn); - mInfoIconButton = rootView.findViewById(R.id.info_icon); - mInfoIconButton.setImageResource(R.drawable.fast_pair_ic_info); - - try { - setScanFastPairStoreItem(ScanFastPairStoreItem.parseFrom(storeFastPairItemBytesArray)); - } catch (InvalidProtocolBufferException e) { - Log.w(TAG, - "DevicePairingFragment: error happens when pass info to half sheet"); - return rootView; - } - - // Config for landscape mode - DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics(); - if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { - rootView.getLayoutParams().height = displayMetrics.heightPixels * 4 / 5; - rootView.getLayoutParams().width = displayMetrics.heightPixels * 4 / 5; - mImage.getLayoutParams().height = displayMetrics.heightPixels / 2; - mImage.getLayoutParams().width = displayMetrics.heightPixels / 2; - mConnectProgressBar.getLayoutParams().width = displayMetrics.heightPixels / 2; - mConnectButton.getLayoutParams().width = displayMetrics.heightPixels / 2; - //TODO(b/213373051): Add cancel button - } - - Bitmap icon = IconUtils.getIcon(mScanFastPairStoreItem.getIconPng().toByteArray(), - mScanFastPairStoreItem.getIconPng().size()); - if (icon != null) { - mImage.setImageBitmap(icon); - } - mConnectButton.setOnClickListener(v -> onConnectClicked()); - mCancelButton.setOnClickListener(v -> - ((HalfSheetActivity) getActivity()).onCancelClicked()); - mSettingsButton.setOnClickListener(v -> onSettingsClicked()); - mSetupButton.setOnClickListener(v -> onSetupClicked()); - mInfoIconButton.setOnClickListener(v -> onHelpClicked()); - return rootView; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // Get access to the activity's menu - setHasOptionsMenu(true); - } - - @Override - public void onStart() { - super.onStart(); - Log.v(TAG, "onStart: invalidate states"); - // If the fragmentState is not NOT_STARTED, it is because the fragment was just resumed from - // configuration change (e.g. rotating the screen or half-sheet resurface). Let's recover - // the UI directly. - if (mFragmentState != NOT_STARTED) { - setState(mFragmentState); - } else { - invalidateState(); - } - } - - @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - super.onSaveInstanceState(savedInstanceState); - - savedInstanceState.putSerializable(ARG_FRAGMENT_STATE, mFragmentState); - savedInstanceState.putBoolean(ARG_SETUP_BUTTON_CLICKED, mSetupButtonClicked); - savedInstanceState.putInt(ARG_PAIRING_RESULT, mPairStatus); - } - - private void onSettingsClicked() { - startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS)); - } - - private void onSetupClicked() { - String companionApp = - FastPairUtils.getCompanionAppFromActionUrl(mScanFastPairStoreItem.getActionUrl()); - Intent intent = - FastPairUtils.createCompanionAppIntent( - Objects.requireNonNull(getContext()), - companionApp, - mScanFastPairStoreItem.getAddress()); - mSetupButtonClicked = true; - if (mFragmentState == PAIRED_LAUNCHABLE) { - if (intent != null) { - startActivity(intent); - } - } else { - Log.d(TAG, "onSetupClick: State is " + mFragmentState); - } - } - - private void onConnectClicked() { - if (mScanFastPairStoreItem == null) { - Log.w(TAG, "No pairing related information in half sheet"); - return; - } - if (getFragmentState() == PAIRING) { - return; - } - mIsConnecting = true; - invalidateState(); - mFastPairUiServiceClient.connect( - new FastPairDevice.Builder() - .addMedium(NearbyDevice.Medium.BLE) - .setBluetoothAddress(mScanFastPairStoreItem.getAddress()) - .setData(FastPairUtils.convertFrom(mScanFastPairStoreItem) - .toByteArray()) - .build()); - } - - private void onHelpClicked() { - HelpUtils.showHelpPage(getContext(), FAST_PAIR_HALF_SHEET_HELP_URL); - ((HalfSheetActivity) getActivity()).sendBanStateResetBroadcast(); - getActivity().finish(); - } - - // Receives callback from service. - @Override - public void onPairUpdate(FastPairDevice fastPairDevice, PairStatusMetadata pairStatusMetadata) { - @PairStatusMetadata.Status int status = pairStatusMetadata.getStatus(); - if (status == PairStatusMetadata.Status.DISMISS && getActivity() != null) { - getActivity().finish(); - } - mIsConnecting = false; - mPairStatus = status; - invalidateState(); - } - - @Override - public void invalidateState() { - HalfSheetFragmentState newState = NOT_STARTED; - if (mIsConnecting) { - newState = PAIRING; - } else { - switch (mPairStatus) { - case PairStatusMetadata.Status.SUCCESS: - newState = mIsLaunchable ? PAIRED_LAUNCHABLE : PAIRED_UNLAUNCHABLE; - break; - case PairStatusMetadata.Status.FAIL: - newState = FAILED; - break; - default: - if (mScanFastPairStoreItem != null) { - newState = FOUND_DEVICE; - } - } - } - if (newState == mFragmentState) { - return; - } - setState(newState); - } - - @Override - public void setState(HalfSheetFragmentState state) { - super.setState(state); - invalidateTitles(); - invalidateButtons(); - } - - private void setScanFastPairStoreItem(ScanFastPairStoreItem item) { - mScanFastPairStoreItem = item; - invalidateLaunchable(); - } - - private void invalidateLaunchable() { - String companionApp = - FastPairUtils.getCompanionAppFromActionUrl(mScanFastPairStoreItem.getActionUrl()); - if (isEmpty(companionApp)) { - mIsLaunchable = false; - return; - } - mIsLaunchable = - FastPairUtils.isLaunchable(Objects.requireNonNull(getContext()), companionApp); - } - - private void invalidateButtons() { - mConnectProgressBar.setVisibility(View.INVISIBLE); - mConnectButton.setVisibility(View.INVISIBLE); - mCancelButton.setVisibility(View.INVISIBLE); - mSetupButton.setVisibility(View.INVISIBLE); - mSettingsButton.setVisibility(View.INVISIBLE); - mInfoIconButton.setVisibility(View.INVISIBLE); - - switch (mFragmentState) { - case FOUND_DEVICE: - mInfoIconButton.setVisibility(View.VISIBLE); - mConnectButton.setVisibility(View.VISIBLE); - break; - case PAIRING: - mConnectProgressBar.setVisibility(View.VISIBLE); - mCancelButton.setVisibility(View.VISIBLE); - setBackgroundClickable(false); - break; - case PAIRED_LAUNCHABLE: - mCancelButton.setVisibility(View.VISIBLE); - mSetupButton.setVisibility(View.VISIBLE); - setBackgroundClickable(true); - break; - case FAILED: - mSettingsButton.setVisibility(View.VISIBLE); - setBackgroundClickable(true); - break; - case NOT_STARTED: - case PAIRED_UNLAUNCHABLE: - default: - mCancelButton.setVisibility(View.VISIBLE); - setBackgroundClickable(true); - } - } - - private void setBackgroundClickable(boolean isClickable) { - HalfSheetActivity activity = (HalfSheetActivity) getActivity(); - if (activity == null) { - Log.w(TAG, "setBackgroundClickable: failed to set clickable to " + isClickable - + " because cannot get HalfSheetActivity."); - return; - } - View background = activity.findViewById(R.id.background); - if (background == null) { - Log.w(TAG, "setBackgroundClickable: failed to set clickable to " + isClickable - + " cannot find background at HalfSheetActivity."); - return; - } - Log.d(TAG, "setBackgroundClickable to " + isClickable); - background.setClickable(isClickable); - } - - private void invalidateTitles() { - String newTitle = getTitle(); - invalidateTextView(mTitleView, newTitle); - String newSubTitle = getSubTitle(); - invalidateTextView(mSubTitleView, newSubTitle); - } - - private void invalidateTextView(TextView textView, String newText) { - CharSequence oldText = - textView.getTag(TAG_PENDING_TEXT) != null - ? (CharSequence) textView.getTag(TAG_PENDING_TEXT) - : textView.getText(); - if (TextUtils.equals(oldText, newText)) { - return; - } - if (TextUtils.isEmpty(oldText)) { - // First time run. Don't animate since there's nothing to animate from. - textView.setText(newText); - } else { - textView.setTag(TAG_PENDING_TEXT, newText); - textView - .animate() - .alpha(0f) - .setDuration(TEXT_ANIMATION_DURATION_MILLISECONDS) - .withEndAction( - () -> { - textView.setText(newText); - textView - .animate() - .alpha(1f) - .setDuration(TEXT_ANIMATION_DURATION_MILLISECONDS); - }); - } - } - - private String getTitle() { - switch (mFragmentState) { - case PAIRED_LAUNCHABLE: - return getString(R.string.fast_pair_title_setup); - case FAILED: - return getString(R.string.fast_pair_title_fail); - case FOUND_DEVICE: - case NOT_STARTED: - case PAIRED_UNLAUNCHABLE: - default: - return mScanFastPairStoreItem.getDeviceName(); - } - } - - private String getSubTitle() { - switch (mFragmentState) { - case PAIRED_LAUNCHABLE: - return String.format( - mScanFastPairStoreItem - .getFastPairStrings() - .getPairingFinishedCompanionAppInstalled(), - mScanFastPairStoreItem.getDeviceName()); - case FAILED: - return mScanFastPairStoreItem.getFastPairStrings().getPairingFailDescription(); - case PAIRED_UNLAUNCHABLE: - return getString(R.string.fast_pair_device_ready); - case FOUND_DEVICE: - case NOT_STARTED: - return mScanFastPairStoreItem.getFastPairStrings().getInitialPairingDescription(); - default: - return ""; - } - } -} diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java deleted file mode 100644 index d87c015cece4dbe8751501b19f9fee5b2ad7d26c..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.nearby.halfsheet.fragment; - -import static com.android.nearby.halfsheet.constants.Constant.TAG; -import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.NOT_STARTED; - -import android.os.Bundle; -import android.util.Log; - -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; - - -/** Base class for all of the half sheet fragment. */ -public abstract class HalfSheetModuleFragment extends Fragment { - - static final int TEXT_ANIMATION_DURATION_MILLISECONDS = 200; - - HalfSheetFragmentState mFragmentState = NOT_STARTED; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @Override - public void onDestroy() { - super.onDestroy(); - } - - /** UI states of the half-sheet fragment. */ - public enum HalfSheetFragmentState { - NOT_STARTED, // Initial status - FOUND_DEVICE, // When a device is found found from Nearby scan service - PAIRING, // When user taps 'Connect' and Fast Pair stars pairing process - PAIRED_LAUNCHABLE, // When pair successfully - // and we found a launchable companion app installed - PAIRED_UNLAUNCHABLE, // When pair successfully - // but we cannot find a companion app to launch it - FAILED, // When paring was failed - FINISHED // When the activity is about to end finished. - } - - /** - * Returns the {@link HalfSheetFragmentState} to the parent activity. - * - *

Overrides this method if the fragment's state needs to be preserved in the parent - * activity. - */ - public HalfSheetFragmentState getFragmentState() { - return mFragmentState; - } - - void setState(HalfSheetFragmentState state) { - Log.v(TAG, "Settings state from " + mFragmentState + " to " + state); - mFragmentState = state; - } - - /** - * Populate data to UI widgets according to the latest {@link HalfSheetFragmentState}. - */ - abstract void invalidateState(); -} diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java deleted file mode 100644 index a1588a96fc518c5895b7941d92ee1d49b6d24a18..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * 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.nearby.halfsheet.utils; - -import static com.android.nearby.halfsheet.constants.UserActionHandlerBase.ACTION_FAST_PAIR; -import static com.android.nearby.halfsheet.constants.UserActionHandlerBase.EXTRA_COMPANION_APP; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.net.URISyntaxException; - -import service.proto.Cache; - -/** - * Util class in half sheet apk - */ -public class FastPairUtils { - - /** FastPair util method check certain app is install on the device or not. */ - public static boolean isAppInstalled(Context context, String packageName) { - try { - context.getPackageManager().getPackageInfo(packageName, 0); - return true; - } catch (PackageManager.NameNotFoundException e) { - return false; - } - } - - /** FastPair util method to properly format the action url extra. */ - @Nullable - public static String getCompanionAppFromActionUrl(String actionUrl) { - try { - Intent intent = Intent.parseUri(actionUrl, Intent.URI_INTENT_SCHEME); - if (!intent.getAction().equals(ACTION_FAST_PAIR)) { - Log.e("FastPairUtils", "Companion app launch attempted from malformed action url"); - return null; - } - return intent.getStringExtra(EXTRA_COMPANION_APP); - } catch (URISyntaxException e) { - Log.e("FastPairUtils", "FastPair: fail to get companion app info from discovery item"); - return null; - } - } - - /** - * Converts {@link service.proto.Cache.StoredDiscoveryItem} from - * {@link service.proto.Cache.ScanFastPairStoreItem} - */ - public static Cache.StoredDiscoveryItem convertFrom(Cache.ScanFastPairStoreItem item) { - return convertFrom(item, /* isSubsequentPair= */ false); - } - - /** - * Converts a {@link service.proto.Cache.ScanFastPairStoreItem} - * to a {@link service.proto.Cache.StoredDiscoveryItem}. - * - *

This is needed to make the new Fast Pair scanning stack compatible with the rest of the - * legacy Fast Pair code. - */ - public static Cache.StoredDiscoveryItem convertFrom( - Cache.ScanFastPairStoreItem item, boolean isSubsequentPair) { - return Cache.StoredDiscoveryItem.newBuilder() - .setId(item.getModelId()) - .setFirstObservationTimestampMillis(item.getFirstObservationTimestampMillis()) - .setLastObservationTimestampMillis(item.getLastObservationTimestampMillis()) - .setActionUrl(item.getActionUrl()) - .setActionUrlType(Cache.ResolvedUrlType.APP) - .setTitle( - isSubsequentPair - ? item.getFastPairStrings().getTapToPairWithoutAccount() - : item.getDeviceName()) - .setMacAddress(item.getAddress()) - .setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED) - .setTriggerId(item.getModelId()) - .setIconPng(item.getIconPng()) - .setIconFifeUrl(item.getIconFifeUrl()) - .setDescription( - isSubsequentPair - ? item.getDeviceName() - : item.getFastPairStrings().getTapToPairWithoutAccount()) - .setAuthenticationPublicKeySecp256R1(item.getAntiSpoofingPublicKey()) - .setCompanionDetail(item.getCompanionDetail()) - .setFastPairStrings(item.getFastPairStrings()) - .setFastPairInformation( - Cache.FastPairInformation.newBuilder() - .setDataOnlyConnection(item.getDataOnlyConnection()) - .setTrueWirelessImages(item.getTrueWirelessImages()) - .setAssistantSupported(item.getAssistantSupported()) - .setCompanyName(item.getCompanyName())) - .build(); - } - - /** - * Returns true the application is installed and can be opened on device. - */ - public static boolean isLaunchable(@NonNull Context context, String companionApp) { - return isAppInstalled(context, companionApp) - && createCompanionAppIntent(context, companionApp, null) != null; - } - - /** - * Returns an intent to launch given the package name and bluetooth address (if provided). - * Returns null if no such an intent can be found. - */ - @Nullable - public static Intent createCompanionAppIntent(@NonNull Context context, String packageName, - @Nullable String address) { - Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName); - if (intent == null) { - return null; - } - if (address != null) { - BluetoothAdapter adapter = getBluetoothAdapter(context); - if (adapter != null) { - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, adapter.getRemoteDevice(address)); - } - } - return intent; - } - - @Nullable - private static BluetoothAdapter getBluetoothAdapter(@NonNull Context context) { - BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class); - return bluetoothManager == null ? null : bluetoothManager.getAdapter(); - } - - private FastPairUtils() {} -} diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/HelpUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/HelpUtils.java deleted file mode 100644 index 98f2242118213c2023b159bb0d9b3e8adbb9e5e9..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/HelpUtils.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.halfsheet.utils; - -import static com.android.nearby.halfsheet.constants.Constant.TAG; - -import static java.util.Objects.requireNonNull; - -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.util.Log; - -/** - * Util class for launching help page in Fast Pair. - */ -public class HelpUtils { - /** - * Sets up the info button to launch a help page - */ - public static void showHelpPage(Context context, String url) { - requireNonNull(context, "context cannot be null"); - requireNonNull(url, "url cannot be null"); - - try { - context.startActivity(createHelpPageIntent(url)); - } catch (ActivityNotFoundException e) { - Log.w(TAG, "Failed to find activity for url " + url, e); - } - } - - /** - * Creates the intent for help page - */ - private static Intent createHelpPageIntent(String url) { - return new Intent(Intent.ACTION_VIEW, Uri.parse(url)).setFlags( - Intent.FLAG_ACTIVITY_NEW_TASK); - } -} diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java deleted file mode 100644 index e547369d03cd912c8c14bb07519721aa577e02cc..0000000000000000000000000000000000000000 --- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.halfsheet.utils; - -import static com.android.nearby.halfsheet.constants.Constant.TAG; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.util.Log; - -import androidx.annotation.Nullable; - -/** - * Utility class for icon size verification. - */ -public class IconUtils { - - private static final float NOTIFICATION_BACKGROUND_PADDING_PERCENT = 0.125f; - private static final float NOTIFICATION_BACKGROUND_ALPHA = 0.7f; - private static final int MIN_ICON_SIZE = 16; - private static final int DESIRED_ICON_SIZE = 32; - - /** - * Verify that the icon is non null and falls in the small bucket. Just because an icon isn't - * small doesn't guarantee it is large or exists. - */ - public static boolean isIconSizedSmall(@Nullable Bitmap bitmap) { - if (bitmap == null) { - return false; - } - return bitmap.getWidth() >= MIN_ICON_SIZE - && bitmap.getWidth() < DESIRED_ICON_SIZE - && bitmap.getHeight() >= MIN_ICON_SIZE - && bitmap.getHeight() < DESIRED_ICON_SIZE; - } - - /** - * Verify that the icon is non null and falls in the regular / default size bucket. Doesn't - * guarantee if not regular then it is small. - */ - static boolean isIconSizedRegular(@Nullable Bitmap bitmap) { - if (bitmap == null) { - return false; - } - return bitmap.getWidth() >= DESIRED_ICON_SIZE && bitmap.getHeight() >= DESIRED_ICON_SIZE; - } - - /** - * All icons that are sized correctly (larger than the MIN_ICON_SIZE icon size) - * are resize on the server to the DESIRED_ICON_SIZE icon size so that - * they appear correct. - */ - public static boolean isIconSizeCorrect(@Nullable Bitmap bitmap) { - if (bitmap == null) { - return false; - } - return isIconSizedSmall(bitmap) || isIconSizedRegular(bitmap); - } - - /** - * Returns the bitmap from the byte array. Returns null if cannot decode or not in correct size. - */ - @Nullable - public static Bitmap getIcon(byte[] imageData, int size) { - try { - Bitmap icon = - BitmapFactory.decodeByteArray(imageData, /* offset= */ 0, size); - if (IconUtils.isIconSizeCorrect(icon)) { - // Do not add background for Half Sheet. - return icon; - } - } catch (OutOfMemoryError e) { - Log.w(TAG, "getIcon: Failed to decode icon, returning null.", e); - } - return null; - } -} - diff --git a/nearby/service/Android.bp b/nearby/service/Android.bp index d860048233eec1244d3fc76c2317747e1b2ff272..46309026b6de1c2dc07242a420cca53f614bffcd 100644 --- a/nearby/service/Android.bp +++ b/nearby/service/Android.bp @@ -24,40 +24,6 @@ filegroup { ], } -// Common lib for nearby end-to-end testing. -java_library { - name: "nearby-common-lib", - srcs: [ - "java/com/android/server/nearby/common/bloomfilter/*.java", - "java/com/android/server/nearby/common/bluetooth/*.java", - "java/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryption.java", - "java/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryption.java", - "java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddress.java", - "java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuids.java", - "java/com/android/server/nearby/common/bluetooth/fastpair/Bytes.java", - "java/com/android/server/nearby/common/bluetooth/fastpair/Constants.java", - "java/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchange.java", - "java/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256.java", - "java/com/android/server/nearby/common/bluetooth/fastpair/Ltv.java", - "java/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoder.java", - "java/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoder.java", - "java/com/android/server/nearby/common/bluetooth/testability/**/*.java", - "java/com/android/server/nearby/common/bluetooth/gatt/*.java", - "java/com/android/server/nearby/common/bluetooth/util/*.java", - ], - libs: [ - "androidx.annotation_annotation", - "androidx.core_core", - "error_prone_annotations", - "framework-bluetooth", - "guava", - ], - sdk_version: "module_current", - visibility: [ - "//packages/modules/Connectivity/nearby/tests/multidevices/clients/test_support/fastpair_provider", - ], -} - // Main lib for nearby services. java_library { name: "service-nearby-pre-jarjar", @@ -73,13 +39,11 @@ java_library { "framework-configinfrastructure", "framework-connectivity-t.impl", "framework-statsd", - "HalfSheetUX", ], static_libs: [ "androidx.core_core", "guava", "libprotobuf-java-lite", - "fast-pair-lite-protos", "modules-utils-build", "modules-utils-handlerexecutor", "modules-utils-preconditions", @@ -88,7 +52,7 @@ java_library { ], sdk_version: "system_server_current", // This is included in service-connectivity which is 30+ - // TODO: allow APEXes to have service jars with higher min_sdk than the APEX + // TODO (b/293613362): allow APEXes to have service jars with higher min_sdk than the APEX // (service-connectivity is only used on 31+) and use 31 here min_sdk_version: "30", diff --git a/nearby/service/java/com/android/server/nearby/NearbyConfiguration.java b/nearby/service/java/com/android/server/nearby/NearbyConfiguration.java index 9b32d699bb0d60634960aac1af309eeccc480141..9ef905d90e34674090c5fe43a995cd49df5be589 100644 --- a/nearby/service/java/com/android/server/nearby/NearbyConfiguration.java +++ b/nearby/service/java/com/android/server/nearby/NearbyConfiguration.java @@ -54,6 +54,11 @@ public class NearbyConfiguration { public static final String NEARBY_REFACTOR_DISCOVERY_MANAGER = "nearby_refactor_discovery_manager"; + /** + * Flag to guard enable BLE during Nearby Service init time. + */ + public static final String NEARBY_ENABLE_BLE_IN_INIT = "nearby_enable_ble_in_init"; + private static final boolean IS_USER_BUILD = "user".equals(Build.TYPE); private final DeviceConfigListener mDeviceConfigListener = new DeviceConfigListener(); @@ -67,6 +72,8 @@ public class NearbyConfiguration { private boolean mSupportTestApp; @GuardedBy("mDeviceConfigLock") private boolean mRefactorDiscoveryManager; + @GuardedBy("mDeviceConfigLock") + private boolean mEnableBleInInit; public NearbyConfiguration() { mDeviceConfigListener.start(); @@ -131,6 +138,15 @@ public class NearbyConfiguration { } } + /** + * @return {@code true} if enableBLE() is called during NearbyService init time. + */ + public boolean enableBleInInit() { + synchronized (mDeviceConfigLock) { + return mEnableBleInInit; + } + } + private class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener { public void start() { DeviceConfig.addOnPropertiesChangedListener(getNamespace(), @@ -149,6 +165,8 @@ public class NearbyConfiguration { NEARBY_SUPPORT_TEST_APP, false /* defaultValue */); mRefactorDiscoveryManager = getDeviceConfigBoolean( NEARBY_REFACTOR_DISCOVERY_MANAGER, false /* defaultValue */); + mEnableBleInInit = getDeviceConfigBoolean( + NEARBY_ENABLE_BLE_IN_INIT, true /* defaultValue */); } } } diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java index 2ac2b002140bf0252e63b4126399dfdb78db93aa..3c183ec22c0532be286a28688c1fa97941615c46 100644 --- a/nearby/service/java/com/android/server/nearby/NearbyService.java +++ b/nearby/service/java/com/android/server/nearby/NearbyService.java @@ -18,7 +18,6 @@ package com.android.server.nearby; import static com.android.server.SystemService.PHASE_BOOT_COMPLETED; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; -import static com.android.server.SystemService.PHASE_THIRD_PARTY_APPS_CAN_START; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -41,15 +40,12 @@ import android.nearby.aidl.IOffloadCallback; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.nearby.common.locator.LocatorContextWrapper; -import com.android.server.nearby.fastpair.FastPairManager; import com.android.server.nearby.injector.Injector; import com.android.server.nearby.managers.BroadcastProviderManager; import com.android.server.nearby.managers.DiscoveryManager; import com.android.server.nearby.managers.DiscoveryProviderManager; import com.android.server.nearby.managers.DiscoveryProviderManagerLegacy; import com.android.server.nearby.presence.PresenceManager; -import com.android.server.nearby.provider.FastPairDataProvider; import com.android.server.nearby.util.identity.CallerIdentity; import com.android.server.nearby.util.permissions.BroadcastPermissions; import com.android.server.nearby.util.permissions.DiscoveryPermissions; @@ -61,7 +57,6 @@ public class NearbyService extends INearbyManager.Stub { public static final Boolean MANUAL_TEST = false; private final Context mContext; - private final FastPairManager mFastPairManager; private final PresenceManager mPresenceManager; private final NearbyConfiguration mNearbyConfiguration; private Injector mInjector; @@ -89,9 +84,7 @@ public class NearbyService extends INearbyManager.Stub { mContext = context; mInjector = new SystemInjector(context); mBroadcastProviderManager = new BroadcastProviderManager(context, mInjector); - final LocatorContextWrapper lcw = new LocatorContextWrapper(context, null); - mFastPairManager = new FastPairManager(lcw); - mPresenceManager = new PresenceManager(lcw); + mPresenceManager = new PresenceManager(context); mNearbyConfiguration = new NearbyConfiguration(); mDiscoveryProviderManager = mNearbyConfiguration.refactorDiscoveryManager() @@ -167,10 +160,6 @@ public class NearbyService extends INearbyManager.Stub { ((SystemInjector) mInjector).initializeAppOpsManager(); } break; - case PHASE_THIRD_PARTY_APPS_CAN_START: - // Ensures that a fast pair data provider exists which will work in direct boot. - FastPairDataProvider.init(mContext); - break; case PHASE_BOOT_COMPLETED: // mInjector needs to be initialized before mProviderManager. if (mInjector instanceof SystemInjector) { @@ -183,7 +172,6 @@ public class NearbyService extends INearbyManager.Stub { mContext.registerReceiver( mBluetoothReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); - mFastPairManager.initiate(); // Only enable for manual Presence test on device. if (MANUAL_TEST) { mPresenceManager.initiate(); diff --git a/nearby/service/java/com/android/server/nearby/common/ble/BleFilter.java b/nearby/service/java/com/android/server/nearby/common/ble/BleFilter.java deleted file mode 100644 index dc4e11e725b743f159d167da492b43ceda5fa973..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/ble/BleFilter.java +++ /dev/null @@ -1,753 +0,0 @@ -/* - * 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.server.nearby.common.ble; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.le.ScanFilter; -import android.os.Parcel; -import android.os.ParcelUuid; -import android.os.Parcelable; -import android.text.TextUtils; - -import androidx.annotation.Nullable; - -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.UUID; - -/** - * Criteria for filtering BLE devices. A {@link BleFilter} allows clients to restrict BLE devices to - * only those that are of interest to them. - * - * - *

Current filtering on the following fields are supported: - *

  • Service UUIDs which identify the bluetooth gatt services running on the device. - *
  • Name of remote Bluetooth LE device. - *
  • Mac address of the remote device. - *
  • Service data which is the data associated with a service. - *
  • Manufacturer specific data which is the data associated with a particular manufacturer. - * - * @see BleSighting - */ -public final class BleFilter implements Parcelable { - - @Nullable - private String mDeviceName; - - @Nullable - private String mDeviceAddress; - - @Nullable - private ParcelUuid mServiceUuid; - - @Nullable - private ParcelUuid mServiceUuidMask; - - @Nullable - private ParcelUuid mServiceDataUuid; - - @Nullable - private byte[] mServiceData; - - @Nullable - private byte[] mServiceDataMask; - - private int mManufacturerId; - - @Nullable - private byte[] mManufacturerData; - - @Nullable - private byte[] mManufacturerDataMask; - - @Override - public int describeContents() { - return 0; - } - - BleFilter() { - } - - BleFilter( - @Nullable String deviceName, - @Nullable String deviceAddress, - @Nullable ParcelUuid serviceUuid, - @Nullable ParcelUuid serviceUuidMask, - @Nullable ParcelUuid serviceDataUuid, - @Nullable byte[] serviceData, - @Nullable byte[] serviceDataMask, - int manufacturerId, - @Nullable byte[] manufacturerData, - @Nullable byte[] manufacturerDataMask) { - this.mDeviceName = deviceName; - this.mDeviceAddress = deviceAddress; - this.mServiceUuid = serviceUuid; - this.mServiceUuidMask = serviceUuidMask; - this.mServiceDataUuid = serviceDataUuid; - this.mServiceData = serviceData; - this.mServiceDataMask = serviceDataMask; - this.mManufacturerId = manufacturerId; - this.mManufacturerData = manufacturerData; - this.mManufacturerDataMask = manufacturerDataMask; - } - - public static final Parcelable.Creator CREATOR = new Creator() { - @Override - public BleFilter createFromParcel(Parcel source) { - BleFilter nBleFilter = new BleFilter(); - nBleFilter.mDeviceName = source.readString(); - nBleFilter.mDeviceAddress = source.readString(); - nBleFilter.mManufacturerId = source.readInt(); - nBleFilter.mManufacturerData = source.marshall(); - nBleFilter.mManufacturerDataMask = source.marshall(); - nBleFilter.mServiceDataUuid = source.readParcelable(null); - nBleFilter.mServiceData = source.marshall(); - nBleFilter.mServiceDataMask = source.marshall(); - nBleFilter.mServiceUuid = source.readParcelable(null); - nBleFilter.mServiceUuidMask = source.readParcelable(null); - return nBleFilter; - } - - @Override - public BleFilter[] newArray(int size) { - return new BleFilter[size]; - } - }; - - - /** Returns the filter set on the device name field of Bluetooth advertisement data. */ - @Nullable - public String getDeviceName() { - return mDeviceName; - } - - /** Returns the filter set on the service uuid. */ - @Nullable - public ParcelUuid getServiceUuid() { - return mServiceUuid; - } - - /** Returns the mask for the service uuid. */ - @Nullable - public ParcelUuid getServiceUuidMask() { - return mServiceUuidMask; - } - - /** Returns the filter set on the device address. */ - @Nullable - public String getDeviceAddress() { - return mDeviceAddress; - } - - /** Returns the filter set on the service data. */ - @Nullable - public byte[] getServiceData() { - return mServiceData; - } - - /** Returns the mask for the service data. */ - @Nullable - public byte[] getServiceDataMask() { - return mServiceDataMask; - } - - /** Returns the filter set on the service data uuid. */ - @Nullable - public ParcelUuid getServiceDataUuid() { - return mServiceDataUuid; - } - - /** Returns the manufacturer id. -1 if the manufacturer filter is not set. */ - public int getManufacturerId() { - return mManufacturerId; - } - - /** Returns the filter set on the manufacturer data. */ - @Nullable - public byte[] getManufacturerData() { - return mManufacturerData; - } - - /** Returns the mask for the manufacturer data. */ - @Nullable - public byte[] getManufacturerDataMask() { - return mManufacturerDataMask; - } - - /** - * Check if the filter matches a {@code BleSighting}. A BLE sighting is considered as a match if - * it matches all the field filters. - */ - public boolean matches(@Nullable BleSighting bleSighting) { - if (bleSighting == null) { - return false; - } - BluetoothDevice device = bleSighting.getDevice(); - // Device match. - if (mDeviceAddress != null && (device == null || !mDeviceAddress.equals( - device.getAddress()))) { - return false; - } - - BleRecord bleRecord = bleSighting.getBleRecord(); - - // Scan record is null but there exist filters on it. - if (bleRecord == null - && (mDeviceName != null - || mServiceUuid != null - || mManufacturerData != null - || mServiceData != null)) { - return false; - } - - // Local name match. - if (mDeviceName != null && !mDeviceName.equals(bleRecord.getDeviceName())) { - return false; - } - - // UUID match. - if (mServiceUuid != null - && !matchesServiceUuids(mServiceUuid, mServiceUuidMask, - bleRecord.getServiceUuids())) { - return false; - } - - // Service data match - if (mServiceDataUuid != null - && !matchesPartialData( - mServiceData, mServiceDataMask, bleRecord.getServiceData(mServiceDataUuid))) { - return false; - } - - // Manufacturer data match. - if (mManufacturerId >= 0 - && !matchesPartialData( - mManufacturerData, - mManufacturerDataMask, - bleRecord.getManufacturerSpecificData(mManufacturerId))) { - return false; - } - - // All filters match. - return true; - } - - /** - * Determines if the characteristics of this filter are a superset of the characteristics of the - * given filter. - */ - public boolean isSuperset(@Nullable BleFilter bleFilter) { - if (bleFilter == null) { - return false; - } - - if (equals(bleFilter)) { - return true; - } - - // Verify device address matches. - if (mDeviceAddress != null && !mDeviceAddress.equals(bleFilter.getDeviceAddress())) { - return false; - } - - // Verify device name matches. - if (mDeviceName != null && !mDeviceName.equals(bleFilter.getDeviceName())) { - return false; - } - - // Verify UUID is a superset. - if (mServiceUuid != null - && !serviceUuidIsSuperset( - mServiceUuid, - mServiceUuidMask, - bleFilter.getServiceUuid(), - bleFilter.getServiceUuidMask())) { - return false; - } - - // Verify service data is a superset. - if (mServiceDataUuid != null - && (!mServiceDataUuid.equals(bleFilter.getServiceDataUuid()) - || !partialDataIsSuperset( - mServiceData, - mServiceDataMask, - bleFilter.getServiceData(), - bleFilter.getServiceDataMask()))) { - return false; - } - - // Verify manufacturer data is a superset. - if (mManufacturerId >= 0 - && (mManufacturerId != bleFilter.getManufacturerId() - || !partialDataIsSuperset( - mManufacturerData, - mManufacturerDataMask, - bleFilter.getManufacturerData(), - bleFilter.getManufacturerDataMask()))) { - return false; - } - - return true; - } - - /** Determines if the first uuid and mask are a superset of the second uuid and mask. */ - private static boolean serviceUuidIsSuperset( - @Nullable ParcelUuid uuid1, - @Nullable ParcelUuid uuidMask1, - @Nullable ParcelUuid uuid2, - @Nullable ParcelUuid uuidMask2) { - // First uuid1 is null so it can match any service UUID. - if (uuid1 == null) { - return true; - } - - // uuid2 is a superset of uuid1, but not the other way around. - if (uuid2 == null) { - return false; - } - - // Without a mask, the uuids must match. - if (uuidMask1 == null) { - return uuid1.equals(uuid2); - } - - // Mask2 should be at least as specific as mask1. - if (uuidMask2 != null) { - long uuid1MostSig = uuidMask1.getUuid().getMostSignificantBits(); - long uuid1LeastSig = uuidMask1.getUuid().getLeastSignificantBits(); - long uuid2MostSig = uuidMask2.getUuid().getMostSignificantBits(); - long uuid2LeastSig = uuidMask2.getUuid().getLeastSignificantBits(); - if (((uuid1MostSig & uuid2MostSig) != uuid1MostSig) - || ((uuid1LeastSig & uuid2LeastSig) != uuid1LeastSig)) { - return false; - } - } - - if (!matchesServiceUuids(uuid1, uuidMask1, Arrays.asList(uuid2))) { - return false; - } - - return true; - } - - /** Determines if the first data and mask are the superset of the second data and mask. */ - private static boolean partialDataIsSuperset( - @Nullable byte[] data1, - @Nullable byte[] dataMask1, - @Nullable byte[] data2, - @Nullable byte[] dataMask2) { - if (Arrays.equals(data1, data2) && Arrays.equals(dataMask1, dataMask2)) { - return true; - } - - if (data1 == null) { - return true; - } - - if (data2 == null) { - return false; - } - - // Mask2 should be at least as specific as mask1. - if (dataMask1 != null && dataMask2 != null) { - for (int i = 0, j = 0; i < dataMask1.length && j < dataMask2.length; i++, j++) { - if ((dataMask1[i] & dataMask2[j]) != dataMask1[i]) { - return false; - } - } - } - - if (!matchesPartialData(data1, dataMask1, data2)) { - return false; - } - - return true; - } - - /** Check if the uuid pattern is contained in a list of parcel uuids. */ - private static boolean matchesServiceUuids( - @Nullable ParcelUuid uuid, @Nullable ParcelUuid parcelUuidMask, - List uuids) { - if (uuid == null) { - // No service uuid filter has been set, so there's a match. - return true; - } - - UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid(); - for (ParcelUuid parcelUuid : uuids) { - if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) { - return true; - } - } - return false; - } - - /** Check if the uuid pattern matches the particular service uuid. */ - private static boolean matchesServiceUuid(UUID uuid, @Nullable UUID mask, UUID data) { - if (mask == null) { - return uuid.equals(data); - } - if ((uuid.getLeastSignificantBits() & mask.getLeastSignificantBits()) - != (data.getLeastSignificantBits() & mask.getLeastSignificantBits())) { - return false; - } - return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits()) - == (data.getMostSignificantBits() & mask.getMostSignificantBits())); - } - - /** - * Check whether the data pattern matches the parsed data. Assumes that {@code data} and {@code - * dataMask} have the same length. - */ - /* package */ - static boolean matchesPartialData( - @Nullable byte[] data, @Nullable byte[] dataMask, @Nullable byte[] parsedData) { - if (data == null || parsedData == null || parsedData.length < data.length) { - return false; - } - if (dataMask == null) { - for (int i = 0; i < data.length; ++i) { - if (parsedData[i] != data[i]) { - return false; - } - } - return true; - } - for (int i = 0; i < data.length; ++i) { - if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) { - return false; - } - } - return true; - } - - @Override - public String toString() { - return "BleFilter [deviceName=" - + mDeviceName - + ", deviceAddress=" - + mDeviceAddress - + ", uuid=" - + mServiceUuid - + ", uuidMask=" - + mServiceUuidMask - + ", serviceDataUuid=" - + mServiceDataUuid - + ", serviceData=" - + Arrays.toString(mServiceData) - + ", serviceDataMask=" - + Arrays.toString(mServiceDataMask) - + ", manufacturerId=" - + mManufacturerId - + ", manufacturerData=" - + Arrays.toString(mManufacturerData) - + ", manufacturerDataMask=" - + Arrays.toString(mManufacturerDataMask) - + "]"; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeString(mDeviceName); - out.writeString(mDeviceAddress); - out.writeInt(mManufacturerId); - out.writeByteArray(mManufacturerData); - out.writeByteArray(mManufacturerDataMask); - out.writeParcelable(mServiceDataUuid, flags); - out.writeByteArray(mServiceData); - out.writeByteArray(mServiceDataMask); - out.writeParcelable(mServiceUuid, flags); - out.writeParcelable(mServiceUuidMask, flags); - } - - @Override - public int hashCode() { - return Objects.hash( - mDeviceName, - mDeviceAddress, - mManufacturerId, - Arrays.hashCode(mManufacturerData), - Arrays.hashCode(mManufacturerDataMask), - mServiceDataUuid, - Arrays.hashCode(mServiceData), - Arrays.hashCode(mServiceDataMask), - mServiceUuid, - mServiceUuidMask); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - BleFilter other = (BleFilter) obj; - return equal(mDeviceName, other.mDeviceName) - && equal(mDeviceAddress, other.mDeviceAddress) - && mManufacturerId == other.mManufacturerId - && Arrays.equals(mManufacturerData, other.mManufacturerData) - && Arrays.equals(mManufacturerDataMask, other.mManufacturerDataMask) - && equal(mServiceDataUuid, other.mServiceDataUuid) - && Arrays.equals(mServiceData, other.mServiceData) - && Arrays.equals(mServiceDataMask, other.mServiceDataMask) - && equal(mServiceUuid, other.mServiceUuid) - && equal(mServiceUuidMask, other.mServiceUuidMask); - } - - /** Builder class for {@link BleFilter}. */ - public static final class Builder { - - private String mDeviceName; - private String mDeviceAddress; - - @Nullable - private ParcelUuid mServiceUuid; - @Nullable - private ParcelUuid mUuidMask; - - private ParcelUuid mServiceDataUuid; - @Nullable - private byte[] mServiceData; - @Nullable - private byte[] mServiceDataMask; - - private int mManufacturerId = -1; - private byte[] mManufacturerData; - @Nullable - private byte[] mManufacturerDataMask; - - /** Set filter on device name. */ - public Builder setDeviceName(String deviceName) { - this.mDeviceName = deviceName; - return this; - } - - /** - * Set filter on device address. - * - * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the - * format of "01:02:03:AB:CD:EF". The device address can be validated - * using {@link - * BluetoothAdapter#checkBluetoothAddress}. - * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. - */ - public Builder setDeviceAddress(String deviceAddress) { - if (!BluetoothAdapter.checkBluetoothAddress(deviceAddress)) { - throw new IllegalArgumentException("invalid device address " + deviceAddress); - } - this.mDeviceAddress = deviceAddress; - return this; - } - - /** Set filter on service uuid. */ - public Builder setServiceUuid(@Nullable ParcelUuid serviceUuid) { - this.mServiceUuid = serviceUuid; - mUuidMask = null; // clear uuid mask - return this; - } - - /** - * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the {@code - * serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the bit in - * {@code serviceUuid}, and 0 to ignore that bit. - * - * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but {@code - * uuidMask} - * is not {@code null}. - */ - public Builder setServiceUuid(@Nullable ParcelUuid serviceUuid, - @Nullable ParcelUuid uuidMask) { - if (uuidMask != null && serviceUuid == null) { - throw new IllegalArgumentException("uuid is null while uuidMask is not null!"); - } - this.mServiceUuid = serviceUuid; - this.mUuidMask = uuidMask; - return this; - } - - /** - * Set filtering on service data. - */ - public Builder setServiceData(ParcelUuid serviceDataUuid, @Nullable byte[] serviceData) { - this.mServiceDataUuid = serviceDataUuid; - this.mServiceData = serviceData; - mServiceDataMask = null; // clear service data mask - return this; - } - - /** - * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to - * match - * the one in service data, otherwise set it to 0 to ignore that bit. - * - *

    The {@code serviceDataMask} must have the same length of the {@code serviceData}. - * - * @throws IllegalArgumentException If {@code serviceDataMask} is {@code null} while {@code - * serviceData} is not or {@code serviceDataMask} and - * {@code serviceData} has different - * length. - */ - public Builder setServiceData( - ParcelUuid serviceDataUuid, - @Nullable byte[] serviceData, - @Nullable byte[] serviceDataMask) { - if (serviceDataMask != null) { - if (serviceData == null) { - throw new IllegalArgumentException( - "serviceData is null while serviceDataMask is not null"); - } - // Since the serviceDataMask is a bit mask for serviceData, the lengths of the two - // byte array need to be the same. - if (serviceData.length != serviceDataMask.length) { - throw new IllegalArgumentException( - "size mismatch for service data and service data mask"); - } - } - this.mServiceDataUuid = serviceDataUuid; - this.mServiceData = serviceData; - this.mServiceDataMask = serviceDataMask; - return this; - } - - /** - * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id. - * - *

    Note the first two bytes of the {@code manufacturerData} is the manufacturerId. - * - * @throws IllegalArgumentException If the {@code manufacturerId} is invalid. - */ - public Builder setManufacturerData(int manufacturerId, @Nullable byte[] manufacturerData) { - return setManufacturerData(manufacturerId, manufacturerData, null /* mask */); - } - - /** - * Set filter on partial manufacture data. For any bit in the mask, set it to 1 if it needs - * to - * match the one in manufacturer data, otherwise set it to 0. - * - *

    The {@code manufacturerDataMask} must have the same length of {@code - * manufacturerData}. - * - * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or {@code - * manufacturerData} is null while {@code - * manufacturerDataMask} is not, or {@code - * manufacturerData} and {@code manufacturerDataMask} have - * different length. - */ - public Builder setManufacturerData( - int manufacturerId, - @Nullable byte[] manufacturerData, - @Nullable byte[] manufacturerDataMask) { - if (manufacturerData != null && manufacturerId < 0) { - throw new IllegalArgumentException("invalid manufacture id"); - } - if (manufacturerDataMask != null) { - if (manufacturerData == null) { - throw new IllegalArgumentException( - "manufacturerData is null while manufacturerDataMask is not null"); - } - // Since the manufacturerDataMask is a bit mask for manufacturerData, the lengths - // of the two byte array need to be the same. - if (manufacturerData.length != manufacturerDataMask.length) { - throw new IllegalArgumentException( - "size mismatch for manufacturerData and manufacturerDataMask"); - } - } - this.mManufacturerId = manufacturerId; - this.mManufacturerData = manufacturerData == null ? new byte[0] : manufacturerData; - this.mManufacturerDataMask = manufacturerDataMask; - return this; - } - - - /** - * Builds the filter. - * - * @throws IllegalArgumentException If the filter cannot be built. - */ - public BleFilter build() { - return new BleFilter( - mDeviceName, - mDeviceAddress, - mServiceUuid, - mUuidMask, - mServiceDataUuid, - mServiceData, - mServiceDataMask, - mManufacturerId, - mManufacturerData, - mManufacturerDataMask); - } - } - - /** - * Changes ble filter to os filter - */ - public ScanFilter toOsFilter() { - ScanFilter.Builder osFilterBuilder = new ScanFilter.Builder(); - if (!TextUtils.isEmpty(getDeviceAddress())) { - osFilterBuilder.setDeviceAddress(getDeviceAddress()); - } - if (!TextUtils.isEmpty(getDeviceName())) { - osFilterBuilder.setDeviceName(getDeviceName()); - } - - byte[] manufacturerData = getManufacturerData(); - if (getManufacturerId() != -1 && manufacturerData != null) { - byte[] manufacturerDataMask = getManufacturerDataMask(); - if (manufacturerDataMask != null) { - osFilterBuilder.setManufacturerData( - getManufacturerId(), manufacturerData, manufacturerDataMask); - } else { - osFilterBuilder.setManufacturerData(getManufacturerId(), manufacturerData); - } - } - - ParcelUuid serviceDataUuid = getServiceDataUuid(); - byte[] serviceData = getServiceData(); - if (serviceDataUuid != null && serviceData != null) { - byte[] serviceDataMask = getServiceDataMask(); - if (serviceDataMask != null) { - osFilterBuilder.setServiceData(serviceDataUuid, serviceData, serviceDataMask); - } else { - osFilterBuilder.setServiceData(serviceDataUuid, serviceData); - } - } - - ParcelUuid serviceUuid = getServiceUuid(); - if (serviceUuid != null) { - ParcelUuid serviceUuidMask = getServiceUuidMask(); - if (serviceUuidMask != null) { - osFilterBuilder.setServiceUuid(serviceUuid, serviceUuidMask); - } else { - osFilterBuilder.setServiceUuid(serviceUuid); - } - } - return osFilterBuilder.build(); - } - - /** - * equal() method for two possibly-null objects - */ - private static boolean equal(@Nullable Object obj1, @Nullable Object obj2) { - return obj1 == obj2 || (obj1 != null && obj1.equals(obj2)); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/ble/BleRecord.java b/nearby/service/java/com/android/server/nearby/common/ble/BleRecord.java deleted file mode 100644 index 103a27fe8036812154510a8c6bcc3ddc3ed46540..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/ble/BleRecord.java +++ /dev/null @@ -1,395 +0,0 @@ -/* - * 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.server.nearby.common.ble; - -import android.os.ParcelUuid; -import android.util.Log; -import android.util.SparseArray; - -import androidx.annotation.Nullable; - -import com.android.server.nearby.common.ble.util.StringUtils; - -import com.google.common.collect.ImmutableList; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -/** - * Represents a BLE record from Bluetooth LE scan. - */ -public final class BleRecord { - - // The following data type values are assigned by Bluetooth SIG. - // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18. - private static final int DATA_TYPE_FLAGS = 0x01; - private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02; - private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03; - private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04; - private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05; - private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06; - private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07; - private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08; - private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09; - private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A; - private static final int DATA_TYPE_SERVICE_DATA = 0x16; - private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; - - /** The base 128-bit UUID representation of a 16-bit UUID. */ - private static final ParcelUuid BASE_UUID = - ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB"); - /** Length of bytes for 16 bit UUID. */ - private static final int UUID_BYTES_16_BIT = 2; - /** Length of bytes for 32 bit UUID. */ - private static final int UUID_BYTES_32_BIT = 4; - /** Length of bytes for 128 bit UUID. */ - private static final int UUID_BYTES_128_BIT = 16; - - // Flags of the advertising data. - // -1 when the scan record is not valid. - private final int mAdvertiseFlags; - - private final ImmutableList mServiceUuids; - - // null when the scan record is not valid. - @Nullable - private final SparseArray mManufacturerSpecificData; - - // null when the scan record is not valid. - @Nullable - private final Map mServiceData; - - // Transmission power level(in dB). - // Integer.MIN_VALUE when the scan record is not valid. - private final int mTxPowerLevel; - - // Local name of the Bluetooth LE device. - // null when the scan record is not valid. - @Nullable - private final String mDeviceName; - - // Raw bytes of scan record. - // Never null, whether valid or not. - private final byte[] mBytes; - - // If the raw scan record byte[] cannot be parsed, all non-primitive args here other than the - // raw scan record byte[] and serviceUudis will be null. See parsefromBytes(). - private BleRecord( - List serviceUuids, - @Nullable SparseArray manufacturerData, - @Nullable Map serviceData, - int advertiseFlags, - int txPowerLevel, - @Nullable String deviceName, - byte[] bytes) { - this.mServiceUuids = ImmutableList.copyOf(serviceUuids); - mManufacturerSpecificData = manufacturerData; - this.mServiceData = serviceData; - this.mDeviceName = deviceName; - this.mAdvertiseFlags = advertiseFlags; - this.mTxPowerLevel = txPowerLevel; - this.mBytes = bytes; - } - - /** - * Returns a list of service UUIDs within the advertisement that are used to identify the - * bluetooth GATT services. - */ - public ImmutableList getServiceUuids() { - return mServiceUuids; - } - - /** - * Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific - * data. - */ - @Nullable - public SparseArray getManufacturerSpecificData() { - return mManufacturerSpecificData; - } - - /** - * Returns the manufacturer specific data associated with the manufacturer id. Returns {@code - * null} if the {@code manufacturerId} is not found. - */ - @Nullable - public byte[] getManufacturerSpecificData(int manufacturerId) { - if (mManufacturerSpecificData == null) { - return null; - } - return mManufacturerSpecificData.get(manufacturerId); - } - - /** Returns a map of service UUID and its corresponding service data. */ - @Nullable - public Map getServiceData() { - return mServiceData; - } - - /** - * Returns the service data byte array associated with the {@code serviceUuid}. Returns {@code - * null} if the {@code serviceDataUuid} is not found. - */ - @Nullable - public byte[] getServiceData(ParcelUuid serviceDataUuid) { - if (serviceDataUuid == null || mServiceData == null) { - return null; - } - return mServiceData.get(serviceDataUuid); - } - - /** - * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE} - * if - * the field is not set. This value can be used to calculate the path loss of a received packet - * using the following equation: - * - *

    pathloss = txPowerLevel - rssi - */ - public int getTxPowerLevel() { - return mTxPowerLevel; - } - - /** Returns the local name of the BLE device. The is a UTF-8 encoded string. */ - @Nullable - public String getDeviceName() { - return mDeviceName; - } - - /** Returns raw bytes of scan record. */ - public byte[] getBytes() { - return mBytes; - } - - /** - * Parse scan record bytes to {@link BleRecord}. - * - *

    The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18. - * - *

    All numerical multi-byte entities and values shall use little-endian byte - * order. - * - * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response. - */ - public static BleRecord parseFromBytes(byte[] scanRecord) { - int currentPos = 0; - int advertiseFlag = -1; - List serviceUuids = new ArrayList<>(); - String localName = null; - int txPowerLevel = Integer.MIN_VALUE; - - SparseArray manufacturerData = new SparseArray<>(); - Map serviceData = new HashMap<>(); - - try { - while (currentPos < scanRecord.length) { - // length is unsigned int. - int length = scanRecord[currentPos++] & 0xFF; - if (length == 0) { - break; - } - // Note the length includes the length of the field type itself. - int dataLength = length - 1; - // fieldType is unsigned int. - int fieldType = scanRecord[currentPos++] & 0xFF; - switch (fieldType) { - case DATA_TYPE_FLAGS: - advertiseFlag = scanRecord[currentPos] & 0xFF; - break; - case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL: - case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE: - parseServiceUuid(scanRecord, currentPos, dataLength, UUID_BYTES_16_BIT, - serviceUuids); - break; - case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL: - case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE: - parseServiceUuid(scanRecord, currentPos, dataLength, UUID_BYTES_32_BIT, - serviceUuids); - break; - case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL: - case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE: - parseServiceUuid(scanRecord, currentPos, dataLength, UUID_BYTES_128_BIT, - serviceUuids); - break; - case DATA_TYPE_LOCAL_NAME_SHORT: - case DATA_TYPE_LOCAL_NAME_COMPLETE: - localName = new String(extractBytes(scanRecord, currentPos, dataLength)); - break; - case DATA_TYPE_TX_POWER_LEVEL: - txPowerLevel = scanRecord[currentPos]; - break; - case DATA_TYPE_SERVICE_DATA: - // The first two bytes of the service data are service data UUID in little - // endian. The rest bytes are service data. - int serviceUuidLength = UUID_BYTES_16_BIT; - byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos, - serviceUuidLength); - ParcelUuid serviceDataUuid = parseUuidFrom(serviceDataUuidBytes); - byte[] serviceDataArray = - extractBytes( - scanRecord, currentPos + serviceUuidLength, - dataLength - serviceUuidLength); - serviceData.put(serviceDataUuid, serviceDataArray); - break; - case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA: - // The first two bytes of the manufacturer specific data are - // manufacturer ids in little endian. - int manufacturerId = - ((scanRecord[currentPos + 1] & 0xFF) << 8) + (scanRecord[currentPos] - & 0xFF); - byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2, - dataLength - 2); - manufacturerData.put(manufacturerId, manufacturerDataBytes); - break; - default: - // Just ignore, we don't handle such data type. - break; - } - currentPos += dataLength; - } - - return new BleRecord( - serviceUuids, - manufacturerData, - serviceData, - advertiseFlag, - txPowerLevel, - localName, - scanRecord); - } catch (Exception e) { - Log.w("BleRecord", "Unable to parse scan record: " + Arrays.toString(scanRecord), e); - // As the record is invalid, ignore all the parsed results for this packet - // and return an empty record with raw scanRecord bytes in results - // check at the top of this method does? Maybe we expect callers to use the - // scanRecord part in - // some fallback. But if that's the reason, it would seem we still can return null. - // They still - // have the raw scanRecord in hand, 'cause they passed it to us. It seems too easy for a - // caller to misuse this "empty" BleRecord (as in b/22693067). - return new BleRecord(ImmutableList.of(), null, null, -1, Integer.MIN_VALUE, null, - scanRecord); - } - } - - // Parse service UUIDs. - private static int parseServiceUuid( - byte[] scanRecord, - int currentPos, - int dataLength, - int uuidLength, - List serviceUuids) { - while (dataLength > 0) { - byte[] uuidBytes = extractBytes(scanRecord, currentPos, uuidLength); - serviceUuids.add(parseUuidFrom(uuidBytes)); - dataLength -= uuidLength; - currentPos += uuidLength; - } - return currentPos; - } - - // Helper method to extract bytes from byte array. - private static byte[] extractBytes(byte[] scanRecord, int start, int length) { - byte[] bytes = new byte[length]; - System.arraycopy(scanRecord, start, bytes, 0, length); - return bytes; - } - - @Override - public String toString() { - return "BleRecord [advertiseFlags=" - + mAdvertiseFlags - + ", serviceUuids=" - + mServiceUuids - + ", manufacturerSpecificData=" - + StringUtils.toString(mManufacturerSpecificData) - + ", serviceData=" - + StringUtils.toString(mServiceData) - + ", txPowerLevel=" - + mTxPowerLevel - + ", deviceName=" - + mDeviceName - + "]"; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj == this) { - return true; - } - if (!(obj instanceof BleRecord)) { - return false; - } - BleRecord record = (BleRecord) obj; - // BleRecord objects are built from bytes, so we only need that field. - return Arrays.equals(mBytes, record.mBytes); - } - - @Override - public int hashCode() { - // BleRecord objects are built from bytes, so we only need that field. - return Arrays.hashCode(mBytes); - } - - /** - * Parse UUID from bytes. The {@code uuidBytes} can represent a 16-bit, 32-bit or 128-bit UUID, - * but the returned UUID is always in 128-bit format. Note UUID is little endian in Bluetooth. - * - * @param uuidBytes Byte representation of uuid. - * @return {@link ParcelUuid} parsed from bytes. - * @throws IllegalArgumentException If the {@code uuidBytes} cannot be parsed. - */ - private static ParcelUuid parseUuidFrom(byte[] uuidBytes) { - if (uuidBytes == null) { - throw new IllegalArgumentException("uuidBytes cannot be null"); - } - int length = uuidBytes.length; - if (length != UUID_BYTES_16_BIT - && length != UUID_BYTES_32_BIT - && length != UUID_BYTES_128_BIT) { - throw new IllegalArgumentException("uuidBytes length invalid - " + length); - } - // Construct a 128 bit UUID. - if (length == UUID_BYTES_128_BIT) { - ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN); - long msb = buf.getLong(8); - long lsb = buf.getLong(0); - return new ParcelUuid(new UUID(msb, lsb)); - } - // For 16 bit and 32 bit UUID we need to convert them to 128 bit value. - // 128_bit_value = uuid * 2^96 + BASE_UUID - long shortUuid; - if (length == UUID_BYTES_16_BIT) { - shortUuid = uuidBytes[0] & 0xFF; - shortUuid += (uuidBytes[1] & 0xFF) << 8; - } else { - shortUuid = uuidBytes[0] & 0xFF; - shortUuid += (uuidBytes[1] & 0xFF) << 8; - shortUuid += (uuidBytes[2] & 0xFF) << 16; - shortUuid += (uuidBytes[3] & 0xFF) << 24; - } - long msb = BASE_UUID.getUuid().getMostSignificantBits() + (shortUuid << 32); - long lsb = BASE_UUID.getUuid().getLeastSignificantBits(); - return new ParcelUuid(new UUID(msb, lsb)); - } -} - diff --git a/nearby/service/java/com/android/server/nearby/common/ble/BleSighting.java b/nearby/service/java/com/android/server/nearby/common/ble/BleSighting.java deleted file mode 100644 index 71ec10cef1b27741e055db440a78db217e51f8e9..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/ble/BleSighting.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * 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.server.nearby.common.ble; - -import android.bluetooth.BluetoothDevice; -import android.bluetooth.le.ScanRecord; -import android.bluetooth.le.ScanResult; -import android.os.Build.VERSION_CODES; -import android.os.Parcel; -import android.os.Parcelable; - -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.annotation.VisibleForTesting; - -import java.util.Arrays; -import java.util.Objects; -import java.util.concurrent.TimeUnit; - -/** - * A sighting of a BLE device found in a Bluetooth LE scan. - */ - -public class BleSighting implements Parcelable { - - public static final Parcelable.Creator CREATOR = new Creator() { - @Override - public BleSighting createFromParcel(Parcel source) { - BleSighting nBleSighting = new BleSighting(source.readParcelable(null), - source.marshall(), source.readInt(), source.readLong()); - return null; - } - - @Override - public BleSighting[] newArray(int size) { - return new BleSighting[size]; - } - }; - - // Max and min rssi value which is from {@link android.bluetooth.le.ScanResult#getRssi()}. - @VisibleForTesting - public static final int MAX_RSSI_VALUE = 126; - @VisibleForTesting - public static final int MIN_RSSI_VALUE = -127; - - /** Remote bluetooth device. */ - private final BluetoothDevice mDevice; - - /** - * BLE record, including advertising data and response data. BleRecord is not parcelable, so - * this - * is created from bleRecordBytes. - */ - private final BleRecord mBleRecord; - - /** The bytes of a BLE record. */ - private final byte[] mBleRecordBytes; - - /** Received signal strength. */ - private final int mRssi; - - /** Nanos timestamp when the ble device was observed (epoch time). */ - private final long mTimestampEpochNanos; - - /** - * Constructor of a BLE sighting. - * - * @param device Remote bluetooth device that is found. - * @param bleRecordBytes The bytes that will create a BleRecord. - * @param rssi Received signal strength. - * @param timestampEpochNanos Nanos timestamp when the BLE device was observed (epoch time). - */ - public BleSighting(BluetoothDevice device, byte[] bleRecordBytes, int rssi, - long timestampEpochNanos) { - this.mDevice = device; - this.mBleRecordBytes = bleRecordBytes; - this.mRssi = rssi; - this.mTimestampEpochNanos = timestampEpochNanos; - mBleRecord = BleRecord.parseFromBytes(bleRecordBytes); - } - - @Override - public int describeContents() { - return 0; - } - - /** Returns the remote bluetooth device identified by the bluetooth device address. */ - public BluetoothDevice getDevice() { - return mDevice; - } - - /** Returns the BLE record, which is a combination of advertisement and scan response. */ - public BleRecord getBleRecord() { - return mBleRecord; - } - - /** Returns the bytes of the BLE record. */ - public byte[] getBleRecordBytes() { - return mBleRecordBytes; - } - - /** Returns the received signal strength in dBm. The valid range is [-127, 127]. */ - public int getRssi() { - return mRssi; - } - - /** - * Returns the received signal strength normalized with the offset specific to the given device. - * 3 is the rssi offset to calculate fast init distance. - *

    This method utilized the rssi offset maintained by Nearby Sharing. - * - * @return normalized rssi which is between [-127, 126] according to {@link - * android.bluetooth.le.ScanResult#getRssi()}. - */ - public int getNormalizedRSSI() { - int adjustedRssi = mRssi + 3; - if (adjustedRssi < MIN_RSSI_VALUE) { - return MIN_RSSI_VALUE; - } else if (adjustedRssi > MAX_RSSI_VALUE) { - return MAX_RSSI_VALUE; - } else { - return adjustedRssi; - } - } - - /** Returns timestamp in epoch time when the scan record was observed. */ - public long getTimestampNanos() { - return mTimestampEpochNanos; - } - - /** Returns timestamp in epoch time when the scan record was observed, in millis. */ - public long getTimestampMillis() { - return TimeUnit.NANOSECONDS.toMillis(mTimestampEpochNanos); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelable(mDevice, flags); - dest.writeByteArray(mBleRecordBytes); - dest.writeInt(mRssi); - dest.writeLong(mTimestampEpochNanos); - } - - @Override - public int hashCode() { - return Objects.hash(mDevice, mRssi, mTimestampEpochNanos, Arrays.hashCode(mBleRecordBytes)); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof BleSighting)) { - return false; - } - BleSighting other = (BleSighting) obj; - return Objects.equals(mDevice, other.mDevice) - && mRssi == other.mRssi - && Arrays.equals(mBleRecordBytes, other.mBleRecordBytes) - && mTimestampEpochNanos == other.mTimestampEpochNanos; - } - - @Override - public String toString() { - return "BleSighting{" - + "device=" - + mDevice - + ", bleRecord=" - + mBleRecord - + ", rssi=" - + mRssi - + ", timestampNanos=" - + mTimestampEpochNanos - + "}"; - } - - /** Creates {@link BleSighting} using the {@link ScanResult}. */ - @RequiresApi(api = VERSION_CODES.LOLLIPOP) - @Nullable - public static BleSighting createFromOsScanResult(ScanResult osResult) { - ScanRecord osScanRecord = osResult.getScanRecord(); - if (osScanRecord == null) { - return null; - } - - return new BleSighting( - osResult.getDevice(), - osScanRecord.getBytes(), - osResult.getRssi(), - // The timestamp from ScanResult is 'nanos since boot', Beacon lib will change it - // as 'nanos - // since epoch', but Nearby never reference this field, just pass it as 'nanos - // since boot'. - // ref to beacon/scan/impl/LBluetoothLeScannerCompat.fromOs for beacon design - // about how to - // convert nanos since boot to epoch. - osResult.getTimestampNanos()); - } -} - diff --git a/nearby/service/java/com/android/server/nearby/common/ble/decode/BeaconDecoder.java b/nearby/service/java/com/android/server/nearby/common/ble/decode/BeaconDecoder.java deleted file mode 100644 index 9e795ac7a1c26db224b7af4abb4f56160015744d..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/ble/decode/BeaconDecoder.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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.server.nearby.common.ble.decode; - -import androidx.annotation.Nullable; - -import com.android.server.nearby.common.ble.BleRecord; - -/** - * This class encapsulates the logic specific to each manufacturer for parsing formats for beacons, - * and presents a common API to access important ADV/EIR packet fields such as: - * - *

      - *
    • UUID (universally unique identifier), a value uniquely identifying a group of one or - * more beacons as belonging to an organization or of a certain type, up to 128 bits. - *
    • Instance a 32-bit unsigned integer that can be used to group related beacons that - * have the same UUID. - *
    • the mathematics of TX signal strength, used for proximity calculations. - *
    - * - * ...and others. - * - * @see BLE Glossary - * @see Bluetooth - * Data Types Specification - */ -public abstract class BeaconDecoder { - /** - * Returns true if the bleRecord corresponds to a beacon format that contains sufficient - * information to construct a BeaconId and contains the Tx power. - */ - public boolean supportsBeaconIdAndTxPower(@SuppressWarnings("unused") BleRecord bleRecord) { - return true; - } - - /** - * Returns true if this decoder supports returning TxPower via {@link - * #getCalibratedBeaconTxPower(BleRecord)}. - */ - public boolean supportsTxPower() { - return true; - } - - /** - * Reads the calibrated transmitted power at 1 meter of the beacon in dBm. This value is - * contained - * in the scan record, as set by the transmitting beacon. Suitable for use in computing path - * loss, - * distance, and related derived values. - * - * @param bleRecord the parsed payload contained in the beacon packet - * @return integer value of the calibrated Tx power in dBm or null if the bleRecord doesn't - * contain sufficient information to calculate the Tx power. - */ - @Nullable - public abstract Integer getCalibratedBeaconTxPower(BleRecord bleRecord); - - /** - * Extract telemetry information from the beacon. Byte 0 of the returned telemetry block should - * encode the telemetry format. - * - * @return telemetry block for this beacon, or null if no telemetry data is found in the scan - * record. - */ - @Nullable - public byte[] getTelemetry(@SuppressWarnings("unused") BleRecord bleRecord) { - return null; - } - - /** Returns the appropriate type for this scan record. */ - public abstract int getBeaconIdType(); - - /** - * Returns an array of bytes which uniquely identify this beacon, for beacons from any of the - * supported beacon types. This unique identifier is the indexing key for various internal - * services. Returns null if the bleRecord doesn't contain sufficient information to construct - * the - * ID. - */ - @Nullable - public abstract byte[] getBeaconIdBytes(BleRecord bleRecord); - - /** - * Returns the URL of the beacon. Returns null if the bleRecord doesn't contain a URL or - * contains - * a malformed URL. - */ - @Nullable - public String getUrl(BleRecord bleRecord) { - return null; - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/ble/decode/FastPairDecoder.java b/nearby/service/java/com/android/server/nearby/common/ble/decode/FastPairDecoder.java deleted file mode 100644 index c1ff9fd31e7c9c681adfecb241fcaab2d7f494a3..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/ble/decode/FastPairDecoder.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * 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.server.nearby.common.ble.decode; - -import android.bluetooth.le.ScanRecord; -import android.os.ParcelUuid; -import android.util.Log; -import android.util.SparseArray; - -import androidx.annotation.Nullable; - -import com.android.server.nearby.common.ble.BleFilter; -import com.android.server.nearby.common.ble.BleRecord; - -import java.util.Arrays; - -/** - * Parses Fast Pair information out of {@link BleRecord}s. - * - *

    There are 2 different packet formats that are supported, which is used can be determined by - * packet length: - * - *

    For 3-byte packets, the full packet is the model ID. - * - *

    For all other packets, the first byte is the header, followed by the model ID, followed by - * zero or more extra fields. Each field has its own header byte followed by the field value. The - * packet header is formatted as 0bVVVLLLLR (V = version, L = model ID length, R = reserved) and - * each extra field header is 0bLLLLTTTT (L = field length, T = field type). - * - * @see go/fast-pair-2-service-data - */ -public class FastPairDecoder extends BeaconDecoder { - - private static final int FIELD_TYPE_BLOOM_FILTER = 0; - private static final int FIELD_TYPE_BLOOM_FILTER_SALT = 1; - private static final int FIELD_TYPE_BLOOM_FILTER_NO_NOTIFICATION = 2; - private static final int FIELD_TYPE_BATTERY = 3; - private static final int FIELD_TYPE_BATTERY_NO_NOTIFICATION = 4; - public static final int FIELD_TYPE_CONNECTION_STATE = 5; - private static final int FIELD_TYPE_RANDOM_RESOLVABLE_DATA = 6; - - /** FE2C is the 16-bit Service UUID. The rest is the base UUID. See BluetoothUuid (hidden). */ - private static final ParcelUuid FAST_PAIR_SERVICE_PARCEL_UUID = - ParcelUuid.fromString("0000FE2C-0000-1000-8000-00805F9B34FB"); - - /** The filter you use to scan for Fast Pair BLE advertisements. */ - public static final BleFilter FILTER = - new BleFilter.Builder().setServiceData(FAST_PAIR_SERVICE_PARCEL_UUID, - new byte[0]).build(); - - // NOTE: Ensure that all bitmasks are always ints, not bytes so that bitshifting works correctly - // without needing worry about signing errors. - private static final int HEADER_VERSION_BITMASK = 0b11100000; - private static final int HEADER_LENGTH_BITMASK = 0b00011110; - private static final int HEADER_VERSION_OFFSET = 5; - private static final int HEADER_LENGTH_OFFSET = 1; - - private static final int EXTRA_FIELD_LENGTH_BITMASK = 0b11110000; - private static final int EXTRA_FIELD_TYPE_BITMASK = 0b00001111; - private static final int EXTRA_FIELD_LENGTH_OFFSET = 4; - private static final int EXTRA_FIELD_TYPE_OFFSET = 0; - - private static final int MIN_ID_LENGTH = 3; - private static final int MAX_ID_LENGTH = 14; - private static final int HEADER_INDEX = 0; - private static final int HEADER_LENGTH = 1; - private static final int FIELD_HEADER_LENGTH = 1; - - // Not using java.util.IllegalFormatException because it is unchecked. - private static class IllegalFormatException extends Exception { - private IllegalFormatException(String message) { - super(message); - } - } - - @Nullable - @Override - public Integer getCalibratedBeaconTxPower(BleRecord bleRecord) { - return null; - } - - // TODO(b/205320613) create beacon type - @Override - public int getBeaconIdType() { - return 1; - } - - /** Returns the Model ID from our service data, if present. */ - @Nullable - @Override - public byte[] getBeaconIdBytes(BleRecord bleRecord) { - return getModelId(bleRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID)); - } - - /** Returns the Model ID from our service data, if present. */ - @Nullable - public static byte[] getModelId(@Nullable byte[] serviceData) { - if (serviceData == null) { - return null; - } - - if (serviceData.length >= MIN_ID_LENGTH) { - if (serviceData.length == MIN_ID_LENGTH) { - // If the length == 3, all bytes are the ID. See flag docs for more about - // endianness. - return serviceData; - } else { - // Otherwise, the first byte is a header which contains the length of the - // big-endian model - // ID that follows. The model ID will be trimmed if it contains leading zeros. - int idIndex = 1; - int end = idIndex + getIdLength(serviceData); - while (serviceData[idIndex] == 0 && end - idIndex > MIN_ID_LENGTH) { - idIndex++; - } - return Arrays.copyOfRange(serviceData, idIndex, end); - } - } - return null; - } - - /** Gets the FastPair service data array if available, otherwise returns null. */ - @Nullable - public static byte[] getServiceDataArray(BleRecord bleRecord) { - return bleRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID); - } - - /** Gets the FastPair service data array if available, otherwise returns null. */ - @Nullable - public static byte[] getServiceDataArray(ScanRecord scanRecord) { - return scanRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID); - } - - /** Gets the bloom filter from the extra fields if available, otherwise returns null. */ - @Nullable - public static byte[] getBloomFilter(@Nullable byte[] serviceData) { - return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER); - } - - /** Gets the bloom filter salt from the extra fields if available, otherwise returns null. */ - @Nullable - public static byte[] getBloomFilterSalt(byte[] serviceData) { - return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER_SALT); - } - - /** - * Gets the suppress notification with bloom filter from the extra fields if available, - * otherwise - * returns null. - */ - @Nullable - public static byte[] getBloomFilterNoNotification(@Nullable byte[] serviceData) { - return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER_NO_NOTIFICATION); - } - - /** Gets the battery level from extra fields if available, otherwise return null. */ - @Nullable - public static byte[] getBatteryLevel(byte[] serviceData) { - return getExtraField(serviceData, FIELD_TYPE_BATTERY); - } - - /** - * Gets the suppress notification with battery level from extra fields if available, otherwise - * return null. - */ - @Nullable - public static byte[] getBatteryLevelNoNotification(byte[] serviceData) { - return getExtraField(serviceData, FIELD_TYPE_BATTERY_NO_NOTIFICATION); - } - - /** - * Gets the random resolvable data from extra fields if available, otherwise - * return null. - */ - @Nullable - public static byte[] getRandomResolvableData(byte[] serviceData) { - return getExtraField(serviceData, FIELD_TYPE_RANDOM_RESOLVABLE_DATA); - } - - @Nullable - private static byte[] getExtraField(@Nullable byte[] serviceData, int fieldId) { - if (serviceData == null || serviceData.length < HEADER_INDEX + HEADER_LENGTH) { - return null; - } - try { - return getExtraFields(serviceData).get(fieldId); - } catch (IllegalFormatException e) { - Log.v("FastPairDecode", "Extra fields incorrectly formatted."); - return null; - } - } - - /** Gets extra field data at the end of the packet, defined by the extra field header. */ - private static SparseArray getExtraFields(byte[] serviceData) - throws IllegalFormatException { - SparseArray extraFields = new SparseArray<>(); - if (getVersion(serviceData) != 0) { - return extraFields; - } - int headerIndex = getFirstExtraFieldHeaderIndex(serviceData); - while (headerIndex < serviceData.length) { - int length = getExtraFieldLength(serviceData, headerIndex); - int index = headerIndex + FIELD_HEADER_LENGTH; - int type = getExtraFieldType(serviceData, headerIndex); - int end = index + length; - if (extraFields.get(type) == null) { - if (end <= serviceData.length) { - extraFields.put(type, Arrays.copyOfRange(serviceData, index, end)); - } else { - throw new IllegalFormatException( - "Invalid length, " + end + " is longer than service data size " - + serviceData.length); - } - } - headerIndex = end; - } - return extraFields; - } - - /** Checks whether or not a valid ID is included in the service data packet. */ - public static boolean hasBeaconIdBytes(BleRecord bleRecord) { - byte[] serviceData = bleRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID); - return checkModelId(serviceData); - } - - /** Check whether byte array is FastPair model id or not. */ - public static boolean checkModelId(@Nullable byte[] scanResult) { - return scanResult != null - // The 3-byte format has no header byte (all bytes are the ID). - && (scanResult.length == MIN_ID_LENGTH - // Header byte exists. We support only format version 0. (A different version - // indicates - // a breaking change in the format.) - || (scanResult.length > MIN_ID_LENGTH - && getVersion(scanResult) == 0 - && isIdLengthValid(scanResult))); - } - - /** Checks whether or not bloom filter is included in the service data packet. */ - public static boolean hasBloomFilter(BleRecord bleRecord) { - return (getBloomFilter(getServiceDataArray(bleRecord)) != null - || getBloomFilterNoNotification(getServiceDataArray(bleRecord)) != null); - } - - /** Checks whether or not bloom filter is included in the service data packet. */ - public static boolean hasBloomFilter(ScanRecord scanRecord) { - return (getBloomFilter(getServiceDataArray(scanRecord)) != null - || getBloomFilterNoNotification(getServiceDataArray(scanRecord)) != null); - } - - private static int getVersion(byte[] serviceData) { - return serviceData.length == MIN_ID_LENGTH - ? 0 - : (serviceData[HEADER_INDEX] & HEADER_VERSION_BITMASK) >> HEADER_VERSION_OFFSET; - } - - private static int getIdLength(byte[] serviceData) { - return serviceData.length == MIN_ID_LENGTH - ? MIN_ID_LENGTH - : (serviceData[HEADER_INDEX] & HEADER_LENGTH_BITMASK) >> HEADER_LENGTH_OFFSET; - } - - private static int getFirstExtraFieldHeaderIndex(byte[] serviceData) { - return HEADER_INDEX + HEADER_LENGTH + getIdLength(serviceData); - } - - private static int getExtraFieldLength(byte[] serviceData, int extraFieldIndex) { - return (serviceData[extraFieldIndex] & EXTRA_FIELD_LENGTH_BITMASK) - >> EXTRA_FIELD_LENGTH_OFFSET; - } - - private static int getExtraFieldType(byte[] serviceData, int extraFieldIndex) { - return (serviceData[extraFieldIndex] & EXTRA_FIELD_TYPE_BITMASK) >> EXTRA_FIELD_TYPE_OFFSET; - } - - private static boolean isIdLengthValid(byte[] serviceData) { - int idLength = getIdLength(serviceData); - return MIN_ID_LENGTH <= idLength - && idLength <= MAX_ID_LENGTH - && idLength + HEADER_LENGTH <= serviceData.length; - } -} - diff --git a/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java b/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java deleted file mode 100644 index b4f46f853f93def61a555e8518bfd0b65c509e57..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.common.ble.testing; - -import com.android.server.nearby.util.ArrayUtils; -import com.android.server.nearby.util.Hex; - -/** - * Test class to provide example to unit test. - */ -public class FastPairTestData { - private static final byte[] FAST_PAIR_RECORD_BIG_ENDIAN = - Hex.stringToBytes("02011E020AF006162CFEAABBCC"); - - /** - * A Fast Pair frame, Note: The service UUID is FE2C, but in the - * packet it's 2CFE, since the core Bluetooth data types are little-endian. - * - *

    However, the model ID is big-endian (multi-byte values in our spec are now big-endian, aka - * network byte order). - * - * @see {http://go/fast-pair-service-data} - */ - public static byte[] getFastPairRecord() { - return FAST_PAIR_RECORD_BIG_ENDIAN; - } - - /** A Fast Pair frame, with a shared account key. */ - public static final byte[] FAST_PAIR_SHARED_ACCOUNT_KEY_RECORD = - Hex.stringToBytes("02011E020AF00C162CFE007011223344556677"); - - /** Model ID in {@link #getFastPairRecord()}. */ - public static final byte[] FAST_PAIR_MODEL_ID = Hex.stringToBytes("AABBCC"); - - /** An arbitrary BLE device address. */ - public static final String DEVICE_ADDRESS = "00:00:00:00:00:01"; - - /** Arbitrary RSSI (Received Signal Strength Indicator). */ - public static final int RSSI = -72; - - /** @see #getFastPairRecord() */ - public static byte[] newFastPairRecord(byte header, byte[] modelId) { - return newFastPairRecord( - modelId.length == 3 ? modelId : ArrayUtils.concatByteArrays(new byte[] {header}, - modelId)); - } - - /** @see #getFastPairRecord() */ - public static byte[] newFastPairRecord(byte[] serviceData) { - int length = /* length of type and service UUID = */ 3 + serviceData.length; - return Hex.stringToBytes( - String.format("02011E020AF0%02X162CFE%s", length, - Hex.bytesToStringUppercase(serviceData))); - } - - // This is an example extended inquiry response for a phone with PANU - // and Hands-free Audio Gateway - public static byte[] eir_1 = { - 0x06, // Length of this Data - 0x09, // <> - 'P', - 'h', - 'o', - 'n', - 'e', - 0x05, // Length of this Data - 0x03, // <> - 0x15, - 0x11, // PANU service class UUID - 0x1F, - 0x11, // Hands-free Audio Gateway service class UUID - 0x01, // Length of this data - 0x05, // <> - 0x11, // Length of this data - 0x07, // <> - 0x01, - 0x02, - 0x03, - 0x04, - 0x05, - 0x06, - 0x07, - 0x08, // Made up UUID - 0x11, - 0x12, - 0x13, - 0x14, - 0x15, - 0x16, - 0x17, - 0x18, // - 0x00 // End of Data (Not transmitted over the air - }; - - // This is an example of advertising data with AD types - public static byte[] adv_1 = { - 0x02, // Length of this Data - 0x01, // <> - 0x01, // LE Limited Discoverable Mode - 0x0A, // Length of this Data - 0x09, // <> - 'P', 'e', 'd', 'o', 'm', 'e', 't', 'e', 'r' - }; - - // This is an example of advertising data with positive TX Power - // Level. - public static byte[] adv_2 = { - 0x02, // Length of this Data - 0x0a, // <> - 127 // Level = 127 - }; - - // Example data including a service data block - public static byte[] sd1 = { - 0x02, // Length of this Data - 0x01, // <> - 0x04, // BR/EDR Not Supported. - 0x03, // Length of this Data - 0x02, // <> - 0x04, - 0x18, // TX Power Service UUID - 0x1e, // Length of this Data - (byte) 0x16, // <> - // Service UUID - (byte) 0xe0, - 0x00, - // gBeacon Header - 0x15, - // Running time ENCRYPT - (byte) 0xd2, - 0x77, - 0x01, - 0x00, - // Scan Freq ENCRYPT - 0x32, - 0x05, - // Time in slow mode - 0x00, - 0x00, - // Time in fast mode - 0x7f, - 0x17, - // Subset of UID - 0x56, - 0x00, - // ID Mask - (byte) 0xd4, - 0x7c, - 0x18, - // RFU (reserved) - 0x00, - // GUID = decimal 1297482358 - 0x76, - 0x02, - 0x56, - 0x4d, - 0x00, - // Ranging Payload Header - 0x24, - // MAC of scanning address - (byte) 0xa4, - (byte) 0xbb, - // NORM RX RSSI -67dBm - (byte) 0xb0, - // NORM TX POWER -77dBm, so actual TX POWER = -36dBm - (byte) 0xb3, - // Note based on the values aboves PATH LOSS = (-36) - (-67) = 31dBm - // Below zero padding added to test it is handled correctly - 0x00 - }; - - // An Eddystone UID frame. go/eddystone for more info - public static byte[] eddystone_header_and_uuid = { - // BLE Flags - (byte) 0x02, - (byte) 0x01, - (byte) 0x06, - // Service UUID - (byte) 0x03, - (byte) 0x03, - (byte) 0xaa, - (byte) 0xfe, - // Service data header - (byte) 0x17, - (byte) 0x16, - (byte) 0xaa, - (byte) 0xfe, - // Eddystone frame type - (byte) 0x00, - // Ranging data - (byte) 0xb3, - // Eddystone ID namespace - (byte) 0x0a, - (byte) 0x09, - (byte) 0x08, - (byte) 0x07, - (byte) 0x06, - (byte) 0x05, - (byte) 0x04, - (byte) 0x03, - (byte) 0x02, - (byte) 0x01, - // Eddystone ID instance - (byte) 0x16, - (byte) 0x15, - (byte) 0x14, - (byte) 0x13, - (byte) 0x12, - (byte) 0x11, - // RFU - (byte) 0x00, - (byte) 0x00 - }; -} diff --git a/nearby/service/java/com/android/server/nearby/common/ble/util/RangingUtils.java b/nearby/service/java/com/android/server/nearby/common/ble/util/RangingUtils.java deleted file mode 100644 index eec52ad14d50e81335142a840c24e248454e6354..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/ble/util/RangingUtils.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * 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.server.nearby.common.ble.util; - - -/** - * Ranging utilities embody the physics of converting RF path loss to distance. The free space path - * loss is proportional to the square of the distance from transmitter to receiver, and to the - * square of the frequency of the propagation signal. - */ -public final class RangingUtils { - private static final int MAX_RSSI_VALUE = 126; - private static final int MIN_RSSI_VALUE = -127; - - private RangingUtils() { - } - - /* This was original derived in {@link com.google.android.gms.beacon.util.RangingUtils} from - * Free-space_path_loss. - * Duplicated here for easy reference. - * - * c = speed of light (2.9979 x 10^8 m/s); - * f = frequency (Bluetooth center frequency is 2.44175GHz = 2.44175x10^9 Hz); - * l = wavelength (in meters); - * d = distance (from transmitter to receiver in meters); - * dB = decibels - * dBm = decibel milliwatts - * - * - * Free-space path loss (FSPL) is proportional to the square of the distance between the - * transmitter and the receiver, and also proportional to the square of the frequency of the - * radio signal. - * - * FSPL = (4 * pi * d / l)^2 = (4 * pi * d * f / c)^2 - * - * FSPL (dB) = 10 * log10((4 * pi * d * f / c)^2) - * = 20 * log10(4 * pi * d * f / c) - * = (20 * log10(d)) + (20 * log10(f)) + (20 * log10(4 * pi/c)) - * - * Calculating constants: - * - * FSPL_FREQ = 20 * log10(f) - * = 20 * log10(2.44175 * 10^9) - * = 187.75 - * - * FSPL_LIGHT = 20 * log10(4 * pi/c) - * = 20 * log10(4 * pi/(2.9979 * 10^8)) - * = 20 * log10(4 * pi/(2.9979 * 10^8)) - * = 20 * log10(41.9172441s * 10^-9) - * = -147.55 - * - * FSPL_DISTANCE_1M = 20 * log10(1) - * = 0 - * - * PATH_LOSS_AT_1M = FSPL_DISTANCE_1M + FSPL_FREQ + FSPL_LIGHT - * = 0 + 187.75 + (-147.55) - * = 40.20db [round to 41db] - * - * Note: Rounding up makes us "closer" and makes us more aggressive at showing notifications. - */ - private static final int RSSI_DROP_OFF_AT_1_M = 41; - - /** - * Convert target distance and txPower to a RSSI value using the Log-distance path loss model - * with Path Loss at 1m of 41db. - * - * @return RSSI expected at distanceInMeters with device broadcasting at txPower. - */ - public static int rssiFromTargetDistance(double distanceInMeters, int txPower) { - /* - * See - * Log-distance path loss model. - * - * PL = total path loss in db - * txPower = TxPower in dbm - * rssi = Received signal strength in dbm - * PL_0 = Path loss at reference distance d_0 {@link RSSI_DROP_OFF_AT_1_M} dbm - * d = length of path - * d_0 = reference distance (1 m) - * gamma = path loss exponent (2 in free space) - * - * Log-distance path loss (LDPL) formula: - * - * PL = txPower - rssi = PL_0 + 10 * gamma * log_10(d / d_0) - * txPower - rssi = RSSI_DROP_OFF_AT_1_M + 10 * 2 * log_10 - * (distanceInMeters / 1) - * - rssi = -txPower + RSSI_DROP_OFF_AT_1_M + 20 * log_10(distanceInMeters) - * rssi = txPower - RSSI_DROP_OFF_AT_1_M - 20 * log_10(distanceInMeters) - */ - txPower = adjustPower(txPower); - return distanceInMeters == 0 - ? txPower - : (int) Math.floor((txPower - RSSI_DROP_OFF_AT_1_M) - - 20 * Math.log10(distanceInMeters)); - } - - /** - * Convert RSSI and txPower to a distance value using the Log-distance path loss model with Path - * Loss at 1m of 41db. - * - * @return distance in meters with device broadcasting at txPower and given RSSI. - */ - public static double distanceFromRssiAndTxPower(int rssi, int txPower) { - /* - * See Log-distance - * path - * loss model. - * - * PL = total path loss in db - * txPower = TxPower in dbm - * rssi = Received signal strength in dbm - * PL_0 = Path loss at reference distance d_0 {@link RSSI_DROP_OFF_AT_1_M} dbm - * d = length of path - * d_0 = reference distance (1 m) - * gamma = path loss exponent (2 in free space) - * - * Log-distance path loss (LDPL) formula: - * - * PL = txPower - rssi = PL_0 + 10 * gamma * log_10(d / - * d_0) - * txPower - rssi = RSSI_DROP_OFF_AT_1_M + 10 * gamma * log_10(d / - * d_0) - * txPower - rssi - RSSI_DROP_OFF_AT_1_M = 10 * 2 * log_10 - * (distanceInMeters / 1) - * txPower - rssi - RSSI_DROP_OFF_AT_1_M = 20 * log_10(distanceInMeters / 1) - * (txPower - rssi - RSSI_DROP_OFF_AT_1_M) / 20 = log_10(distanceInMeters) - * 10 ^ ((txPower - rssi - RSSI_DROP_OFF_AT_1_M) / 20) = distanceInMeters - */ - txPower = adjustPower(txPower); - rssi = adjustPower(rssi); - return Math.pow(10, (txPower - rssi - RSSI_DROP_OFF_AT_1_M) / 20.0); - } - - /** - * Prevents the power from becoming too large or too small. - */ - private static int adjustPower(int power) { - if (power > MAX_RSSI_VALUE) { - return MAX_RSSI_VALUE; - } - if (power < MIN_RSSI_VALUE) { - return MIN_RSSI_VALUE; - } - return power; - } -} - diff --git a/nearby/service/java/com/android/server/nearby/common/ble/util/StringUtils.java b/nearby/service/java/com/android/server/nearby/common/ble/util/StringUtils.java deleted file mode 100644 index 4d90b6d554388456144fac2f67d55903fe7e5f20..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/ble/util/StringUtils.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.server.nearby.common.ble.util; - -import android.annotation.Nullable; -import android.util.SparseArray; - -import java.util.Arrays; -import java.util.Iterator; -import java.util.Map; - -/** Helper class for Bluetooth LE utils. */ -public final class StringUtils { - private StringUtils() { - } - - /** Returns a string composed from a {@link SparseArray}. */ - public static String toString(@Nullable SparseArray array) { - if (array == null) { - return "null"; - } - if (array.size() == 0) { - return "{}"; - } - StringBuilder buffer = new StringBuilder(); - buffer.append('{'); - for (int i = 0; i < array.size(); ++i) { - buffer.append(array.keyAt(i)).append("=").append(Arrays.toString(array.valueAt(i))); - } - buffer.append('}'); - return buffer.toString(); - } - - /** Returns a string composed from a {@link Map}. */ - public static String toString(@Nullable Map map) { - if (map == null) { - return "null"; - } - if (map.isEmpty()) { - return "{}"; - } - StringBuilder buffer = new StringBuilder(); - buffer.append('{'); - Iterator> it = map.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = it.next(); - Object key = entry.getKey(); - buffer.append(key).append("=").append(Arrays.toString(map.get(key))); - if (it.hasNext()) { - buffer.append(", "); - } - } - buffer.append('}'); - return buffer.toString(); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bloomfilter/BloomFilter.java b/nearby/service/java/com/android/server/nearby/common/bloomfilter/BloomFilter.java deleted file mode 100644 index 6d4275f94350441fef51fc3fc27b38dcbbf5aa39..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bloomfilter/BloomFilter.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.common.bloomfilter; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import com.google.common.primitives.UnsignedInts; - -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.BitSet; - -/** - * A bloom filter that gives access to the underlying BitSet. - */ -public class BloomFilter { - private static final Charset CHARSET = UTF_8; - - /** - * Receives a value and converts it into an array of ints that will be converted to indexes for - * the filter. - */ - public interface Hasher { - /** - * Generate hash value. - */ - int[] getHashes(byte[] value); - } - - // The backing data for this bloom filter. As additions are made, they're OR'd until it - // eventually reaches 0xFF. - private final BitSet mBits; - // The max length of bits. - private final int mBitLength; - // The hasher to use for converting a value into an array of hashes. - private final Hasher mHasher; - - public BloomFilter(byte[] bytes, Hasher hasher) { - this.mBits = BitSet.valueOf(bytes); - this.mBitLength = bytes.length * 8; - this.mHasher = hasher; - } - - /** - * Return the bloom filter check bit set as byte array. - */ - public byte[] asBytes() { - // BitSet.toByteArray() truncates all the unset bits after the last set bit (eg. [0,0,1,0] - // becomes [0,0,1]) so we re-add those bytes if needed with Arrays.copy(). - byte[] b = mBits.toByteArray(); - if (b.length == mBitLength / 8) { - return b; - } - return Arrays.copyOf(b, mBitLength / 8); - } - - /** - * Add string value to bloom filter hash. - */ - public void add(String s) { - add(s.getBytes(CHARSET)); - } - - /** - * Adds value to bloom filter hash. - */ - public void add(byte[] value) { - int[] hashes = mHasher.getHashes(value); - for (int hash : hashes) { - mBits.set(UnsignedInts.remainder(hash, mBitLength)); - } - } - - /** - * Check if the string format has collision. - */ - public boolean possiblyContains(String s) { - return possiblyContains(s.getBytes(CHARSET)); - } - - /** - * Checks if value after hash will have collision. - */ - public boolean possiblyContains(byte[] value) { - int[] hashes = mHasher.getHashes(value); - for (int hash : hashes) { - if (!mBits.get(UnsignedInts.remainder(hash, mBitLength))) { - return false; - } - } - return true; - } -} - diff --git a/nearby/service/java/com/android/server/nearby/common/bloomfilter/FastPairBloomFilterHasher.java b/nearby/service/java/com/android/server/nearby/common/bloomfilter/FastPairBloomFilterHasher.java deleted file mode 100644 index 0ccee97255b3aef61097e85f41a7344aa88bea7c..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bloomfilter/FastPairBloomFilterHasher.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.common.bloomfilter; - -import com.google.common.hash.Hashing; - -import java.nio.ByteBuffer; - -/** - * Hasher which hashes a value using SHA-256 and splits it into parts, each of which can be - * converted to an index. - */ -public class FastPairBloomFilterHasher implements BloomFilter.Hasher { - - private static final int NUM_INDEXES = 8; - - @Override - public int[] getHashes(byte[] value) { - byte[] hash = Hashing.sha256().hashBytes(value).asBytes(); - ByteBuffer buffer = ByteBuffer.wrap(hash); - int[] hashes = new int[NUM_INDEXES]; - for (int i = 0; i < NUM_INDEXES; i++) { - hashes[i] = buffer.getInt(); - } - return hashes; - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothConsts.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothConsts.java deleted file mode 100644 index 3a02b18fb8f800a056dd9ddbb748b4bd8fa50f1f..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothConsts.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth; - -import java.util.UUID; - -/** - * Bluetooth constants. - */ -public class BluetoothConsts { - - /** - * Default MTU when value is unknown. - */ - public static final int DEFAULT_MTU = 23; - - // The following random uuids are used to indicate that the device has dynamic services. - /** - * UUID of dynamic service. - */ - public static final UUID SERVICE_DYNAMIC_SERVICE = - UUID.fromString("00000100-0af3-11e5-a6c0-1697f925ec7b"); - - /** - * UUID of dynamic characteristic. - */ - public static final UUID SERVICE_DYNAMIC_CHARACTERISTIC = - UUID.fromString("00002A05-0af3-11e5-a6c0-1697f925ec7b"); -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothGattException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothGattException.java deleted file mode 100644 index 5ac4882fc93747cf64c5f7924a2b7b691b961431..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/BluetoothGattException.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth; - -/** - * Exception for Bluetooth GATT operations. - */ -public class BluetoothGattException extends BluetoothException { - private final int mErrorCode; - - /** Constructor. */ - public BluetoothGattException(String message, int errorCode) { - super(message); - mErrorCode = errorCode; - } - - /** Constructor. */ - public BluetoothGattException(String message, int errorCode, Throwable cause) { - super(message, cause); - mErrorCode = errorCode; - } - - /** Returns Gatt error code. */ - public int getGattErrorCode() { - return mErrorCode; - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/ReservedUuids.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/ReservedUuids.java deleted file mode 100644 index 249011a827e6f9dfa5ea281e692aed519bb952a0..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/ReservedUuids.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth; - -import java.util.UUID; - -/** - * Reserved UUIDS by BT SIG. - *

    - * See https://developer.bluetooth.org for more details. - */ -public class ReservedUuids { - /** UUIDs reserved for services. */ - public static class Services { - /** - * The Device Information Service exposes manufacturer and/or vendor info about a device. - *

    - * See reserved UUID org.bluetooth.service.device_information. - */ - public static final UUID DEVICE_INFORMATION = fromShortUuid((short) 0x180A); - - /** - * Generic attribute service. - *

    - * See reserved UUID org.bluetooth.service.generic_attribute. - */ - public static final UUID GENERIC_ATTRIBUTE = fromShortUuid((short) 0x1801); - } - - /** UUIDs reserved for characteristics. */ - public static class Characteristics { - /** - * The value of this characteristic is a UTF-8 string representing the firmware revision for - * the firmware within the device. - *

    - * See reserved UUID org.bluetooth.characteristic.firmware_revision_string. - */ - public static final UUID FIRMWARE_REVISION_STRING = fromShortUuid((short) 0x2A26); - - /** - * Service change characteristic. - *

    - * See reserved UUID org.bluetooth.characteristic.gatt.service_changed. - */ - public static final UUID SERVICE_CHANGE = fromShortUuid((short) 0x2A05); - } - - /** UUIDs reserved for descriptors. */ - public static class Descriptors { - /** - * This descriptor shall be persistent across connections for bonded devices. The Client - * Characteristic Configuration descriptor is unique for each client. A client may read and - * write this descriptor to determine and set the configuration for that client. - * Authentication and authorization may be required by the server to write this descriptor. - * The default value for the Client Characteristic Configuration descriptor is 0x00. Upon - * connection of non-binded clients, this descriptor is set to the default value. - *

    - * See reserved UUID org.bluetooth.descriptor.gatt.client_characteristic_configuration. - */ - public static final UUID CLIENT_CHARACTERISTIC_CONFIGURATION = - fromShortUuid((short) 0x2902); - } - - /** The base 128-bit UUID representation of a 16-bit UUID */ - public static final UUID BASE_16_BIT_UUID = - UUID.fromString("00000000-0000-1000-8000-00805F9B34FB"); - - /** Converts from short UUId to UUID. */ - public static UUID fromShortUuid(short shortUuid) { - return new UUID(((((long) shortUuid) << 32) & 0x0000FFFF00000000L) - | ReservedUuids.BASE_16_BIT_UUID.getMostSignificantBits(), - ReservedUuids.BASE_16_BIT_UUID.getLeastSignificantBits()); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGenerator.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGenerator.java deleted file mode 100644 index 28a9c33a4f822e0ddbc9515d289e1cfa7d4aecfd..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AccountKeyGenerator.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.generateKey; - -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AccountKeyCharacteristic; - -import java.security.NoSuchAlgorithmException; - -/** - * This is to generate account key with fast-pair style. - */ -public final class AccountKeyGenerator { - - // Generate a key where the first byte is always defined as the type, 0x04. This maintains 15 - // bytes of entropy in the key while also allowing providers to verify that they have received - // a properly formatted key and decrypted it correctly, minimizing the risk of replay attacks. - - /** - * Creates account key. - */ - public static byte[] createAccountKey() throws NoSuchAlgorithmException { - byte[] accountKey = generateKey(); - accountKey[0] = AccountKeyCharacteristic.TYPE; - return accountKey; - } - - private AccountKeyGenerator() { - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoder.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoder.java deleted file mode 100644 index c9ccfd503501b36e0787b66f47a3d6779ad9624d..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoder.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.NONCE_SIZE; - -import static com.google.common.primitives.Bytes.concat; - -import java.security.GeneralSecurityException; -import java.util.Arrays; - -/** - * Utilities for encoding/decoding the additional data packet and verifying both the data integrity - * and the authentication. - * - *

    Additional Data packet is: - * - *

      - *
    1. AdditionalData_Packet[0 - 7]: the first 8-byte of HMAC. - *
    2. AdditionalData_Packet[8 - var]: the encrypted message by AES-CTR, with 8-byte nonce - * appended to the front. - *
    - * - * See https://developers.google.com/nearby/fast-pair/spec#AdditionalData. - */ -public final class AdditionalDataEncoder { - - static final int EXTRACT_HMAC_SIZE = 8; - static final int MAX_LENGTH_OF_DATA = 64; - - /** - * Encodes the given data to additional data packet by the given secret. - */ - static byte[] encodeAdditionalDataPacket(byte[] secret, byte[] additionalData) - throws GeneralSecurityException { - if (secret == null || secret.length != AesCtrMultipleBlockEncryption.KEY_LENGTH) { - throw new GeneralSecurityException( - "Incorrect secret for encoding additional data packet, secret.length = " - + (secret == null ? "NULL" : secret.length)); - } - - if ((additionalData == null) - || (additionalData.length == 0) - || (additionalData.length > MAX_LENGTH_OF_DATA)) { - throw new GeneralSecurityException( - "Invalid data for encoding additional data packet, data = " - + (additionalData == null ? "NULL" : additionalData.length)); - } - - byte[] encryptedData = AesCtrMultipleBlockEncryption.encrypt(secret, additionalData); - byte[] extractedHmac = - Arrays.copyOf(HmacSha256.build(secret, encryptedData), EXTRACT_HMAC_SIZE); - - return concat(extractedHmac, encryptedData); - } - - /** - * Decodes additional data packet by the given secret. - * - * @param secret AES-128 key used in the encryption to decrypt data - * @param additionalDataPacket additional data packet which is encoded by the given secret - * @return the data byte array decoded from the given packet - * @throws GeneralSecurityException if the given key or additional data packet is invalid for - * decoding - */ - static byte[] decodeAdditionalDataPacket(byte[] secret, byte[] additionalDataPacket) - throws GeneralSecurityException { - if (secret == null || secret.length != AesCtrMultipleBlockEncryption.KEY_LENGTH) { - throw new GeneralSecurityException( - "Incorrect secret for decoding additional data packet, secret.length = " - + (secret == null ? "NULL" : secret.length)); - } - if (additionalDataPacket == null - || additionalDataPacket.length <= EXTRACT_HMAC_SIZE - || additionalDataPacket.length - > (MAX_LENGTH_OF_DATA + EXTRACT_HMAC_SIZE + NONCE_SIZE)) { - throw new GeneralSecurityException( - "Additional data packet size is incorrect, additionalDataPacket.length is " - + (additionalDataPacket == null ? "NULL" - : additionalDataPacket.length)); - } - - if (!verifyHmac(secret, additionalDataPacket)) { - throw new GeneralSecurityException( - "Verify HMAC failed, could be incorrect key or packet."); - } - byte[] encryptedData = - Arrays.copyOfRange( - additionalDataPacket, EXTRACT_HMAC_SIZE, additionalDataPacket.length); - return AesCtrMultipleBlockEncryption.decrypt(secret, encryptedData); - } - - // Computes the HMAC of the given key and additional data, and compares the first 8-byte of the - // HMAC result with the one from additional data packet. - // Must call constant-time comparison to prevent a possible timing attack, e.g. time the same - // MAC with all different first byte for a given ciphertext, the right one will take longer as - // it will fail on the second byte's verification. - private static boolean verifyHmac(byte[] key, byte[] additionalDataPacket) - throws GeneralSecurityException { - byte[] packetHmac = - Arrays.copyOfRange(additionalDataPacket, /* from= */ 0, EXTRACT_HMAC_SIZE); - byte[] encryptedData = - Arrays.copyOfRange( - additionalDataPacket, EXTRACT_HMAC_SIZE, additionalDataPacket.length); - byte[] computedHmac = Arrays.copyOf( - HmacSha256.build(key, encryptedData), EXTRACT_HMAC_SIZE); - - return HmacSha256.compareTwoHMACs(packetHmac, computedHmac); - } - - private AdditionalDataEncoder() { - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryption.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryption.java deleted file mode 100644 index 50a818b722f07e08634f98db301d2ad099827846..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AesCtrMultipleBlockEncryption.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static com.google.common.primitives.Bytes.concat; - -import androidx.annotation.VisibleForTesting; - -import java.security.GeneralSecurityException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Arrays; - -/** - * AES-CTR utilities used for encrypting and decrypting Fast Pair packets that contain multiple - * blocks. Encrypts input data by: - * - *
      - *
    1. encryptedBlock[i] = clearBlock[i] ^ AES(counter), and - *
    2. concat(encryptedBlock[0], encryptedBlock[1],...) to create the encrypted result, where - *
    3. counter: the 16-byte input of AES. counter = iv + block_index. - *
    4. iv: extend 8-byte nonce to 16 bytes with zero padding. i.e. concat(0x0000000000000000, - * nonce). - *
    5. nonce: the cryptographically random 8 bytes, must never be reused with the same key. - *
    - */ -final class AesCtrMultipleBlockEncryption { - - /** Length for AES-128 key. */ - static final int KEY_LENGTH = AesEcbSingleBlockEncryption.KEY_LENGTH; - - @VisibleForTesting - static final int AES_BLOCK_LENGTH = AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH; - - /** Length of the nonce, a byte array of cryptographically random bytes. */ - static final int NONCE_SIZE = 8; - - private static final int IV_SIZE = AES_BLOCK_LENGTH; - private static final int MAX_NUMBER_OF_BLOCKS = 4; - - private AesCtrMultipleBlockEncryption() {} - - /** Generates a 16-byte AES key. */ - static byte[] generateKey() throws NoSuchAlgorithmException { - return AesEcbSingleBlockEncryption.generateKey(); - } - - /** - * Encrypts data using AES-CTR by the given secret. - * - * @param secret AES-128 key. - * @param data the plaintext to be encrypted. - * @return the encrypted data with the 8-byte nonce appended to the front. - */ - static byte[] encrypt(byte[] secret, byte[] data) throws GeneralSecurityException { - byte[] nonce = generateNonce(); - return concat(nonce, doAesCtr(secret, data, nonce)); - } - - /** - * Decrypts data using AES-CTR by the given secret and nonce. - * - * @param secret AES-128 key. - * @param data the first 8 bytes is the nonce, and the remaining is the encrypted data to be - * decrypted. - * @return the decrypted data. - */ - static byte[] decrypt(byte[] secret, byte[] data) throws GeneralSecurityException { - if (data == null || data.length <= NONCE_SIZE) { - throw new GeneralSecurityException( - "Incorrect data length " - + (data == null ? "NULL" : data.length) - + " to decrypt, the data should contain nonce."); - } - byte[] nonce = Arrays.copyOf(data, NONCE_SIZE); - byte[] encryptedData = Arrays.copyOfRange(data, NONCE_SIZE, data.length); - return doAesCtr(secret, encryptedData, nonce); - } - - /** - * Generates cryptographically random NONCE_SIZE bytes nonce. This nonce can be used only once. - * Always call this function to generate a new nonce before a new encryption. - */ - // Suppression for a warning for potentially insecure random numbers on Android 4.3 and older. - // Fast Pair service is only for Android 6.0+ devices. - static byte[] generateNonce() { - SecureRandom random = new SecureRandom(); - byte[] nonce = new byte[NONCE_SIZE]; - random.nextBytes(nonce); - - return nonce; - } - - // AES-CTR implementation. - @VisibleForTesting - static byte[] doAesCtr(byte[] secret, byte[] data, byte[] nonce) - throws GeneralSecurityException { - if (secret.length != KEY_LENGTH) { - throw new IllegalArgumentException( - "Incorrect key length for encryption, only supports 16-byte AES Key."); - } - if (nonce.length != NONCE_SIZE) { - throw new IllegalArgumentException( - "Incorrect nonce length for encryption, " - + "Fast Pair naming scheme only supports 8-byte nonce."); - } - - // Keeps the following operations on this byte[], returns it as the final AES-CTR result. - byte[] aesCtrResult = new byte[data.length]; - System.arraycopy(data, /*srcPos=*/ 0, aesCtrResult, /*destPos=*/ 0, data.length); - - // Initializes counter as IV. - byte[] counter = createIv(nonce); - // The length of the given data is permitted to non-align block size. - int numberOfBlocks = - (data.length / AES_BLOCK_LENGTH) + ((data.length % AES_BLOCK_LENGTH == 0) ? 0 : 1); - - if (numberOfBlocks > MAX_NUMBER_OF_BLOCKS) { - throw new IllegalArgumentException( - "Incorrect data size, Fast Pair naming scheme only supports 4 blocks."); - } - - for (int i = 0; i < numberOfBlocks; i++) { - // Performs the operation: encryptedBlock[i] = clearBlock[i] ^ AES(counter). - counter[0] = (byte) (i & 0xFF); - byte[] aesOfCounter = doAesSingleBlock(secret, counter); - int start = i * AES_BLOCK_LENGTH; - // The size of the last block of data may not be 16 bytes. If not, still do xor to the - // last byte of data. - int end = Math.min(start + AES_BLOCK_LENGTH, data.length); - for (int j = 0; start < end; j++, start++) { - aesCtrResult[start] ^= aesOfCounter[j]; - } - } - return aesCtrResult; - } - - private static byte[] doAesSingleBlock(byte[] secret, byte[] counter) - throws GeneralSecurityException { - return AesEcbSingleBlockEncryption.encrypt(secret, counter); - } - - /** Extends 8-byte nonce to 16 bytes with zero padding to create IV. */ - private static byte[] createIv(byte[] nonce) { - return concat(new byte[IV_SIZE - NONCE_SIZE], nonce); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryption.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryption.java deleted file mode 100644 index 547931e651069c83a509b73ec8177e91400973bd..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/AesEcbSingleBlockEncryption.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import android.annotation.SuppressLint; - -import java.security.GeneralSecurityException; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.spec.SecretKeySpec; - -/** - * Utilities used for encrypting and decrypting Fast Pair packets. - */ -// SuppressLint for ""ecb encryption mode should not be used". -// Reasons: -// 1. FastPair data is guaranteed to be only 1 AES block in size, ECB is secure. -// 2. In each case, the encrypted data is less than 16-bytes and is -// padded up to 16-bytes using random data to fill the rest of the byte array, -// so the plaintext will never be the same. -@SuppressLint("GetInstance") -public final class AesEcbSingleBlockEncryption { - - public static final int AES_BLOCK_LENGTH = 16; - public static final int KEY_LENGTH = 16; - - private AesEcbSingleBlockEncryption() { - } - - /** - * Generates a 16-byte AES key. - */ - public static byte[] generateKey() throws NoSuchAlgorithmException { - KeyGenerator generator = KeyGenerator.getInstance("AES"); - generator.init(KEY_LENGTH * 8); // Ensure a 16-byte key is always used. - return generator.generateKey().getEncoded(); - } - - /** - * Encrypts data with the provided secret. - */ - public static byte[] encrypt(byte[] secret, byte[] data) throws GeneralSecurityException { - return doEncryption(Cipher.ENCRYPT_MODE, secret, data); - } - - /** - * Decrypts data with the provided secret. - */ - public static byte[] decrypt(byte[] secret, byte[] data) throws GeneralSecurityException { - return doEncryption(Cipher.DECRYPT_MODE, secret, data); - } - - private static byte[] doEncryption(int mode, byte[] secret, byte[] data) - throws GeneralSecurityException { - if (data.length != AES_BLOCK_LENGTH) { - throw new IllegalArgumentException("This encrypter only supports 16-byte inputs."); - } - Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); - cipher.init(mode, new SecretKeySpec(secret, "AES")); - return cipher.doFinal(data); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddress.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddress.java deleted file mode 100644 index 9bb5a8601c3abff84ffb5bada12223a8988ec94b..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAddress.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static com.google.common.io.BaseEncoding.base16; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.content.Context; -import android.provider.Settings; - -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import com.google.common.base.Ascii; -import com.google.common.io.BaseEncoding; - -import java.util.Locale; - -/** Utils for dealing with Bluetooth addresses. */ -public final class BluetoothAddress { - - private static final BaseEncoding ENCODING = base16().upperCase().withSeparator(":", 2); - - @VisibleForTesting - static final String SECURE_SETTINGS_KEY_BLUETOOTH_ADDRESS = "bluetooth_address"; - - /** - * @return The string format used by e.g. {@link android.bluetooth.BluetoothDevice}. Upper case. - * Example: "AA:BB:CC:11:22:33" - */ - public static String encode(byte[] address) { - return ENCODING.encode(address); - } - - /** - * @param address The string format used by e.g. {@link android.bluetooth.BluetoothDevice}. - * Case-insensitive. Example: "AA:BB:CC:11:22:33" - */ - public static byte[] decode(String address) { - return ENCODING.decode(address.toUpperCase(Locale.US)); - } - - /** - * Get public bluetooth address. - * - * @param context a valid {@link Context} instance. - */ - public static @Nullable byte[] getPublicAddress(Context context) { - String publicAddress = - Settings.Secure.getString( - context.getContentResolver(), SECURE_SETTINGS_KEY_BLUETOOTH_ADDRESS); - return publicAddress != null && BluetoothAdapter.checkBluetoothAddress(publicAddress) - ? decode(publicAddress) - : null; - } - - /** - * Hides partial information of Bluetooth address. - * ex1: input is null, output should be empty string - * ex2: input is String(AA:BB:CC), output should be AA:BB:CC - * ex3: input is String(AA:BB:CC:DD:EE:FF), output should be XX:XX:XX:XX:EE:FF - * ex4: input is String(Aa:Bb:Cc:Dd:Ee:Ff), output should be XX:XX:XX:XX:EE:FF - * ex5: input is BluetoothDevice(AA:BB:CC:DD:EE:FF), output should be XX:XX:XX:XX:EE:FF - */ - public static String maskBluetoothAddress(@Nullable Object address) { - if (address == null) { - return ""; - } - - if (address instanceof String) { - String originalAddress = (String) address; - String upperCasedAddress = Ascii.toUpperCase(originalAddress); - if (!BluetoothAdapter.checkBluetoothAddress(upperCasedAddress)) { - return originalAddress; - } - return convert(upperCasedAddress); - } else if (address instanceof BluetoothDevice) { - return convert(((BluetoothDevice) address).getAddress()); - } - - // For others, returns toString(). - return address.toString(); - } - - private static String convert(String address) { - return "XX:XX:XX:XX:" + address.substring(12); - } - - private BluetoothAddress() {} -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairer.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairer.java deleted file mode 100644 index 07306c199f9bbcbae23c0cb0098bc913ccfeba6e..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairer.java +++ /dev/null @@ -1,774 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static android.bluetooth.BluetoothDevice.BOND_BONDED; -import static android.bluetooth.BluetoothDevice.BOND_BONDING; -import static android.bluetooth.BluetoothDevice.BOND_NONE; -import static android.bluetooth.BluetoothDevice.ERROR; -import static android.bluetooth.BluetoothProfile.A2DP; -import static android.bluetooth.BluetoothProfile.HEADSET; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; - -import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress; - -import static java.util.concurrent.Executors.newSingleThreadExecutor; - -import android.Manifest.permission; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothProfile; -import android.content.Context; -import android.content.Intent; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Parcelable; -import android.os.SystemClock; -import android.util.Log; - -import androidx.annotation.Nullable; -import androidx.annotation.UiThread; -import androidx.annotation.WorkerThread; -import androidx.core.content.ContextCompat; - -import com.android.server.nearby.common.bluetooth.BluetoothException; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.PasskeyCharacteristic; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.Profile; -import com.android.server.nearby.common.bluetooth.fastpair.TimingLogger.ScopedTiming; -import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection; -import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection.ChangeObserver; -import com.android.server.nearby.intdefs.FastPairEventIntDefs.ConnectErrorCode; -import com.android.server.nearby.intdefs.FastPairEventIntDefs.CreateBondErrorCode; -import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode; - -import com.google.common.base.Preconditions; -import com.google.common.util.concurrent.SettableFuture; - -import java.security.GeneralSecurityException; -import java.util.Arrays; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Pairs to Bluetooth audio devices. - */ -public class BluetoothAudioPairer { - - private static final String TAG = BluetoothAudioPairer.class.getSimpleName(); - - /** - * Hidden, see {@link BluetoothDevice}. - */ - // TODO(b/202549655): remove Hidden usage. - private static final String EXTRA_REASON = "android.bluetooth.device.extra.REASON"; - - /** - * Hidden, see {@link BluetoothDevice}. - */ - // TODO(b/202549655): remove Hidden usage. - private static final int PAIRING_VARIANT_CONSENT = 3; - - /** - * Hidden, see {@link BluetoothDevice}. - */ - // TODO(b/202549655): remove Hidden usage. - public static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4; - - private static final int DISCOVERY_STATE_CHANGE_TIMEOUT_MS = 3000; - - private final Context mContext; - private final Preferences mPreferences; - private final EventLoggerWrapper mEventLogger; - private final BluetoothDevice mDevice; - @Nullable - private final KeyBasedPairingInfo mKeyBasedPairingInfo; - @Nullable - private final PasskeyConfirmationHandler mPasskeyConfirmationHandler; - private final TimingLogger mTimingLogger; - - private static boolean sTestMode = false; - - static void enableTestMode() { - sTestMode = true; - } - - static class KeyBasedPairingInfo { - - private final byte[] mSecret; - private final GattConnectionManager mGattConnectionManager; - private final boolean mProviderInitiatesBonding; - - /** - * @param secret The secret negotiated during the initial BLE handshake for Key-based - * Pairing. See {@link FastPairConnection#handshake}. - * @param gattConnectionManager A manager that knows how to get and create Gatt connections - * to the remote device. - */ - KeyBasedPairingInfo( - byte[] secret, - GattConnectionManager gattConnectionManager, - boolean providerInitiatesBonding) { - this.mSecret = secret; - this.mGattConnectionManager = gattConnectionManager; - this.mProviderInitiatesBonding = providerInitiatesBonding; - } - } - - public BluetoothAudioPairer( - Context context, - BluetoothDevice device, - Preferences preferences, - EventLoggerWrapper eventLogger, - @Nullable KeyBasedPairingInfo keyBasedPairingInfo, - @Nullable PasskeyConfirmationHandler passkeyConfirmationHandler, - TimingLogger timingLogger) - throws PairingException { - this.mContext = context; - this.mDevice = device; - this.mPreferences = preferences; - this.mEventLogger = eventLogger; - this.mKeyBasedPairingInfo = keyBasedPairingInfo; - this.mPasskeyConfirmationHandler = passkeyConfirmationHandler; - this.mTimingLogger = timingLogger; - - // TODO(b/203455314): follow up with the following comments. - // The OS should give the user some UI to choose if they want to allow access, but there - // seems to be a bug where if we don't reject access, it's auto-granted in some cases - // (Plantronics headset gets contacts access when pairing with my Taimen via Bluetooth - // Settings, without me seeing any UI about it). b/64066631 - // - // If that OS bug doesn't get fixed, we can flip these flags to force-reject the - // permissions. - if (preferences.getRejectPhonebookAccess() && (sTestMode ? false : - !device.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED))) { - throw new PairingException("Failed to deny contacts (phonebook) access."); - } - if (preferences.getRejectMessageAccess() - && (sTestMode ? false : - !device.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED))) { - throw new PairingException("Failed to deny message access."); - } - if (preferences.getRejectSimAccess() - && (sTestMode ? false : - !device.setSimAccessPermission(BluetoothDevice.ACCESS_REJECTED))) { - throw new PairingException("Failed to deny SIM access."); - } - } - - boolean isPaired() { - return (sTestMode ? false : mDevice.getBondState() == BOND_BONDED); - } - - /** - * Unpairs from the device. Throws an exception if any error occurs. - */ - @WorkerThread - void unpair() - throws InterruptedException, ExecutionException, TimeoutException, PairingException { - int bondState = sTestMode ? BOND_NONE : mDevice.getBondState(); - try (UnbondedReceiver unbondedReceiver = new UnbondedReceiver(); - ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, - "Unpair for state: " + bondState)) { - // We'll only get a state change broadcast if we're actually unbonding (method returns - // true). - if (bondState == BluetoothDevice.BOND_BONDED) { - mEventLogger.setCurrentEvent(EventCode.REMOVE_BOND); - Log.i(TAG, "removeBond with " + maskBluetoothAddress(mDevice)); - mDevice.removeBond(); - unbondedReceiver.await( - mPreferences.getRemoveBondTimeoutSeconds(), TimeUnit.SECONDS); - } else if (bondState == BluetoothDevice.BOND_BONDING) { - mEventLogger.setCurrentEvent(EventCode.CANCEL_BOND); - Log.i(TAG, "cancelBondProcess with " + maskBluetoothAddress(mDevice)); - mDevice.cancelBondProcess(); - unbondedReceiver.await( - mPreferences.getRemoveBondTimeoutSeconds(), TimeUnit.SECONDS); - } else { - // The OS may have beaten us in a race, unbonding before we called the method. So if - // we're (somehow) in the desired state then we're happy, if not then bail. - if (bondState != BluetoothDevice.BOND_NONE) { - throw new PairingException("returned false, state=%s", bondState); - } - } - } - - // This seems to improve the probability that createBond will succeed after removeBond. - SystemClock.sleep(mPreferences.getRemoveBondSleepMillis()); - mEventLogger.logCurrentEventSucceeded(); - } - - /** - * Pairs with the device. Throws an exception if any error occurs. - */ - @WorkerThread - void pair() - throws InterruptedException, ExecutionException, TimeoutException, PairingException { - // Unpair first, because if we have a bond, but the other device has forgotten its bond, - // it can send us a pairing request that we're not ready for (which can pop up a dialog). - // Or, if we're in the middle of a (too-long) bonding attempt, we want to cancel. - unpair(); - - mEventLogger.setCurrentEvent(EventCode.CREATE_BOND); - try (BondedReceiver bondedReceiver = new BondedReceiver(); - ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Create bond")) { - // If the provider's initiating the bond, we do nothing but wait for broadcasts. - if (mKeyBasedPairingInfo == null || !mKeyBasedPairingInfo.mProviderInitiatesBonding) { - if (!sTestMode) { - Log.i(TAG, "createBond with " + maskBluetoothAddress(mDevice) + ", type=" - + mDevice.getType()); - if (mPreferences.getSpecifyCreateBondTransportType()) { - mDevice.createBond(mPreferences.getCreateBondTransportType()); - } else { - mDevice.createBond(); - } - } - } - try { - bondedReceiver.await(mPreferences.getCreateBondTimeoutSeconds(), TimeUnit.SECONDS); - } catch (TimeoutException e) { - Log.w(TAG, "bondedReceiver time out after " + mPreferences - .getCreateBondTimeoutSeconds() + " seconds"); - if (mPreferences.getIgnoreUuidTimeoutAfterBonded() && isPaired()) { - Log.w(TAG, "Created bond but never received UUIDs, attempting to continue."); - } else { - // Rethrow e to cause the pairing to fail and be retried if necessary. - throw e; - } - } - } - mEventLogger.logCurrentEventSucceeded(); - } - - /** - * Connects to the given profile. Throws an exception if any error occurs. - * - *

    If remote device clears the link key, the BOND_BONDED state would transit to BOND_BONDING - * (and go through the pairing process again) when directly connecting the profile. By enabling - * enablePairingBehavior, we provide both pairing and connecting behaviors at the same time. See - * b/145699390 for more details. - */ - // Suppression for possible null from ImmutableMap#get. See go/lsc-get-nullable - @SuppressWarnings("nullness:argument") - @WorkerThread - public void connect(short profileUuid, boolean enablePairingBehavior) - throws InterruptedException, ReflectionException, TimeoutException, ExecutionException, - ConnectException { - if (!mPreferences.isSupportedProfile(profileUuid)) { - throw new ConnectException( - ConnectErrorCode.UNSUPPORTED_PROFILE, "Unsupported profile=%s", profileUuid); - } - Profile profile = Constants.PROFILES.get(profileUuid); - Log.i(TAG, - "Connecting to profile=" + profile + " on device=" + maskBluetoothAddress(mDevice)); - try (BondedReceiver bondedReceiver = enablePairingBehavior ? new BondedReceiver() : null; - ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, - "Connect: " + profile)) { - connectByProfileProxy(profile); - } - } - - private void connectByProfileProxy(Profile profile) - throws ReflectionException, InterruptedException, ExecutionException, TimeoutException, - ConnectException { - try (BluetoothProfileWrapper autoClosingProxy = new BluetoothProfileWrapper(profile); - ConnectedReceiver connectedReceiver = new ConnectedReceiver(profile)) { - BluetoothProfile proxy = autoClosingProxy.mProxy; - - // Try to connect via reflection - Log.v(TAG, "Connect to proxy=" + proxy); - - if (!sTestMode) { - if (!(Boolean) Reflect.on(proxy).withMethod("connect", BluetoothDevice.class) - .get(mDevice)) { - // If we're already connecting, connect() may return false. :/ - Log.w(TAG, "connect returned false, expected if connecting, state=" - + proxy.getConnectionState(mDevice)); - } - } - - // If we're already connected, the OS may not send the connection state broadcast, so - // return immediately for that case. - if (!sTestMode) { - if (proxy.getConnectionState(mDevice) == BluetoothProfile.STATE_CONNECTED) { - Log.v(TAG, "connectByProfileProxy: already connected to device=" - + maskBluetoothAddress(mDevice)); - return; - } - } - - try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Wait connection")) { - // Wait for connecting to succeed or fail (via event or timeout). - connectedReceiver - .await(mPreferences.getCreateBondTimeoutSeconds(), TimeUnit.SECONDS); - } - } - } - - private class BluetoothProfileWrapper implements AutoCloseable { - - // incompatible types in assignment. - @SuppressWarnings("nullness:assignment") - private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - - private final Profile mProfile; - private final BluetoothProfile mProxy; - - /** - * Blocks until we get the proxy. Throws on error. - */ - private BluetoothProfileWrapper(Profile profile) - throws InterruptedException, ExecutionException, TimeoutException, - ConnectException { - this.mProfile = profile; - mProxy = getProfileProxy(profile); - } - - @Override - public void close() { - try (ScopedTiming scopedTiming = - new ScopedTiming(mTimingLogger, "Close profile: " + mProfile)) { - if (!sTestMode) { - mBluetoothAdapter.closeProfileProxy(mProfile.type, mProxy); - } - } - } - - private BluetoothProfile getProfileProxy(BluetoothProfileWrapper this, Profile profile) - throws InterruptedException, ExecutionException, TimeoutException, - ConnectException { - if (profile.type != A2DP && profile.type != HEADSET) { - throw new IllegalArgumentException("Unsupported profile type=" + profile.type); - } - - SettableFuture proxyFuture = SettableFuture.create(); - BluetoothProfile.ServiceListener listener = - new BluetoothProfile.ServiceListener() { - @UiThread - @Override - public void onServiceConnected(int profileType, BluetoothProfile proxy) { - proxyFuture.set(proxy); - } - - @Override - public void onServiceDisconnected(int profileType) { - Log.v(TAG, "proxy disconnected for profile=" + profile); - } - }; - - if (!mBluetoothAdapter.getProfileProxy(mContext, listener, profile.type)) { - throw new ConnectException( - ConnectErrorCode.GET_PROFILE_PROXY_FAILED, - "getProfileProxy failed immediately"); - } - - return proxyFuture.get(mPreferences.getProxyTimeoutSeconds(), TimeUnit.SECONDS); - } - } - - private class UnbondedReceiver extends DeviceIntentReceiver { - - private UnbondedReceiver() { - super(mContext, mPreferences, mDevice, BluetoothDevice.ACTION_BOND_STATE_CHANGED); - } - - @Override - protected void onReceiveDeviceIntent(Intent intent) throws Exception { - if (mDevice.getBondState() == BOND_NONE) { - try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, - "Close UnbondedReceiver")) { - close(); - } - } - } - } - - /** - * Receiver that closes after bonding has completed. - */ - class BondedReceiver extends DeviceIntentReceiver { - - private boolean mReceivedUuids = false; - private boolean mReceivedPasskey = false; - - private BondedReceiver() { - super( - mContext, - mPreferences, - mDevice, - BluetoothDevice.ACTION_PAIRING_REQUEST, - BluetoothDevice.ACTION_BOND_STATE_CHANGED, - BluetoothDevice.ACTION_UUID); - } - - // switching on a possibly-null value (intent.getAction()) - // incompatible types in argument. - @SuppressWarnings({"nullness:switching.nullable", "nullness:argument"}) - @Override - protected void onReceiveDeviceIntent(Intent intent) - throws PairingException, InterruptedException, ExecutionException, TimeoutException, - BluetoothException, GeneralSecurityException { - switch (intent.getAction()) { - case BluetoothDevice.ACTION_PAIRING_REQUEST: - int variant = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, ERROR); - int passkey = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, ERROR); - handlePairingRequest(variant, passkey); - break; - case BluetoothDevice.ACTION_BOND_STATE_CHANGED: - // Use the state in the intent, not device.getBondState(), to avoid a race where - // we log the wrong failure reason during a rapid transition. - int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, ERROR); - int reason = intent.getIntExtra(EXTRA_REASON, ERROR); - handleBondStateChanged(bondState, reason); - break; - case BluetoothDevice.ACTION_UUID: - // According to eisenbach@ and pavlin@, there's always a UUID broadcast when - // pairing (it can happen either before or after the transition to BONDED). - if (mPreferences.getWaitForUuidsAfterBonding()) { - Parcelable[] uuids = intent - .getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID); - handleUuids(uuids); - } - break; - default: - break; - } - } - - private void handlePairingRequest(int variant, int passkey) { - Log.i(TAG, "Pairing request, variant=" + variant + ", passkey=" + (passkey == ERROR - ? "(none)" : String.valueOf(passkey))); - if (mPreferences.getMoreEventLogForQuality()) { - mEventLogger.setCurrentEvent(EventCode.HANDLE_PAIRING_REQUEST); - } - - if (mPreferences.getSupportHidDevice() && variant == PAIRING_VARIANT_DISPLAY_PASSKEY) { - mReceivedPasskey = true; - extendAwaitSecond( - mPreferences.getHidCreateBondTimeoutSeconds() - - mPreferences.getCreateBondTimeoutSeconds()); - triggerDiscoverStateChange(); - if (mPreferences.getMoreEventLogForQuality()) { - mEventLogger.logCurrentEventSucceeded(); - } - return; - - } else { - // Prevent Bluetooth Settings from getting the pairing request and showing its own - // UI. - abortBroadcast(); - - if (variant == PAIRING_VARIANT_CONSENT - && mKeyBasedPairingInfo == null // Fast Pair 1.0 device - && mPreferences.getAcceptConsentForFastPairOne()) { - // Previously, if Bluetooth decided to use the Just Works variant (e.g. Fast - // Pair 1.0), we don't get a pairing request broadcast at all. - // However, after CVE-2019-2225, Bluetooth will decide to ask consent from - // users. Details: - // https://source.android.com/security/bulletin/2019-12-01#system - // Since we've certified the Fast Pair 1.0 devices, and user taps to pair it - // (with the device's image), we could help user to accept the consent. - if (!sTestMode) { - mDevice.setPairingConfirmation(true); - } - if (mPreferences.getMoreEventLogForQuality()) { - mEventLogger.logCurrentEventSucceeded(); - } - return; - } else if (variant != BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) { - if (!sTestMode) { - mDevice.setPairingConfirmation(false); - } - if (mPreferences.getMoreEventLogForQuality()) { - mEventLogger.logCurrentEventFailed( - new CreateBondException( - CreateBondErrorCode.INCORRECT_VARIANT, 0, - "Incorrect variant for FastPair")); - } - return; - } - mReceivedPasskey = true; - - if (mKeyBasedPairingInfo == null) { - if (mPreferences.getAcceptPasskey()) { - // Must be the simulator using FP 1.0 (no Key-based Pairing). Real - // headphones using FP 1.0 use Just Works instead (and maybe we should - // disable this flag for them). - if (!sTestMode) { - mDevice.setPairingConfirmation(true); - } - } - if (mPreferences.getMoreEventLogForQuality()) { - if (!sTestMode) { - mEventLogger.logCurrentEventSucceeded(); - } - } - return; - } - } - - if (mPreferences.getMoreEventLogForQuality()) { - mEventLogger.logCurrentEventSucceeded(); - } - - newSingleThreadExecutor() - .execute( - () -> { - try (ScopedTiming scopedTiming1 = - new ScopedTiming(mTimingLogger, "Exchange passkey")) { - mEventLogger.setCurrentEvent(EventCode.PASSKEY_EXCHANGE); - - // We already check above, but the static analyzer's not - // convinced without this. - Preconditions.checkNotNull(mKeyBasedPairingInfo); - BluetoothGattConnection connection = - mKeyBasedPairingInfo.mGattConnectionManager - .getConnection(); - UUID characteristicUuid = - PasskeyCharacteristic.getId(connection); - ChangeObserver remotePasskeyObserver = - connection.enableNotification(FastPairService.ID, - characteristicUuid); - Log.i(TAG, "Sending local passkey."); - byte[] encryptedData; - try (ScopedTiming scopedTiming2 = - new ScopedTiming(mTimingLogger, "Encrypt passkey")) { - encryptedData = - PasskeyCharacteristic.encrypt( - PasskeyCharacteristic.Type.SEEKER, - mKeyBasedPairingInfo.mSecret, passkey); - } - try (ScopedTiming scopedTiming3 = - new ScopedTiming(mTimingLogger, - "Send passkey to remote")) { - connection.writeCharacteristic( - FastPairService.ID, characteristicUuid, - encryptedData); - } - Log.i(TAG, "Waiting for remote passkey."); - byte[] encryptedRemotePasskey; - try (ScopedTiming scopedTiming4 = - new ScopedTiming(mTimingLogger, - "Wait for remote passkey")) { - encryptedRemotePasskey = - remotePasskeyObserver.waitForUpdate( - TimeUnit.SECONDS.toMillis(mPreferences - .getGattOperationTimeoutSeconds())); - } - int remotePasskey; - try (ScopedTiming scopedTiming5 = - new ScopedTiming(mTimingLogger, "Decrypt passkey")) { - remotePasskey = - PasskeyCharacteristic.decrypt( - PasskeyCharacteristic.Type.PROVIDER, - mKeyBasedPairingInfo.mSecret, - encryptedRemotePasskey); - } - - // We log success if we made it through with no exceptions. - // If the passkey was wrong, pairing will fail and we'll log - // BOND_BROKEN with reason = AUTH_FAILED. - mEventLogger.logCurrentEventSucceeded(); - - boolean isPasskeyCorrect = passkey == remotePasskey; - if (isPasskeyCorrect) { - Log.i(TAG, "Passkey correct."); - } else { - Log.e(TAG, "Passkey incorrect, local= " + passkey - + ", remote=" + remotePasskey); - } - - // Don't estimate the {@code ScopedTiming} because the - // passkey confirmation is done by UI. - if (isPasskeyCorrect - && mPreferences.getHandlePasskeyConfirmationByUi() - && mPasskeyConfirmationHandler != null) { - Log.i(TAG, "Callback the passkey to UI for confirmation."); - mPasskeyConfirmationHandler - .onPasskeyConfirmation(mDevice, passkey); - } else { - try (ScopedTiming scopedTiming6 = - new ScopedTiming( - mTimingLogger, "Confirm the pairing: " - + isPasskeyCorrect)) { - mDevice.setPairingConfirmation(isPasskeyCorrect); - } - } - } catch (BluetoothException - | GeneralSecurityException - | InterruptedException - | ExecutionException - | TimeoutException e) { - mEventLogger.logCurrentEventFailed(e); - closeWithError(e); - } - }); - } - - /** - * Workaround to let Settings popup a pairing dialog instead of notification. When pairing - * request intent passed to Settings, it'll check several conditions to decide that it - * should show a dialog or a notification. One of those conditions is to check if the device - * is in discovery mode recently, which can be fulfilled by calling {@link - * BluetoothAdapter#startDiscovery()}. This method aims to fulfill the condition, and block - * the pairing broadcast for at most - * {@link BluetoothAudioPairer#DISCOVERY_STATE_CHANGE_TIMEOUT_MS} - * to make sure that we fulfill the condition first and successful. - */ - // dereference of possibly-null reference bluetoothAdapter - @SuppressWarnings("nullness:dereference.of.nullable") - private void triggerDiscoverStateChange() { - BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - - if (bluetoothAdapter.isDiscovering()) { - return; - } - - HandlerThread backgroundThread = new HandlerThread("TriggerDiscoverStateChangeThread"); - backgroundThread.start(); - - AtomicBoolean result = new AtomicBoolean(false); - SimpleBroadcastReceiver receiver = - new SimpleBroadcastReceiver( - mContext, - mPreferences, - new Handler(backgroundThread.getLooper()), - BluetoothAdapter.ACTION_DISCOVERY_STARTED, - BluetoothAdapter.ACTION_DISCOVERY_FINISHED) { - - @Override - protected void onReceive(Intent intent) throws Exception { - result.set(true); - close(); - } - }; - - Log.i(TAG, "triggerDiscoverStateChange call startDiscovery."); - // Uses startDiscovery to trigger Settings show pairing dialog instead of notification. - if (!sTestMode) { - bluetoothAdapter.startDiscovery(); - bluetoothAdapter.cancelDiscovery(); - } - try { - receiver.await(DISCOVERY_STATE_CHANGE_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - Log.w(TAG, "triggerDiscoverStateChange failed!"); - } - - backgroundThread.quitSafely(); - try { - backgroundThread.join(); - } catch (InterruptedException e) { - Log.i(TAG, "triggerDiscoverStateChange backgroundThread.join meet exception!", e); - } - - if (result.get()) { - Log.i(TAG, "triggerDiscoverStateChange successful."); - } - } - - private void handleBondStateChanged(int bondState, int reason) - throws PairingException, InterruptedException, ExecutionException, - TimeoutException { - Log.i(TAG, "Bond state changed to " + bondState + ", reason=" + reason); - switch (bondState) { - case BOND_BONDED: - if (mKeyBasedPairingInfo != null && !mReceivedPasskey) { - // The device bonded with Just Works, although we did the Key-based Pairing - // GATT handshake and agreed on a pairing secret. It might be a Person In - // The Middle Attack! - try (ScopedTiming scopedTiming = - new ScopedTiming(mTimingLogger, - "Close BondedReceiver: POSSIBLE_MITM")) { - closeWithError( - new CreateBondException( - CreateBondErrorCode.POSSIBLE_MITM, - reason, - "Unexpectedly bonded without a passkey. It might be a " - + "Person In The Middle Attack! Unbonding!")); - } - unpair(); - } else if (!mPreferences.getWaitForUuidsAfterBonding() - || (mPreferences.getReceiveUuidsAndBondedEventBeforeClose() - && mReceivedUuids)) { - try (ScopedTiming scopedTiming = - new ScopedTiming(mTimingLogger, "Close BondedReceiver")) { - close(); - } - } - break; - case BOND_NONE: - throw new CreateBondException( - CreateBondErrorCode.BOND_BROKEN, reason, "Bond broken, reason=%d", - reason); - case BOND_BONDING: - default: - break; - } - } - - private void handleUuids(Parcelable[] uuids) { - Log.i(TAG, "Got UUIDs for " + maskBluetoothAddress(mDevice) + ": " - + Arrays.toString(uuids)); - mReceivedUuids = true; - if (!mPreferences.getReceiveUuidsAndBondedEventBeforeClose() || isPaired()) { - try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, - "Close BondedReceiver")) { - close(); - } - } - } - } - - private class ConnectedReceiver extends DeviceIntentReceiver { - - private ConnectedReceiver(Profile profile) throws ConnectException { - super(mContext, mPreferences, mDevice, profile.connectionStateAction); - } - - @Override - public void onReceiveDeviceIntent(Intent intent) throws PairingException { - int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, ERROR); - Log.i(TAG, "Connection state changed to " + state); - switch (state) { - case BluetoothAdapter.STATE_CONNECTED: - try (ScopedTiming scopedTiming = - new ScopedTiming(mTimingLogger, "Close ConnectedReceiver")) { - close(); - } - break; - case BluetoothAdapter.STATE_DISCONNECTED: - throw new ConnectException(ConnectErrorCode.DISCONNECTED, "Disconnected"); - case BluetoothAdapter.STATE_CONNECTING: - case BluetoothAdapter.STATE_DISCONNECTING: - default: - break; - } - } - } - - private boolean hasPermission(String permission) { - return ContextCompat.checkSelfPermission(mContext, permission) == PERMISSION_GRANTED; - } - - public BluetoothDevice getDevice() { - return mDevice; - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairer.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairer.java deleted file mode 100644 index 6c467d3cf04f7ef3b9d5ee3e0b3948d12c5c60e0..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairer.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static android.bluetooth.BluetoothDevice.BOND_BONDED; -import static android.bluetooth.BluetoothDevice.BOND_BONDING; -import static android.bluetooth.BluetoothDevice.BOND_NONE; -import static android.bluetooth.BluetoothDevice.ERROR; -import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE; - -import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress; - -import static java.util.concurrent.TimeUnit.SECONDS; - -import android.annotation.SuppressLint; -import android.bluetooth.BluetoothDevice; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -import androidx.annotation.WorkerThread; - -import com.google.common.base.Strings; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; - -/** - * Pairs to Bluetooth classic devices with passkey confirmation. - */ -// TODO(b/202524672): Add class unit test. -public class BluetoothClassicPairer { - - private static final String TAG = BluetoothClassicPairer.class.getSimpleName(); - /** - * Hidden, see {@link BluetoothDevice}. - */ - private static final String EXTRA_REASON = "android.bluetooth.device.extra.REASON"; - - private final Context mContext; - private final BluetoothDevice mDevice; - private final Preferences mPreferences; - private final PasskeyConfirmationHandler mPasskeyConfirmationHandler; - - public BluetoothClassicPairer( - Context context, - BluetoothDevice device, - Preferences preferences, - PasskeyConfirmationHandler passkeyConfirmationHandler) { - this.mContext = context; - this.mDevice = device; - this.mPreferences = preferences; - this.mPasskeyConfirmationHandler = passkeyConfirmationHandler; - } - - /** - * Pairs with the device. Throws a {@link PairingException} if any error occurs. - */ - @WorkerThread - public void pair() throws PairingException { - Log.i(TAG, "BluetoothClassicPairer, createBond with " + maskBluetoothAddress(mDevice) - + ", type=" + mDevice.getType()); - try (BondedReceiver bondedReceiver = new BondedReceiver()) { - if (mDevice.createBond()) { - bondedReceiver.await(mPreferences.getCreateBondTimeoutSeconds(), SECONDS); - } else { - throw new PairingException( - "BluetoothClassicPairer, createBond got immediate error"); - } - } catch (TimeoutException | InterruptedException | ExecutionException e) { - throw new PairingException("BluetoothClassicPairer, createBond failed", e); - } - } - - protected boolean isPaired() { - return mDevice.getBondState() == BOND_BONDED; - } - - /** - * Receiver that closes after bonding has completed. - */ - private class BondedReceiver extends DeviceIntentReceiver { - - private BondedReceiver() { - super( - mContext, - mPreferences, - mDevice, - BluetoothDevice.ACTION_PAIRING_REQUEST, - BluetoothDevice.ACTION_BOND_STATE_CHANGED); - } - - /** - * Called with ACTION_PAIRING_REQUEST and ACTION_BOND_STATE_CHANGED about the interesting - * device (see {@link DeviceIntentReceiver}). - * - *

    The ACTION_PAIRING_REQUEST intent provides the passkey which will be sent to the - * {@link PasskeyConfirmationHandler} for showing the UI, and the ACTION_BOND_STATE_CHANGED - * will provide the result of the bonding. - */ - @Override - protected void onReceiveDeviceIntent(Intent intent) { - String intentAction = intent.getAction(); - BluetoothDevice remoteDevice = intent.getParcelableExtra(EXTRA_DEVICE); - if (Strings.isNullOrEmpty(intentAction) - || remoteDevice == null - || !remoteDevice.getAddress().equals(mDevice.getAddress())) { - Log.w(TAG, - "BluetoothClassicPairer, receives " + intentAction - + " from unexpected device " + maskBluetoothAddress(remoteDevice)); - return; - } - switch (intentAction) { - case BluetoothDevice.ACTION_PAIRING_REQUEST: - handlePairingRequest( - remoteDevice, - intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, ERROR), - intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, ERROR)); - break; - case BluetoothDevice.ACTION_BOND_STATE_CHANGED: - handleBondStateChanged( - intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, ERROR), - intent.getIntExtra(EXTRA_REASON, ERROR)); - break; - default: - break; - } - } - - private void handlePairingRequest(BluetoothDevice device, int variant, int passkey) { - Log.i(TAG, - "BluetoothClassicPairer, pairing request, " + device + ", " + variant + ", " - + passkey); - // Prevent Bluetooth Settings from getting the pairing request and showing its own UI. - abortBroadcast(); - mPasskeyConfirmationHandler.onPasskeyConfirmation(device, passkey); - } - - private void handleBondStateChanged(int bondState, int reason) { - Log.i(TAG, - "BluetoothClassicPairer, bond state changed to " + bondState + ", reason=" - + reason); - switch (bondState) { - case BOND_BONDING: - // Don't close! - return; - case BOND_BONDED: - close(); - return; - case BOND_NONE: - default: - closeWithError( - new PairingException( - "BluetoothClassicPairer, createBond failed, reason:" + reason)); - } - } - } - - // Applies UsesPermission annotation will create circular dependency. - @SuppressLint("MissingPermission") - static void setPairingConfirmation(BluetoothDevice device, boolean confirm) { - Log.i(TAG, "BluetoothClassicPairer: setPairingConfirmation " + maskBluetoothAddress(device) - + ", confirm: " + confirm); - device.setPairingConfirmation(confirm); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuids.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuids.java deleted file mode 100644 index c5475a69ed09ce212cbd8ddbfe89e5bb45b84552..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BluetoothUuids.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import java.util.UUID; - -/** - * Utilities for dealing with UUIDs assigned by the Bluetooth SIG. Has a lot in common with - * com.android.BluetoothUuid, but that class is hidden. - */ -public class BluetoothUuids { - - /** - * The Base UUID is used for calculating 128-bit UUIDs from "short UUIDs" (16- and 32-bit). - * - * @see {https://www.bluetooth.com/specifications/assigned-numbers/service-discovery} - */ - private static final UUID BASE_UUID = UUID.fromString("00000000-0000-1000-8000-00805F9B34FB"); - - /** - * Fast Pair custom GATT characteristics 128-bit UUIDs base. - * - *

    Notes: The 16-bit value locates at the 3rd and 4th bytes. - * - * @see {go/fastpair-128bit-gatt} - */ - private static final UUID FAST_PAIR_BASE_UUID = - UUID.fromString("FE2C0000-8366-4814-8EB0-01DE32100BEA"); - - private static final int BIT_INDEX_OF_16_BIT_UUID = 32; - - private BluetoothUuids() {} - - /** - * Returns the 16-bit version of the UUID. If this is not a 16-bit UUID, throws - * IllegalArgumentException. - */ - public static short get16BitUuid(UUID uuid) { - if (!is16BitUuid(uuid)) { - throw new IllegalArgumentException("Not a 16-bit Bluetooth UUID: " + uuid); - } - return (short) (uuid.getMostSignificantBits() >> BIT_INDEX_OF_16_BIT_UUID); - } - - /** Checks whether the UUID is 16 bit */ - public static boolean is16BitUuid(UUID uuid) { - // See Service Discovery Protocol in the Bluetooth Core Specification. Bits at index 32-48 - // are the 16-bit UUID, and the rest must match the Base UUID. - return uuid.getLeastSignificantBits() == BASE_UUID.getLeastSignificantBits() - && (uuid.getMostSignificantBits() & 0xFFFF0000FFFFFFFFL) - == BASE_UUID.getMostSignificantBits(); - } - - /** Converts short UUID to 128 bit UUID */ - public static UUID to128BitUuid(short shortUuid) { - return new UUID( - ((shortUuid & 0xFFFFL) << BIT_INDEX_OF_16_BIT_UUID) - | BASE_UUID.getMostSignificantBits(), BASE_UUID.getLeastSignificantBits()); - } - - /** Transfers the 16-bit Fast Pair custom GATT characteristics to 128-bit. */ - public static UUID toFastPair128BitUuid(short shortUuid) { - return new UUID( - ((shortUuid & 0xFFFFL) << BIT_INDEX_OF_16_BIT_UUID) - | FAST_PAIR_BASE_UUID.getMostSignificantBits(), - FAST_PAIR_BASE_UUID.getLeastSignificantBits()); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BroadcastConstants.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BroadcastConstants.java deleted file mode 100644 index c26c6adf958075a49118c69100249456b29fc6c8..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/BroadcastConstants.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -/** - * Constants to share with the cloud syncing process. - */ -public class BroadcastConstants { - - // TODO: Set right value for AOSP. - /** Package name of the cloud syncing logic. */ - public static final String PACKAGE_NAME = "PACKAGE_NAME"; - /** Service name of the cloud syncing instance. */ - public static final String SERVICE_NAME = PACKAGE_NAME + ".SERVICE_NAME"; - private static final String PREFIX = PACKAGE_NAME + ".PREFIX_NAME."; - - /** Action when a fast pair device is added. */ - public static final String ACTION_FAST_PAIR_DEVICE_ADDED = - PREFIX + "ACTION_FAST_PAIR_DEVICE_ADDED"; - /** - * The BLE address of a device. BLE is used here instead of public because the caller of the - * library never knows what the device's public address is. - */ - public static final String EXTRA_ADDRESS = PREFIX + "BLE_ADDRESS"; - /** The public address of a device. */ - public static final String EXTRA_PUBLIC_ADDRESS = PREFIX + "PUBLIC_ADDRESS"; - /** Account key. */ - public static final String EXTRA_ACCOUNT_KEY = PREFIX + "ACCOUNT_KEY"; - /** Whether a paring is retroactive. */ - public static final String EXTRA_RETROACTIVE_PAIR = PREFIX + "EXTRA_RETROACTIVE_PAIR"; - - private BroadcastConstants() { - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Bytes.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Bytes.java deleted file mode 100644 index f6e77e69255897ae49caaa21334dddc941380eed..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Bytes.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.ShortBuffer; -import java.util.Arrays; - -/** Represents a block of bytes, with hashCode and equals. */ -public abstract class Bytes { - private static final char[] sHexDigits = "0123456789abcdef".toCharArray(); - private final byte[] mBytes; - - /** - * A logical value consisting of one or more bytes in the given order (little-endian, i.e. - * LSO...MSO, or big-endian, i.e. MSO...LSO). E.g. the Fast Pair Model ID is a 3-byte value, - * and a Bluetooth device address is a 6-byte value. - */ - public static class Value extends Bytes { - private final ByteOrder mByteOrder; - - /** - * Constructor. - */ - public Value(byte[] bytes, ByteOrder byteOrder) { - super(bytes); - this.mByteOrder = byteOrder; - } - - /** - * Gets bytes. - */ - public byte[] getBytes(ByteOrder byteOrder) { - return this.mByteOrder.equals(byteOrder) ? getBytes() : reverse(getBytes()); - } - - @VisibleForTesting - static byte[] reverse(byte[] bytes) { - byte[] reversedBytes = new byte[bytes.length]; - for (int i = 0; i < bytes.length; i++) { - reversedBytes[i] = bytes[bytes.length - i - 1]; - } - return reversedBytes; - } - } - - Bytes(byte[] bytes) { - mBytes = bytes; - } - - private static String toHexString(byte[] bytes) { - StringBuilder sb = new StringBuilder(2 * bytes.length); - for (byte b : bytes) { - sb.append(sHexDigits[(b >> 4) & 0xf]).append(sHexDigits[b & 0xf]); - } - return sb.toString(); - } - - /** Returns 2-byte values in the same order, each using the given byte order. */ - public static byte[] toBytes(ByteOrder byteOrder, short... shorts) { - ByteBuffer byteBuffer = ByteBuffer.allocate(shorts.length * 2).order(byteOrder); - for (short s : shorts) { - byteBuffer.putShort(s); - } - return byteBuffer.array(); - } - - /** Returns the shorts in the same order, each converted using the given byte order. */ - static short[] toShorts(ByteOrder byteOrder, byte[] bytes) { - ShortBuffer shortBuffer = ByteBuffer.wrap(bytes).order(byteOrder).asShortBuffer(); - short[] shorts = new short[shortBuffer.remaining()]; - shortBuffer.get(shorts); - return shorts; - } - - /** @return The bytes. */ - public byte[] getBytes() { - return mBytes; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Bytes)) { - return false; - } - Bytes that = (Bytes) o; - return Arrays.equals(mBytes, that.mBytes); - } - - @Override - public int hashCode() { - return Arrays.hashCode(mBytes); - } - - @Override - public String toString() { - return toHexString(mBytes); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ConnectException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ConnectException.java deleted file mode 100644 index 9c8d292b2c1fed08ebca315a40730d75dce24f42..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ConnectException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import com.android.server.nearby.intdefs.FastPairEventIntDefs.ConnectErrorCode; - - -/** Thrown when connecting to a bluetooth device fails. */ -public class ConnectException extends PairingException { - final @ConnectErrorCode int mErrorCode; - - ConnectException(@ConnectErrorCode int errorCode, String format, Object... objects) { - super(format, objects); - this.mErrorCode = errorCode; - } - - /** Returns error code. */ - public @ConnectErrorCode int getErrorCode() { - return mErrorCode; - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Constants.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Constants.java deleted file mode 100644 index cfecd2f5be5a69400b8a8aae547f009ffa8ee41a..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Constants.java +++ /dev/null @@ -1,703 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static android.bluetooth.BluetoothProfile.A2DP; -import static android.bluetooth.BluetoothProfile.HEADSET; - -import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid; -import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.toFastPair128BitUuid; - -import static com.google.common.primitives.Bytes.concat; - -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothHeadset; -import android.util.Log; - -import androidx.annotation.IntDef; - -import com.android.server.nearby.common.bluetooth.BluetoothException; -import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableMap; -import com.google.common.primitives.Shorts; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.nio.ByteBuffer; -import java.security.GeneralSecurityException; -import java.util.Random; -import java.util.UUID; - -/** - * Fast Pair and Transport Discovery Service constants. - * - *

    Unless otherwise specified, these numbers come from - * {https://www.bluetooth.com/specifications/gatt}. - */ -public final class Constants { - - /** A2DP sink service uuid. */ - public static final short A2DP_SINK_SERVICE_UUID = 0x110B; - - /** Headset service uuid. */ - public static final short HEADSET_SERVICE_UUID = 0x1108; - - /** Hands free sink service uuid. */ - public static final short HANDS_FREE_SERVICE_UUID = 0x111E; - - /** Bluetooth address length. */ - public static final int BLUETOOTH_ADDRESS_LENGTH = 6; - - private static final String TAG = Constants.class.getSimpleName(); - - /** - * Defined by https://developers.google.com/nearby/fast-pair/spec. - */ - public static final class FastPairService { - - /** Fast Pair service UUID. */ - public static final UUID ID = to128BitUuid((short) 0xFE2C); - - /** - * Characteristic to write verification bytes to during the key handshake. - */ - public static final class KeyBasedPairingCharacteristic { - - private static final short SHORT_UUID = 0x1234; - - /** - * Gets the new 128-bit UUID of this characteristic. - * - *

    Note: For GATT server only. GATT client should use {@link - * KeyBasedPairingCharacteristic#getId(BluetoothGattConnection)}. - */ - public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID); - - /** - * Gets the {@link UUID} of this characteristic. - * - *

    This method is designed for being backward compatible with old version of UUID - * therefore needs the {@link BluetoothGattConnection} parameter to check the supported - * status of the Fast Pair provider. - */ - public static UUID getId(BluetoothGattConnection gattConnection) { - return getSupportedUuid(gattConnection, SHORT_UUID); - } - - /** - * Constants related to the decrypted request written to this characteristic. - */ - public static final class Request { - - /** - * The size of this message. - */ - public static final int SIZE = 16; - - /** - * The index of this message for indicating the type byte. - */ - public static final int TYPE_INDEX = 0; - - /** - * The index of this message for indicating the flags byte. - */ - public static final int FLAGS_INDEX = 1; - - /** - * The index of this message for indicating the verification data start from. - */ - public static final int VERIFICATION_DATA_INDEX = 2; - - /** - * The length of verification data, it is Provider’s current BLE address or public - * address. - */ - public static final int VERIFICATION_DATA_LENGTH = BLUETOOTH_ADDRESS_LENGTH; - - /** - * The index of this message for indicating the seeker's public address start from. - */ - public static final int SEEKER_PUBLIC_ADDRESS_INDEX = 8; - - /** - * The index of this message for indicating event group. - */ - public static final int EVENT_GROUP_INDEX = 8; - - /** - * The index of this message for indicating event code. - */ - public static final int EVENT_CODE_INDEX = 9; - - /** - * The index of this message for indicating the length of additional data of the - * event. - */ - public static final int EVENT_ADDITIONAL_DATA_LENGTH_INDEX = 10; - - /** - * The index of this message for indicating the event additional data start from. - */ - public static final int EVENT_ADDITIONAL_DATA_INDEX = 11; - - /** - * The index of this message for indicating the additional data type used in the - * following Additional Data characteristic. - */ - public static final int ADDITIONAL_DATA_TYPE_INDEX = 10; - - /** - * The type of this message for Key-based Pairing Request. - */ - public static final byte TYPE_KEY_BASED_PAIRING_REQUEST = 0x00; - - /** - * The bit indicating that the Fast Pair device should temporarily become - * discoverable. - */ - public static final byte REQUEST_DISCOVERABLE = (byte) (1 << 7); - - /** - * The bit indicating that the requester (Seeker) has included their public address - * in bytes [7,12] of the request, and the Provider should initiate bonding to that - * address. - */ - public static final byte PROVIDER_INITIATES_BONDING = (byte) (1 << 6); - - /** - * The bit indicating that Seeker requests Provider shall return the existing name. - */ - public static final byte REQUEST_DEVICE_NAME = (byte) (1 << 5); - - /** - * The bit to request retroactive pairing. - */ - public static final byte REQUEST_RETROACTIVE_PAIR = (byte) (1 << 4); - - /** - * The type of this message for action over BLE. - */ - public static final byte TYPE_ACTION_OVER_BLE = 0x10; - - private Request() { - } - } - - /** - * Enumerates all flags of key-based pairing request. - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - value = { - KeyBasedPairingRequestFlag.REQUEST_DISCOVERABLE, - KeyBasedPairingRequestFlag.PROVIDER_INITIATES_BONDING, - KeyBasedPairingRequestFlag.REQUEST_DEVICE_NAME, - KeyBasedPairingRequestFlag.REQUEST_RETROACTIVE_PAIR, - }) - public @interface KeyBasedPairingRequestFlag { - /** - * The bit indicating that the Fast Pair device should temporarily become - * discoverable. - */ - int REQUEST_DISCOVERABLE = (byte) (1 << 7); - /** - * The bit indicating that the requester (Seeker) has included their public address - * in bytes [7,12] of the request, and the Provider should initiate bonding to that - * address. - */ - int PROVIDER_INITIATES_BONDING = (byte) (1 << 6); - /** - * The bit indicating that Seeker requests Provider shall return the existing name. - */ - int REQUEST_DEVICE_NAME = (byte) (1 << 5); - /** - * The bit indicating that the Seeker request retroactive pairing. - */ - int REQUEST_RETROACTIVE_PAIR = (byte) (1 << 4); - } - - /** - * Enumerates all flags of action over BLE request, see Fast Pair spec for details. - */ - @IntDef( - value = { - ActionOverBleFlag.DEVICE_ACTION, - ActionOverBleFlag.ADDITIONAL_DATA_CHARACTERISTIC, - }) - public @interface ActionOverBleFlag { - /** - * The bit indicating that the handshaking is for Device Action. - */ - int DEVICE_ACTION = (byte) (1 << 7); - /** - * The bit indicating that this handshake will be followed by Additional Data - * characteristic. - */ - int ADDITIONAL_DATA_CHARACTERISTIC = (byte) (1 << 6); - } - - - /** - * Constants related to the decrypted response sent back in a notify. - */ - public static final class Response { - - /** - * The type of this message = Key-based Pairing Response. - */ - public static final byte TYPE = 0x01; - - private Response() { - } - } - - private KeyBasedPairingCharacteristic() { - } - } - - /** - * Characteristic used during Key-based Pairing, to exchange the encrypted passkey. - */ - public static final class PasskeyCharacteristic { - - private static final short SHORT_UUID = 0x1235; - - /** - * Gets the new 128-bit UUID of this characteristic. - * - *

    Note: For GATT server only. GATT client should use {@link - * PasskeyCharacteristic#getId(BluetoothGattConnection)}. - */ - public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID); - - /** - * Gets the {@link UUID} of this characteristic. - * - *

    This method is designed for being backward compatible with old version of UUID - * therefore - * needs the {@link BluetoothGattConnection} parameter to check the supported status of - * the Fast Pair provider. - */ - public static UUID getId(BluetoothGattConnection gattConnection) { - return getSupportedUuid(gattConnection, SHORT_UUID); - } - - /** - * The type of the Passkey Block message. - */ - @IntDef( - value = { - Type.SEEKER, - Type.PROVIDER, - }) - public @interface Type { - /** - * Seeker's Passkey. - */ - int SEEKER = (byte) 0x02; - /** - * Provider's Passkey. - */ - int PROVIDER = (byte) 0x03; - } - - /** - * Constructs the encrypted value to write to the characteristic. - */ - public static byte[] encrypt(@Type int type, byte[] secret, int passkey) - throws GeneralSecurityException { - Preconditions.checkArgument( - 0 < passkey && passkey < /*2^24=*/ 16777216, - "Passkey %s must be positive and fit in 3 bytes", - passkey); - byte[] passkeyBytes = - new byte[]{(byte) (passkey >>> 16), (byte) (passkey >>> 8), (byte) passkey}; - byte[] salt = - new byte[AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH - 1 - - passkeyBytes.length]; - new Random().nextBytes(salt); - return AesEcbSingleBlockEncryption.encrypt( - secret, concat(new byte[]{(byte) type}, passkeyBytes, salt)); - } - - /** - * Extracts the passkey from the encrypted characteristic value. - */ - public static int decrypt(@Type int type, byte[] secret, - byte[] passkeyCharacteristicValue) - throws GeneralSecurityException { - byte[] decrypted = AesEcbSingleBlockEncryption - .decrypt(secret, passkeyCharacteristicValue); - if (decrypted[0] != (byte) type) { - throw new GeneralSecurityException( - "Wrong Passkey Block type (expected " + type + ", got " - + decrypted[0] + ")"); - } - return ByteBuffer.allocate(4) - .put((byte) 0) - .put(decrypted, /*offset=*/ 1, /*length=*/ 3) - .getInt(0); - } - - private PasskeyCharacteristic() { - } - } - - /** - * Characteristic to write to during the key exchange. - */ - public static final class AccountKeyCharacteristic { - - private static final short SHORT_UUID = 0x1236; - - /** - * Gets the new 128-bit UUID of this characteristic. - * - *

    Note: For GATT server only. GATT client should use {@link - * AccountKeyCharacteristic#getId(BluetoothGattConnection)}. - */ - public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID); - - /** - * Gets the {@link UUID} of this characteristic. - * - *

    This method is designed for being backward compatible with old version of UUID - * therefore - * needs the {@link BluetoothGattConnection} parameter to check the supported status of - * the Fast Pair provider. - */ - public static UUID getId(BluetoothGattConnection gattConnection) { - return getSupportedUuid(gattConnection, SHORT_UUID); - } - - /** - * The type for this message, account key request. - */ - public static final byte TYPE = 0x04; - - private AccountKeyCharacteristic() { - } - } - - /** - * Characteristic to write to and notify on for handling personalized name, see {@link - * NamingEncoder}. - */ - public static final class NameCharacteristic { - - private static final short SHORT_UUID = 0x1237; - - /** - * Gets the new 128-bit UUID of this characteristic. - * - *

    Note: For GATT server only. GATT client should use {@link - * NameCharacteristic#getId(BluetoothGattConnection)}. - */ - public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID); - - /** - * Gets the {@link UUID} of this characteristic. - * - *

    This method is designed for being backward compatible with old version of UUID - * therefore - * needs the {@link BluetoothGattConnection} parameter to check the supported status of - * the Fast Pair provider. - */ - public static UUID getId(BluetoothGattConnection gattConnection) { - return getSupportedUuid(gattConnection, SHORT_UUID); - } - - private NameCharacteristic() { - } - } - - /** - * Characteristic to write to and notify on for handling additional data, see - * https://developers.google.com/nearby/fast-pair/early-access/spec#AdditionalData - */ - public static final class AdditionalDataCharacteristic { - - private static final short SHORT_UUID = 0x1237; - - public static final int DATA_ID_INDEX = 0; - public static final int DATA_LENGTH_INDEX = 1; - public static final int DATA_START_INDEX = 2; - - /** - * Gets the new 128-bit UUID of this characteristic. - * - *

    Note: For GATT server only. GATT client should use {@link - * AdditionalDataCharacteristic#getId(BluetoothGattConnection)}. - */ - public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID); - - /** - * Gets the {@link UUID} of this characteristic. - * - *

    This method is designed for being backward compatible with old version of UUID - * therefore - * needs the {@link BluetoothGattConnection} parameter to check the supported status of - * the Fast Pair provider. - */ - public static UUID getId(BluetoothGattConnection gattConnection) { - return getSupportedUuid(gattConnection, SHORT_UUID); - } - - /** - * Enumerates all types of additional data. - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - value = { - AdditionalDataType.PERSONALIZED_NAME, - AdditionalDataType.UNKNOWN, - }) - public @interface AdditionalDataType { - /** - * The value indicating that the type is for personalized name. - */ - int PERSONALIZED_NAME = (byte) 0x01; - int UNKNOWN = (byte) 0x00; // and all others. - } - } - - /** - * Characteristic to control the beaconing feature (FastPair+Eddystone). - */ - public static final class BeaconActionsCharacteristic { - - private static final short SHORT_UUID = 0x1238; - - /** - * Gets the new 128-bit UUID of this characteristic. - * - *

    Note: For GATT server only. GATT client should use {@link - * BeaconActionsCharacteristic#getId(BluetoothGattConnection)}. - */ - public static final UUID CUSTOM_128_BIT_UUID = toFastPair128BitUuid(SHORT_UUID); - - /** - * Gets the {@link UUID} of this characteristic. - * - *

    This method is designed for being backward compatible with old version of UUID - * therefore - * needs the {@link BluetoothGattConnection} parameter to check the supported status of - * the Fast Pair provider. - */ - public static UUID getId(BluetoothGattConnection gattConnection) { - return getSupportedUuid(gattConnection, SHORT_UUID); - } - - /** - * Enumerates all types of beacon actions. - */ - /** Fast Pair Bond State. */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - value = { - BeaconActionType.READ_BEACON_PARAMETERS, - BeaconActionType.READ_PROVISIONING_STATE, - BeaconActionType.SET_EPHEMERAL_IDENTITY_KEY, - BeaconActionType.CLEAR_EPHEMERAL_IDENTITY_KEY, - BeaconActionType.READ_EPHEMERAL_IDENTITY_KEY, - BeaconActionType.RING, - BeaconActionType.READ_RINGING_STATE, - BeaconActionType.UNKNOWN, - }) - public @interface BeaconActionType { - int READ_BEACON_PARAMETERS = (byte) 0x00; - int READ_PROVISIONING_STATE = (byte) 0x01; - int SET_EPHEMERAL_IDENTITY_KEY = (byte) 0x02; - int CLEAR_EPHEMERAL_IDENTITY_KEY = (byte) 0x03; - int READ_EPHEMERAL_IDENTITY_KEY = (byte) 0x04; - int RING = (byte) 0x05; - int READ_RINGING_STATE = (byte) 0x06; - int UNKNOWN = (byte) 0xFF; // and all others - } - - /** Converts value to enum. */ - public static @BeaconActionType int valueOf(byte value) { - switch(value) { - case BeaconActionType.READ_BEACON_PARAMETERS: - case BeaconActionType.READ_PROVISIONING_STATE: - case BeaconActionType.SET_EPHEMERAL_IDENTITY_KEY: - case BeaconActionType.CLEAR_EPHEMERAL_IDENTITY_KEY: - case BeaconActionType.READ_EPHEMERAL_IDENTITY_KEY: - case BeaconActionType.RING: - case BeaconActionType.READ_RINGING_STATE: - case BeaconActionType.UNKNOWN: - return value; - default: - return BeaconActionType.UNKNOWN; - } - } - } - - - /** - * Characteristic to read for checking firmware version. 0X2A26 is assigned number from - * bluetooth SIG website. - */ - public static final class FirmwareVersionCharacteristic { - - /** UUID for firmware version. */ - public static final UUID ID = to128BitUuid((short) 0x2A26); - - private FirmwareVersionCharacteristic() { - } - } - - private FastPairService() { - } - } - - /** - * Defined by the BR/EDR Handover Profile. Pre-release version here: - * {https://jfarfel.users.x20web.corp.google.com/Bluetooth%20Handover%20d09.pdf} - */ - public interface TransportDiscoveryService { - - UUID ID = to128BitUuid((short) 0x1824); - - byte BLUETOOTH_SIG_ORGANIZATION_ID = 0x01; - byte SERVICE_UUIDS_16_BIT_LIST_TYPE = 0x01; - byte SERVICE_UUIDS_32_BIT_LIST_TYPE = 0x02; - byte SERVICE_UUIDS_128_BIT_LIST_TYPE = 0x03; - - /** - * Writing to this allows you to activate the BR/EDR transport. - */ - interface ControlPointCharacteristic { - - UUID ID = to128BitUuid((short) 0x2ABC); - byte ACTIVATE_TRANSPORT_OP_CODE = 0x01; - } - - /** - * Info necessary to pair (mostly the Bluetooth Address). - */ - interface BrHandoverDataCharacteristic { - - UUID ID = to128BitUuid((short) 0x2C01); - - /** - * All bits are reserved for future use. - */ - byte BR_EDR_FEATURES = 0x00; - } - - /** - * This characteristic exists only to wrap the descriptor. - */ - interface BluetoothSigDataCharacteristic { - - UUID ID = to128BitUuid((short) 0x2C02); - - /** - * The entire Transport Block data (e.g. supported Bluetooth services). - */ - interface BrTransportBlockDataDescriptor { - - UUID ID = to128BitUuid((short) 0x2C03); - } - } - } - - public static final UUID CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID = - to128BitUuid((short) 0x2902); - - /** - * Wrapper for Bluetooth profile - */ - public static class Profile { - - public final int type; - public final String name; - public final String connectionStateAction; - - private Profile(int type, String name, String connectionStateAction) { - this.type = type; - this.name = name; - this.connectionStateAction = connectionStateAction; - } - - @Override - public String toString() { - return name; - } - } - - /** - * {@link BluetoothHeadset} is used for both Headset and HandsFree (HFP). - */ - private static final Profile HEADSET_AND_HANDS_FREE_PROFILE = - new Profile( - HEADSET, "HEADSET_AND_HANDS_FREE", - BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); - - /** Fast Pair supported profiles. */ - public static final ImmutableMap PROFILES = - ImmutableMap.builder() - .put( - Constants.A2DP_SINK_SERVICE_UUID, - new Profile(A2DP, "A2DP", - BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) - .put(Constants.HEADSET_SERVICE_UUID, HEADSET_AND_HANDS_FREE_PROFILE) - .put(Constants.HANDS_FREE_SERVICE_UUID, HEADSET_AND_HANDS_FREE_PROFILE) - .build(); - - static short[] getSupportedProfiles() { - return Shorts.toArray(PROFILES.keySet()); - } - - /** - * Helper method of getting 128-bit UUID for Fast Pair custom GATT characteristics. - * - *

    This method is designed for being backward compatible with old version of UUID therefore - * needs the {@link BluetoothGattConnection} parameter to check the supported status of the Fast - * Pair provider. - * - *

    Note: For new custom GATT characteristics, don't need to use this helper and please just - * call {@code toFastPair128BitUuid(shortUuid)} to get the UUID. Which also implies that callers - * don't need to provide {@link BluetoothGattConnection} to get the UUID anymore. - */ - private static UUID getSupportedUuid(BluetoothGattConnection gattConnection, short shortUuid) { - // In worst case (new characteristic not found), this method's performance impact is about - // 6ms - // by using Pixel2 + JBL LIVE220. And the impact should be less and less along with more and - // more devices adopt the new characteristics. - try { - // Checks the new UUID first. - if (gattConnection - .getCharacteristic(FastPairService.ID, toFastPair128BitUuid(shortUuid)) - != null) { - Log.d(TAG, "Uses new KeyBasedPairingCharacteristic.ID"); - return toFastPair128BitUuid(shortUuid); - } - } catch (BluetoothException e) { - Log.d(TAG, "Uses old KeyBasedPairingCharacteristic.ID"); - } - // Returns the old UUID for default. - return to128BitUuid(shortUuid); - } - - private Constants() { - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/CreateBondException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/CreateBondException.java deleted file mode 100644 index d6aa3b2baa912b6ad6b3611ddaad7348a5ce198c..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/CreateBondException.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import com.android.server.nearby.intdefs.FastPairEventIntDefs.CreateBondErrorCode; - -/** Thrown when binding (pairing) with a bluetooth device fails. */ -public class CreateBondException extends PairingException { - final @CreateBondErrorCode int mErrorCode; - int mReason; - - CreateBondException(@CreateBondErrorCode int errorCode, int reason, String format, - Object... objects) { - super(format, objects); - this.mErrorCode = errorCode; - this.mReason = reason; - } - - /** Returns error code. */ - public @CreateBondErrorCode int getErrorCode() { - return mErrorCode; - } - - /** Returns reason. */ - public int getReason() { - return mReason; - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiver.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiver.java deleted file mode 100644 index 5bcf10ab2b7278c3111566146ca739b7fd0f6b24..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiver.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress; - -import android.bluetooth.BluetoothDevice; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -/** - * Like {@link SimpleBroadcastReceiver}, but for intents about a certain {@link BluetoothDevice}. - */ -abstract class DeviceIntentReceiver extends SimpleBroadcastReceiver { - - private static final String TAG = DeviceIntentReceiver.class.getSimpleName(); - - private final BluetoothDevice mDevice; - - static DeviceIntentReceiver oneShotReceiver( - Context context, Preferences preferences, BluetoothDevice device, String... actions) { - return new DeviceIntentReceiver(context, preferences, device, actions) { - @Override - protected void onReceiveDeviceIntent(Intent intent) throws Exception { - close(); - } - }; - } - - /** - * @param context The context to use to register / unregister the receiver. - * @param device The interesting device. We ignore intents about other devices. - * @param actions The actions to include in our intent filter. - */ - protected DeviceIntentReceiver( - Context context, Preferences preferences, BluetoothDevice device, String... actions) { - super(context, preferences, actions); - this.mDevice = device; - } - - /** - * Called with intents about the interesting device (see {@link #DeviceIntentReceiver}). Any - * exception thrown by this method will be delivered via {@link #await}. - */ - protected abstract void onReceiveDeviceIntent(Intent intent) throws Exception; - - // incompatible types in argument. - @Override - protected void onReceive(Intent intent) throws Exception { - BluetoothDevice intentDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (mDevice == null || mDevice.equals(intentDevice)) { - onReceiveDeviceIntent(intent); - } else { - Log.v(TAG, - "Ignoring intent for device=" + maskBluetoothAddress(intentDevice) - + "(expected " - + maskBluetoothAddress(mDevice) + ")"); - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchange.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchange.java deleted file mode 100644 index dbcdf077ea16a08167ddff513858161b33d85e88..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/EllipticCurveDiffieHellmanExchange.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static com.google.common.primitives.Bytes.concat; - -import androidx.annotation.Nullable; - -import java.math.BigInteger; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPrivateKeySpec; -import java.security.spec.ECPublicKeySpec; -import java.util.Arrays; - -import javax.crypto.KeyAgreement; - -/** - * Helper for generating keys based off of the Elliptic-Curve Diffie-Hellman algorithm (ECDH). - */ -public final class EllipticCurveDiffieHellmanExchange { - - public static final int PUBLIC_KEY_LENGTH = 64; - static final int PRIVATE_KEY_LENGTH = 32; - - private static final String[] PROVIDERS = {"GmsCore_OpenSSL", "AndroidOpenSSL", "SC", "BC"}; - - private static final String EC_ALGORITHM = "EC"; - - /** - * Also known as prime256v1 or NIST P-256. - */ - private static final ECGenParameterSpec EC_GEN_PARAMS = new ECGenParameterSpec("secp256r1"); - - @Nullable - private final ECPublicKey mPublicKey; - private final ECPrivateKey mPrivateKey; - - /** - * Creates a new EllipticCurveDiffieHellmanExchange object. - */ - public static EllipticCurveDiffieHellmanExchange create() throws GeneralSecurityException { - KeyPair keyPair = generateKeyPair(); - return new EllipticCurveDiffieHellmanExchange( - (ECPublicKey) keyPair.getPublic(), (ECPrivateKey) keyPair.getPrivate()); - } - - /** - * Creates a new EllipticCurveDiffieHellmanExchange object. - */ - public static EllipticCurveDiffieHellmanExchange create(byte[] privateKey) - throws GeneralSecurityException { - ECPrivateKey ecPrivateKey = (ECPrivateKey) generatePrivateKey(privateKey); - return new EllipticCurveDiffieHellmanExchange(/*publicKey=*/ null, ecPrivateKey); - } - - private EllipticCurveDiffieHellmanExchange( - @Nullable ECPublicKey publicKey, ECPrivateKey privateKey) { - this.mPublicKey = publicKey; - this.mPrivateKey = privateKey; - } - - /** - * @param otherPublicKey Another party's public key. See {@link #getPublicKey()} for format. - * @return The shared secret. Given our public key (and its private key), the other party can - * generate the same secret. This is a key meant for symmetric encryption. - */ - public byte[] generateSecret(byte[] otherPublicKey) throws GeneralSecurityException { - KeyAgreement agreement = keyAgreement(); - agreement.init(mPrivateKey); - agreement.doPhase(generatePublicKey(otherPublicKey), /*lastPhase=*/ true); - byte[] secret = agreement.generateSecret(); - // Headsets only support AES with 128-bit keys. So, hash the secret so that the entropy is - // high and then take only the first 128-bits. - secret = MessageDigest.getInstance("SHA-256").digest(secret); - return Arrays.copyOf(secret, 16); - } - - /** - * Returns a public point W on the NIST P-256 elliptic curve. First 32 bytes are the X - * coordinate, next 32 bytes are the Y coordinate. Each coordinate is an unsigned big-endian - * integer. - */ - public @Nullable byte[] getPublicKey() { - if (mPublicKey == null) { - return null; - } - ECPoint w = mPublicKey.getW(); - // See getPrivateKey for why we're resizing. - byte[] x = resizeWithLeadingZeros(w.getAffineX().toByteArray(), 32); - byte[] y = resizeWithLeadingZeros(w.getAffineY().toByteArray(), 32); - return concat(x, y); - } - - /** - * Returns a private value S, an unsigned big-endian integer. - */ - public byte[] getPrivateKey() { - // Note that BigInteger.toByteArray() returns a signed representation, so it will add an - // extra zero byte to the front if the first bit is 1. - // We must remove that leading zero (we know the number is unsigned). We must also add - // leading zeros if the number is too small. - return resizeWithLeadingZeros(mPrivateKey.getS().toByteArray(), 32); - } - - /** - * Removes or adds leading zeros until we have an array of size {@code n}. - */ - private static byte[] resizeWithLeadingZeros(byte[] x, int n) { - if (n < x.length) { - int start = x.length - n; - for (int i = 0; i < start; i++) { - if (x[i] != 0) { - throw new IllegalArgumentException( - "More than " + n + " non-zero bytes in " + Arrays.toString(x)); - } - } - return Arrays.copyOfRange(x, start, x.length); - } - return concat(new byte[n - x.length], x); - } - - /** - * @param publicKey See {@link #getPublicKey()} for format. - */ - private static PublicKey generatePublicKey(byte[] publicKey) throws GeneralSecurityException { - if (publicKey.length != PUBLIC_KEY_LENGTH) { - throw new GeneralSecurityException("Public key length incorrect: " + publicKey.length); - } - byte[] x = Arrays.copyOf(publicKey, publicKey.length / 2); - byte[] y = Arrays.copyOfRange(publicKey, publicKey.length / 2, publicKey.length); - return keyFactory() - .generatePublic( - new ECPublicKeySpec( - new ECPoint(new BigInteger(/*signum=*/ 1, x), - new BigInteger(/*signum=*/ 1, y)), - ecParameterSpec())); - } - - /** - * @param privateKey See {@link #getPrivateKey()} for format. - */ - private static PrivateKey generatePrivateKey(byte[] privateKey) - throws GeneralSecurityException { - if (privateKey.length != PRIVATE_KEY_LENGTH) { - throw new GeneralSecurityException("Private key length incorrect: " - + privateKey.length); - } - return keyFactory() - .generatePrivate( - new ECPrivateKeySpec(new BigInteger(/*signum=*/ 1, privateKey), - ecParameterSpec())); - } - - private static ECParameterSpec ecParameterSpec() throws GeneralSecurityException { - // This seems to be the simplest way to get the curve's ECParameterSpec. Verified that it's - // the same whether you get it from the public or private key, and that it's the same as the - // raw params in SecAggEcUtil.getNistP256Params(). - return ((ECPublicKey) generateKeyPair().getPublic()).getParams(); - } - - private static KeyPair generateKeyPair() throws GeneralSecurityException { - KeyPairGenerator generator = findProvider(p -> KeyPairGenerator.getInstance(EC_ALGORITHM, - p)); - generator.initialize(EC_GEN_PARAMS); - return generator.generateKeyPair(); - } - - private static KeyAgreement keyAgreement() throws NoSuchProviderException { - return findProvider(p -> KeyAgreement.getInstance("ECDH", p)); - } - - private static KeyFactory keyFactory() throws NoSuchProviderException { - return findProvider(p -> KeyFactory.getInstance(EC_ALGORITHM, p)); - } - - private interface ProviderConsumer { - - T tryProvider(String provider) throws NoSuchAlgorithmException, NoSuchProviderException; - } - - private static T findProvider(ProviderConsumer providerConsumer) - throws NoSuchProviderException { - for (String provider : PROVIDERS) { - try { - return providerConsumer.tryProvider(provider); - } catch (NoSuchAlgorithmException | NoSuchProviderException e) { - // No-op - } - } - throw new NoSuchProviderException(); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Event.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Event.java deleted file mode 100644 index 7a0548b49b94959076b8997c9dcaff9a5301d3ea..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Event.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import android.bluetooth.BluetoothDevice; -import android.os.Parcel; -import android.os.Parcelable; - -import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode; - -import java.util.Objects; - -import javax.annotation.Nullable; - -/** - * Describes events that are happening during fast pairing. EventCode is required, everything else - * is optional. - */ -public class Event implements Parcelable { - - private final @EventCode int mEventCode; - private final long mTimestamp; - private final Short mProfile; - private final BluetoothDevice mBluetoothDevice; - private final Exception mException; - - private Event(@EventCode int eventCode, long timestamp, @Nullable Short profile, - @Nullable BluetoothDevice bluetoothDevice, @Nullable Exception exception) { - mEventCode = eventCode; - mTimestamp = timestamp; - mProfile = profile; - mBluetoothDevice = bluetoothDevice; - mException = exception; - } - - /** - * Returns event code. - */ - public @EventCode int getEventCode() { - return mEventCode; - } - - /** - * Returns timestamp. - */ - public long getTimestamp() { - return mTimestamp; - } - - /** - * Returns profile. - */ - @Nullable - public Short getProfile() { - return mProfile; - } - - /** - * Returns Bluetooth device. - */ - @Nullable - public BluetoothDevice getBluetoothDevice() { - return mBluetoothDevice; - } - - /** - * Returns exception. - */ - @Nullable - public Exception getException() { - return mException; - } - - /** - * Returns whether profile is not null. - */ - public boolean hasProfile() { - return getProfile() != null; - } - - /** - * Returns whether Bluetooth device is not null. - */ - public boolean hasBluetoothDevice() { - return getBluetoothDevice() != null; - } - - /** - * Returns a builder. - */ - public static Builder builder() { - return new Event.Builder(); - } - - /** - * Returns whether it fails. - */ - public boolean isFailure() { - return getException() != null; - } - - @Override - public String toString() { - return "Event{" - + "eventCode=" + mEventCode + ", " - + "timestamp=" + mTimestamp + ", " - + "profile=" + mProfile + ", " - + "bluetoothDevice=" + mBluetoothDevice + ", " - + "exception=" + mException - + "}"; - } - - @Override - public boolean equals(@Nullable Object o) { - if (o == this) { - return true; - } - if (o instanceof Event) { - Event that = (Event) o; - return this.mEventCode == that.getEventCode() - && this.mTimestamp == that.getTimestamp() - && (this.mBluetoothDevice == null - ? that.getBluetoothDevice() == null : - this.mBluetoothDevice.equals(that.getBluetoothDevice())) - && (this.mProfile == null - ? that.getProfile() == null : this.mProfile.equals(that.getProfile())); - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(mEventCode, mTimestamp, mProfile, mBluetoothDevice, mException); - } - - /** - * Builder - */ - public static class Builder { - private @EventCode int mEventCode; - private long mTimestamp; - private Short mProfile; - private BluetoothDevice mBluetoothDevice; - private Exception mException; - - /** - * Set event code. - */ - public Builder setEventCode(@EventCode int eventCode) { - this.mEventCode = eventCode; - return this; - } - - /** - * Set timestamp. - */ - public Builder setTimestamp(long timestamp) { - this.mTimestamp = timestamp; - return this; - } - - /** - * Set profile. - */ - public Builder setProfile(@Nullable Short profile) { - this.mProfile = profile; - return this; - } - - /** - * Set Bluetooth device. - */ - public Builder setBluetoothDevice(@Nullable BluetoothDevice device) { - this.mBluetoothDevice = device; - return this; - } - - /** - * Set exception. - */ - public Builder setException(@Nullable Exception exception) { - this.mException = exception; - return this; - } - - /** - * Builds event. - */ - public Event build() { - return new Event(mEventCode, mTimestamp, mProfile, mBluetoothDevice, mException); - } - } - - @Override - public final void writeToParcel(Parcel dest, int flags) { - dest.writeInt(getEventCode()); - dest.writeLong(getTimestamp()); - dest.writeValue(getProfile()); - dest.writeParcelable(getBluetoothDevice(), 0); - dest.writeSerializable(getException()); - } - - @Override - public final int describeContents() { - return 0; - } - - /** - * Event Creator instance. - */ - public static final Creator CREATOR = - new Creator() { - @Override - /** Creates Event from Parcel. */ - public Event createFromParcel(Parcel in) { - return Event.builder() - .setEventCode(in.readInt()) - .setTimestamp(in.readLong()) - .setProfile((Short) in.readValue(Short.class.getClassLoader())) - .setBluetoothDevice( - in.readParcelable(BluetoothDevice.class.getClassLoader())) - .setException((Exception) in.readSerializable()) - .build(); - } - - @Override - /** Returns Event array. */ - public Event[] newArray(int size) { - return new Event[size]; - } - }; -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/EventLoggerWrapper.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/EventLoggerWrapper.java deleted file mode 100644 index 024bfdee8a94e6dc73f7dee6c099a5e4d74d8095..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/EventLoggerWrapper.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import android.bluetooth.BluetoothDevice; -import android.content.Context; - -import com.android.server.nearby.common.bluetooth.fastpair.Preferences.ExtraLoggingInformation; -import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode; - -import javax.annotation.Nullable; - -/** - * Convenience wrapper around EventLogger. - */ -// TODO(b/202559985): cleanup EventLoggerWrapper. -class EventLoggerWrapper { - - EventLoggerWrapper(@Nullable EventLogger eventLogger) { - } - - /** - * Binds to the logging service. This operation blocks until binding has completed or timed - * out. - */ - void bind( - Context context, String address, - @Nullable ExtraLoggingInformation extraLoggingInformation) { - } - - boolean isBound() { - return false; - } - - void unbind(Context context) { - } - - void setCurrentEvent(@EventCode int code) { - } - - void setCurrentProfile(short profile) { - } - - void logCurrentEventFailed(Exception e) { - } - - void logCurrentEventSucceeded() { - } - - void setDevice(@Nullable BluetoothDevice device) { - } - - boolean isCurrentEvent() { - return false; - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java deleted file mode 100644 index c963aa6076879afccddab03adeee4bf3c4947e27..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import android.annotation.WorkerThread; -import android.bluetooth.BluetoothDevice; - -import androidx.annotation.Nullable; -import androidx.core.util.Consumer; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.nearby.common.bluetooth.BluetoothException; - -import java.security.GeneralSecurityException; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; - -/** Abstract class for pairing or connecting via FastPair. */ -public abstract class FastPairConnection { - @Nullable protected OnPairedCallback mPairedCallback; - @Nullable protected OnGetBluetoothAddressCallback mOnGetBluetoothAddressCallback; - @Nullable protected PasskeyConfirmationHandler mPasskeyConfirmationHandler; - @Nullable protected FastPairSignalChecker mFastPairSignalChecker; - @Nullable protected Consumer mRescueFromError; - @Nullable protected Runnable mPrepareCreateBondCallback; - protected boolean mPasskeyIsGotten; - - /** Sets a callback to be invoked once the device is paired. */ - public void setOnPairedCallback(OnPairedCallback callback) { - this.mPairedCallback = callback; - } - - /** Sets a callback to be invoked while the target bluetooth address is decided. */ - public void setOnGetBluetoothAddressCallback(OnGetBluetoothAddressCallback callback) { - this.mOnGetBluetoothAddressCallback = callback; - } - - /** Sets a callback to be invoked while handling the passkey confirmation. */ - public void setPasskeyConfirmationHandler( - PasskeyConfirmationHandler passkeyConfirmationHandler) { - this.mPasskeyConfirmationHandler = passkeyConfirmationHandler; - } - - public void setFastPairSignalChecker(FastPairSignalChecker fastPairSignalChecker) { - this.mFastPairSignalChecker = fastPairSignalChecker; - } - - public void setRescueFromError(Consumer rescueFromError) { - this.mRescueFromError = rescueFromError; - } - - public void setPrepareCreateBondCallback(Runnable runnable) { - this.mPrepareCreateBondCallback = runnable; - } - - @VisibleForTesting - @Nullable - public Runnable getPrepareCreateBondCallback() { - return mPrepareCreateBondCallback; - } - - /** - * Sets the fast pair history for identifying whether or not the provider has paired with the - * primary account on other phones before. - */ - @WorkerThread - public abstract void setFastPairHistory(List fastPairHistoryItem); - - /** Sets the device name to the Provider. */ - public abstract void setProviderDeviceName(String deviceName); - - /** Gets the device name from the Provider. */ - @Nullable - public abstract String getProviderDeviceName(); - - /** - * Gets the existing account key of the Provider. - * - * @return the existing account key if the Provider has paired with the account, null otherwise - */ - @WorkerThread - @Nullable - public abstract byte[] getExistingAccountKey(); - - /** - * Pairs with Provider. Synchronous: Blocks until paired and connected. Throws on any error. - * - * @return the secret key for the user's account, if written - */ - @WorkerThread - @Nullable - public abstract SharedSecret pair() - throws BluetoothException, InterruptedException, TimeoutException, ExecutionException, - PairingException, ReflectionException; - - /** - * Pairs with Provider. Synchronous: Blocks until paired and connected. Throws on any error. - * - * @param key can be in two different formats. If it is 16 bytes long, then it is an AES account - * key. Otherwise, it's a public key generated by {@link EllipticCurveDiffieHellmanExchange}. - * See go/fast-pair-2-spec for how each of these keys are used. - * @return the secret key for the user's account, if written - */ - @WorkerThread - @Nullable - public abstract SharedSecret pair(@Nullable byte[] key) - throws BluetoothException, InterruptedException, TimeoutException, ExecutionException, - PairingException, GeneralSecurityException, ReflectionException; - - /** Unpairs with Provider. Synchronous: Blocks until unpaired. Throws on any error. */ - @WorkerThread - public abstract void unpair(BluetoothDevice device) - throws InterruptedException, TimeoutException, ExecutionException, PairingException, - ReflectionException; - - /** Gets the public address of the Provider. */ - @Nullable - public abstract String getPublicAddress(); - - - /** Callback for getting notifications when pairing has completed. */ - public interface OnPairedCallback { - /** Called when the device at address has finished pairing. */ - void onPaired(String address); - } - - /** Callback for getting bluetooth address Bisto oobe need this information */ - public interface OnGetBluetoothAddressCallback { - /** Called when the device has received bluetooth address. */ - void onGetBluetoothAddress(String address); - } - - /** Holds the exchanged secret key and the public mac address of the device. */ - public static class SharedSecret { - private final byte[] mKey; - private final String mAddress; - private SharedSecret(byte[] key, String address) { - mKey = key; - mAddress = address; - } - - /** Creates Shared Secret. */ - public static SharedSecret create(byte[] key, String address) { - return new SharedSecret(key, address); - } - - /** Gets Shared Secret Key. */ - public byte[] getKey() { - return mKey; - } - - /** Gets Shared Secret Address. */ - public String getAddress() { - return mAddress; - } - - @Override - public String toString() { - return "SharedSecret{" - + "key=" + Arrays.toString(mKey) + ", " - + "address=" + mAddress - + "}"; - } - - @Override - public boolean equals(@Nullable Object o) { - if (o == this) { - return true; - } - if (o instanceof SharedSecret) { - SharedSecret that = (SharedSecret) o; - return Arrays.equals(this.mKey, that.getKey()) - && this.mAddress.equals(that.getAddress()); - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(Arrays.hashCode(mKey), mAddress); - } - } - - /** Invokes if gotten the passkey. */ - public void setPasskeyIsGotten() { - mPasskeyIsGotten = true; - } - - /** Returns the value of passkeyIsGotten. */ - public boolean getPasskeyIsGotten() { - return mPasskeyIsGotten; - } - - /** Interface to get latest address of ModelId. */ - public interface FastPairSignalChecker { - /** Gets address of ModelId. */ - String getValidAddressForModelId(String currentDevice); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConstants.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConstants.java deleted file mode 100644 index 008891fa57bcc0feaddeaff4ec41113c3c7bae81..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConstants.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static com.android.nearby.halfsheet.constants.FastPairConstants.PREFIX; - -import android.bluetooth.BluetoothDevice; - -/** Constants to share with other team. */ -public class FastPairConstants { - /** CONNECTION_ID item name for extended intent field. */ - public static final String EXTRA_CONNECTION_ID = PREFIX + "CONNECTION_ID"; - /** BLUETOOTH_MAC_ADDRESS item name for extended intent field. */ - public static final String EXTRA_BLUETOOTH_MAC_ADDRESS = PREFIX + "BLUETOOTH_MAC_ADDRESS"; - /** COMPANION_SCAN_ITEM item name for extended intent field. */ - public static final String EXTRA_SCAN_ITEM = PREFIX + "COMPANION_SCAN_ITEM"; - /** BOND_RESULT item name for extended intent field. */ - public static final String EXTRA_BOND_RESULT = PREFIX + "EXTRA_BOND_RESULT"; - - /** - * The bond result of the {@link BluetoothDevice} when FastPair launches the companion app, it - * means device is BONDED but the pairing process is not triggered by FastPair. - */ - public static final int BOND_RESULT_SUCCESS_WITHOUT_FP = 0; - - /** - * The bond result of the {@link BluetoothDevice} when FastPair launches the companion app, it - * means device is BONDED and the pairing process is triggered by FastPair. - */ - public static final int BOND_RESULT_SUCCESS_WITH_FP = 1; - - /** - * The bond result of the {@link BluetoothDevice} when FastPair launches the companion app, it - * means the pairing process triggered by FastPair is failed due to the lack of PIN code. - */ - public static final int BOND_RESULT_FAIL_WITH_FP_WITHOUT_PIN = 2; - - /** - * The bond result of the {@link BluetoothDevice} when FastPair launches the companion app, it - * means the pairing process triggered by FastPair is failed due to the PIN code is not - * confirmed by the user. - */ - public static final int BOND_RESULT_FAIL_WITH_FP_WITH_PIN_NOT_CONFIRMED = 3; - - /** - * The bond result of the {@link BluetoothDevice} when FastPair launches the companion app, it - * means the pairing process triggered by FastPair is failed due to the user thinks the PIN is - * wrong. - */ - public static final int BOND_RESULT_FAIL_WITH_FP_WITH_PIN_WRONG = 4; - - /** - * The bond result of the {@link BluetoothDevice} when FastPair launches the companion app, it - * means the pairing process triggered by FastPair is failed even after the user confirmed the - * PIN code is correct. - */ - public static final int BOND_RESULT_FAIL_WITH_FP_WITH_PIN_CORRECT = 5; - - private FastPairConstants() {} -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java deleted file mode 100644 index 60afa31c1b1d20b42c4f45c38c3ccd286d1b17bd..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java +++ /dev/null @@ -1,2128 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static android.bluetooth.BluetoothDevice.BOND_BONDED; -import static android.bluetooth.BluetoothDevice.BOND_BONDING; -import static android.bluetooth.BluetoothDevice.BOND_NONE; - -import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress; -import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.get16BitUuid; -import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid; -import static com.android.server.nearby.common.bluetooth.fastpair.Bytes.toBytes; -import static com.android.server.nearby.common.bluetooth.fastpair.Bytes.toShorts; - -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Verify.verifyNotNull; -import static com.google.common.io.BaseEncoding.base16; -import static com.google.common.primitives.Bytes.concat; - -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothGattCharacteristic; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.ParcelUuid; -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.GuardedBy; -import androidx.annotation.IntDef; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.annotation.WorkerThread; - -import com.android.server.nearby.common.bluetooth.BluetoothException; -import com.android.server.nearby.common.bluetooth.BluetoothGattException; -import com.android.server.nearby.common.bluetooth.BluetoothTimeoutException; -import com.android.server.nearby.common.bluetooth.fastpair.BluetoothAudioPairer.KeyBasedPairingInfo; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AccountKeyCharacteristic; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AdditionalDataCharacteristic.AdditionalDataType; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.FirmwareVersionCharacteristic; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.NameCharacteristic; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService; -import com.android.server.nearby.common.bluetooth.fastpair.HandshakeHandler.ActionOverBle; -import com.android.server.nearby.common.bluetooth.fastpair.HandshakeHandler.HandshakeException; -import com.android.server.nearby.common.bluetooth.fastpair.HandshakeHandler.HandshakeMessage; -import com.android.server.nearby.common.bluetooth.fastpair.HandshakeHandler.KeyBasedPairingRequest; -import com.android.server.nearby.common.bluetooth.fastpair.Ltv.ParseException; -import com.android.server.nearby.common.bluetooth.fastpair.TimingLogger.ScopedTiming; -import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection; -import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection.ChangeObserver; -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter; -import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException; -import com.android.server.nearby.common.locator.Locator; -import com.android.server.nearby.fastpair.FastPairController; -import com.android.server.nearby.intdefs.FastPairEventIntDefs.BrEdrHandoverErrorCode; -import com.android.server.nearby.intdefs.FastPairEventIntDefs.ConnectErrorCode; -import com.android.server.nearby.intdefs.FastPairEventIntDefs.CreateBondErrorCode; -import com.android.server.nearby.intdefs.FastPairEventIntDefs.ErrorCode; -import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode; - -import com.google.common.base.Ascii; -import com.google.common.base.Preconditions; -import com.google.common.primitives.Shorts; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.nio.ByteOrder; -import java.security.GeneralSecurityException; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Supports Fast Pair pairing with certain Bluetooth headphones, Auto, etc. - * - *

    Based on https://developers.google.com/nearby/fast-pair/spec, the pairing is constructed by - * both BLE and BREDR connections. Example state transitions for Fast Pair 2, ie a pairing key is - * included in the request (note: timeouts and retries are governed by flags, may change): - * - *

    - * {@code
    - *   Connect GATT
    - *     A) Success -> Handshake
    - *     B) Failure (3s timeout) -> Retry 2x -> end
    - *
    - *   Handshake
    - *     A) Generate a shared secret with the headset (either using anti-spoofing key or account key)
    - *       1) Account key is used directly as the key
    - *       2) Anti-spoofing key is used by combining out private key with the headset's public and
    - *          sending our public to the headset to combine with their private to generate a shared
    - *          key. Sending our public key to headset takes ~3s.
    - *     B) Write an encrypted packet to the headset containing their BLE address for verification
    - *        that both sides have the same key (headset decodes this packet and checks it against their
    - *        own address) (~250ms).
    - *     C) Receive a response from the headset containing their public address (~250ms).
    - *
    - *   Discovery (for devices < Oreo)
    - *     A) Success -> Create Bond
    - *     B) Failure (10s timeout) -> Sleep 1s, Retry 3x -> end
    - *
    - *   Connect to device
    - *     A) If already bonded
    - *       1) Attempt directly connecting to supported profiles (A2DP, etc)
    - *         a) Success -> Write Account Key
    - *         b) Failure (15s timeout, usually fails within a ~2s) -> Remove bond (~1s) -> Create bond
    - *     B) If not already bonded
    - *       1) Create bond
    - *         a) Success -> Connect profile
    - *         b) Failure (15s timeout) -> Retry 2x -> end
    - *       2) Connect profile
    - *         a) Success -> Write account key
    - *         b) Failure -> Retry -> end
    - *
    - *   Write account key
    - *     A) Callback that pairing succeeded
    - *     B) Disconnect GATT
    - *     C) Reconnect GATT for secure connection
    - *     D) Write account key (~3s)
    - * }
    - * 
    - * - * The performance profiling result by {@link TimingLogger}: - * - *
    - *   FastPairDualConnection [Exclusive time] / [Total time] ([Timestamp])
    - *     Connect GATT #1 3054ms (0)
    - *     Handshake 32ms / 740ms (3054)
    - *       Generate key via ECDH 10ms (3054)
    - *       Add salt 1ms (3067)
    - *       Encrypt request 3ms (3068)
    - *       Write data to GATT 692ms (3097)
    - *       Wait response from GATT 0ms (3789)
    - *       Decrypt response 2ms (3789)
    - *     Get BR/EDR handover information via SDP 1ms (3795)
    - *     Pair device #1 6ms / 4887ms (3805)
    - *       Create bond 3965ms / 4881ms (3809)
    - *         Exchange passkey 587ms / 915ms (7124)
    - *           Encrypt passkey 6ms (7694)
    - *           Send passkey to remote 290ms (7700)
    - *           Wait for remote passkey 0ms (7993)
    - *           Decrypt passkey 18ms (7994)
    - *           Confirm the pairing: true 14ms (8025)
    - *         Close BondedReceiver 1ms (8688)
    - *     Connect: A2DP 19ms / 370ms (8701)
    - *       Wait connection 348ms / 349ms (8720)
    - *         Close ConnectedReceiver 1ms (9068)
    - *       Close profile: A2DP 2ms (9069)
    - *     Write account key 2ms / 789ms (9163)
    - *       Encrypt key 0ms (9164)
    - *       Write key via GATT #1 777ms / 783ms (9164)
    - *         Close GATT 6ms (9941)
    - *       Start CloudSyncing 2ms (9947)
    - *       Broadcast Validator 2ms (9949)
    - *   FastPairDualConnection end, 9952ms
    - * 
    - */ -// TODO(b/203441105): break down FastPairDualConnection into smaller classes. -public class FastPairDualConnection extends FastPairConnection { - - private static final String TAG = FastPairDualConnection.class.getSimpleName(); - - @VisibleForTesting - static final int GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST = 10000; - @VisibleForTesting - static final int GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED = 20000; - @VisibleForTesting - static final int GATT_ERROR_CODE_USER_RETRY = 30000; - @VisibleForTesting - static final int GATT_ERROR_CODE_PAIR_WITH_SAME_MODEL_ID_COUNT = 40000; - @VisibleForTesting - static final int GATT_ERROR_CODE_TIMEOUT = 1000; - - @Nullable - private static String sInitialConnectionFirmwareVersion; - private static final byte[] REQUESTED_SERVICES_LTV = - new Ltv( - TransportDiscoveryService.SERVICE_UUIDS_16_BIT_LIST_TYPE, - toBytes( - ByteOrder.LITTLE_ENDIAN, - Constants.A2DP_SINK_SERVICE_UUID, - Constants.HANDS_FREE_SERVICE_UUID, - Constants.HEADSET_SERVICE_UUID)) - .getBytes(); - private static final byte[] TDS_CONTROL_POINT_REQUEST = - concat( - new byte[]{ - TransportDiscoveryService.ControlPointCharacteristic - .ACTIVATE_TRANSPORT_OP_CODE, - TransportDiscoveryService.BLUETOOTH_SIG_ORGANIZATION_ID - }, - REQUESTED_SERVICES_LTV); - - private static boolean sTestMode = false; - - static void enableTestMode() { - sTestMode = true; - } - - /** - * Operation Result Code. - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - value = { - ResultCode.UNKNOWN, - ResultCode.SUCCESS, - ResultCode.OP_CODE_NOT_SUPPORTED, - ResultCode.INVALID_PARAMETER, - ResultCode.UNSUPPORTED_ORGANIZATION_ID, - ResultCode.OPERATION_FAILED, - }) - - public @interface ResultCode { - - int UNKNOWN = (byte) 0xFF; - int SUCCESS = (byte) 0x00; - int OP_CODE_NOT_SUPPORTED = (byte) 0x01; - int INVALID_PARAMETER = (byte) 0x02; - int UNSUPPORTED_ORGANIZATION_ID = (byte) 0x03; - int OPERATION_FAILED = (byte) 0x04; - } - - - private static @ResultCode int fromTdsControlPointIndication(byte[] response) { - return response == null || response.length < 2 ? ResultCode.UNKNOWN : from(response[1]); - } - - private static @ResultCode int from(byte byteValue) { - switch (byteValue) { - case ResultCode.UNKNOWN: - case ResultCode.SUCCESS: - case ResultCode.OP_CODE_NOT_SUPPORTED: - case ResultCode.INVALID_PARAMETER: - case ResultCode.UNSUPPORTED_ORGANIZATION_ID: - case ResultCode.OPERATION_FAILED: - return byteValue; - default: - return ResultCode.UNKNOWN; - } - } - - private static class BrEdrHandoverInformation { - - private final byte[] mBluetoothAddress; - private final short[] mProfiles; - - private BrEdrHandoverInformation(byte[] bluetoothAddress, short[] profiles) { - this.mBluetoothAddress = bluetoothAddress; - - // For now, since we only connect to one profile, prefer A2DP Sink over headset/HFP. - // TODO(b/37167120): Connect to more than one profile. - Set profileSet = new HashSet<>(Shorts.asList(profiles)); - if (profileSet.contains(Constants.A2DP_SINK_SERVICE_UUID)) { - profileSet.remove(Constants.HEADSET_SERVICE_UUID); - profileSet.remove(Constants.HANDS_FREE_SERVICE_UUID); - } - this.mProfiles = Shorts.toArray(profileSet); - } - - @Override - public String toString() { - return "BrEdrHandoverInformation{" - + maskBluetoothAddress(BluetoothAddress.encode(mBluetoothAddress)) - + ", profiles=" - + (mProfiles.length > 0 ? Shorts.join(",", mProfiles) : "(none)") - + "}"; - } - } - - private final Context mContext; - private final Preferences mPreferences; - private final EventLoggerWrapper mEventLogger; - private final BluetoothAdapter mBluetoothAdapter = - checkNotNull(BluetoothAdapter.getDefaultAdapter()); - private String mBleAddress; - - private final TimingLogger mTimingLogger; - private GattConnectionManager mGattConnectionManager; - private boolean mProviderInitiatesBonding; - private @Nullable - byte[] mPairingSecret; - private @Nullable - byte[] mPairingKey; - @Nullable - private String mPublicAddress; - @VisibleForTesting - @Nullable - FastPairHistoryFinder mPairedHistoryFinder; - @Nullable - private String mProviderDeviceName = null; - private boolean mNeedUpdateProviderName = false; - @Nullable - DeviceNameReceiver mDeviceNameReceiver; - @Nullable - private HandshakeHandler mHandshakeHandlerForTest; - @Nullable - private Runnable mBeforeDirectlyConnectProfileFromCacheForTest; - - public FastPairDualConnection( - Context context, - String bleAddress, - Preferences preferences, - @Nullable EventLogger eventLogger) { - this(context, bleAddress, preferences, eventLogger, - new TimingLogger("FastPairDualConnection", preferences)); - } - - @VisibleForTesting - FastPairDualConnection( - Context context, - String bleAddress, - Preferences preferences, - @Nullable EventLogger eventLogger, - TimingLogger timingLogger) { - this.mContext = context; - this.mPreferences = preferences; - this.mEventLogger = new EventLoggerWrapper(eventLogger); - this.mBleAddress = bleAddress; - this.mTimingLogger = timingLogger; - } - - /** - * Unpairs with headphones. Synchronous: Blocks until unpaired. Throws on any error. - */ - @WorkerThread - public void unpair(BluetoothDevice device) - throws ReflectionException, InterruptedException, ExecutionException, TimeoutException, - PairingException { - if (mPreferences.getExtraLoggingInformation() != null) { - mEventLogger - .bind(mContext, device.getAddress(), mPreferences.getExtraLoggingInformation()); - } - new BluetoothAudioPairer( - mContext, - device, - mPreferences, - mEventLogger, - /* keyBasedPairingInfo= */ null, - /* passkeyConfirmationHandler= */ null, - mTimingLogger) - .unpair(); - if (mEventLogger.isBound()) { - mEventLogger.unbind(mContext); - } - } - - /** - * Sets the fast pair history for identifying the provider which has paired (without being - * forgotten) with the primary account on the device, i.e. the history is not limited on this - * phone, can be on other phones with the same account. If they have already paired, Fast Pair - * should not generate new account key and default personalized name for it after initial pair. - */ - @WorkerThread - public void setFastPairHistory(List fastPairHistoryItem) { - Log.i(TAG, "Paired history has been set."); - this.mPairedHistoryFinder = new FastPairHistoryFinder(fastPairHistoryItem); - } - - /** - * Update the provider device name when we take provider default name and account based name - * into consideration. - */ - public void setProviderDeviceName(String deviceName) { - Log.i(TAG, "Update provider device name = " + deviceName); - mProviderDeviceName = deviceName; - mNeedUpdateProviderName = true; - } - - /** - * Gets the device name from the Provider (via GATT notify). - */ - @Nullable - public String getProviderDeviceName() { - if (mDeviceNameReceiver == null) { - Log.i(TAG, "getProviderDeviceName failed, deviceNameReceiver == null."); - return null; - } - if (mPairingSecret == null) { - Log.i(TAG, "getProviderDeviceName failed, pairingSecret == null."); - return null; - } - String deviceName = mDeviceNameReceiver.getParsedResult(mPairingSecret); - Log.i(TAG, "getProviderDeviceName = " + deviceName); - - return deviceName; - } - - /** - * Get the existing account key of the provider, this API can be called after handshake. - * - * @return the existing account key if the provider has paired with the account before. - * Otherwise, return null, i.e. it is a real initial pairing. - */ - @WorkerThread - @Nullable - public byte[] getExistingAccountKey() { - return mPairedHistoryFinder == null ? null : mPairedHistoryFinder.getExistingAccountKey(); - } - - /** - * Pairs with headphones. Synchronous: Blocks until paired and connected. Throws on any error. - * - * @return the secret key for the user's account, if written. - */ - @WorkerThread - @Nullable - public SharedSecret pair() - throws BluetoothException, InterruptedException, ReflectionException, TimeoutException, - ExecutionException, PairingException { - try { - return pair(/*key=*/ null); - } catch (GeneralSecurityException e) { - throw new RuntimeException("Should never happen, no security key!", e); - } - } - - /** - * Pairs with headphones. Synchronous: Blocks until paired and connected. Throws on any error. - * - * @param key can be in two different formats. If it is 16 bytes long, then it is an AES account - * key. Otherwise, it's a public key generated by {@link EllipticCurveDiffieHellmanExchange}. - * See go/fast-pair-2-spec for how each of these keys are used. - * @return the secret key for the user's account, if written - */ - @WorkerThread - @Nullable - public SharedSecret pair(@Nullable byte[] key) - throws BluetoothException, InterruptedException, ReflectionException, TimeoutException, - ExecutionException, PairingException, GeneralSecurityException { - mPairingKey = key; - if (key != null) { - Log.i(TAG, "Starting to pair " + maskBluetoothAddress(mBleAddress) + ": key[" - + key.length + "], " + mPreferences); - } else { - Log.i(TAG, "Pairing " + maskBluetoothAddress(mBleAddress) + ": " + mPreferences); - } - if (mPreferences.getExtraLoggingInformation() != null) { - this.mEventLogger.bind( - mContext, mBleAddress, mPreferences.getExtraLoggingInformation()); - } - // Provider never initiates if key is null (Fast Pair 1.0). - if (key != null && mPreferences.getProviderInitiatesBondingIfSupported()) { - // Provider can't initiate if we can't get our own public address, so check. - this.mEventLogger.setCurrentEvent(EventCode.GET_LOCAL_PUBLIC_ADDRESS); - if (BluetoothAddress.getPublicAddress(mContext) != null) { - this.mEventLogger.logCurrentEventSucceeded(); - mProviderInitiatesBonding = true; - } else { - this.mEventLogger - .logCurrentEventFailed(new IllegalStateException("null bluetooth_address")); - Log.e(TAG, - "Want provider to initiate bonding, but cannot access Bluetooth public " - + "address. Falling back to initiating bonding ourselves."); - } - } - - // User might be pairing with a bonded device. In this case, we just connect profile - // directly and finish pairing. - if (directConnectProfileWithCachedAddress()) { - callbackOnPaired(); - mTimingLogger.dump(); - if (mEventLogger.isBound()) { - mEventLogger.unbind(mContext); - } - return null; - } - - // Lazily initialize a new connection manager for each pairing request. - initGattConnectionManager(); - boolean isSecretHandshakeCompleted = true; - - try { - if (key != null && key.length > 0) { - // GATT_CONNECTION_AND_SECRET_HANDSHAKE start. - mEventLogger.setCurrentEvent(EventCode.GATT_CONNECTION_AND_SECRET_HANDSHAKE); - isSecretHandshakeCompleted = false; - Exception lastException = null; - boolean lastExceptionFromHandshake = false; - long startTime = SystemClock.elapsedRealtime(); - // We communicate over this connection twice for Key-based Pairing: once before - // bonding begins, and once during (to transfer the passkey). Empirically, keeping - // it alive throughout is far more reliable than disconnecting and reconnecting for - // each step. The while loop is for retry of GATT connection and handshake only. - do { - boolean isHandshaking = false; - try (BluetoothGattConnection connection = - mGattConnectionManager - .getConnectionWithSignalLostCheck(mRescueFromError)) { - mEventLogger.setCurrentEvent(EventCode.SECRET_HANDSHAKE); - if (lastException != null && !lastExceptionFromHandshake) { - logRetrySuccessEvent(EventCode.RECOVER_BY_RETRY_GATT, lastException, - mEventLogger); - lastException = null; - } - try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, - "Handshake")) { - isHandshaking = true; - handshakeForKeyBasedPairing(key); - // After handshake, Fast Pair has the public address of the provider, so - // we can check if it has paired with the account. - if (mPublicAddress != null && mPairedHistoryFinder != null) { - if (mPairedHistoryFinder.isInPairedHistory(mPublicAddress)) { - Log.i(TAG, "The provider is found in paired history."); - } else { - Log.i(TAG, "The provider is not found in paired history."); - } - } - } - isHandshaking = false; - // SECRET_HANDSHAKE end. - mEventLogger.logCurrentEventSucceeded(); - isSecretHandshakeCompleted = true; - if (mPrepareCreateBondCallback != null) { - mPrepareCreateBondCallback.run(); - } - if (lastException != null && lastExceptionFromHandshake) { - logRetrySuccessEvent(EventCode.RECOVER_BY_RETRY_HANDSHAKE_RECONNECT, - lastException, mEventLogger); - } - logManualRetryCounts(/* success= */ true); - // GATT_CONNECTION_AND_SECRET_HANDSHAKE end. - mEventLogger.logCurrentEventSucceeded(); - return pair(mPreferences.getEnableBrEdrHandover()); - } catch (SignalLostException e) { - long spentTime = SystemClock.elapsedRealtime() - startTime; - if (spentTime > mPreferences.getAddressRotateRetryMaxSpentTimeMs()) { - Log.w(TAG, "Signal lost but already spend too much time " + spentTime - + "ms"); - throw e; - } - - logCurrentEventFailedBySignalLost(e); - lastException = (Exception) e.getCause(); - lastExceptionFromHandshake = isHandshaking; - if (mRescueFromError != null && isHandshaking) { - mRescueFromError.accept(ErrorCode.SUCCESS_SECRET_HANDSHAKE_RECONNECT); - } - Log.i(TAG, "Signal lost, retry"); - // In case we meet some GATT error which is not recoverable and fail very - // quick. - SystemClock.sleep(mPreferences.getPairingRetryDelayMs()); - } catch (SignalRotatedException e) { - long spentTime = SystemClock.elapsedRealtime() - startTime; - if (spentTime > mPreferences.getAddressRotateRetryMaxSpentTimeMs()) { - Log.w(TAG, "Address rotated but already spend too much time " - + spentTime + "ms"); - throw e; - } - - logCurrentEventFailedBySignalRotated(e); - setBleAddress(e.getNewAddress()); - lastException = (Exception) e.getCause(); - lastExceptionFromHandshake = isHandshaking; - if (mRescueFromError != null) { - mRescueFromError.accept(ErrorCode.SUCCESS_ADDRESS_ROTATE); - } - Log.i(TAG, "Address rotated, retry"); - } catch (HandshakeException e) { - long spentTime = SystemClock.elapsedRealtime() - startTime; - if (spentTime > mPreferences - .getSecretHandshakeRetryGattConnectionMaxSpentTimeMs()) { - Log.w(TAG, "Secret handshake failed but already spend too much time " - + spentTime + "ms"); - throw e.getOriginalException(); - } - if (mEventLogger.isCurrentEvent()) { - mEventLogger.logCurrentEventFailed(e.getOriginalException()); - } - initGattConnectionManager(); - lastException = e.getOriginalException(); - lastExceptionFromHandshake = true; - if (mRescueFromError != null) { - mRescueFromError.accept(ErrorCode.SUCCESS_SECRET_HANDSHAKE_RECONNECT); - } - Log.i(TAG, "Handshake failed, retry GATT connection"); - } - } while (mPreferences.getRetryGattConnectionAndSecretHandshake()); - } - if (mPrepareCreateBondCallback != null) { - mPrepareCreateBondCallback.run(); - } - return pair(mPreferences.getEnableBrEdrHandover()); - } catch (SignalLostException e) { - logCurrentEventFailedBySignalLost(e); - // GATT_CONNECTION_AND_SECRET_HANDSHAKE end. - if (!isSecretHandshakeCompleted) { - logManualRetryCounts(/* success= */ false); - logCurrentEventFailedBySignalLost(e); - } - throw e; - } catch (SignalRotatedException e) { - logCurrentEventFailedBySignalRotated(e); - // GATT_CONNECTION_AND_SECRET_HANDSHAKE end. - if (!isSecretHandshakeCompleted) { - logManualRetryCounts(/* success= */ false); - logCurrentEventFailedBySignalRotated(e); - } - throw e; - } catch (BluetoothException - | InterruptedException - | ReflectionException - | TimeoutException - | ExecutionException - | PairingException - | GeneralSecurityException e) { - if (mEventLogger.isCurrentEvent()) { - mEventLogger.logCurrentEventFailed(e); - } - // GATT_CONNECTION_AND_SECRET_HANDSHAKE end. - if (!isSecretHandshakeCompleted) { - logManualRetryCounts(/* success= */ false); - if (mEventLogger.isCurrentEvent()) { - mEventLogger.logCurrentEventFailed(e); - } - } - throw e; - } finally { - mTimingLogger.dump(); - if (mEventLogger.isBound()) { - mEventLogger.unbind(mContext); - } - } - } - - private boolean directConnectProfileWithCachedAddress() throws ReflectionException { - if (TextUtils.isEmpty(mPreferences.getCachedDeviceAddress()) - || !mPreferences.getDirectConnectProfileIfModelIdInCache() - || mPreferences.getSkipConnectingProfiles()) { - return false; - } - Log.i(TAG, "Try to direct connect profile with cached address " - + maskBluetoothAddress(mPreferences.getCachedDeviceAddress())); - mEventLogger.setCurrentEvent(EventCode.DIRECTLY_CONNECT_PROFILE_WITH_CACHED_ADDRESS); - BluetoothDevice device = - mBluetoothAdapter.getRemoteDevice(mPreferences.getCachedDeviceAddress()).unwrap(); - AtomicBoolean interruptConnection = new AtomicBoolean(false); - BroadcastReceiver receiver = - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent == null - || !BluetoothDevice.ACTION_PAIRING_REQUEST - .equals(intent.getAction())) { - return; - } - BluetoothDevice pairingDevice = intent - .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (pairingDevice == null || !device.getAddress() - .equals(pairingDevice.getAddress())) { - return; - } - abortBroadcast(); - // Should be the clear link key case, make it fail directly to go back to - // initial pairing process. - pairingDevice.setPairingConfirmation(/* confirm= */ false); - Log.w(TAG, "Get pairing request broadcast for device " - + maskBluetoothAddress(device.getAddress()) - + " while try to direct connect profile with cached address, reject" - + " and to go back to initial pairing process"); - interruptConnection.set(true); - } - }; - mContext.registerReceiver(receiver, - new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST)); - try (ScopedTiming scopedTiming = - new ScopedTiming(mTimingLogger, - "Connect to profile with cached address directly")) { - if (mBeforeDirectlyConnectProfileFromCacheForTest != null) { - mBeforeDirectlyConnectProfileFromCacheForTest.run(); - } - attemptConnectProfiles( - new BluetoothAudioPairer( - mContext, - device, - mPreferences, - mEventLogger, - /* keyBasedPairingInfo= */ null, - /* passkeyConfirmationHandler= */ null, - mTimingLogger), - maskBluetoothAddress(device), - getSupportedProfiles(device), - /* numConnectionAttempts= */ 1, - /* enablePairingBehavior= */ false, - interruptConnection); - Log.i(TAG, - "Directly connected to " + maskBluetoothAddress(device) - + "with cached address."); - mEventLogger.logCurrentEventSucceeded(); - mEventLogger.setDevice(device); - logPairWithPossibleCachedAddress(device.getAddress()); - return true; - } catch (PairingException e) { - if (interruptConnection.get()) { - Log.w(TAG, "Fail to connected to " + maskBluetoothAddress(device) - + " with cached address due to link key is cleared.", e); - mEventLogger.logCurrentEventFailed( - new ConnectException(ConnectErrorCode.LINK_KEY_CLEARED, - "Link key is cleared")); - } else { - Log.w(TAG, "Fail to connected to " + maskBluetoothAddress(device) - + " with cached address.", e); - mEventLogger.logCurrentEventFailed(e); - } - return false; - } finally { - mContext.unregisterReceiver(receiver); - } - } - - /** - * Logs for user retry, check go/fastpairquality21q3 for more details. - */ - private void logManualRetryCounts(boolean success) { - if (!mPreferences.getLogUserManualRetry()) { - return; - } - - // We don't want to be the final event on analytics. - if (!mEventLogger.isCurrentEvent()) { - return; - } - - mEventLogger.setCurrentEvent(EventCode.GATT_HANDSHAKE_MANUAL_RETRY_ATTEMPTS); - if (mPreferences.getPairFailureCounts() <= 0 && success) { - mEventLogger.logCurrentEventSucceeded(); - } else { - int errorCode = mPreferences.getPairFailureCounts(); - if (errorCode > 99) { - errorCode = 99; - } - errorCode += success ? 0 : 100; - // To not conflict with current error codes. - errorCode += GATT_ERROR_CODE_USER_RETRY; - mEventLogger.logCurrentEventFailed( - new BluetoothGattException("Error for manual retry", errorCode)); - } - } - - static void logRetrySuccessEvent( - @EventCode int eventCode, - @Nullable Exception recoverFromException, - EventLoggerWrapper eventLogger) { - if (recoverFromException == null) { - return; - } - eventLogger.setCurrentEvent(eventCode); - eventLogger.logCurrentEventFailed(recoverFromException); - } - - private void initGattConnectionManager() { - mGattConnectionManager = - new GattConnectionManager( - mContext, - mPreferences, - mEventLogger, - mBluetoothAdapter, - this::toggleBluetooth, - mBleAddress, - mTimingLogger, - mFastPairSignalChecker, - isPairingWithAntiSpoofingPublicKey()); - } - - private void logCurrentEventFailedBySignalRotated(SignalRotatedException e) { - if (!mEventLogger.isCurrentEvent()) { - return; - } - - Log.w(TAG, "BLE Address for pairing device might rotated!"); - mEventLogger.logCurrentEventFailed( - new BluetoothGattException( - "BLE Address for pairing device might rotated", - appendMoreErrorCode(GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED, - e.getCause()), - e)); - } - - private void logCurrentEventFailedBySignalLost(SignalLostException e) { - if (!mEventLogger.isCurrentEvent()) { - return; - } - - Log.w(TAG, "BLE signal for pairing device might lost!"); - mEventLogger.logCurrentEventFailed( - new BluetoothGattException( - "BLE signal for pairing device might lost", - appendMoreErrorCode(GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST, e.getCause()), - e)); - } - - @VisibleForTesting - static int appendMoreErrorCode(int masterErrorCode, @Nullable Throwable cause) { - if (cause instanceof BluetoothGattException) { - return masterErrorCode + ((BluetoothGattException) cause).getGattErrorCode(); - } else if (cause instanceof TimeoutException - || cause instanceof BluetoothTimeoutException - || cause instanceof BluetoothOperationTimeoutException) { - return masterErrorCode + GATT_ERROR_CODE_TIMEOUT; - } else { - return masterErrorCode; - } - } - - private void setBleAddress(String newAddress) { - if (TextUtils.isEmpty(newAddress) || Ascii.equalsIgnoreCase(newAddress, mBleAddress)) { - return; - } - - mBleAddress = newAddress; - - // Recreates a GattConnectionManager with the new address for establishing a new GATT - // connection later. - initGattConnectionManager(); - - mEventLogger.setDevice(mBluetoothAdapter.getRemoteDevice(mBleAddress).unwrap()); - } - - /** - * Gets the public address of the headset used in the connection. Before the handshake, this - * could be null. - */ - @Nullable - public String getPublicAddress() { - return mPublicAddress; - } - - /** - * Pairs with a Bluetooth device. In general, this process goes through the following steps: - * - *
      - *
    1. Get BrEdr handover information if requested - *
    2. Discover the device (on Android N and lower to work around a bug) - *
    3. Connect to the device - *
        - *
      • Attempt a direct connection to a supported profile if we're already bonded - *
      • Create a new bond with the not bonded device and then connect to a supported - * profile - *
      - *
    4. Write the account secret - *
    - * - *

    Blocks until paired. May take 10+ seconds, so run on a background thread. - */ - @Nullable - private SharedSecret pair(boolean enableBrEdrHandover) - throws BluetoothException, InterruptedException, ReflectionException, TimeoutException, - ExecutionException, PairingException, GeneralSecurityException { - BrEdrHandoverInformation brEdrHandoverInformation = null; - if (enableBrEdrHandover) { - try (ScopedTiming scopedTiming = - new ScopedTiming(mTimingLogger, "Get BR/EDR handover information via GATT")) { - brEdrHandoverInformation = - getBrEdrHandoverInformation(mGattConnectionManager.getConnection()); - } catch (BluetoothException | TdsException e) { - Log.w(TAG, - "Couldn't get BR/EDR Handover info via TDS. Trying direct connect.", e); - mEventLogger.logCurrentEventFailed(e); - } - } - - if (brEdrHandoverInformation == null) { - // Pair directly to the BLE address. Works if the BLE and Bluetooth Classic addresses - // are the same, or if we can do BLE cross-key transport. - brEdrHandoverInformation = - new BrEdrHandoverInformation( - BluetoothAddress - .decode(mPublicAddress != null ? mPublicAddress : mBleAddress), - attemptGetBluetoothClassicProfiles( - mBluetoothAdapter.getRemoteDevice(mBleAddress).unwrap(), - mPreferences.getNumSdpAttempts())); - } - - BluetoothDevice device = - mBluetoothAdapter.getRemoteDevice(brEdrHandoverInformation.mBluetoothAddress) - .unwrap(); - callbackOnGetAddress(device.getAddress()); - mEventLogger.setDevice(device); - - Log.i(TAG, "Pairing with " + brEdrHandoverInformation); - KeyBasedPairingInfo keyBasedPairingInfo = - mPairingSecret == null - ? null - : new KeyBasedPairingInfo( - mPairingSecret, mGattConnectionManager, mProviderInitiatesBonding); - - BluetoothAudioPairer pairer = - new BluetoothAudioPairer( - mContext, - device, - mPreferences, - mEventLogger, - keyBasedPairingInfo, - mPasskeyConfirmationHandler, - mTimingLogger); - - logPairWithPossibleCachedAddress(device.getAddress()); - logPairWithModelIdInCacheAndDiscoveryFailForCachedAddress(device); - - // In the case where we are already bonded, we should first just try connecting to supported - // profiles. If successful, then this will be much faster than recreating the bond like we - // normally do and we can finish early. It is also more reliable than tearing down the bond - // and recreating it. - try { - if (!sTestMode) { - attemptDirectConnectionIfBonded(device, pairer); - } - callbackOnPaired(); - return maybeWriteAccountKey(device); - } catch (PairingException e) { - Log.i(TAG, "Failed to directly connect to supported profiles: " + e.getMessage()); - // Catches exception when we fail to connect support profile. And makes the flow to go - // through step to write account key when device is bonded. - if (mPreferences.getEnablePairFlowShowUiWithoutProfileConnection() - && device.getBondState() == BluetoothDevice.BOND_BONDED) { - if (mPreferences.getSkipConnectingProfiles() - && !mPreferences.getCheckBondStateWhenSkipConnectingProfiles()) { - Log.i(TAG, "For notCheckBondStateWhenSkipConnectingProfiles case should do " - + "re-bond"); - } else { - Log.i(TAG, "Fail to connect profile when device is bonded, still call back on" - + "pair callback to show ui"); - callbackOnPaired(); - return maybeWriteAccountKey(device); - } - } - } - - if (mPreferences.getMoreEventLogForQuality()) { - switch (device.getBondState()) { - case BOND_BONDED: - mEventLogger.setCurrentEvent(EventCode.BEFORE_CREATE_BOND_BONDED); - break; - case BOND_BONDING: - mEventLogger.setCurrentEvent(EventCode.BEFORE_CREATE_BOND_BONDING); - break; - case BOND_NONE: - default: - mEventLogger.setCurrentEvent(EventCode.BEFORE_CREATE_BOND); - } - } - - for (int i = 1; i <= mPreferences.getNumCreateBondAttempts(); i++) { - try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Pair device #" + i)) { - pairer.pair(); - if (mPreferences.getMoreEventLogForQuality()) { - // For EventCode.BEFORE_CREATE_BOND - mEventLogger.logCurrentEventSucceeded(); - } - break; - } catch (Exception e) { - mEventLogger.logCurrentEventFailed(e); - if (mPasskeyIsGotten) { - Log.w(TAG, - "createBond() failed because of " + e.getMessage() - + " after getting the passkey. Skip retry."); - if (mPreferences.getMoreEventLogForQuality()) { - // For EventCode.BEFORE_CREATE_BOND - mEventLogger.logCurrentEventFailed( - new CreateBondException( - CreateBondErrorCode.FAILED_BUT_ALREADY_RECEIVE_PASS_KEY, - 0, - "Already get the passkey")); - } - break; - } - Log.e(TAG, - "removeBond() or createBond() failed, attempt " + i + " of " + mPreferences - .getNumCreateBondAttempts() + ". Bond state " - + device.getBondState(), e); - if (i < mPreferences.getNumCreateBondAttempts()) { - toggleBluetooth(); - - // We've seen 3 createBond() failures within 100ms (!). And then success again - // later (even without turning on/off bluetooth). So create some minimum break - // time. - Log.i(TAG, "Sleeping 1 sec after createBond() failure."); - SystemClock.sleep(1000); - } else if (mPreferences.getMoreEventLogForQuality()) { - // For EventCode.BEFORE_CREATE_BOND - mEventLogger.logCurrentEventFailed(e); - } - } - } - boolean deviceCreateBondFailWithNullSecret = false; - if (!pairer.isPaired()) { - if (mPairingSecret != null) { - // Bonding could fail for a few different reasons here. It could be an error, an - // attacker may have tried to bond, or the device may not be up to spec. - throw new PairingException("createBond() failed, exiting connection process."); - } else if (mPreferences.getSkipConnectingProfiles()) { - throw new PairingException( - "createBond() failed and skipping connecting to a profile."); - } else { - // When bond creation has failed, connecting a profile will still work most of the - // time for Fast Pair 1.0 devices (ie, pairing secret is null), so continue on with - // the spec anyways and attempt to connect supported profiles. - Log.w(TAG, "createBond() failed, will try connecting profiles anyway."); - deviceCreateBondFailWithNullSecret = true; - } - } else if (mPreferences.getEnablePairFlowShowUiWithoutProfileConnection()) { - Log.i(TAG, "new flow to call on paired callback for ui when pairing step is finished"); - callbackOnPaired(); - } - - if (!mPreferences.getSkipConnectingProfiles()) { - if (mPreferences.getWaitForUuidsAfterBonding() - && brEdrHandoverInformation.mProfiles.length == 0) { - short[] supportedProfiles = getCachedUuids(device); - if (supportedProfiles.length == 0 - && mPreferences.getNumSdpAttemptsAfterBonded() > 0) { - Log.i(TAG, "Found no supported profiles in UUID cache, manually trigger SDP."); - attemptGetBluetoothClassicProfiles(device, - mPreferences.getNumSdpAttemptsAfterBonded()); - } - brEdrHandoverInformation = - new BrEdrHandoverInformation( - brEdrHandoverInformation.mBluetoothAddress, supportedProfiles); - } - short[] profiles = brEdrHandoverInformation.mProfiles; - if (profiles.length == 0) { - profiles = Constants.getSupportedProfiles(); - Log.w(TAG, - "Attempting to connect constants profiles, " + Arrays.toString(profiles)); - } else { - Log.i(TAG, "Attempting to connect device profiles, " + Arrays.toString(profiles)); - } - - try { - attemptConnectProfiles( - pairer, - maskBluetoothAddress(device), - profiles, - mPreferences.getNumConnectAttempts(), - /* enablePairingBehavior= */ false); - } catch (PairingException e) { - // For new pair flow to show ui, we already show success ui when finishing the - // createBond step. So we should catch the exception from connecting profile to - // avoid showing fail ui for user. - if (mPreferences.getEnablePairFlowShowUiWithoutProfileConnection() - && !deviceCreateBondFailWithNullSecret) { - Log.i(TAG, "Fail to connect profile when device is bonded"); - } else { - throw e; - } - } - } - if (!mPreferences.getEnablePairFlowShowUiWithoutProfileConnection()) { - Log.i(TAG, "original flow to call on paired callback for ui"); - callbackOnPaired(); - } else if (deviceCreateBondFailWithNullSecret) { - // This paired callback is called for device which create bond fail with null secret - // such as FastPair 1.0 device when directly connecting to any supported profile. - Log.i(TAG, "call on paired callback for ui for device with null secret without bonded " - + "state"); - callbackOnPaired(); - } - if (mPreferences.getEnableFirmwareVersionCharacteristic() - && validateBluetoothGattCharacteristic( - mGattConnectionManager.getConnection(), FirmwareVersionCharacteristic.ID)) { - try { - sInitialConnectionFirmwareVersion = readFirmwareVersion(); - } catch (BluetoothException e) { - Log.i(TAG, "Fast Pair: head phone does not support firmware read", e); - } - } - - // Catch exception when writing account key or name fail to avoid showing pairing failure - // notice for user. Because device is already paired successfully based on paring step. - SharedSecret secret = null; - try { - secret = maybeWriteAccountKey(device); - } catch (InterruptedException - | ExecutionException - | TimeoutException - | NoSuchAlgorithmException - | BluetoothException e) { - Log.w(TAG, "Fast Pair: Got exception when writing account key or name to provider", e); - } - - return secret; - } - - private void logPairWithPossibleCachedAddress(String brEdrAddressForBonding) { - if (TextUtils.isEmpty(mPreferences.getPossibleCachedDeviceAddress()) - || !mPreferences.getLogPairWithCachedModelId()) { - return; - } - mEventLogger.setCurrentEvent(EventCode.PAIR_WITH_CACHED_MODEL_ID); - if (Ascii.equalsIgnoreCase( - mPreferences.getPossibleCachedDeviceAddress(), brEdrAddressForBonding)) { - mEventLogger.logCurrentEventSucceeded(); - Log.i(TAG, "Repair with possible cached device " - + maskBluetoothAddress(mPreferences.getPossibleCachedDeviceAddress())); - } else { - mEventLogger.logCurrentEventFailed( - new PairingException("Pairing with 2nd device with same model ID")); - Log.i(TAG, "Pair with a new device " + maskBluetoothAddress(brEdrAddressForBonding) - + " with model ID in cache " - + maskBluetoothAddress(mPreferences.getPossibleCachedDeviceAddress())); - } - } - - /** - * Logs two type of events. First, why cachedAddress mechanism doesn't work if it's repair with - * bonded device case. Second, if it's not the case, log how many devices with the same model Id - * is already paired. - */ - private void logPairWithModelIdInCacheAndDiscoveryFailForCachedAddress(BluetoothDevice device) { - if (!mPreferences.getLogPairWithCachedModelId()) { - return; - } - - if (device.getBondState() == BOND_BONDED) { - if (mPreferences.getSameModelIdPairedDeviceCount() <= 0) { - Log.i(TAG, "Device is bonded but we don't have this model Id in cache."); - } else if (TextUtils.isEmpty(mPreferences.getCachedDeviceAddress()) - && mPreferences.getDirectConnectProfileIfModelIdInCache() - && !mPreferences.getSkipConnectingProfiles()) { - // Pair with bonded device case. Log why the cached address is not found. - mEventLogger.setCurrentEvent( - EventCode.DIRECTLY_CONNECT_PROFILE_WITH_CACHED_ADDRESS); - mEventLogger.logCurrentEventFailed( - mPreferences.getIsDeviceFinishCheckAddressFromCache() - ? new ConnectException(ConnectErrorCode.FAIL_TO_DISCOVERY, - "Failed to discovery") - : new ConnectException( - ConnectErrorCode.DISCOVERY_NOT_FINISHED, - "Discovery not finished")); - Log.i(TAG, "Failed to get cached address due to " - + (mPreferences.getIsDeviceFinishCheckAddressFromCache() - ? "Failed to discovery" - : "Discovery not finished")); - } - } else if (device.getBondState() == BOND_NONE) { - // Pair with new device case, log how many devices with the same model id is in FastPair - // cache already. - mEventLogger.setCurrentEvent(EventCode.PAIR_WITH_NEW_MODEL); - if (mPreferences.getSameModelIdPairedDeviceCount() <= 0) { - mEventLogger.logCurrentEventSucceeded(); - } else { - mEventLogger.logCurrentEventFailed( - new BluetoothGattException( - "Already have this model ID in cache", - GATT_ERROR_CODE_PAIR_WITH_SAME_MODEL_ID_COUNT - + mPreferences.getSameModelIdPairedDeviceCount())); - } - Log.i(TAG, "This device already has " + mPreferences.getSameModelIdPairedDeviceCount() - + " peripheral with the same model Id"); - } - } - - /** - * Attempts to directly connect to any supported profile if we're already bonded, this will save - * time over tearing down the bond and recreating it. - */ - private void attemptDirectConnectionIfBonded(BluetoothDevice device, - BluetoothAudioPairer pairer) - throws PairingException { - if (mPreferences.getSkipConnectingProfiles()) { - if (mPreferences.getCheckBondStateWhenSkipConnectingProfiles() - && device.getBondState() == BluetoothDevice.BOND_BONDED) { - Log.i(TAG, "Skipping connecting to profiles by preferences."); - return; - } - throw new PairingException( - "Skipping connecting to profiles, no direct connection possible."); - } else if (!mPreferences.getAttemptDirectConnectionWhenPreviouslyBonded() - || device.getBondState() != BluetoothDevice.BOND_BONDED) { - throw new PairingException( - "Not previously bonded skipping direct connection, %s", device.getBondState()); - } - short[] supportedProfiles = getSupportedProfiles(device); - mEventLogger.setCurrentEvent(EventCode.DIRECTLY_CONNECTED_TO_PROFILE); - try (ScopedTiming scopedTiming = - new ScopedTiming(mTimingLogger, "Connect to profile directly")) { - attemptConnectProfiles( - pairer, - maskBluetoothAddress(device), - supportedProfiles, - mPreferences.getEnablePairFlowShowUiWithoutProfileConnection() - ? mPreferences.getNumConnectAttempts() - : 1, - mPreferences.getEnablePairingWhileDirectlyConnecting()); - Log.i(TAG, "Directly connected to " + maskBluetoothAddress(device)); - mEventLogger.logCurrentEventSucceeded(); - } catch (PairingException e) { - mEventLogger.logCurrentEventFailed(e); - // Rethrow e so that the exception bubbles up and we continue the normal pairing - // process. - throw e; - } - } - - @VisibleForTesting - void attemptConnectProfiles( - BluetoothAudioPairer pairer, - String deviceMaskedBluetoothAddress, - short[] profiles, - int numConnectionAttempts, - boolean enablePairingBehavior) - throws PairingException { - attemptConnectProfiles( - pairer, - deviceMaskedBluetoothAddress, - profiles, - numConnectionAttempts, - enablePairingBehavior, - new AtomicBoolean(false)); - } - - private void attemptConnectProfiles( - BluetoothAudioPairer pairer, - String deviceMaskedBluetoothAddress, - short[] profiles, - int numConnectionAttempts, - boolean enablePairingBehavior, - AtomicBoolean interruptConnection) - throws PairingException { - if (mPreferences.getMoreEventLogForQuality()) { - mEventLogger.setCurrentEvent(EventCode.BEFORE_CONNECT_PROFILE); - } - Exception lastException = null; - for (short profile : profiles) { - if (interruptConnection.get()) { - Log.w(TAG, "attemptConnectProfiles interrupted"); - break; - } - if (!mPreferences.isSupportedProfile(profile)) { - Log.w(TAG, "Ignoring unsupported profile=" + profile); - continue; - } - for (int i = 1; i <= numConnectionAttempts; i++) { - if (interruptConnection.get()) { - Log.w(TAG, "attemptConnectProfiles interrupted"); - break; - } - mEventLogger.setCurrentEvent(EventCode.CONNECT_PROFILE); - mEventLogger.setCurrentProfile(profile); - try { - pairer.connect(profile, enablePairingBehavior); - mEventLogger.logCurrentEventSucceeded(); - if (mPreferences.getMoreEventLogForQuality()) { - // For EventCode.BEFORE_CONNECT_PROFILE - mEventLogger.logCurrentEventSucceeded(); - } - // If successful, we're done. - // TODO(b/37167120): Connect to more than one profile. - return; - } catch (InterruptedException - | ReflectionException - | TimeoutException - | ExecutionException - | ConnectException e) { - Log.w(TAG, - "Error connecting to profile=" + profile - + " for device=" + deviceMaskedBluetoothAddress - + " (attempt " + i + " of " + mPreferences - .getNumConnectAttempts(), e); - mEventLogger.logCurrentEventFailed(e); - lastException = e; - } - } - } - if (mPreferences.getMoreEventLogForQuality()) { - // For EventCode.BEFORE_CONNECT_PROFILE - if (lastException != null) { - mEventLogger.logCurrentEventFailed(lastException); - } else { - mEventLogger.logCurrentEventSucceeded(); - } - } - throw new PairingException( - "Unable to connect to any profiles in: %s", Arrays.toString(profiles)); - } - - /** - * Checks whether or not an account key should be written to the device and writes it if so. - * This is called after handle notifying the pairedCallback that we've finished pairing, because - * at this point the headset is ready to use. - */ - @Nullable - private SharedSecret maybeWriteAccountKey(BluetoothDevice device) - throws InterruptedException, ExecutionException, TimeoutException, - NoSuchAlgorithmException, - BluetoothException { - if (!sTestMode) { - Locator.get(mContext, FastPairController.class).setShouldUpload(false); - } - if (!shouldWriteAccountKey()) { - // For FastPair 2.0, here should be a subsequent pairing case. - return null; - } - - // Check if it should be a subsequent pairing but go through initial pairing. If there is an - // existed paired history found, use the same account key instead of creating a new one. - byte[] accountKey = - mPairedHistoryFinder == null ? null : mPairedHistoryFinder.getExistingAccountKey(); - if (accountKey == null) { - // It is a real initial pairing, generate a new account key for the headset. - try (ScopedTiming scopedTiming1 = - new ScopedTiming(mTimingLogger, "Write account key")) { - accountKey = doWriteAccountKey(createAccountKey(), device.getAddress()); - if (accountKey == null) { - // Without writing account key back to provider, close the connection. - mGattConnectionManager.closeConnection(); - return null; - } - if (!mPreferences.getIsRetroactivePairing()) { - try (ScopedTiming scopedTiming2 = new ScopedTiming(mTimingLogger, - "Start CloudSyncing")) { - // Start to sync to the footprint - Locator.get(mContext, FastPairController.class).setShouldUpload(true); - //mContext.startService(createCloudSyncingIntent(accountKey)); - } catch (SecurityException e) { - Log.w(TAG, "Error adding device.", e); - } - } - } - } else if (shouldWriteAccountKeyForExistingCase(accountKey)) { - // There is an existing account key, but go through initial pairing, and still write the - // existing account key. - doWriteAccountKey(accountKey, device.getAddress()); - } - - // When finish writing account key in initial pairing, write new device name back to - // provider. - UUID characteristicUuid = NameCharacteristic.getId(mGattConnectionManager.getConnection()); - if (mPreferences.getEnableNamingCharacteristic() - && mNeedUpdateProviderName - && validateBluetoothGattCharacteristic( - mGattConnectionManager.getConnection(), characteristicUuid)) { - try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, - "WriteNameToProvider")) { - writeNameToProvider(this.mProviderDeviceName, device.getAddress()); - } - } - - // When finish writing account key and name back to provider, close the connection. - mGattConnectionManager.closeConnection(); - return SharedSecret.create(accountKey, device.getAddress()); - } - - private boolean shouldWriteAccountKey() { - return isWritingAccountKeyEnabled() && isPairingWithAntiSpoofingPublicKey(); - } - - private boolean isWritingAccountKeyEnabled() { - return mPreferences.getNumWriteAccountKeyAttempts() > 0; - } - - private boolean isPairingWithAntiSpoofingPublicKey() { - return isPairingWithAntiSpoofingPublicKey(mPairingKey); - } - - private boolean isPairingWithAntiSpoofingPublicKey(@Nullable byte[] key) { - return key != null && key.length == EllipticCurveDiffieHellmanExchange.PUBLIC_KEY_LENGTH; - } - - /** - * Creates and writes an account key to the provided mac address. - */ - @Nullable - private byte[] doWriteAccountKey(byte[] accountKey, String macAddress) - throws InterruptedException, ExecutionException, TimeoutException, BluetoothException { - byte[] localPairingSecret = mPairingSecret; - if (localPairingSecret == null) { - Log.w(TAG, "Pairing secret was null, account key couldn't be encrypted or written."); - return null; - } - if (!mPreferences.getSkipDisconnectingGattBeforeWritingAccountKey()) { - try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, - "Close GATT and sleep")) { - // Make a new connection instead of reusing gattConnection, because this is - // post-pairing and we need an encrypted connection. - mGattConnectionManager.closeConnection(); - // Sleep before re-connecting to gatt, for writing account key, could increase - // stability. - Thread.sleep(mPreferences.getWriteAccountKeySleepMillis()); - } - } - - byte[] encryptedKey; - try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Encrypt key")) { - encryptedKey = AesEcbSingleBlockEncryption.encrypt(localPairingSecret, accountKey); - } catch (GeneralSecurityException e) { - Log.w("Failed to encrypt key.", e); - return null; - } - - for (int i = 1; i <= mPreferences.getNumWriteAccountKeyAttempts(); i++) { - mEventLogger.setCurrentEvent(EventCode.WRITE_ACCOUNT_KEY); - try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, - "Write key via GATT #" + i)) { - writeAccountKey(encryptedKey, macAddress); - mEventLogger.logCurrentEventSucceeded(); - return accountKey; - } catch (BluetoothException e) { - Log.w("Error writing account key attempt " + i + " of " + mPreferences - .getNumWriteAccountKeyAttempts(), e); - mEventLogger.logCurrentEventFailed(e); - // Retry with a while for stability. - Thread.sleep(mPreferences.getWriteAccountKeySleepMillis()); - } - } - return null; - } - - private byte[] createAccountKey() throws NoSuchAlgorithmException { - return AccountKeyGenerator.createAccountKey(); - } - - @VisibleForTesting - boolean shouldWriteAccountKeyForExistingCase(byte[] existingAccountKey) { - if (!mPreferences.getKeepSameAccountKeyWrite()) { - Log.i(TAG, - "The provider has already paired with the account, skip writing account key."); - return false; - } - if (existingAccountKey[0] != AccountKeyCharacteristic.TYPE) { - Log.i(TAG, - "The provider has already paired with the account, but accountKey[0] != 0x04." - + " Forget the device from the account and re-try"); - - return false; - } - Log.i(TAG, "The provider has already paired with the account, still write the same account " - + "key."); - return true; - } - - /** - * Performs a key-based pairing request handshake to authenticate and get the remote device's - * public address. - * - * @param key is described in {@link #pair(byte[])} - */ - @VisibleForTesting - SharedSecret handshakeForKeyBasedPairing(byte[] key) - throws InterruptedException, ExecutionException, TimeoutException, BluetoothException, - GeneralSecurityException, PairingException { - // We may also initialize gattConnectionManager of prepareForHandshake() that will be used - // in registerNotificationForNamePacket(), so we need to call it here. - HandshakeHandler handshakeHandler = prepareForHandshake(); - KeyBasedPairingRequest.Builder keyBasedPairingRequestBuilder = - new KeyBasedPairingRequest.Builder() - .setVerificationData(BluetoothAddress.decode(mBleAddress)); - if (mProviderInitiatesBonding) { - keyBasedPairingRequestBuilder - .addFlag(KeyBasedPairingRequestFlag.PROVIDER_INITIATES_BONDING); - } - // Seeker only request provider device name in initial pairing. - if (mPreferences.getEnableNamingCharacteristic() && isPairingWithAntiSpoofingPublicKey( - key)) { - keyBasedPairingRequestBuilder.addFlag(KeyBasedPairingRequestFlag.REQUEST_DEVICE_NAME); - // Register listener to receive name characteristic response from provider. - registerNotificationForNamePacket(); - } - if (mPreferences.getIsRetroactivePairing()) { - keyBasedPairingRequestBuilder - .addFlag(KeyBasedPairingRequestFlag.REQUEST_RETROACTIVE_PAIR); - keyBasedPairingRequestBuilder.setSeekerPublicAddress( - Preconditions.checkNotNull(BluetoothAddress.getPublicAddress(mContext))); - } - - return performHandshakeWithRetryAndSignalLostCheck( - handshakeHandler, key, keyBasedPairingRequestBuilder.build(), /* withRetry= */ - true); - } - - /** - * Performs an action-over-BLE request handshake for authentication, i.e. to identify the shared - * secret. The given key should be the account key. - */ - private SharedSecret handshakeForActionOverBle(byte[] key, - @AdditionalDataType int additionalDataType) - throws InterruptedException, ExecutionException, TimeoutException, BluetoothException, - GeneralSecurityException, PairingException { - HandshakeHandler handshakeHandler = prepareForHandshake(); - return performHandshakeWithRetryAndSignalLostCheck( - handshakeHandler, - key, - new ActionOverBle.Builder() - .setVerificationData(BluetoothAddress.decode(mBleAddress)) - .setAdditionalDataType(additionalDataType) - .build(), - /* withRetry= */ false); - } - - private HandshakeHandler prepareForHandshake() { - if (mGattConnectionManager == null) { - mGattConnectionManager = - new GattConnectionManager( - mContext, - mPreferences, - mEventLogger, - mBluetoothAdapter, - this::toggleBluetooth, - mBleAddress, - mTimingLogger, - mFastPairSignalChecker, - isPairingWithAntiSpoofingPublicKey()); - } - if (mHandshakeHandlerForTest != null) { - Log.w(TAG, "Use handshakeHandlerForTest!"); - return verifyNotNull(mHandshakeHandlerForTest); - } - return new HandshakeHandler( - mGattConnectionManager, mBleAddress, mPreferences, mEventLogger, - mFastPairSignalChecker); - } - - @VisibleForTesting - void setHandshakeHandlerForTest(@Nullable HandshakeHandler handshakeHandlerForTest) { - this.mHandshakeHandlerForTest = handshakeHandlerForTest; - } - - private SharedSecret performHandshakeWithRetryAndSignalLostCheck( - HandshakeHandler handshakeHandler, - byte[] key, - HandshakeMessage handshakeMessage, - boolean withRetry) - throws GeneralSecurityException, ExecutionException, BluetoothException, - InterruptedException, TimeoutException, PairingException { - SharedSecret handshakeResult = - withRetry - ? handshakeHandler.doHandshakeWithRetryAndSignalLostCheck( - key, handshakeMessage, mRescueFromError) - : handshakeHandler.doHandshake(key, handshakeMessage); - // TODO: Try to remove these two global variables, publicAddress and pairingSecret. - mPublicAddress = handshakeResult.getAddress(); - mPairingSecret = handshakeResult.getKey(); - return handshakeResult; - } - - private void toggleBluetooth() - throws InterruptedException, ExecutionException, TimeoutException { - if (!mPreferences.getToggleBluetoothOnFailure()) { - return; - } - - Log.i(TAG, "Turning Bluetooth off."); - mEventLogger.setCurrentEvent(EventCode.DISABLE_BLUETOOTH); - mBluetoothAdapter.unwrap().disable(); - disableBle(mBluetoothAdapter.unwrap()); - try { - waitForBluetoothState(android.bluetooth.BluetoothAdapter.STATE_OFF); - mEventLogger.logCurrentEventSucceeded(); - } catch (TimeoutException e) { - mEventLogger.logCurrentEventFailed(e); - // Soldier on despite failing to turn off Bluetooth. We can't control whether other - // clients (even inside GCore) kept it enabled in BLE-only mode. - Log.w(TAG, "Bluetooth still on. BluetoothAdapter state=" - + getBleState(mBluetoothAdapter.unwrap()), e); - } - - // Note: Intentionally don't re-enable BLE-only mode, because we don't know which app - // enabled it. The client app should listen to Bluetooth events and enable as necessary - // (because the user can toggle at any time; e.g. via Airplane mode). - Log.i(TAG, "Turning Bluetooth on."); - mEventLogger.setCurrentEvent(EventCode.ENABLE_BLUETOOTH); - mBluetoothAdapter.unwrap().enable(); - waitForBluetoothState(android.bluetooth.BluetoothAdapter.STATE_ON); - mEventLogger.logCurrentEventSucceeded(); - } - - private void waitForBluetoothState(int state) - throws TimeoutException, ExecutionException, InterruptedException { - waitForBluetoothStateUsingPolling(state); - } - - private void waitForBluetoothStateUsingPolling(int state) throws TimeoutException { - // There's a bug where we (pretty often!) never get the broadcast for STATE_ON or STATE_OFF. - // So poll instead. - long start = SystemClock.elapsedRealtime(); - long timeoutMillis = mPreferences.getBluetoothToggleTimeoutSeconds() * 1000L; - while (SystemClock.elapsedRealtime() - start < timeoutMillis) { - if (state == getBleState(mBluetoothAdapter.unwrap())) { - break; - } - SystemClock.sleep(mPreferences.getBluetoothStatePollingMillis()); - } - - if (state != getBleState(mBluetoothAdapter.unwrap())) { - throw new TimeoutException( - String.format( - Locale.getDefault(), - "Timed out waiting for state %d, current state is %d", - state, - getBleState(mBluetoothAdapter.unwrap()))); - } - } - - private BrEdrHandoverInformation getBrEdrHandoverInformation(BluetoothGattConnection connection) - throws BluetoothException, TdsException, InterruptedException, ExecutionException, - TimeoutException { - Log.i(TAG, "Connecting GATT server to BLE address=" + maskBluetoothAddress(mBleAddress)); - Log.i(TAG, "Telling device to become discoverable"); - mEventLogger.setCurrentEvent(EventCode.BR_EDR_HANDOVER_WRITE_CONTROL_POINT_REQUEST); - ChangeObserver changeObserver = - connection.enableNotification( - TransportDiscoveryService.ID, - TransportDiscoveryService.ControlPointCharacteristic.ID); - connection.writeCharacteristic( - TransportDiscoveryService.ID, - TransportDiscoveryService.ControlPointCharacteristic.ID, - TDS_CONTROL_POINT_REQUEST); - - byte[] response = - changeObserver.waitForUpdate( - TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds())); - @ResultCode int resultCode = fromTdsControlPointIndication(response); - if (resultCode != ResultCode.SUCCESS) { - throw new TdsException( - BrEdrHandoverErrorCode.CONTROL_POINT_RESULT_CODE_NOT_SUCCESS, - "TDS Control Point result code (%s) was not success in response %s", - resultCode, - base16().lowerCase().encode(response)); - } - mEventLogger.logCurrentEventSucceeded(); - return new BrEdrHandoverInformation( - getAddressFromBrEdrConnection(connection), - getProfilesFromBrEdrConnection(connection)); - } - - private byte[] getAddressFromBrEdrConnection(BluetoothGattConnection connection) - throws BluetoothException, TdsException { - Log.i(TAG, "Getting Bluetooth MAC"); - mEventLogger.setCurrentEvent(EventCode.BR_EDR_HANDOVER_READ_BLUETOOTH_MAC); - byte[] brHandoverData = - connection.readCharacteristic( - TransportDiscoveryService.ID, - to128BitUuid(mPreferences.getBrHandoverDataCharacteristicId())); - if (brHandoverData == null || brHandoverData.length < 7) { - throw new TdsException( - BrEdrHandoverErrorCode.BLUETOOTH_MAC_INVALID, - "Bluetooth MAC not contained in BR handover data: %s", - brHandoverData != null ? base16().lowerCase().encode(brHandoverData) - : "(none)"); - } - byte[] bluetoothAddress = - new Bytes.Value(Arrays.copyOfRange(brHandoverData, 1, 7), ByteOrder.LITTLE_ENDIAN) - .getBytes(ByteOrder.BIG_ENDIAN); - mEventLogger.logCurrentEventSucceeded(); - return bluetoothAddress; - } - - private short[] getProfilesFromBrEdrConnection(BluetoothGattConnection connection) { - mEventLogger.setCurrentEvent(EventCode.BR_EDR_HANDOVER_READ_TRANSPORT_BLOCK); - try { - byte[] transportBlock = - connection.readDescriptor( - TransportDiscoveryService.ID, - to128BitUuid(mPreferences.getBluetoothSigDataCharacteristicId()), - to128BitUuid(mPreferences.getBrTransportBlockDataDescriptorId())); - Log.i(TAG, "Got transport block: " + base16().lowerCase().encode(transportBlock)); - short[] profiles = getSupportedProfiles(transportBlock); - mEventLogger.logCurrentEventSucceeded(); - return profiles; - } catch (BluetoothException | TdsException | ParseException e) { - Log.w(TAG, "Failed to get supported profiles from transport block.", e); - mEventLogger.logCurrentEventFailed(e); - } - return new short[0]; - } - - @VisibleForTesting - boolean writeNameToProvider(@Nullable String deviceName, @Nullable String address) - throws InterruptedException, TimeoutException, ExecutionException { - if (deviceName == null || address == null) { - Log.i(TAG, "writeNameToProvider fail because provider name or address is null."); - return false; - } - if (mPairingSecret == null) { - Log.i(TAG, "writeNameToProvider fail because no pairingSecret."); - return false; - } - byte[] encryptedDeviceNamePacket; - try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Encode device name")) { - encryptedDeviceNamePacket = - NamingEncoder.encodeNamingPacket(mPairingSecret, deviceName); - } catch (GeneralSecurityException e) { - Log.w(TAG, "Failed to encrypt device name.", e); - return false; - } - - for (int i = 1; i <= mPreferences.getNumWriteAccountKeyAttempts(); i++) { - mEventLogger.setCurrentEvent(EventCode.WRITE_DEVICE_NAME); - try { - writeDeviceName(encryptedDeviceNamePacket, address); - mEventLogger.logCurrentEventSucceeded(); - return true; - } catch (BluetoothException e) { - Log.w(TAG, "Error writing name attempt " + i + " of " - + mPreferences.getNumWriteAccountKeyAttempts()); - mEventLogger.logCurrentEventFailed(e); - // Reuses the existing preference because the same usage. - Thread.sleep(mPreferences.getWriteAccountKeySleepMillis()); - } - } - return false; - } - - private void writeAccountKey(byte[] encryptedAccountKey, String address) - throws BluetoothException, InterruptedException, ExecutionException, TimeoutException { - Log.i(TAG, "Writing account key to address=" + maskBluetoothAddress(address)); - BluetoothGattConnection connection = mGattConnectionManager.getConnection(); - connection.setOperationTimeout( - TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds())); - UUID characteristicUuid = AccountKeyCharacteristic.getId(connection); - connection.writeCharacteristic(FastPairService.ID, characteristicUuid, encryptedAccountKey); - Log.i(TAG, - "Finished writing encrypted account key=" + base16().encode(encryptedAccountKey)); - } - - private void writeDeviceName(byte[] naming, String address) - throws BluetoothException, InterruptedException, ExecutionException, TimeoutException { - Log.i(TAG, "Writing new device name to address=" + maskBluetoothAddress(address)); - BluetoothGattConnection connection = mGattConnectionManager.getConnection(); - connection.setOperationTimeout( - TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds())); - UUID characteristicUuid = NameCharacteristic.getId(connection); - connection.writeCharacteristic(FastPairService.ID, characteristicUuid, naming); - Log.i(TAG, "Finished writing new device name=" + base16().encode(naming)); - } - - /** - * Reads firmware version after write account key to provider since simulator is more stable to - * read firmware version in initial gatt connection. This function will also read firmware when - * detect bloomfilter. Need to verify this after real device come out. TODO(b/130592473) - */ - @Nullable - public String readFirmwareVersion() - throws BluetoothException, InterruptedException, ExecutionException, TimeoutException { - if (!TextUtils.isEmpty(sInitialConnectionFirmwareVersion)) { - String result = sInitialConnectionFirmwareVersion; - sInitialConnectionFirmwareVersion = null; - return result; - } - if (mGattConnectionManager == null) { - mGattConnectionManager = - new GattConnectionManager( - mContext, - mPreferences, - mEventLogger, - mBluetoothAdapter, - this::toggleBluetooth, - mBleAddress, - mTimingLogger, - mFastPairSignalChecker, - /* setMtu= */ true); - mGattConnectionManager.closeConnection(); - } - if (sTestMode) { - return null; - } - BluetoothGattConnection connection = mGattConnectionManager.getConnection(); - connection.setOperationTimeout( - TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds())); - - try { - String firmwareVersion = - new String( - connection.readCharacteristic( - FastPairService.ID, - to128BitUuid( - mPreferences.getFirmwareVersionCharacteristicId()))); - Log.i(TAG, "FastPair: Got the firmware info version number = " + firmwareVersion); - mGattConnectionManager.closeConnection(); - return firmwareVersion; - } catch (BluetoothException e) { - Log.i(TAG, "FastPair: can't read firmware characteristic.", e); - mGattConnectionManager.closeConnection(); - return null; - } - } - - @VisibleForTesting - @Nullable - String getInitialConnectionFirmware() { - return sInitialConnectionFirmwareVersion; - } - - private void registerNotificationForNamePacket() - throws BluetoothException, InterruptedException, ExecutionException, TimeoutException { - Log.i(TAG, - "register for the device name response from address=" + maskBluetoothAddress( - mBleAddress)); - - BluetoothGattConnection gattConnection = mGattConnectionManager.getConnection(); - gattConnection.setOperationTimeout( - TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds())); - try { - mDeviceNameReceiver = new DeviceNameReceiver(gattConnection); - } catch (BluetoothException e) { - Log.i(TAG, "Can't register for device name response, no naming characteristic."); - return; - } - } - - private short[] getSupportedProfiles(BluetoothDevice device) { - short[] supportedProfiles = getCachedUuids(device); - if (supportedProfiles.length == 0 && mPreferences.getNumSdpAttemptsAfterBonded() > 0) { - supportedProfiles = - attemptGetBluetoothClassicProfiles(device, - mPreferences.getNumSdpAttemptsAfterBonded()); - } - if (supportedProfiles.length == 0) { - supportedProfiles = Constants.getSupportedProfiles(); - Log.w(TAG, "Attempting to connect constants profiles, " - + Arrays.toString(supportedProfiles)); - } else { - Log.i(TAG, - "Attempting to connect device profiles, " + Arrays.toString(supportedProfiles)); - } - return supportedProfiles; - } - - private static short[] getSupportedProfiles(byte[] transportBlock) - throws TdsException, ParseException { - if (transportBlock == null || transportBlock.length < 4) { - throw new TdsException( - BrEdrHandoverErrorCode.TRANSPORT_BLOCK_INVALID, - "Transport Block null or too short: %s", - base16().lowerCase().encode(transportBlock)); - } - int transportDataLength = transportBlock[2]; - if (transportBlock.length < 3 + transportDataLength) { - throw new TdsException( - BrEdrHandoverErrorCode.TRANSPORT_BLOCK_INVALID, - "Transport Block has wrong length byte: %s", - base16().lowerCase().encode(transportBlock)); - } - byte[] transportData = Arrays.copyOfRange(transportBlock, 3, 3 + transportDataLength); - for (Ltv ltv : Ltv.parse(transportData)) { - int uuidLength = uuidLength(ltv.mType); - // We currently only support a single list of 2-byte UUIDs. - // TODO(b/37539535): Support multiple lists, and longer (32-bit, 128-bit) IDs? - if (uuidLength == 2) { - return toShorts(ByteOrder.LITTLE_ENDIAN, ltv.mValue); - } - } - return new short[0]; - } - - /** - * Returns 0 if the type is not one of the UUID list types; otherwise returns length in bytes. - */ - private static int uuidLength(byte dataType) { - switch (dataType) { - case TransportDiscoveryService.SERVICE_UUIDS_16_BIT_LIST_TYPE: - return 2; - case TransportDiscoveryService.SERVICE_UUIDS_32_BIT_LIST_TYPE: - return 4; - case TransportDiscoveryService.SERVICE_UUIDS_128_BIT_LIST_TYPE: - return 16; - default: - return 0; - } - } - - private short[] attemptGetBluetoothClassicProfiles(BluetoothDevice device, int numSdpAttempts) { - // The docs say that if fetchUuidsWithSdp() has an error or "takes a long time", we get an - // intent containing only the stuff in the cache (i.e. nothing). Retry a few times. - short[] supportedProfiles = null; - for (int i = 1; i <= numSdpAttempts; i++) { - mEventLogger.setCurrentEvent(EventCode.GET_PROFILES_VIA_SDP); - try (ScopedTiming scopedTiming = - new ScopedTiming(mTimingLogger, - "Get BR/EDR handover information via SDP #" + i)) { - supportedProfiles = getSupportedProfilesViaBluetoothClassic(device); - } catch (ExecutionException | InterruptedException | TimeoutException e) { - // Ignores and retries if needed. - } - if (supportedProfiles != null && supportedProfiles.length != 0) { - mEventLogger.logCurrentEventSucceeded(); - break; - } else { - mEventLogger.logCurrentEventFailed(new TimeoutException()); - Log.w(TAG, "SDP returned no UUIDs from " + maskBluetoothAddress(device.getAddress()) - + ", assuming timeout (attempt " + i + " of " + numSdpAttempts + ")."); - } - } - return (supportedProfiles == null) ? new short[0] : supportedProfiles; - } - - private short[] getSupportedProfilesViaBluetoothClassic(BluetoothDevice device) - throws ExecutionException, InterruptedException, TimeoutException { - Log.i(TAG, "Getting supported profiles via SDP (Bluetooth Classic) for " - + maskBluetoothAddress(device.getAddress())); - try (DeviceIntentReceiver supportedProfilesReceiver = - DeviceIntentReceiver.oneShotReceiver( - mContext, mPreferences, device, BluetoothDevice.ACTION_UUID)) { - device.fetchUuidsWithSdp(); - supportedProfilesReceiver.await(mPreferences.getSdpTimeoutSeconds(), TimeUnit.SECONDS); - } - return getCachedUuids(device); - } - - private static short[] getCachedUuids(BluetoothDevice device) { - ParcelUuid[] parcelUuids = device.getUuids(); - Log.i(TAG, "Got supported UUIDs: " + Arrays.toString(parcelUuids)); - if (parcelUuids == null) { - // The OS can return null. - parcelUuids = new ParcelUuid[0]; - } - - List shortUuids = new ArrayList<>(parcelUuids.length); - for (ParcelUuid parcelUuid : parcelUuids) { - UUID uuid = parcelUuid.getUuid(); - if (BluetoothUuids.is16BitUuid(uuid)) { - shortUuids.add(get16BitUuid(uuid)); - } - } - return Shorts.toArray(shortUuids); - } - - private void callbackOnPaired() { - if (mPairedCallback != null) { - mPairedCallback.onPaired(mPublicAddress != null ? mPublicAddress : mBleAddress); - } - } - - private void callbackOnGetAddress(String address) { - if (mOnGetBluetoothAddressCallback != null) { - mOnGetBluetoothAddressCallback.onGetBluetoothAddress(address); - } - } - - private boolean validateBluetoothGattCharacteristic( - BluetoothGattConnection connection, UUID characteristicUUID) { - try (ScopedTiming scopedTiming = - new ScopedTiming(mTimingLogger, "Get service characteristic list")) { - List serviceCharacteristicList = - connection.getService(FastPairService.ID).getCharacteristics(); - for (BluetoothGattCharacteristic characteristic : serviceCharacteristicList) { - if (characteristicUUID.equals(characteristic.getUuid())) { - Log.i(TAG, "characteristic is exists, uuid = " + characteristicUUID); - return true; - } - } - } catch (BluetoothException e) { - Log.w(TAG, "Can't get service characteristic list.", e); - } - Log.i(TAG, "can't find characteristic, uuid = " + characteristicUUID); - return false; - } - - // This method is only for testing to make test method block until get name response or time - // out. - /** - * Set name response countdown latch. - */ - public void setNameResponseCountDownLatch(CountDownLatch countDownLatch) { - if (mDeviceNameReceiver != null) { - mDeviceNameReceiver.setCountDown(countDownLatch); - Log.v(TAG, "set up nameResponseCountDown"); - } - } - - private static int getBleState(android.bluetooth.BluetoothAdapter bluetoothAdapter) { - // Can't use the public isLeEnabled() API, because it returns false for - // STATE_BLE_TURNING_(ON|OFF). So if we assume false == STATE_OFF, that can be - // very wrong. - return getLeState(bluetoothAdapter); - } - - @VisibleForTesting - static int getLeState(android.bluetooth.BluetoothAdapter adapter) { - try { - return (Integer) Reflect.on(adapter).withMethod("getLeState").get(); - } catch (ReflectionException e) { - Log.i(TAG, "Can't call getLeState", e); - } - return adapter.getState(); - } - - private static void disableBle(android.bluetooth.BluetoothAdapter adapter) { - adapter.disableBLE(); - } - - /** - * Handle the searching of Fast Pair history. Since there is only one public address using - * during Fast Pair connection, {@link #isInPairedHistory(String)} only needs to be called once, - * then the result is kept, and call {@link #getExistingAccountKey()} to get the result. - */ - @VisibleForTesting - static final class FastPairHistoryFinder { - - private @Nullable - byte[] mExistingAccountKey; - @Nullable - private final List mHistoryItems; - - FastPairHistoryFinder(List historyItems) { - this.mHistoryItems = historyItems; - } - - @WorkerThread - @VisibleForTesting - boolean isInPairedHistory(String publicAddress) { - if (mHistoryItems == null || mHistoryItems.isEmpty()) { - return false; - } - for (FastPairHistoryItem item : mHistoryItems) { - if (item.isMatched(BluetoothAddress.decode(publicAddress))) { - mExistingAccountKey = item.accountKey().toByteArray(); - return true; - } - } - return false; - } - - // This function should be called after isInPairedHistory(). Or it will just return null. - @WorkerThread - @VisibleForTesting - @Nullable - byte[] getExistingAccountKey() { - return mExistingAccountKey; - } - } - - private static final class DeviceNameReceiver { - - @GuardedBy("this") - private @Nullable - byte[] mEncryptedResponse; - - @GuardedBy("this") - @Nullable - private String mDecryptedDeviceName; - - @Nullable - private CountDownLatch mResponseCountDown; - - DeviceNameReceiver(BluetoothGattConnection gattConnection) throws BluetoothException { - UUID characteristicUuid = NameCharacteristic.getId(gattConnection); - ChangeObserver observer = - gattConnection.enableNotification(FastPairService.ID, characteristicUuid); - observer.setListener( - (byte[] value) -> { - synchronized (DeviceNameReceiver.this) { - Log.i(TAG, "DeviceNameReceiver: device name response size = " - + value.length); - // We don't decrypt it here because we may not finish handshaking and - // the pairing - // secret is not available. - mEncryptedResponse = value; - } - // For testing to know we get the device name from provider. - if (mResponseCountDown != null) { - mResponseCountDown.countDown(); - Log.v(TAG, "Finish nameResponseCountDown."); - } - }); - } - - void setCountDown(CountDownLatch countDownLatch) { - this.mResponseCountDown = countDownLatch; - } - - synchronized @Nullable String getParsedResult(byte[] secret) { - if (mDecryptedDeviceName != null) { - return mDecryptedDeviceName; - } - if (mEncryptedResponse == null) { - Log.i(TAG, "DeviceNameReceiver: no device name sent from the Provider."); - return null; - } - try { - mDecryptedDeviceName = NamingEncoder.decodeNamingPacket(secret, mEncryptedResponse); - Log.i(TAG, "DeviceNameReceiver: decrypted provider's name from naming response, " - + "name = " + mDecryptedDeviceName); - } catch (GeneralSecurityException e) { - Log.w(TAG, "DeviceNameReceiver: fail to parse the NameCharacteristic from provider" - + ".", e); - return null; - } - return mDecryptedDeviceName; - } - } - - static void checkFastPairSignal( - FastPairSignalChecker fastPairSignalChecker, - String currentAddress, - Exception originalException) - throws SignalLostException, SignalRotatedException { - String newAddress = fastPairSignalChecker.getValidAddressForModelId(currentAddress); - if (TextUtils.isEmpty(newAddress)) { - throw new SignalLostException("Signal lost", originalException); - } else if (!Ascii.equalsIgnoreCase(currentAddress, newAddress)) { - throw new SignalRotatedException("Address rotated", newAddress, originalException); - } - } - - @VisibleForTesting - public Preferences getPreferences() { - return mPreferences; - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairHistoryItem.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairHistoryItem.java deleted file mode 100644 index e7748860e62e4d07073b4803f6d10eeecdef799e..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairHistoryItem.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static com.google.common.primitives.Bytes.concat; - -import com.google.common.hash.Hashing; -import com.google.protobuf.ByteString; - -import java.util.Arrays; - -/** - * It contains the sha256 of "account key + headset's public address" to identify the headset which - * has paired with the account. Previously, account key is the only information for Fast Pair to - * identify the headset, but Fast Pair can't identify the headset in initial pairing, there is no - * account key data advertising from headset. - */ -public class FastPairHistoryItem { - - private final ByteString mAccountKey; - private final ByteString mSha256AccountKeyPublicAddress; - - FastPairHistoryItem(ByteString accountkey, ByteString sha256AccountKeyPublicAddress) { - mAccountKey = accountkey; - mSha256AccountKeyPublicAddress = sha256AccountKeyPublicAddress; - } - - /** - * Creates an instance of {@link FastPairHistoryItem}. - * - * @param accountKey key of an account that has paired with the headset. - * @param sha256AccountKeyPublicAddress hash value of account key and headset's public address. - */ - public static FastPairHistoryItem create( - ByteString accountKey, ByteString sha256AccountKeyPublicAddress) { - return new FastPairHistoryItem(accountKey, sha256AccountKeyPublicAddress); - } - - ByteString accountKey() { - return mAccountKey; - } - - ByteString sha256AccountKeyPublicAddress() { - return mSha256AccountKeyPublicAddress; - } - - // Return true if the input public address is considered the same as this history item. Because - // of privacy concern, Fast Pair does not really store the public address, it is identified by - // the SHA256 of the account key and the public key. - final boolean isMatched(byte[] publicAddress) { - return Arrays.equals( - sha256AccountKeyPublicAddress().toByteArray(), - Hashing.sha256().hashBytes(concat(accountKey().toByteArray(), publicAddress)) - .asBytes()); - } -} - diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManager.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManager.java deleted file mode 100644 index e7ce4bfde675a4e45db4777aaf42542ec24a32b0..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManager.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH; -import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress; -import static com.android.server.nearby.common.bluetooth.fastpair.FastPairDualConnection.logRetrySuccessEvent; - -import static com.google.common.base.Preconditions.checkNotNull; - -import android.content.Context; -import android.os.SystemClock; -import android.util.Log; - -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.core.util.Consumer; - -import com.android.server.nearby.common.bluetooth.BluetoothException; -import com.android.server.nearby.common.bluetooth.BluetoothGattException; -import com.android.server.nearby.common.bluetooth.BluetoothTimeoutException; -import com.android.server.nearby.common.bluetooth.fastpair.TimingLogger.ScopedTiming; -import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection; -import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattHelper; -import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattHelper.ConnectionOptions; -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter; -import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException; -import com.android.server.nearby.intdefs.FastPairEventIntDefs.ErrorCode; -import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * Manager for working with Gatt connections. - * - *

    This helper class allows for opening and closing GATT connections to a provided address. - * Optionally, it can also support automatically reopening a connection in the case that it has been - * closed when it's next needed through {@link Preferences#getAutomaticallyReconnectGattWhenNeeded}. - */ -// TODO(b/202524672): Add class unit test. -final class GattConnectionManager { - - private static final String TAG = GattConnectionManager.class.getSimpleName(); - - private final Context mContext; - private final Preferences mPreferences; - private final EventLoggerWrapper mEventLogger; - private final BluetoothAdapter mBluetoothAdapter; - private final ToggleBluetoothTask mToggleBluetooth; - private final String mAddress; - private final TimingLogger mTimingLogger; - private final boolean mSetMtu; - @Nullable - private final FastPairConnection.FastPairSignalChecker mFastPairSignalChecker; - @Nullable - private BluetoothGattConnection mGattConnection; - private static boolean sTestMode = false; - - static void enableTestMode() { - sTestMode = true; - } - - GattConnectionManager( - Context context, - Preferences preferences, - EventLoggerWrapper eventLogger, - BluetoothAdapter bluetoothAdapter, - ToggleBluetoothTask toggleBluetooth, - String address, - TimingLogger timingLogger, - @Nullable FastPairConnection.FastPairSignalChecker fastPairSignalChecker, - boolean setMtu) { - this.mContext = context; - this.mPreferences = preferences; - this.mEventLogger = eventLogger; - this.mBluetoothAdapter = bluetoothAdapter; - this.mToggleBluetooth = toggleBluetooth; - this.mAddress = address; - this.mTimingLogger = timingLogger; - this.mFastPairSignalChecker = fastPairSignalChecker; - this.mSetMtu = setMtu; - } - - /** - * Gets a gatt connection to address. If this connection does not exist, it creates one. - */ - BluetoothGattConnection getConnection() - throws InterruptedException, ExecutionException, TimeoutException, BluetoothException { - if (mGattConnection == null) { - try { - mGattConnection = - connect(mAddress, /* checkSignalWhenFail= */ false, - /* rescueFromError= */ null); - } catch (SignalLostException | SignalRotatedException e) { - // Impossible to happen here because we didn't do signal check. - throw new ExecutionException("getConnection throws SignalLostException", e); - } - } - return mGattConnection; - } - - BluetoothGattConnection getConnectionWithSignalLostCheck( - @Nullable Consumer rescueFromError) - throws InterruptedException, ExecutionException, TimeoutException, BluetoothException, - SignalLostException, SignalRotatedException { - if (mGattConnection == null) { - mGattConnection = connect(mAddress, /* checkSignalWhenFail= */ true, - rescueFromError); - } - return mGattConnection; - } - - /** - * Closes the gatt connection when it is open. - */ - void closeConnection() throws BluetoothException { - if (mGattConnection != null) { - try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Close GATT")) { - mGattConnection.close(); - mGattConnection = null; - } - } - } - - private BluetoothGattConnection connect( - String address, boolean checkSignalWhenFail, - @Nullable Consumer rescueFromError) - throws InterruptedException, ExecutionException, TimeoutException, BluetoothException, - SignalLostException, SignalRotatedException { - int i = 1; - boolean isRecoverable = true; - long startElapsedRealtime = SystemClock.elapsedRealtime(); - BluetoothException lastException = null; - mEventLogger.setCurrentEvent(EventCode.GATT_CONNECT); - while (isRecoverable) { - try (ScopedTiming scopedTiming = - new ScopedTiming(mTimingLogger, "Connect GATT #" + i)) { - Log.i(TAG, "Connecting to GATT server at " + maskBluetoothAddress(address)); - if (sTestMode) { - return null; - } - BluetoothGattConnection connection = - new BluetoothGattHelper(mContext, mBluetoothAdapter) - .connect( - mBluetoothAdapter.getRemoteDevice(address), - getConnectionOptions(startElapsedRealtime)); - connection.setOperationTimeout( - TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds())); - if (mPreferences.getAutomaticallyReconnectGattWhenNeeded()) { - connection.addCloseListener( - () -> { - Log.i(TAG, "Gatt connection with " + maskBluetoothAddress(address) - + " closed."); - mGattConnection = null; - }); - } - mEventLogger.logCurrentEventSucceeded(); - if (lastException != null) { - logRetrySuccessEvent(EventCode.RECOVER_BY_RETRY_GATT, lastException, - mEventLogger); - } - return connection; - } catch (BluetoothException e) { - lastException = e; - - boolean ableToRetry; - if (mPreferences.getGattConnectRetryTimeoutMillis() > 0) { - ableToRetry = - (SystemClock.elapsedRealtime() - startElapsedRealtime) - < mPreferences.getGattConnectRetryTimeoutMillis(); - Log.i(TAG, "Retry connecting GATT by timeout: " + ableToRetry); - } else { - ableToRetry = i < mPreferences.getNumAttempts(); - } - - if (mPreferences.getRetryGattConnectionAndSecretHandshake()) { - if (isNoRetryError(mPreferences, e)) { - ableToRetry = false; - } - - if (ableToRetry) { - if (rescueFromError != null) { - rescueFromError.accept( - e instanceof BluetoothOperationTimeoutException - ? ErrorCode.SUCCESS_RETRY_GATT_TIMEOUT - : ErrorCode.SUCCESS_RETRY_GATT_ERROR); - } - if (mFastPairSignalChecker != null && checkSignalWhenFail) { - FastPairDualConnection - .checkFastPairSignal(mFastPairSignalChecker, address, e); - } - } - isRecoverable = ableToRetry; - if (ableToRetry && mPreferences.getPairingRetryDelayMs() > 0) { - SystemClock.sleep(mPreferences.getPairingRetryDelayMs()); - } - } else { - isRecoverable = - ableToRetry - && (e instanceof BluetoothOperationTimeoutException - || e instanceof BluetoothTimeoutException - || (e instanceof BluetoothGattException - && ((BluetoothGattException) e).getGattErrorCode() == 133)); - } - Log.w(TAG, "GATT connect attempt " + i + "of " + mPreferences.getNumAttempts() - + " failed, " + (isRecoverable ? "recovering" : "permanently"), e); - if (isRecoverable) { - // If we're going to retry, log failure here. If we throw, an upper level will - // log it. - mToggleBluetooth.toggleBluetooth(); - i++; - mEventLogger.logCurrentEventFailed(e); - mEventLogger.setCurrentEvent(EventCode.GATT_CONNECT); - } - } - } - throw checkNotNull(lastException); - } - - static boolean isNoRetryError(Preferences preferences, BluetoothException e) { - return e instanceof BluetoothGattException - && preferences - .getGattConnectionAndSecretHandshakeNoRetryGattError() - .contains(((BluetoothGattException) e).getGattErrorCode()); - } - - @VisibleForTesting - long getTimeoutMs(long spentTime) { - long timeoutInMs; - if (mPreferences.getRetryGattConnectionAndSecretHandshake()) { - timeoutInMs = - spentTime < mPreferences.getGattConnectShortTimeoutRetryMaxSpentTimeMs() - ? mPreferences.getGattConnectShortTimeoutMs() - : mPreferences.getGattConnectLongTimeoutMs(); - } else { - timeoutInMs = TimeUnit.SECONDS.toMillis(mPreferences.getGattConnectionTimeoutSeconds()); - } - return timeoutInMs; - } - - private ConnectionOptions getConnectionOptions(long startElapsedRealtime) { - return createConnectionOptions( - mSetMtu, - getTimeoutMs(SystemClock.elapsedRealtime() - startElapsedRealtime)); - } - - public static ConnectionOptions createConnectionOptions(boolean setMtu, long timeoutInMs) { - ConnectionOptions.Builder builder = ConnectionOptions.builder(); - if (setMtu) { - // There are 3 overhead bytes added to BLE packets. - builder.setMtu( - AES_BLOCK_LENGTH + EllipticCurveDiffieHellmanExchange.PUBLIC_KEY_LENGTH + 3); - } - builder.setConnectionTimeoutMillis(timeoutInMs); - return builder.build(); - } - - @VisibleForTesting - void setGattConnection(BluetoothGattConnection gattConnection) { - this.mGattConnection = gattConnection; - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HandshakeHandler.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HandshakeHandler.java deleted file mode 100644 index 984133b153e430141678ef0692fc01097a909eda..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HandshakeHandler.java +++ /dev/null @@ -1,560 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH; -import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.decrypt; -import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.encrypt; -import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress; -import static com.android.server.nearby.common.bluetooth.fastpair.Constants.BLUETOOTH_ADDRESS_LENGTH; -import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.ADDITIONAL_DATA_CHARACTERISTIC; -import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.DEVICE_ACTION; -import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.ADDITIONAL_DATA_TYPE_INDEX; -import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.EVENT_ADDITIONAL_DATA_INDEX; -import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.EVENT_ADDITIONAL_DATA_LENGTH_INDEX; -import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.EVENT_CODE_INDEX; -import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.EVENT_GROUP_INDEX; -import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.FLAGS_INDEX; -import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.SEEKER_PUBLIC_ADDRESS_INDEX; -import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.TYPE_ACTION_OVER_BLE; -import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.TYPE_INDEX; -import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.TYPE_KEY_BASED_PAIRING_REQUEST; -import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.VERIFICATION_DATA_INDEX; -import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.VERIFICATION_DATA_LENGTH; -import static com.android.server.nearby.common.bluetooth.fastpair.FastPairDualConnection.logRetrySuccessEvent; -import static com.android.server.nearby.common.bluetooth.fastpair.GattConnectionManager.isNoRetryError; - -import static com.google.common.base.Verify.verifyNotNull; -import static com.google.common.io.BaseEncoding.base16; -import static com.google.common.primitives.Bytes.concat; - -import static java.util.concurrent.TimeUnit.SECONDS; - -import android.os.SystemClock; -import android.util.Log; - -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.core.util.Consumer; - -import com.android.server.nearby.common.bluetooth.BluetoothException; -import com.android.server.nearby.common.bluetooth.BluetoothTimeoutException; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AdditionalDataCharacteristic.AdditionalDataType; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag; -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request; -import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection.SharedSecret; -import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection; -import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection.ChangeObserver; -import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException; -import com.android.server.nearby.intdefs.FastPairEventIntDefs.ErrorCode; -import com.android.server.nearby.intdefs.NearbyEventIntDefs.EventCode; - -import java.security.GeneralSecurityException; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; - -/** - * Handles the handshake step of Fast Pair, the Provider's public address and the shared secret will - * be disclosed during this step. It is the first step of all key-based operations, e.g. key-based - * pairing and action over BLE. - * - * @see - * Fastpair Spec Procedure - */ -public class HandshakeHandler { - - private static final String TAG = HandshakeHandler.class.getSimpleName(); - private final GattConnectionManager mGattConnectionManager; - private final String mProviderBleAddress; - private final Preferences mPreferences; - private final EventLoggerWrapper mEventLogger; - @Nullable - private final FastPairConnection.FastPairSignalChecker mFastPairSignalChecker; - - /** - * Keeps the keys used during handshaking, generated by {@link #createKey(byte[])}. - */ - private static final class Keys { - - private final byte[] mSharedSecret; - private final byte[] mPublicKey; - - private Keys(byte[] sharedSecret, byte[] publicKey) { - this.mSharedSecret = sharedSecret; - this.mPublicKey = publicKey; - } - } - - public HandshakeHandler( - GattConnectionManager gattConnectionManager, - String bleAddress, - Preferences preferences, - EventLoggerWrapper eventLogger, - @Nullable FastPairConnection.FastPairSignalChecker fastPairSignalChecker) { - this.mGattConnectionManager = gattConnectionManager; - this.mProviderBleAddress = bleAddress; - this.mPreferences = preferences; - this.mEventLogger = eventLogger; - this.mFastPairSignalChecker = fastPairSignalChecker; - } - - /** - * Performs a handshake to authenticate and get the remote device's public address. Returns the - * AES-128 key as the shared secret for this pairing session. - */ - public SharedSecret doHandshake(byte[] key, HandshakeMessage message) - throws GeneralSecurityException, InterruptedException, ExecutionException, - TimeoutException, BluetoothException, PairingException { - Keys keys = createKey(key); - Log.i(TAG, - "Handshake " + maskBluetoothAddress(mProviderBleAddress) + ", flags " - + message.mFlags); - byte[] handshakeResponse = - processGattCommunication( - createPacket(keys, message), - SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds())); - String providerPublicAddress = decodeResponse(keys.mSharedSecret, handshakeResponse); - - return SharedSecret.create(keys.mSharedSecret, providerPublicAddress); - } - - /** - * Performs a handshake to authenticate and get the remote device's public address. Returns the - * AES-128 key as the shared secret for this pairing session. Will retry and also performs - * FastPair signal check if fails. - */ - public SharedSecret doHandshakeWithRetryAndSignalLostCheck( - byte[] key, HandshakeMessage message, @Nullable Consumer rescueFromError) - throws GeneralSecurityException, InterruptedException, ExecutionException, - TimeoutException, BluetoothException, PairingException { - Keys keys = createKey(key); - Log.i(TAG, - "Handshake " + maskBluetoothAddress(mProviderBleAddress) + ", flags " - + message.mFlags); - int retryCount = 0; - byte[] handshakeResponse = null; - long startTime = SystemClock.elapsedRealtime(); - BluetoothException lastException = null; - do { - try { - mEventLogger.setCurrentEvent(EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION); - handshakeResponse = - processGattCommunication( - createPacket(keys, message), - getTimeoutMs(SystemClock.elapsedRealtime() - startTime)); - mEventLogger.logCurrentEventSucceeded(); - if (lastException != null) { - logRetrySuccessEvent(EventCode.RECOVER_BY_RETRY_HANDSHAKE, lastException, - mEventLogger); - } - } catch (BluetoothException e) { - lastException = e; - long spentTime = SystemClock.elapsedRealtime() - startTime; - Log.w(TAG, "Secret handshake failed, address=" - + maskBluetoothAddress(mProviderBleAddress) - + ", spent time=" + spentTime + "ms, retryCount=" + retryCount); - mEventLogger.logCurrentEventFailed(e); - - if (!mPreferences.getRetryGattConnectionAndSecretHandshake()) { - throw e; - } - - if (spentTime > mPreferences.getSecretHandshakeLongTimeoutRetryMaxSpentTimeMs()) { - Log.w(TAG, "Spent too long time for handshake, timeInMs=" + spentTime); - throw e; - } - if (isNoRetryError(mPreferences, e)) { - throw e; - } - - if (mFastPairSignalChecker != null) { - FastPairDualConnection - .checkFastPairSignal(mFastPairSignalChecker, mProviderBleAddress, e); - } - retryCount++; - if (retryCount > mPreferences.getSecretHandshakeRetryAttempts() - || ((e instanceof BluetoothOperationTimeoutException) - && !mPreferences.getRetrySecretHandshakeTimeout())) { - throw new HandshakeException("Fail on handshake!", e); - } - if (rescueFromError != null) { - rescueFromError.accept( - (e instanceof BluetoothTimeoutException - || e instanceof BluetoothOperationTimeoutException) - ? ErrorCode.SUCCESS_RETRY_SECRET_HANDSHAKE_TIMEOUT - : ErrorCode.SUCCESS_RETRY_SECRET_HANDSHAKE_ERROR); - } - } - } while (mPreferences.getRetryGattConnectionAndSecretHandshake() - && handshakeResponse == null); - if (retryCount > 0) { - Log.i(TAG, "Secret handshake failed but restored by retry, retry count=" + retryCount); - } - String providerPublicAddress = - decodeResponse(keys.mSharedSecret, verifyNotNull(handshakeResponse)); - - return SharedSecret.create(keys.mSharedSecret, providerPublicAddress); - } - - @VisibleForTesting - long getTimeoutMs(long spentTime) { - if (!mPreferences.getRetryGattConnectionAndSecretHandshake()) { - return SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds()); - } else { - return spentTime < mPreferences.getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs() - ? mPreferences.getSecretHandshakeShortTimeoutMs() - : mPreferences.getSecretHandshakeLongTimeoutMs(); - } - } - - /** - * If the given key is an ecc-256 public key (currently, we are using secp256r1), the shared - * secret is generated by ECDH; if the input key is AES-128 key (should be the account key), - * then it is the shared secret. - */ - private Keys createKey(byte[] key) throws GeneralSecurityException { - if (key.length == EllipticCurveDiffieHellmanExchange.PUBLIC_KEY_LENGTH) { - EllipticCurveDiffieHellmanExchange exchange = EllipticCurveDiffieHellmanExchange - .create(); - byte[] publicKey = exchange.getPublicKey(); - if (publicKey != null) { - Log.i(TAG, "Handshake " + maskBluetoothAddress(mProviderBleAddress) - + ", generates key by ECDH."); - } else { - throw new GeneralSecurityException("Failed to do ECDH."); - } - return new Keys(exchange.generateSecret(key), publicKey); - } else if (key.length == AesEcbSingleBlockEncryption.KEY_LENGTH) { - Log.i(TAG, "Handshake " + maskBluetoothAddress(mProviderBleAddress) - + ", using the given secret."); - return new Keys(key, new byte[0]); - } else { - throw new GeneralSecurityException("Key length is not correct: " + key.length); - } - } - - private static byte[] createPacket(Keys keys, HandshakeMessage message) - throws GeneralSecurityException { - byte[] encryptedMessage = encrypt(keys.mSharedSecret, message.getBytes()); - return concat(encryptedMessage, keys.mPublicKey); - } - - private byte[] processGattCommunication(byte[] packet, long gattOperationTimeoutMS) - throws BluetoothException, InterruptedException, ExecutionException, TimeoutException { - BluetoothGattConnection gattConnection = mGattConnectionManager.getConnection(); - gattConnection.setOperationTimeout(gattOperationTimeoutMS); - UUID characteristicUuid = KeyBasedPairingCharacteristic.getId(gattConnection); - ChangeObserver changeObserver = - gattConnection.enableNotification(FastPairService.ID, characteristicUuid); - - Log.i(TAG, - "Writing handshake packet to address=" + maskBluetoothAddress(mProviderBleAddress)); - gattConnection.writeCharacteristic(FastPairService.ID, characteristicUuid, packet); - Log.i(TAG, "Waiting handshake packet from address=" + maskBluetoothAddress( - mProviderBleAddress)); - return changeObserver.waitForUpdate(gattOperationTimeoutMS); - } - - private String decodeResponse(byte[] sharedSecret, byte[] response) - throws PairingException, GeneralSecurityException { - if (response.length != AES_BLOCK_LENGTH) { - throw new PairingException( - "Handshake failed because of incorrect response: " + base16().encode(response)); - } - // 1 byte type, 6 bytes public address, remainder random salt. - byte[] decryptedResponse = decrypt(sharedSecret, response); - if (decryptedResponse[0] != KeyBasedPairingCharacteristic.Response.TYPE) { - throw new PairingException( - "Handshake response type incorrect: " + decryptedResponse[0]); - } - String address = BluetoothAddress.encode(Arrays.copyOfRange(decryptedResponse, 1, 7)); - Log.i(TAG, "Handshake success with public " + maskBluetoothAddress(address) + ", ble " - + maskBluetoothAddress(mProviderBleAddress)); - return address; - } - - /** - * The base class for handshake message that contains the common data: message type, flags and - * verification data. - */ - abstract static class HandshakeMessage { - - final byte mType; - final byte mFlags; - private final byte[] mVerificationData; - - HandshakeMessage(Builder builder) { - this.mType = builder.mType; - this.mVerificationData = builder.mVerificationData; - this.mFlags = builder.mFlags; - } - - abstract static class Builder> { - - byte mType; - byte mFlags; - private byte[] mVerificationData; - - abstract T getThis(); - - T setVerificationData(byte[] verificationData) { - if (verificationData.length != BLUETOOTH_ADDRESS_LENGTH) { - throw new IllegalArgumentException( - "Incorrect verification data length: " + verificationData.length + "."); - } - this.mVerificationData = verificationData; - return getThis(); - } - } - - /** - * Constructs the base handshake message according to the format of Fast Pair spec. - */ - byte[] constructBaseBytes() { - byte[] rawMessage = new byte[Request.SIZE]; - new SecureRandom().nextBytes(rawMessage); - rawMessage[TYPE_INDEX] = mType; - rawMessage[FLAGS_INDEX] = mFlags; - - System.arraycopy( - mVerificationData, - /* srcPos= */ 0, - rawMessage, - VERIFICATION_DATA_INDEX, - VERIFICATION_DATA_LENGTH); - return rawMessage; - } - - /** - * Returns the raw handshake message. - */ - abstract byte[] getBytes(); - } - - /** - * Extends {@link HandshakeMessage} and contains the required data for key-based pairing - * request. - */ - public static class KeyBasedPairingRequest extends HandshakeMessage { - - @Nullable - private final byte[] mSeekerPublicAddress; - - private KeyBasedPairingRequest(Builder builder) { - super(builder); - this.mSeekerPublicAddress = builder.mSeekerPublicAddress; - } - - @Override - byte[] getBytes() { - byte[] rawMessage = constructBaseBytes(); - if (mSeekerPublicAddress != null) { - System.arraycopy( - mSeekerPublicAddress, - /* srcPos= */ 0, - rawMessage, - SEEKER_PUBLIC_ADDRESS_INDEX, - BLUETOOTH_ADDRESS_LENGTH); - } - Log.i(TAG, - "Handshake Message: type (" + rawMessage[TYPE_INDEX] + "), flag (" - + rawMessage[FLAGS_INDEX] + ")."); - return rawMessage; - } - - /** - * Builder class for key-based pairing request. - */ - public static class Builder extends HandshakeMessage.Builder { - - @Nullable - private byte[] mSeekerPublicAddress; - - /** - * Adds flags without changing other flags. - */ - public Builder addFlag(@KeyBasedPairingRequestFlag int flag) { - this.mFlags |= (byte) flag; - return this; - } - - /** - * Set seeker's public address. - */ - public Builder setSeekerPublicAddress(byte[] seekerPublicAddress) { - this.mSeekerPublicAddress = seekerPublicAddress; - return this; - } - - /** - * Buulds KeyBasedPairigRequest. - */ - public KeyBasedPairingRequest build() { - mType = TYPE_KEY_BASED_PAIRING_REQUEST; - return new KeyBasedPairingRequest(this); - } - - @Override - Builder getThis() { - return this; - } - } - } - - /** - * Extends {@link HandshakeMessage} and contains the required data for action over BLE request. - */ - public static class ActionOverBle extends HandshakeMessage { - - private final byte mEventGroup; - private final byte mEventCode; - @Nullable - private final byte[] mEventData; - private final byte mAdditionalDataType; - - private ActionOverBle(Builder builder) { - super(builder); - this.mEventGroup = builder.mEventGroup; - this.mEventCode = builder.mEventCode; - this.mEventData = builder.mEventData; - this.mAdditionalDataType = builder.mAdditionalDataType; - } - - @Override - byte[] getBytes() { - byte[] rawMessage = constructBaseBytes(); - StringBuilder stringBuilder = - new StringBuilder( - String.format( - "type (%02X), flag (%02X)", rawMessage[TYPE_INDEX], - rawMessage[FLAGS_INDEX])); - if ((mFlags & (byte) DEVICE_ACTION) != 0) { - rawMessage[EVENT_GROUP_INDEX] = mEventGroup; - rawMessage[EVENT_CODE_INDEX] = mEventCode; - - if (mEventData != null) { - rawMessage[EVENT_ADDITIONAL_DATA_LENGTH_INDEX] = (byte) mEventData.length; - System.arraycopy( - mEventData, - /* srcPos= */ 0, - rawMessage, - EVENT_ADDITIONAL_DATA_INDEX, - mEventData.length); - } else { - rawMessage[EVENT_ADDITIONAL_DATA_LENGTH_INDEX] = (byte) 0; - } - stringBuilder.append( - String.format( - ", group(%02X), code(%02X), length(%02X)", - rawMessage[EVENT_GROUP_INDEX], - rawMessage[EVENT_CODE_INDEX], - rawMessage[EVENT_ADDITIONAL_DATA_LENGTH_INDEX])); - } - if ((mFlags & (byte) ADDITIONAL_DATA_CHARACTERISTIC) != 0) { - rawMessage[ADDITIONAL_DATA_TYPE_INDEX] = mAdditionalDataType; - stringBuilder.append( - String.format(", data id(%02X)", rawMessage[ADDITIONAL_DATA_TYPE_INDEX])); - } - Log.i(TAG, "Handshake Message: " + stringBuilder); - return rawMessage; - } - - /** - * Builder class for action over BLE request. - */ - public static class Builder extends HandshakeMessage.Builder { - - private byte mEventGroup; - private byte mEventCode; - @Nullable - private byte[] mEventData; - private byte mAdditionalDataType; - - // Adds a flag to this handshake message. This can be called repeatedly for adding - // different preference. - - /** - * Adds flag without changing other flags. - */ - public Builder addFlag(@ActionOverBleFlag int flag) { - this.mFlags |= (byte) flag; - return this; - } - - /** - * Set event group and event code. - */ - public Builder setEvent(int eventGroup, int eventCode) { - this.mFlags |= (byte) DEVICE_ACTION; - this.mEventGroup = (byte) (eventGroup & 0xFF); - this.mEventCode = (byte) (eventCode & 0xFF); - return this; - } - - /** - * Set event additional data. - */ - public Builder setEventAdditionalData(byte[] data) { - this.mEventData = data; - return this; - } - - /** - * Set event additional data type. - */ - public Builder setAdditionalDataType(@AdditionalDataType int additionalDataType) { - this.mFlags |= (byte) ADDITIONAL_DATA_CHARACTERISTIC; - this.mAdditionalDataType = (byte) additionalDataType; - return this; - } - - @Override - Builder getThis() { - return this; - } - - ActionOverBle build() { - mType = TYPE_ACTION_OVER_BLE; - return new ActionOverBle(this); - } - } - } - - /** - * Exception for handshake failure. - */ - public static class HandshakeException extends PairingException { - - private final BluetoothException mOriginalException; - - @VisibleForTesting - HandshakeException(String format, BluetoothException e) { - super(format); - mOriginalException = e; - } - - public BluetoothException getOriginalException() { - return mOriginalException; - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPiece.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPiece.java deleted file mode 100644 index 26ff79fab58a3a0e533026e5314f9a243c373ff2..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPiece.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; - -import androidx.annotation.Nullable; -import androidx.core.content.FileProvider; - -import java.util.Arrays; -import java.util.Objects; - -/** - * This class is subclass of real headset. It contains image url, battery value and charging - * status. - */ -public class HeadsetPiece implements Parcelable { - private int mLowLevelThreshold; - private int mBatteryLevel; - private String mImageUrl; - private boolean mCharging; - private Uri mImageContentUri; - - private HeadsetPiece( - int lowLevelThreshold, - int batteryLevel, - String imageUrl, - boolean charging, - @Nullable Uri imageContentUri) { - this.mLowLevelThreshold = lowLevelThreshold; - this.mBatteryLevel = batteryLevel; - this.mImageUrl = imageUrl; - this.mCharging = charging; - this.mImageContentUri = imageContentUri; - } - - /** - * Returns a builder of HeadsetPiece. - */ - public static HeadsetPiece.Builder builder() { - return new HeadsetPiece.Builder(); - } - - /** - * The low level threshold. - */ - public int lowLevelThreshold() { - return mLowLevelThreshold; - } - - /** - * The battery level. - */ - public int batteryLevel() { - return mBatteryLevel; - } - - /** - * The web URL of the image. - */ - public String imageUrl() { - return mImageUrl; - } - - /** - * Whether the headset is charging. - */ - public boolean charging() { - return mCharging; - } - - /** - * The content Uri of the image if it could be downloaded from the web URL and generated through - * {@link FileProvider#getUriForFile} successfully, otherwise null. - */ - @Nullable - public Uri imageContentUri() { - return mImageContentUri; - } - - /** - * @return whether battery is low or not. - */ - public boolean isBatteryLow() { - return batteryLevel() <= lowLevelThreshold() && batteryLevel() >= 0 && !charging(); - } - - @Override - public String toString() { - return "HeadsetPiece{" - + "lowLevelThreshold=" + mLowLevelThreshold + ", " - + "batteryLevel=" + mBatteryLevel + ", " - + "imageUrl=" + mImageUrl + ", " - + "charging=" + mCharging + ", " - + "imageContentUri=" + mImageContentUri - + "}"; - } - - /** - * Builder function for headset piece. - */ - public static class Builder { - private int mLowLevelThreshold; - private int mBatteryLevel; - private String mImageUrl; - private boolean mCharging; - private Uri mImageContentUri; - - /** - * Set low level threshold. - */ - public HeadsetPiece.Builder setLowLevelThreshold(int lowLevelThreshold) { - this.mLowLevelThreshold = lowLevelThreshold; - return this; - } - - /** - * Set battery level. - */ - public HeadsetPiece.Builder setBatteryLevel(int level) { - this.mBatteryLevel = level; - return this; - } - - /** - * Set image url. - */ - public HeadsetPiece.Builder setImageUrl(String url) { - this.mImageUrl = url; - return this; - } - - /** - * Set charging. - */ - public HeadsetPiece.Builder setCharging(boolean charging) { - this.mCharging = charging; - return this; - } - - /** - * Set image content Uri. - */ - public HeadsetPiece.Builder setImageContentUri(Uri uri) { - this.mImageContentUri = uri; - return this; - } - - /** - * Builds HeadSetPiece. - */ - public HeadsetPiece build() { - return new HeadsetPiece(mLowLevelThreshold, mBatteryLevel, mImageUrl, mCharging, - mImageContentUri); - } - } - - @Override - public final void writeToParcel(Parcel dest, int flags) { - dest.writeString(imageUrl()); - dest.writeInt(lowLevelThreshold()); - dest.writeInt(batteryLevel()); - // Writes 1 if charging, otherwise 0. - dest.writeByte((byte) (charging() ? 1 : 0)); - dest.writeParcelable(imageContentUri(), flags); - } - - @Override - public final int describeContents() { - return 0; - } - - public static final Creator CREATOR = - new Creator() { - @Override - public HeadsetPiece createFromParcel(Parcel in) { - String imageUrl = in.readString(); - return HeadsetPiece.builder() - .setImageUrl(imageUrl != null ? imageUrl : "") - .setLowLevelThreshold(in.readInt()) - .setBatteryLevel(in.readInt()) - .setCharging(in.readByte() != 0) - .setImageContentUri(in.readParcelable(Uri.class.getClassLoader())) - .build(); - } - - @Override - public HeadsetPiece[] newArray(int size) { - return new HeadsetPiece[size]; - } - }; - - @Override - public final int hashCode() { - return Arrays.hashCode( - new Object[]{ - lowLevelThreshold(), batteryLevel(), imageUrl(), charging(), - imageContentUri() - }); - } - - @Override - public final boolean equals(@Nullable Object other) { - if (other == null) { - return false; - } - - if (this == other) { - return true; - } - - if (!(other instanceof HeadsetPiece)) { - return false; - } - - HeadsetPiece that = (HeadsetPiece) other; - return lowLevelThreshold() == that.lowLevelThreshold() - && batteryLevel() == that.batteryLevel() - && Objects.equals(imageUrl(), that.imageUrl()) - && charging() == that.charging() - && Objects.equals(imageContentUri(), that.imageContentUri()); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256.java deleted file mode 100644 index cc7a300dbb3692a29adef4378ab71d91c3848878..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/HmacSha256.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.KEY_LENGTH; - -import androidx.annotation.VisibleForTesting; - -import java.security.GeneralSecurityException; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -/** - * HMAC-SHA256 utility used to generate key-SHA256 based message authentication code. This is - * specific for Fast Pair GATT connection exchanging data to verify both the data integrity and the - * authentication of a message. It is defined as: - * - *

      - *
    1. SHA256(concat((key ^ opad),SHA256(concat((key ^ ipad), data)))), where - *
    2. key is the given secret extended to 64 bytes by concat(secret, ZEROS). - *
    3. opad is 64 bytes outer padding, consisting of repeated bytes valued 0x5c. - *
    4. ipad is 64 bytes inner padding, consisting of repeated bytes valued 0x36. - *
    - * - */ -final class HmacSha256 { - @VisibleForTesting static final int HMAC_SHA256_BLOCK_SIZE = 64; - - private HmacSha256() {} - - /** - * Generates the HMAC for given parameters, this is specific for Fast Pair GATT connection - * exchanging data which is encrypted using AES-CTR. - * - * @param secret 16 bytes shared secret. - * @param data the data encrypted using AES-CTR and the given nonce. - * @return HMAC-SHA256 result. - */ - static byte[] build(byte[] secret, byte[] data) throws GeneralSecurityException { - // Currently we only accept AES-128 key here, the second check is to secure we won't - // modify KEY_LENGTH to > HMAC_SHA256_BLOCK_SIZE by mistake. - if (secret.length != KEY_LENGTH) { - throw new GeneralSecurityException("Incorrect key length, should be the AES-128 key."); - } - if (KEY_LENGTH > HMAC_SHA256_BLOCK_SIZE) { - throw new GeneralSecurityException("KEY_LENGTH > HMAC_SHA256_BLOCK_SIZE!"); - } - - return buildWith64BytesKey(secret, data); - } - - /** - * Generates the HMAC for given parameters, this is specific for Fast Pair GATT connection - * exchanging data which is encrypted using AES-CTR. - * - * @param secret 16 bytes shared secret. - * @param data the data encrypted using AES-CTR and the given nonce. - * @return HMAC-SHA256 result. - */ - static byte[] buildWith64BytesKey(byte[] secret, byte[] data) throws GeneralSecurityException { - if (secret.length > HMAC_SHA256_BLOCK_SIZE) { - throw new GeneralSecurityException("KEY_LENGTH > HMAC_SHA256_BLOCK_SIZE!"); - } - - Mac mac = Mac.getInstance("HmacSHA256"); - SecretKeySpec keySpec = new SecretKeySpec(secret, "HmacSHA256"); - mac.init(keySpec); - - return mac.doFinal(data); - } - - /** - * Constant-time HMAC comparison to prevent a possible timing attack, e.g. time the same MAC - * with all different first byte for a given ciphertext, the right one will take longer as it - * will fail on the second byte's verification. - * - * @param hmac1 HMAC want to be compared with. - * @param hmac2 HMAC want to be compared with. - * @return true if and ony if the give 2 HMACs are identical and non-null. - */ - static boolean compareTwoHMACs(byte[] hmac1, byte[] hmac2) { - if (hmac1 == null || hmac2 == null) { - return false; - } - - if (hmac1.length != hmac2.length) { - return false; - } - // This is for constant-time comparison, don't optimize it. - int res = 0; - for (int i = 0; i < hmac1.length; i++) { - res |= hmac1[i] ^ hmac2[i]; - } - return res == 0; - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Ltv.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Ltv.java deleted file mode 100644 index 88c9484d34a1ef1d6c7b17f07ab157a0a2789960..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Ltv.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static com.google.common.io.BaseEncoding.base16; - -import com.google.common.primitives.Bytes; -import com.google.errorprone.annotations.FormatMethod; -import com.google.errorprone.annotations.FormatString; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * A length, type, value (LTV) data block. - */ -public class Ltv { - - private static final int SIZE_OF_LEN_TYPE = 2; - - final byte mType; - final byte[] mValue; - - /** - * Thrown if there's an error during {@link #parse}. - */ - public static class ParseException extends Exception { - - @FormatMethod - private ParseException(@FormatString String format, Object... objects) { - super(String.format(format, objects)); - } - } - - /** - * Constructor. - */ - public Ltv(byte type, byte... value) { - this.mType = type; - this.mValue = value; - } - - /** - * Parses a list of LTV blocks out of the input byte block. - */ - static List parse(byte[] bytes) throws ParseException { - List ltvs = new ArrayList<>(); - // The "+ 2" is for the length and type bytes. - for (int valueLength, i = 0; i < bytes.length; i += SIZE_OF_LEN_TYPE + valueLength) { - // - 1 since the length in the packet includes the type byte. - valueLength = bytes[i] - 1; - if (valueLength < 0 || bytes.length < i + SIZE_OF_LEN_TYPE + valueLength) { - throw new ParseException( - "Wrong length=%d at index=%d in LTVs=%s", bytes[i], i, - base16().encode(bytes)); - } - ltvs.add(new Ltv(bytes[i + 1], Arrays.copyOfRange(bytes, i + SIZE_OF_LEN_TYPE, - i + SIZE_OF_LEN_TYPE + valueLength))); - } - return ltvs; - } - - /** - * Returns an LTV block, where length is mValue.length + 1 (for the type byte). - */ - public byte[] getBytes() { - return Bytes.concat(new byte[]{(byte) (mValue.length + 1), mType}, mValue); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoder.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoder.java deleted file mode 100644 index b04cf7352afb3f45d1e705007bd7b4f01be72f2c..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/MessageStreamHmacEncoder.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.generateNonce; - -import static com.google.common.primitives.Bytes.concat; - -import java.security.GeneralSecurityException; -import java.util.Arrays; - -/** - * Message stream utilities for encoding raw packet with HMAC. - * - *

    Encoded packet is: - * - *

      - *
    1. Packet[0 - (data length - 1)]: the raw data. - *
    2. Packet[data length - (data length + 7)]: the 8-byte message nonce. - *
    3. Packet[(data length + 8) - (data length + 15)]: the 8-byte of HMAC. - *
    - */ -public class MessageStreamHmacEncoder { - public static final int EXTRACT_HMAC_SIZE = 8; - public static final int SECTION_NONCE_LENGTH = 8; - - private MessageStreamHmacEncoder() {} - - /** Encodes Message Packet. */ - public static byte[] encodeMessagePacket(byte[] accountKey, byte[] sectionNonce, byte[] data) - throws GeneralSecurityException { - checkAccountKeyAndSectionNonce(accountKey, sectionNonce); - - if (data == null || data.length == 0) { - throw new GeneralSecurityException("No input data for encodeMessagePacket"); - } - - byte[] messageNonce = generateNonce(); - byte[] extractedHmac = - Arrays.copyOf( - HmacSha256.buildWith64BytesKey( - accountKey, concat(sectionNonce, messageNonce, data)), - EXTRACT_HMAC_SIZE); - - return concat(data, messageNonce, extractedHmac); - } - - /** Verifies Hmac. */ - public static boolean verifyHmac(byte[] accountKey, byte[] sectionNonce, byte[] data) - throws GeneralSecurityException { - checkAccountKeyAndSectionNonce(accountKey, sectionNonce); - if (data == null) { - throw new GeneralSecurityException("data is null"); - } - if (data.length <= EXTRACT_HMAC_SIZE + SECTION_NONCE_LENGTH) { - throw new GeneralSecurityException("data.length too short"); - } - - byte[] hmac = Arrays.copyOfRange(data, data.length - EXTRACT_HMAC_SIZE, data.length); - byte[] messageNonce = - Arrays.copyOfRange( - data, - data.length - EXTRACT_HMAC_SIZE - SECTION_NONCE_LENGTH, - data.length - EXTRACT_HMAC_SIZE); - byte[] rawData = Arrays.copyOf( - data, data.length - EXTRACT_HMAC_SIZE - SECTION_NONCE_LENGTH); - return Arrays.equals( - Arrays.copyOf( - HmacSha256.buildWith64BytesKey( - accountKey, concat(sectionNonce, messageNonce, rawData)), - EXTRACT_HMAC_SIZE), - hmac); - } - - private static void checkAccountKeyAndSectionNonce(byte[] accountKey, byte[] sectionNonce) - throws GeneralSecurityException { - if (accountKey == null || accountKey.length == 0) { - throw new GeneralSecurityException( - "Incorrect accountKey for encoding message packet, accountKey.length = " - + (accountKey == null ? "NULL" : accountKey.length)); - } - - if (sectionNonce == null || sectionNonce.length != SECTION_NONCE_LENGTH) { - throw new GeneralSecurityException( - "Incorrect sectionNonce for encoding message packet, sectionNonce.length = " - + (sectionNonce == null ? "NULL" : sectionNonce.length)); - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoder.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoder.java deleted file mode 100644 index 1521be6033871485e2ed85c1048bf453a00538bf..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/NamingEncoder.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.NONCE_SIZE; - -import static com.google.common.primitives.Bytes.concat; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import android.annotation.TargetApi; -import android.os.Build.VERSION_CODES; - -import com.google.common.base.Utf8; - -import java.security.GeneralSecurityException; -import java.util.Arrays; - -/** - * Naming utilities for encoding naming packet, decoding naming packet and verifying both the data - * integrity and the authentication of a message by checking HMAC. - * - *

    Naming packet is: - * - *

      - *
    1. Naming_Packet[0 - 7]: the first 8-byte of HMAC. - *
    2. Naming_Packet[8 - var]: the encrypted name (with 8-byte nonce appended to the front). - *
    - */ -@TargetApi(VERSION_CODES.M) -public final class NamingEncoder { - - static final int EXTRACT_HMAC_SIZE = 8; - static final int MAX_LENGTH_OF_NAME = 48; - - private NamingEncoder() { - } - - /** - * Encodes the name to naming packet by the given secret. - * - * @param secret AES-128 key for encryption. - * @param name the given name to be encoded. - * @return the encrypted data with the 8-byte extracted HMAC appended to the front. - * @throws GeneralSecurityException if the given key or name is invalid for encoding. - */ - public static byte[] encodeNamingPacket(byte[] secret, String name) - throws GeneralSecurityException { - if (secret == null || secret.length != AesCtrMultipleBlockEncryption.KEY_LENGTH) { - throw new GeneralSecurityException( - "Incorrect secret for encoding name packet, secret.length = " - + (secret == null ? "NULL" : secret.length)); - } - - if ((name == null) || (name.length() == 0) || (Utf8.encodedLength(name) - > MAX_LENGTH_OF_NAME)) { - throw new GeneralSecurityException( - "Invalid name for encoding name packet, Utf8.encodedLength(name) = " - + (name == null ? "NULL" : Utf8.encodedLength(name))); - } - - byte[] encryptedData = AesCtrMultipleBlockEncryption.encrypt(secret, name.getBytes(UTF_8)); - byte[] extractedHmac = - Arrays.copyOf(HmacSha256.build(secret, encryptedData), EXTRACT_HMAC_SIZE); - - return concat(extractedHmac, encryptedData); - } - - /** - * Decodes the name from naming packet by the given secret. - * - * @param secret AES-128 key used in the encryption to decrypt data. - * @param namingPacket naming packet which is encoded by the given secret.. - * @return the name decoded from the given packet. - * @throws GeneralSecurityException if the given key or naming packet is invalid for decoding. - */ - public static String decodeNamingPacket(byte[] secret, byte[] namingPacket) - throws GeneralSecurityException { - if (secret == null || secret.length != AesCtrMultipleBlockEncryption.KEY_LENGTH) { - throw new GeneralSecurityException( - "Incorrect secret for decoding name packet, secret.length = " - + (secret == null ? "NULL" : secret.length)); - } - if (namingPacket == null - || namingPacket.length <= EXTRACT_HMAC_SIZE - || namingPacket.length > (MAX_LENGTH_OF_NAME + EXTRACT_HMAC_SIZE + NONCE_SIZE)) { - throw new GeneralSecurityException( - "Naming packet size is incorrect, namingPacket.length is " - + (namingPacket == null ? "NULL" : namingPacket.length)); - } - - if (!verifyHmac(secret, namingPacket)) { - throw new GeneralSecurityException( - "Verify HMAC failed, could be incorrect key or naming packet."); - } - byte[] encryptedData = Arrays - .copyOfRange(namingPacket, EXTRACT_HMAC_SIZE, namingPacket.length); - return new String(AesCtrMultipleBlockEncryption.decrypt(secret, encryptedData), UTF_8); - } - - // Computes the HMAC of the given key and name, and compares the first 8-byte of the HMAC result - // with the one from name packet. Must call constant-time comparison to prevent a possible - // timing attack, e.g. time the same MAC with all different first byte for a given ciphertext, - // the right one will take longer as it will fail on the second byte's verification. - private static boolean verifyHmac(byte[] key, byte[] namingPacket) - throws GeneralSecurityException { - byte[] packetHmac = Arrays.copyOfRange(namingPacket, /* from= */ 0, EXTRACT_HMAC_SIZE); - byte[] encryptedData = Arrays - .copyOfRange(namingPacket, EXTRACT_HMAC_SIZE, namingPacket.length); - byte[] computedHmac = Arrays - .copyOf(HmacSha256.build(key, encryptedData), EXTRACT_HMAC_SIZE); - - return HmacSha256.compareTwoHMACs(packetHmac, computedHmac); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PairingException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PairingException.java deleted file mode 100644 index 722dc85c8320cdc55a0ca8be5244cb11a90de74c..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PairingException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -/** Base class for pairing exceptions. */ -// TODO(b/200594968): convert exceptions into error codes to save memory. -public class PairingException extends Exception { - PairingException(String format, Object... objects) { - super(String.format(format, objects)); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PairingProgressListener.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PairingProgressListener.java deleted file mode 100644 index 270cb42fa7f9322d1a8a7b5ebaeba3b8a6163cdf..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PairingProgressListener.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import androidx.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** Callback interface for pairing progress. */ -public interface PairingProgressListener { - - /** Fast Pair Bond State. */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - value = { - PairingEvent.START, - PairingEvent.SUCCESS, - PairingEvent.FAILED, - PairingEvent.UNKNOWN, - }) - public @interface PairingEvent { - int START = 0; - int SUCCESS = 1; - int FAILED = 2; - int UNKNOWN = 3; - } - - /** Returns enum based on the ordinal index. */ - static @PairingEvent int fromOrdinal(int ordinal) { - switch (ordinal) { - case 0: - return PairingEvent.START; - case 1: - return PairingEvent.SUCCESS; - case 2: - return PairingEvent.FAILED; - case 3: - return PairingEvent.UNKNOWN; - default: - return PairingEvent.UNKNOWN; - } - } - - /** Callback function upon pairing progress update. */ - void onPairingProgressUpdating(@PairingEvent int event, String message); -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PasskeyConfirmationHandler.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PasskeyConfirmationHandler.java deleted file mode 100644 index f5807a3254f03ddb3f812e7b1a04358f10e5ec61..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/PasskeyConfirmationHandler.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import android.bluetooth.BluetoothDevice; - -/** Interface for getting the passkey confirmation request. */ -public interface PasskeyConfirmationHandler { - /** Called when getting the passkey confirmation request while pairing. */ - void onPasskeyConfirmation(BluetoothDevice device, int passkey); -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Preferences.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Preferences.java deleted file mode 100644 index eb5bad57c4e8d36be15d9b1670c8d9e4007b799c..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Preferences.java +++ /dev/null @@ -1,2216 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.get16BitUuid; - -import androidx.annotation.Nullable; - -import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.FirmwareVersionCharacteristic; - -import com.google.common.collect.ImmutableSet; -import com.google.common.primitives.Shorts; - -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.Objects; - -/** - * Preferences that tweak the Fast Pairing process: timeouts, number of retries... All preferences - * have default values which should be reasonable for all clients. - */ -public class Preferences { - - private final int mGattOperationTimeoutSeconds; - private final int mGattConnectionTimeoutSeconds; - private final int mBluetoothToggleTimeoutSeconds; - private final int mBluetoothToggleSleepSeconds; - private final int mClassicDiscoveryTimeoutSeconds; - private final int mNumDiscoverAttempts; - private final int mDiscoveryRetrySleepSeconds; - private final boolean mIgnoreDiscoveryError; - private final int mSdpTimeoutSeconds; - private final int mNumSdpAttempts; - private final int mNumCreateBondAttempts; - private final int mNumConnectAttempts; - private final int mNumWriteAccountKeyAttempts; - private final boolean mToggleBluetoothOnFailure; - private final boolean mBluetoothStateUsesPolling; - private final int mBluetoothStatePollingMillis; - private final int mNumAttempts; - private final boolean mEnableBrEdrHandover; - private final short mBrHandoverDataCharacteristicId; - private final short mBluetoothSigDataCharacteristicId; - private final short mFirmwareVersionCharacteristicId; - private final short mBrTransportBlockDataDescriptorId; - private final boolean mWaitForUuidsAfterBonding; - private final boolean mReceiveUuidsAndBondedEventBeforeClose; - private final int mRemoveBondTimeoutSeconds; - private final int mRemoveBondSleepMillis; - private final int mCreateBondTimeoutSeconds; - private final int mHidCreateBondTimeoutSeconds; - private final int mProxyTimeoutSeconds; - private final boolean mRejectPhonebookAccess; - private final boolean mRejectMessageAccess; - private final boolean mRejectSimAccess; - private final int mWriteAccountKeySleepMillis; - private final boolean mSkipDisconnectingGattBeforeWritingAccountKey; - private final boolean mMoreEventLogForQuality; - private final boolean mRetryGattConnectionAndSecretHandshake; - private final long mGattConnectShortTimeoutMs; - private final long mGattConnectLongTimeoutMs; - private final long mGattConnectShortTimeoutRetryMaxSpentTimeMs; - private final long mAddressRotateRetryMaxSpentTimeMs; - private final long mPairingRetryDelayMs; - private final long mSecretHandshakeShortTimeoutMs; - private final long mSecretHandshakeLongTimeoutMs; - private final long mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs; - private final long mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs; - private final long mSecretHandshakeRetryAttempts; - private final long mSecretHandshakeRetryGattConnectionMaxSpentTimeMs; - private final long mSignalLostRetryMaxSpentTimeMs; - private final ImmutableSet mGattConnectionAndSecretHandshakeNoRetryGattError; - private final boolean mRetrySecretHandshakeTimeout; - private final boolean mLogUserManualRetry; - private final int mPairFailureCounts; - private final String mCachedDeviceAddress; - private final String mPossibleCachedDeviceAddress; - private final int mSameModelIdPairedDeviceCount; - private final boolean mIsDeviceFinishCheckAddressFromCache; - private final boolean mLogPairWithCachedModelId; - private final boolean mDirectConnectProfileIfModelIdInCache; - private final boolean mAcceptPasskey; - private final byte[] mSupportedProfileUuids; - private final boolean mProviderInitiatesBondingIfSupported; - private final boolean mAttemptDirectConnectionWhenPreviouslyBonded; - private final boolean mAutomaticallyReconnectGattWhenNeeded; - private final boolean mSkipConnectingProfiles; - private final boolean mIgnoreUuidTimeoutAfterBonded; - private final boolean mSpecifyCreateBondTransportType; - private final int mCreateBondTransportType; - private final boolean mIncreaseIntentFilterPriority; - private final boolean mEvaluatePerformance; - private final Preferences.ExtraLoggingInformation mExtraLoggingInformation; - private final boolean mEnableNamingCharacteristic; - private final boolean mEnableFirmwareVersionCharacteristic; - private final boolean mKeepSameAccountKeyWrite; - private final boolean mIsRetroactivePairing; - private final int mNumSdpAttemptsAfterBonded; - private final boolean mSupportHidDevice; - private final boolean mEnablePairingWhileDirectlyConnecting; - private final boolean mAcceptConsentForFastPairOne; - private final int mGattConnectRetryTimeoutMillis; - private final boolean mEnable128BitCustomGattCharacteristicsId; - private final boolean mEnableSendExceptionStepToValidator; - private final boolean mEnableAdditionalDataTypeWhenActionOverBle; - private final boolean mCheckBondStateWhenSkipConnectingProfiles; - private final boolean mHandlePasskeyConfirmationByUi; - private final boolean mEnablePairFlowShowUiWithoutProfileConnection; - - private Preferences( - int gattOperationTimeoutSeconds, - int gattConnectionTimeoutSeconds, - int bluetoothToggleTimeoutSeconds, - int bluetoothToggleSleepSeconds, - int classicDiscoveryTimeoutSeconds, - int numDiscoverAttempts, - int discoveryRetrySleepSeconds, - boolean ignoreDiscoveryError, - int sdpTimeoutSeconds, - int numSdpAttempts, - int numCreateBondAttempts, - int numConnectAttempts, - int numWriteAccountKeyAttempts, - boolean toggleBluetoothOnFailure, - boolean bluetoothStateUsesPolling, - int bluetoothStatePollingMillis, - int numAttempts, - boolean enableBrEdrHandover, - short brHandoverDataCharacteristicId, - short bluetoothSigDataCharacteristicId, - short firmwareVersionCharacteristicId, - short brTransportBlockDataDescriptorId, - boolean waitForUuidsAfterBonding, - boolean receiveUuidsAndBondedEventBeforeClose, - int removeBondTimeoutSeconds, - int removeBondSleepMillis, - int createBondTimeoutSeconds, - int hidCreateBondTimeoutSeconds, - int proxyTimeoutSeconds, - boolean rejectPhonebookAccess, - boolean rejectMessageAccess, - boolean rejectSimAccess, - int writeAccountKeySleepMillis, - boolean skipDisconnectingGattBeforeWritingAccountKey, - boolean moreEventLogForQuality, - boolean retryGattConnectionAndSecretHandshake, - long gattConnectShortTimeoutMs, - long gattConnectLongTimeoutMs, - long gattConnectShortTimeoutRetryMaxSpentTimeMs, - long addressRotateRetryMaxSpentTimeMs, - long pairingRetryDelayMs, - long secretHandshakeShortTimeoutMs, - long secretHandshakeLongTimeoutMs, - long secretHandshakeShortTimeoutRetryMaxSpentTimeMs, - long secretHandshakeLongTimeoutRetryMaxSpentTimeMs, - long secretHandshakeRetryAttempts, - long secretHandshakeRetryGattConnectionMaxSpentTimeMs, - long signalLostRetryMaxSpentTimeMs, - ImmutableSet gattConnectionAndSecretHandshakeNoRetryGattError, - boolean retrySecretHandshakeTimeout, - boolean logUserManualRetry, - int pairFailureCounts, - String cachedDeviceAddress, - String possibleCachedDeviceAddress, - int sameModelIdPairedDeviceCount, - boolean isDeviceFinishCheckAddressFromCache, - boolean logPairWithCachedModelId, - boolean directConnectProfileIfModelIdInCache, - boolean acceptPasskey, - byte[] supportedProfileUuids, - boolean providerInitiatesBondingIfSupported, - boolean attemptDirectConnectionWhenPreviouslyBonded, - boolean automaticallyReconnectGattWhenNeeded, - boolean skipConnectingProfiles, - boolean ignoreUuidTimeoutAfterBonded, - boolean specifyCreateBondTransportType, - int createBondTransportType, - boolean increaseIntentFilterPriority, - boolean evaluatePerformance, - @Nullable Preferences.ExtraLoggingInformation extraLoggingInformation, - boolean enableNamingCharacteristic, - boolean enableFirmwareVersionCharacteristic, - boolean keepSameAccountKeyWrite, - boolean isRetroactivePairing, - int numSdpAttemptsAfterBonded, - boolean supportHidDevice, - boolean enablePairingWhileDirectlyConnecting, - boolean acceptConsentForFastPairOne, - int gattConnectRetryTimeoutMillis, - boolean enable128BitCustomGattCharacteristicsId, - boolean enableSendExceptionStepToValidator, - boolean enableAdditionalDataTypeWhenActionOverBle, - boolean checkBondStateWhenSkipConnectingProfiles, - boolean handlePasskeyConfirmationByUi, - boolean enablePairFlowShowUiWithoutProfileConnection) { - this.mGattOperationTimeoutSeconds = gattOperationTimeoutSeconds; - this.mGattConnectionTimeoutSeconds = gattConnectionTimeoutSeconds; - this.mBluetoothToggleTimeoutSeconds = bluetoothToggleTimeoutSeconds; - this.mBluetoothToggleSleepSeconds = bluetoothToggleSleepSeconds; - this.mClassicDiscoveryTimeoutSeconds = classicDiscoveryTimeoutSeconds; - this.mNumDiscoverAttempts = numDiscoverAttempts; - this.mDiscoveryRetrySleepSeconds = discoveryRetrySleepSeconds; - this.mIgnoreDiscoveryError = ignoreDiscoveryError; - this.mSdpTimeoutSeconds = sdpTimeoutSeconds; - this.mNumSdpAttempts = numSdpAttempts; - this.mNumCreateBondAttempts = numCreateBondAttempts; - this.mNumConnectAttempts = numConnectAttempts; - this.mNumWriteAccountKeyAttempts = numWriteAccountKeyAttempts; - this.mToggleBluetoothOnFailure = toggleBluetoothOnFailure; - this.mBluetoothStateUsesPolling = bluetoothStateUsesPolling; - this.mBluetoothStatePollingMillis = bluetoothStatePollingMillis; - this.mNumAttempts = numAttempts; - this.mEnableBrEdrHandover = enableBrEdrHandover; - this.mBrHandoverDataCharacteristicId = brHandoverDataCharacteristicId; - this.mBluetoothSigDataCharacteristicId = bluetoothSigDataCharacteristicId; - this.mFirmwareVersionCharacteristicId = firmwareVersionCharacteristicId; - this.mBrTransportBlockDataDescriptorId = brTransportBlockDataDescriptorId; - this.mWaitForUuidsAfterBonding = waitForUuidsAfterBonding; - this.mReceiveUuidsAndBondedEventBeforeClose = receiveUuidsAndBondedEventBeforeClose; - this.mRemoveBondTimeoutSeconds = removeBondTimeoutSeconds; - this.mRemoveBondSleepMillis = removeBondSleepMillis; - this.mCreateBondTimeoutSeconds = createBondTimeoutSeconds; - this.mHidCreateBondTimeoutSeconds = hidCreateBondTimeoutSeconds; - this.mProxyTimeoutSeconds = proxyTimeoutSeconds; - this.mRejectPhonebookAccess = rejectPhonebookAccess; - this.mRejectMessageAccess = rejectMessageAccess; - this.mRejectSimAccess = rejectSimAccess; - this.mWriteAccountKeySleepMillis = writeAccountKeySleepMillis; - this.mSkipDisconnectingGattBeforeWritingAccountKey = - skipDisconnectingGattBeforeWritingAccountKey; - this.mMoreEventLogForQuality = moreEventLogForQuality; - this.mRetryGattConnectionAndSecretHandshake = retryGattConnectionAndSecretHandshake; - this.mGattConnectShortTimeoutMs = gattConnectShortTimeoutMs; - this.mGattConnectLongTimeoutMs = gattConnectLongTimeoutMs; - this.mGattConnectShortTimeoutRetryMaxSpentTimeMs = - gattConnectShortTimeoutRetryMaxSpentTimeMs; - this.mAddressRotateRetryMaxSpentTimeMs = addressRotateRetryMaxSpentTimeMs; - this.mPairingRetryDelayMs = pairingRetryDelayMs; - this.mSecretHandshakeShortTimeoutMs = secretHandshakeShortTimeoutMs; - this.mSecretHandshakeLongTimeoutMs = secretHandshakeLongTimeoutMs; - this.mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs = - secretHandshakeShortTimeoutRetryMaxSpentTimeMs; - this.mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs = - secretHandshakeLongTimeoutRetryMaxSpentTimeMs; - this.mSecretHandshakeRetryAttempts = secretHandshakeRetryAttempts; - this.mSecretHandshakeRetryGattConnectionMaxSpentTimeMs = - secretHandshakeRetryGattConnectionMaxSpentTimeMs; - this.mSignalLostRetryMaxSpentTimeMs = signalLostRetryMaxSpentTimeMs; - this.mGattConnectionAndSecretHandshakeNoRetryGattError = - gattConnectionAndSecretHandshakeNoRetryGattError; - this.mRetrySecretHandshakeTimeout = retrySecretHandshakeTimeout; - this.mLogUserManualRetry = logUserManualRetry; - this.mPairFailureCounts = pairFailureCounts; - this.mCachedDeviceAddress = cachedDeviceAddress; - this.mPossibleCachedDeviceAddress = possibleCachedDeviceAddress; - this.mSameModelIdPairedDeviceCount = sameModelIdPairedDeviceCount; - this.mIsDeviceFinishCheckAddressFromCache = isDeviceFinishCheckAddressFromCache; - this.mLogPairWithCachedModelId = logPairWithCachedModelId; - this.mDirectConnectProfileIfModelIdInCache = directConnectProfileIfModelIdInCache; - this.mAcceptPasskey = acceptPasskey; - this.mSupportedProfileUuids = supportedProfileUuids; - this.mProviderInitiatesBondingIfSupported = providerInitiatesBondingIfSupported; - this.mAttemptDirectConnectionWhenPreviouslyBonded = - attemptDirectConnectionWhenPreviouslyBonded; - this.mAutomaticallyReconnectGattWhenNeeded = automaticallyReconnectGattWhenNeeded; - this.mSkipConnectingProfiles = skipConnectingProfiles; - this.mIgnoreUuidTimeoutAfterBonded = ignoreUuidTimeoutAfterBonded; - this.mSpecifyCreateBondTransportType = specifyCreateBondTransportType; - this.mCreateBondTransportType = createBondTransportType; - this.mIncreaseIntentFilterPriority = increaseIntentFilterPriority; - this.mEvaluatePerformance = evaluatePerformance; - this.mExtraLoggingInformation = extraLoggingInformation; - this.mEnableNamingCharacteristic = enableNamingCharacteristic; - this.mEnableFirmwareVersionCharacteristic = enableFirmwareVersionCharacteristic; - this.mKeepSameAccountKeyWrite = keepSameAccountKeyWrite; - this.mIsRetroactivePairing = isRetroactivePairing; - this.mNumSdpAttemptsAfterBonded = numSdpAttemptsAfterBonded; - this.mSupportHidDevice = supportHidDevice; - this.mEnablePairingWhileDirectlyConnecting = enablePairingWhileDirectlyConnecting; - this.mAcceptConsentForFastPairOne = acceptConsentForFastPairOne; - this.mGattConnectRetryTimeoutMillis = gattConnectRetryTimeoutMillis; - this.mEnable128BitCustomGattCharacteristicsId = enable128BitCustomGattCharacteristicsId; - this.mEnableSendExceptionStepToValidator = enableSendExceptionStepToValidator; - this.mEnableAdditionalDataTypeWhenActionOverBle = enableAdditionalDataTypeWhenActionOverBle; - this.mCheckBondStateWhenSkipConnectingProfiles = checkBondStateWhenSkipConnectingProfiles; - this.mHandlePasskeyConfirmationByUi = handlePasskeyConfirmationByUi; - this.mEnablePairFlowShowUiWithoutProfileConnection = - enablePairFlowShowUiWithoutProfileConnection; - } - - /** - * Timeout for each GATT operation (not for the whole pairing process). - */ - public int getGattOperationTimeoutSeconds() { - return mGattOperationTimeoutSeconds; - } - - /** - * Timeout for Gatt connection operation. - */ - public int getGattConnectionTimeoutSeconds() { - return mGattConnectionTimeoutSeconds; - } - - /** - * Timeout for Bluetooth toggle. - */ - public int getBluetoothToggleTimeoutSeconds() { - return mBluetoothToggleTimeoutSeconds; - } - - /** - * Sleep time for Bluetooth toggle. - */ - public int getBluetoothToggleSleepSeconds() { - return mBluetoothToggleSleepSeconds; - } - - /** - * Timeout for classic discovery. - */ - public int getClassicDiscoveryTimeoutSeconds() { - return mClassicDiscoveryTimeoutSeconds; - } - - /** - * Number of discovery attempts allowed. - */ - public int getNumDiscoverAttempts() { - return mNumDiscoverAttempts; - } - - /** - * Sleep time between discovery retry. - */ - public int getDiscoveryRetrySleepSeconds() { - return mDiscoveryRetrySleepSeconds; - } - - /** - * Whether to ignore error incurred during discovery. - */ - public boolean getIgnoreDiscoveryError() { - return mIgnoreDiscoveryError; - } - - /** - * Timeout for Sdp. - */ - public int getSdpTimeoutSeconds() { - return mSdpTimeoutSeconds; - } - - /** - * Number of Sdp attempts allowed. - */ - public int getNumSdpAttempts() { - return mNumSdpAttempts; - } - - /** - * Number of create bond attempts allowed. - */ - public int getNumCreateBondAttempts() { - return mNumCreateBondAttempts; - } - - /** - * Number of connect attempts allowed. - */ - public int getNumConnectAttempts() { - return mNumConnectAttempts; - } - - /** - * Number of write account key attempts allowed. - */ - public int getNumWriteAccountKeyAttempts() { - return mNumWriteAccountKeyAttempts; - } - - /** - * Returns whether it is OK toggle bluetooth to retry upon failure. - */ - public boolean getToggleBluetoothOnFailure() { - return mToggleBluetoothOnFailure; - } - - /** - * Whether to get Bluetooth state using polling. - */ - public boolean getBluetoothStateUsesPolling() { - return mBluetoothStateUsesPolling; - } - - /** - * Polling time when retrieving Bluetooth state. - */ - public int getBluetoothStatePollingMillis() { - return mBluetoothStatePollingMillis; - } - - /** - * The number of times to attempt a generic operation, before giving up. - */ - public int getNumAttempts() { - return mNumAttempts; - } - - /** - * Returns whether BrEdr handover is enabled. - */ - public boolean getEnableBrEdrHandover() { - return mEnableBrEdrHandover; - } - - /** - * Returns characteristic Id for Br Handover data. - */ - public short getBrHandoverDataCharacteristicId() { - return mBrHandoverDataCharacteristicId; - } - - /** - * Returns characteristic Id for Bluethoth Sig data. - */ - public short getBluetoothSigDataCharacteristicId() { - return mBluetoothSigDataCharacteristicId; - } - - /** - * Returns characteristic Id for Firmware version. - */ - public short getFirmwareVersionCharacteristicId() { - return mFirmwareVersionCharacteristicId; - } - - /** - * Returns descripter Id for Br transport block data. - */ - public short getBrTransportBlockDataDescriptorId() { - return mBrTransportBlockDataDescriptorId; - } - - /** - * Whether to wait for Uuids after bonding. - */ - public boolean getWaitForUuidsAfterBonding() { - return mWaitForUuidsAfterBonding; - } - - /** - * Whether to get received Uuids and bonded events before close. - */ - public boolean getReceiveUuidsAndBondedEventBeforeClose() { - return mReceiveUuidsAndBondedEventBeforeClose; - } - - /** - * Timeout for remove bond operation. - */ - public int getRemoveBondTimeoutSeconds() { - return mRemoveBondTimeoutSeconds; - } - - /** - * Sleep time for remove bond operation. - */ - public int getRemoveBondSleepMillis() { - return mRemoveBondSleepMillis; - } - - /** - * This almost always succeeds (or fails) in 2-10 seconds (Taimen running O -> Nexus 6P sim). - */ - public int getCreateBondTimeoutSeconds() { - return mCreateBondTimeoutSeconds; - } - - /** - * Timeout for creating bond with Hid devices. - */ - public int getHidCreateBondTimeoutSeconds() { - return mHidCreateBondTimeoutSeconds; - } - - /** - * Timeout for get proxy operation. - */ - public int getProxyTimeoutSeconds() { - return mProxyTimeoutSeconds; - } - - /** - * Whether to reject phone book access. - */ - public boolean getRejectPhonebookAccess() { - return mRejectPhonebookAccess; - } - - /** - * Whether to reject message access. - */ - public boolean getRejectMessageAccess() { - return mRejectMessageAccess; - } - - /** - * Whether to reject sim access. - */ - public boolean getRejectSimAccess() { - return mRejectSimAccess; - } - - /** - * Sleep time for write account key operation. - */ - public int getWriteAccountKeySleepMillis() { - return mWriteAccountKeySleepMillis; - } - - /** - * Whether to skip disconneting gatt before writing account key. - */ - public boolean getSkipDisconnectingGattBeforeWritingAccountKey() { - return mSkipDisconnectingGattBeforeWritingAccountKey; - } - - /** - * Whether to get more event log for quality improvement. - */ - public boolean getMoreEventLogForQuality() { - return mMoreEventLogForQuality; - } - - /** - * Whether to retry gatt connection and secrete handshake. - */ - public boolean getRetryGattConnectionAndSecretHandshake() { - return mRetryGattConnectionAndSecretHandshake; - } - - /** - * Short Gatt connection timeoout. - */ - public long getGattConnectShortTimeoutMs() { - return mGattConnectShortTimeoutMs; - } - - /** - * Long Gatt connection timeout. - */ - public long getGattConnectLongTimeoutMs() { - return mGattConnectLongTimeoutMs; - } - - /** - * Short Timeout for Gatt connection, including retry. - */ - public long getGattConnectShortTimeoutRetryMaxSpentTimeMs() { - return mGattConnectShortTimeoutRetryMaxSpentTimeMs; - } - - /** - * Timeout for address rotation, including retry. - */ - public long getAddressRotateRetryMaxSpentTimeMs() { - return mAddressRotateRetryMaxSpentTimeMs; - } - - /** - * Returns pairing retry delay time. - */ - public long getPairingRetryDelayMs() { - return mPairingRetryDelayMs; - } - - /** - * Short timeout for secrete handshake. - */ - public long getSecretHandshakeShortTimeoutMs() { - return mSecretHandshakeShortTimeoutMs; - } - - /** - * Long timeout for secret handshake. - */ - public long getSecretHandshakeLongTimeoutMs() { - return mSecretHandshakeLongTimeoutMs; - } - - /** - * Short timeout for secret handshake, including retry. - */ - public long getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs() { - return mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs; - } - - /** - * Long timeout for secret handshake, including retry. - */ - public long getSecretHandshakeLongTimeoutRetryMaxSpentTimeMs() { - return mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs; - } - - /** - * Number of secrete handshake retry allowed. - */ - public long getSecretHandshakeRetryAttempts() { - return mSecretHandshakeRetryAttempts; - } - - /** - * Timeout for secrete handshake and gatt connection, including retry. - */ - public long getSecretHandshakeRetryGattConnectionMaxSpentTimeMs() { - return mSecretHandshakeRetryGattConnectionMaxSpentTimeMs; - } - - /** - * Timeout for signal lost handling, including retry. - */ - public long getSignalLostRetryMaxSpentTimeMs() { - return mSignalLostRetryMaxSpentTimeMs; - } - - /** - * Returns error for gatt connection and secrete handshake, without retry. - */ - public ImmutableSet getGattConnectionAndSecretHandshakeNoRetryGattError() { - return mGattConnectionAndSecretHandshakeNoRetryGattError; - } - - /** - * Whether to retry upon secrete handshake timeout. - */ - public boolean getRetrySecretHandshakeTimeout() { - return mRetrySecretHandshakeTimeout; - } - - /** - * Wehther to log user manual retry. - */ - public boolean getLogUserManualRetry() { - return mLogUserManualRetry; - } - - /** - * Returns number of pairing failure counts. - */ - public int getPairFailureCounts() { - return mPairFailureCounts; - } - - /** - * Returns cached device address. - */ - public String getCachedDeviceAddress() { - return mCachedDeviceAddress; - } - - /** - * Returns possible cached device address. - */ - public String getPossibleCachedDeviceAddress() { - return mPossibleCachedDeviceAddress; - } - - /** - * Returns count of paired devices from the same model Id. - */ - public int getSameModelIdPairedDeviceCount() { - return mSameModelIdPairedDeviceCount; - } - - /** - * Whether the bonded device address is in the Cache . - */ - public boolean getIsDeviceFinishCheckAddressFromCache() { - return mIsDeviceFinishCheckAddressFromCache; - } - - /** - * Whether to log pairing info when cached model Id is hit. - */ - public boolean getLogPairWithCachedModelId() { - return mLogPairWithCachedModelId; - } - - /** - * Whether to directly connnect to a profile of a device, whose model Id is in cache. - */ - public boolean getDirectConnectProfileIfModelIdInCache() { - return mDirectConnectProfileIfModelIdInCache; - } - - /** - * Whether to auto-accept - * {@link android.bluetooth.BluetoothDevice#PAIRING_VARIANT_PASSKEY_CONFIRMATION}. - * Only the Fast Pair Simulator (which runs on an Android device) sends this. Since real - * Bluetooth headphones don't have displays, they use secure simple pairing (no pin code - * confirmation; we get no pairing request broadcast at all). So we may want to turn this off in - * prod. - */ - public boolean getAcceptPasskey() { - return mAcceptPasskey; - } - - /** - * Returns Uuids for supported profiles. - */ - @SuppressWarnings("mutable") - public byte[] getSupportedProfileUuids() { - return mSupportedProfileUuids; - } - - /** - * If true, after the Key-based Pairing BLE handshake, we wait for the headphones to send a - * pairing request to us; if false, we send the request to them. - */ - public boolean getProviderInitiatesBondingIfSupported() { - return mProviderInitiatesBondingIfSupported; - } - - /** - * If true, the first step will be attempting to connect directly to our supported profiles when - * a device has previously been bonded. This will help with performance on subsequent bondings - * and help to increase reliability in some cases. - */ - public boolean getAttemptDirectConnectionWhenPreviouslyBonded() { - return mAttemptDirectConnectionWhenPreviouslyBonded; - } - - /** - * If true, closed Gatt connections will be reopened when they are needed again. Otherwise, they - * will remain closed until they are explicitly reopened. - */ - public boolean getAutomaticallyReconnectGattWhenNeeded() { - return mAutomaticallyReconnectGattWhenNeeded; - } - - /** - * If true, we'll finish the pairing process after we've created a bond instead of after - * connecting a profile. - */ - public boolean getSkipConnectingProfiles() { - return mSkipConnectingProfiles; - } - - /** - * If true, continues the pairing process if we've timed out due to not receiving UUIDs from the - * headset. We can still attempt to connect to A2DP afterwards. If false, Fast Pair will fail - * after this step since we're expecting to receive the UUIDs. - */ - public boolean getIgnoreUuidTimeoutAfterBonded() { - return mIgnoreUuidTimeoutAfterBonded; - } - - /** - * If true, a specific transport type will be included in the create bond request, which will be - * used for dual mode devices. Otherwise, we'll use the platform defined default which is - * BluetoothDevice.TRANSPORT_AUTO. See {@link #getCreateBondTransportType()}. - */ - public boolean getSpecifyCreateBondTransportType() { - return mSpecifyCreateBondTransportType; - } - - /** - * The transport type to use when creating a bond when - * {@link #getSpecifyCreateBondTransportType() is true. This should be one of - * BluetoothDevice.TRANSPORT_AUTO, BluetoothDevice.TRANSPORT_BREDR, - * or BluetoothDevice.TRANSPORT_LE. - */ - public int getCreateBondTransportType() { - return mCreateBondTransportType; - } - - /** - * Whether to increase intent filter priority. - */ - public boolean getIncreaseIntentFilterPriority() { - return mIncreaseIntentFilterPriority; - } - - /** - * Whether to evaluate performance. - */ - public boolean getEvaluatePerformance() { - return mEvaluatePerformance; - } - - /** - * Returns extra logging information. - */ - @Nullable - public ExtraLoggingInformation getExtraLoggingInformation() { - return mExtraLoggingInformation; - } - - /** - * Whether to enable naming characteristic. - */ - public boolean getEnableNamingCharacteristic() { - return mEnableNamingCharacteristic; - } - - /** - * Whether to enable firmware version characteristic. - */ - public boolean getEnableFirmwareVersionCharacteristic() { - return mEnableFirmwareVersionCharacteristic; - } - - /** - * If true, even Fast Pair identifies a provider have paired with the account, still writes the - * identified account key to the provider. - */ - public boolean getKeepSameAccountKeyWrite() { - return mKeepSameAccountKeyWrite; - } - - /** - * If true, run retroactive pairing. - */ - public boolean getIsRetroactivePairing() { - return mIsRetroactivePairing; - } - - /** - * If it's larger than 0, {@link android.bluetooth.BluetoothDevice#fetchUuidsWithSdp} would be - * triggered with number of attempts after device is bonded and no profiles were automatically - * discovered". - */ - public int getNumSdpAttemptsAfterBonded() { - return mNumSdpAttemptsAfterBonded; - } - - /** - * If true, supports HID device for fastpair. - */ - public boolean getSupportHidDevice() { - return mSupportHidDevice; - } - - /** - * If true, we'll enable the pairing behavior to handle the state transition from BOND_BONDED to - * BOND_BONDING when directly connecting profiles. - */ - public boolean getEnablePairingWhileDirectlyConnecting() { - return mEnablePairingWhileDirectlyConnecting; - } - - /** - * If true, we will accept the user confirmation when bonding with FastPair 1.0 devices. - */ - public boolean getAcceptConsentForFastPairOne() { - return mAcceptConsentForFastPairOne; - } - - /** - * If it's larger than 0, we will retry connecting GATT within the timeout. - */ - public int getGattConnectRetryTimeoutMillis() { - return mGattConnectRetryTimeoutMillis; - } - - /** - * If true, then uses the new custom GATT characteristics {go/fastpair-128bit-gatt}. - */ - public boolean getEnable128BitCustomGattCharacteristicsId() { - return mEnable128BitCustomGattCharacteristicsId; - } - - /** - * If true, then sends the internal pair step or Exception to Validator by Intent. - */ - public boolean getEnableSendExceptionStepToValidator() { - return mEnableSendExceptionStepToValidator; - } - - /** - * If true, then adds the additional data type in the handshake packet when action over BLE. - */ - public boolean getEnableAdditionalDataTypeWhenActionOverBle() { - return mEnableAdditionalDataTypeWhenActionOverBle; - } - - /** - * If true, then checks the bond state when skips connecting profiles in the pairing shortcut. - */ - public boolean getCheckBondStateWhenSkipConnectingProfiles() { - return mCheckBondStateWhenSkipConnectingProfiles; - } - - /** - * If true, the passkey confirmation will be handled by the half-sheet UI. - */ - public boolean getHandlePasskeyConfirmationByUi() { - return mHandlePasskeyConfirmationByUi; - } - - /** - * If true, then use pair flow to show ui when pairing is finished without connecting profile. - */ - public boolean getEnablePairFlowShowUiWithoutProfileConnection() { - return mEnablePairFlowShowUiWithoutProfileConnection; - } - - @Override - public String toString() { - return "Preferences{" - + "gattOperationTimeoutSeconds=" + mGattOperationTimeoutSeconds + ", " - + "gattConnectionTimeoutSeconds=" + mGattConnectionTimeoutSeconds + ", " - + "bluetoothToggleTimeoutSeconds=" + mBluetoothToggleTimeoutSeconds + ", " - + "bluetoothToggleSleepSeconds=" + mBluetoothToggleSleepSeconds + ", " - + "classicDiscoveryTimeoutSeconds=" + mClassicDiscoveryTimeoutSeconds + ", " - + "numDiscoverAttempts=" + mNumDiscoverAttempts + ", " - + "discoveryRetrySleepSeconds=" + mDiscoveryRetrySleepSeconds + ", " - + "ignoreDiscoveryError=" + mIgnoreDiscoveryError + ", " - + "sdpTimeoutSeconds=" + mSdpTimeoutSeconds + ", " - + "numSdpAttempts=" + mNumSdpAttempts + ", " - + "numCreateBondAttempts=" + mNumCreateBondAttempts + ", " - + "numConnectAttempts=" + mNumConnectAttempts + ", " - + "numWriteAccountKeyAttempts=" + mNumWriteAccountKeyAttempts + ", " - + "toggleBluetoothOnFailure=" + mToggleBluetoothOnFailure + ", " - + "bluetoothStateUsesPolling=" + mBluetoothStateUsesPolling + ", " - + "bluetoothStatePollingMillis=" + mBluetoothStatePollingMillis + ", " - + "numAttempts=" + mNumAttempts + ", " - + "enableBrEdrHandover=" + mEnableBrEdrHandover + ", " - + "brHandoverDataCharacteristicId=" + mBrHandoverDataCharacteristicId + ", " - + "bluetoothSigDataCharacteristicId=" + mBluetoothSigDataCharacteristicId + ", " - + "firmwareVersionCharacteristicId=" + mFirmwareVersionCharacteristicId + ", " - + "brTransportBlockDataDescriptorId=" + mBrTransportBlockDataDescriptorId + ", " - + "waitForUuidsAfterBonding=" + mWaitForUuidsAfterBonding + ", " - + "receiveUuidsAndBondedEventBeforeClose=" + mReceiveUuidsAndBondedEventBeforeClose - + ", " - + "removeBondTimeoutSeconds=" + mRemoveBondTimeoutSeconds + ", " - + "removeBondSleepMillis=" + mRemoveBondSleepMillis + ", " - + "createBondTimeoutSeconds=" + mCreateBondTimeoutSeconds + ", " - + "hidCreateBondTimeoutSeconds=" + mHidCreateBondTimeoutSeconds + ", " - + "proxyTimeoutSeconds=" + mProxyTimeoutSeconds + ", " - + "rejectPhonebookAccess=" + mRejectPhonebookAccess + ", " - + "rejectMessageAccess=" + mRejectMessageAccess + ", " - + "rejectSimAccess=" + mRejectSimAccess + ", " - + "writeAccountKeySleepMillis=" + mWriteAccountKeySleepMillis + ", " - + "skipDisconnectingGattBeforeWritingAccountKey=" - + mSkipDisconnectingGattBeforeWritingAccountKey + ", " - + "moreEventLogForQuality=" + mMoreEventLogForQuality + ", " - + "retryGattConnectionAndSecretHandshake=" + mRetryGattConnectionAndSecretHandshake - + ", " - + "gattConnectShortTimeoutMs=" + mGattConnectShortTimeoutMs + ", " - + "gattConnectLongTimeoutMs=" + mGattConnectLongTimeoutMs + ", " - + "gattConnectShortTimeoutRetryMaxSpentTimeMs=" - + mGattConnectShortTimeoutRetryMaxSpentTimeMs + ", " - + "addressRotateRetryMaxSpentTimeMs=" + mAddressRotateRetryMaxSpentTimeMs + ", " - + "pairingRetryDelayMs=" + mPairingRetryDelayMs + ", " - + "secretHandshakeShortTimeoutMs=" + mSecretHandshakeShortTimeoutMs + ", " - + "secretHandshakeLongTimeoutMs=" + mSecretHandshakeLongTimeoutMs + ", " - + "secretHandshakeShortTimeoutRetryMaxSpentTimeMs=" - + mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs + ", " - + "secretHandshakeLongTimeoutRetryMaxSpentTimeMs=" - + mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs + ", " - + "secretHandshakeRetryAttempts=" + mSecretHandshakeRetryAttempts + ", " - + "secretHandshakeRetryGattConnectionMaxSpentTimeMs=" - + mSecretHandshakeRetryGattConnectionMaxSpentTimeMs + ", " - + "signalLostRetryMaxSpentTimeMs=" + mSignalLostRetryMaxSpentTimeMs + ", " - + "gattConnectionAndSecretHandshakeNoRetryGattError=" - + mGattConnectionAndSecretHandshakeNoRetryGattError + ", " - + "retrySecretHandshakeTimeout=" + mRetrySecretHandshakeTimeout + ", " - + "logUserManualRetry=" + mLogUserManualRetry + ", " - + "pairFailureCounts=" + mPairFailureCounts + ", " - + "cachedDeviceAddress=" + mCachedDeviceAddress + ", " - + "possibleCachedDeviceAddress=" + mPossibleCachedDeviceAddress + ", " - + "sameModelIdPairedDeviceCount=" + mSameModelIdPairedDeviceCount + ", " - + "isDeviceFinishCheckAddressFromCache=" + mIsDeviceFinishCheckAddressFromCache - + ", " - + "logPairWithCachedModelId=" + mLogPairWithCachedModelId + ", " - + "directConnectProfileIfModelIdInCache=" + mDirectConnectProfileIfModelIdInCache - + ", " - + "acceptPasskey=" + mAcceptPasskey + ", " - + "supportedProfileUuids=" + Arrays.toString(mSupportedProfileUuids) + ", " - + "providerInitiatesBondingIfSupported=" + mProviderInitiatesBondingIfSupported - + ", " - + "attemptDirectConnectionWhenPreviouslyBonded=" - + mAttemptDirectConnectionWhenPreviouslyBonded + ", " - + "automaticallyReconnectGattWhenNeeded=" + mAutomaticallyReconnectGattWhenNeeded - + ", " - + "skipConnectingProfiles=" + mSkipConnectingProfiles + ", " - + "ignoreUuidTimeoutAfterBonded=" + mIgnoreUuidTimeoutAfterBonded + ", " - + "specifyCreateBondTransportType=" + mSpecifyCreateBondTransportType + ", " - + "createBondTransportType=" + mCreateBondTransportType + ", " - + "increaseIntentFilterPriority=" + mIncreaseIntentFilterPriority + ", " - + "evaluatePerformance=" + mEvaluatePerformance + ", " - + "extraLoggingInformation=" + mExtraLoggingInformation + ", " - + "enableNamingCharacteristic=" + mEnableNamingCharacteristic + ", " - + "enableFirmwareVersionCharacteristic=" + mEnableFirmwareVersionCharacteristic - + ", " - + "keepSameAccountKeyWrite=" + mKeepSameAccountKeyWrite + ", " - + "isRetroactivePairing=" + mIsRetroactivePairing + ", " - + "numSdpAttemptsAfterBonded=" + mNumSdpAttemptsAfterBonded + ", " - + "supportHidDevice=" + mSupportHidDevice + ", " - + "enablePairingWhileDirectlyConnecting=" + mEnablePairingWhileDirectlyConnecting - + ", " - + "acceptConsentForFastPairOne=" + mAcceptConsentForFastPairOne + ", " - + "gattConnectRetryTimeoutMillis=" + mGattConnectRetryTimeoutMillis + ", " - + "enable128BitCustomGattCharacteristicsId=" - + mEnable128BitCustomGattCharacteristicsId + ", " - + "enableSendExceptionStepToValidator=" + mEnableSendExceptionStepToValidator + ", " - + "enableAdditionalDataTypeWhenActionOverBle=" - + mEnableAdditionalDataTypeWhenActionOverBle + ", " - + "checkBondStateWhenSkipConnectingProfiles=" - + mCheckBondStateWhenSkipConnectingProfiles + ", " - + "handlePasskeyConfirmationByUi=" + mHandlePasskeyConfirmationByUi + ", " - + "enablePairFlowShowUiWithoutProfileConnection=" - + mEnablePairFlowShowUiWithoutProfileConnection - + "}"; - } - - /** - * Converts an instance to a builder. - */ - public Builder toBuilder() { - return new Preferences.Builder(this); - } - - /** - * Constructs a builder. - */ - public static Builder builder() { - return new Preferences.Builder() - .setGattOperationTimeoutSeconds(10) - .setGattConnectionTimeoutSeconds(15) - .setBluetoothToggleTimeoutSeconds(10) - .setBluetoothToggleSleepSeconds(2) - .setClassicDiscoveryTimeoutSeconds(13) - .setNumDiscoverAttempts(3) - .setDiscoveryRetrySleepSeconds(1) - .setIgnoreDiscoveryError(true) - .setSdpTimeoutSeconds(10) - .setNumSdpAttempts(0) - .setNumCreateBondAttempts(3) - .setNumConnectAttempts(2) - .setNumWriteAccountKeyAttempts(3) - .setToggleBluetoothOnFailure(false) - .setBluetoothStateUsesPolling(true) - .setBluetoothStatePollingMillis(1000) - .setNumAttempts(2) - .setEnableBrEdrHandover(false) - .setBrHandoverDataCharacteristicId(get16BitUuid( - Constants.TransportDiscoveryService.BrHandoverDataCharacteristic.ID)) - .setBluetoothSigDataCharacteristicId(get16BitUuid( - Constants.TransportDiscoveryService.BluetoothSigDataCharacteristic.ID)) - .setFirmwareVersionCharacteristicId(get16BitUuid(FirmwareVersionCharacteristic.ID)) - .setBrTransportBlockDataDescriptorId( - get16BitUuid( - Constants.TransportDiscoveryService.BluetoothSigDataCharacteristic - .BrTransportBlockDataDescriptor.ID)) - .setWaitForUuidsAfterBonding(true) - .setReceiveUuidsAndBondedEventBeforeClose(true) - .setRemoveBondTimeoutSeconds(5) - .setRemoveBondSleepMillis(1000) - .setCreateBondTimeoutSeconds(15) - .setHidCreateBondTimeoutSeconds(40) - .setProxyTimeoutSeconds(2) - .setRejectPhonebookAccess(false) - .setRejectMessageAccess(false) - .setRejectSimAccess(false) - .setAcceptPasskey(true) - .setSupportedProfileUuids(Constants.getSupportedProfiles()) - .setWriteAccountKeySleepMillis(2000) - .setProviderInitiatesBondingIfSupported(false) - .setAttemptDirectConnectionWhenPreviouslyBonded(true) - .setAutomaticallyReconnectGattWhenNeeded(true) - .setSkipDisconnectingGattBeforeWritingAccountKey(true) - .setSkipConnectingProfiles(false) - .setIgnoreUuidTimeoutAfterBonded(true) - .setSpecifyCreateBondTransportType(false) - .setCreateBondTransportType(0 /*BluetoothDevice.TRANSPORT_AUTO*/) - .setIncreaseIntentFilterPriority(true) - .setEvaluatePerformance(true) - .setKeepSameAccountKeyWrite(true) - .setEnableNamingCharacteristic(true) - .setEnableFirmwareVersionCharacteristic(true) - .setIsRetroactivePairing(false) - .setNumSdpAttemptsAfterBonded(1) - .setSupportHidDevice(false) - .setEnablePairingWhileDirectlyConnecting(true) - .setAcceptConsentForFastPairOne(true) - .setGattConnectRetryTimeoutMillis(18000) - .setEnable128BitCustomGattCharacteristicsId(true) - .setEnableSendExceptionStepToValidator(true) - .setEnableAdditionalDataTypeWhenActionOverBle(true) - .setCheckBondStateWhenSkipConnectingProfiles(true) - .setHandlePasskeyConfirmationByUi(false) - .setMoreEventLogForQuality(true) - .setRetryGattConnectionAndSecretHandshake(true) - .setGattConnectShortTimeoutMs(7000) - .setGattConnectLongTimeoutMs(15000) - .setGattConnectShortTimeoutRetryMaxSpentTimeMs(10000) - .setAddressRotateRetryMaxSpentTimeMs(15000) - .setPairingRetryDelayMs(100) - .setSecretHandshakeShortTimeoutMs(3000) - .setSecretHandshakeLongTimeoutMs(10000) - .setSecretHandshakeShortTimeoutRetryMaxSpentTimeMs(5000) - .setSecretHandshakeLongTimeoutRetryMaxSpentTimeMs(7000) - .setSecretHandshakeRetryAttempts(3) - .setSecretHandshakeRetryGattConnectionMaxSpentTimeMs(15000) - .setSignalLostRetryMaxSpentTimeMs(15000) - .setGattConnectionAndSecretHandshakeNoRetryGattError(ImmutableSet.of(257)) - .setRetrySecretHandshakeTimeout(false) - .setLogUserManualRetry(true) - .setPairFailureCounts(0) - .setEnablePairFlowShowUiWithoutProfileConnection(true) - .setPairFailureCounts(0) - .setLogPairWithCachedModelId(true) - .setDirectConnectProfileIfModelIdInCache(true) - .setCachedDeviceAddress("") - .setPossibleCachedDeviceAddress("") - .setSameModelIdPairedDeviceCount(0) - .setIsDeviceFinishCheckAddressFromCache(true); - } - - /** - * Preferences builder. - */ - public static class Builder { - - private int mGattOperationTimeoutSeconds; - private int mGattConnectionTimeoutSeconds; - private int mBluetoothToggleTimeoutSeconds; - private int mBluetoothToggleSleepSeconds; - private int mClassicDiscoveryTimeoutSeconds; - private int mNumDiscoverAttempts; - private int mDiscoveryRetrySleepSeconds; - private boolean mIgnoreDiscoveryError; - private int mSdpTimeoutSeconds; - private int mNumSdpAttempts; - private int mNumCreateBondAttempts; - private int mNumConnectAttempts; - private int mNumWriteAccountKeyAttempts; - private boolean mToggleBluetoothOnFailure; - private boolean mBluetoothStateUsesPolling; - private int mBluetoothStatePollingMillis; - private int mNumAttempts; - private boolean mEnableBrEdrHandover; - private short mBrHandoverDataCharacteristicId; - private short mBluetoothSigDataCharacteristicId; - private short mFirmwareVersionCharacteristicId; - private short mBrTransportBlockDataDescriptorId; - private boolean mWaitForUuidsAfterBonding; - private boolean mReceiveUuidsAndBondedEventBeforeClose; - private int mRemoveBondTimeoutSeconds; - private int mRemoveBondSleepMillis; - private int mCreateBondTimeoutSeconds; - private int mHidCreateBondTimeoutSeconds; - private int mProxyTimeoutSeconds; - private boolean mRejectPhonebookAccess; - private boolean mRejectMessageAccess; - private boolean mRejectSimAccess; - private int mWriteAccountKeySleepMillis; - private boolean mSkipDisconnectingGattBeforeWritingAccountKey; - private boolean mMoreEventLogForQuality; - private boolean mRetryGattConnectionAndSecretHandshake; - private long mGattConnectShortTimeoutMs; - private long mGattConnectLongTimeoutMs; - private long mGattConnectShortTimeoutRetryMaxSpentTimeMs; - private long mAddressRotateRetryMaxSpentTimeMs; - private long mPairingRetryDelayMs; - private long mSecretHandshakeShortTimeoutMs; - private long mSecretHandshakeLongTimeoutMs; - private long mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs; - private long mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs; - private long mSecretHandshakeRetryAttempts; - private long mSecretHandshakeRetryGattConnectionMaxSpentTimeMs; - private long mSignalLostRetryMaxSpentTimeMs; - private ImmutableSet mGattConnectionAndSecretHandshakeNoRetryGattError; - private boolean mRetrySecretHandshakeTimeout; - private boolean mLogUserManualRetry; - private int mPairFailureCounts; - private String mCachedDeviceAddress; - private String mPossibleCachedDeviceAddress; - private int mSameModelIdPairedDeviceCount; - private boolean mIsDeviceFinishCheckAddressFromCache; - private boolean mLogPairWithCachedModelId; - private boolean mDirectConnectProfileIfModelIdInCache; - private boolean mAcceptPasskey; - private byte[] mSupportedProfileUuids; - private boolean mProviderInitiatesBondingIfSupported; - private boolean mAttemptDirectConnectionWhenPreviouslyBonded; - private boolean mAutomaticallyReconnectGattWhenNeeded; - private boolean mSkipConnectingProfiles; - private boolean mIgnoreUuidTimeoutAfterBonded; - private boolean mSpecifyCreateBondTransportType; - private int mCreateBondTransportType; - private boolean mIncreaseIntentFilterPriority; - private boolean mEvaluatePerformance; - private Preferences.ExtraLoggingInformation mExtraLoggingInformation; - private boolean mEnableNamingCharacteristic; - private boolean mEnableFirmwareVersionCharacteristic; - private boolean mKeepSameAccountKeyWrite; - private boolean mIsRetroactivePairing; - private int mNumSdpAttemptsAfterBonded; - private boolean mSupportHidDevice; - private boolean mEnablePairingWhileDirectlyConnecting; - private boolean mAcceptConsentForFastPairOne; - private int mGattConnectRetryTimeoutMillis; - private boolean mEnable128BitCustomGattCharacteristicsId; - private boolean mEnableSendExceptionStepToValidator; - private boolean mEnableAdditionalDataTypeWhenActionOverBle; - private boolean mCheckBondStateWhenSkipConnectingProfiles; - private boolean mHandlePasskeyConfirmationByUi; - private boolean mEnablePairFlowShowUiWithoutProfileConnection; - - private Builder() { - } - - private Builder(Preferences source) { - this.mGattOperationTimeoutSeconds = source.getGattOperationTimeoutSeconds(); - this.mGattConnectionTimeoutSeconds = source.getGattConnectionTimeoutSeconds(); - this.mBluetoothToggleTimeoutSeconds = source.getBluetoothToggleTimeoutSeconds(); - this.mBluetoothToggleSleepSeconds = source.getBluetoothToggleSleepSeconds(); - this.mClassicDiscoveryTimeoutSeconds = source.getClassicDiscoveryTimeoutSeconds(); - this.mNumDiscoverAttempts = source.getNumDiscoverAttempts(); - this.mDiscoveryRetrySleepSeconds = source.getDiscoveryRetrySleepSeconds(); - this.mIgnoreDiscoveryError = source.getIgnoreDiscoveryError(); - this.mSdpTimeoutSeconds = source.getSdpTimeoutSeconds(); - this.mNumSdpAttempts = source.getNumSdpAttempts(); - this.mNumCreateBondAttempts = source.getNumCreateBondAttempts(); - this.mNumConnectAttempts = source.getNumConnectAttempts(); - this.mNumWriteAccountKeyAttempts = source.getNumWriteAccountKeyAttempts(); - this.mToggleBluetoothOnFailure = source.getToggleBluetoothOnFailure(); - this.mBluetoothStateUsesPolling = source.getBluetoothStateUsesPolling(); - this.mBluetoothStatePollingMillis = source.getBluetoothStatePollingMillis(); - this.mNumAttempts = source.getNumAttempts(); - this.mEnableBrEdrHandover = source.getEnableBrEdrHandover(); - this.mBrHandoverDataCharacteristicId = source.getBrHandoverDataCharacteristicId(); - this.mBluetoothSigDataCharacteristicId = source.getBluetoothSigDataCharacteristicId(); - this.mFirmwareVersionCharacteristicId = source.getFirmwareVersionCharacteristicId(); - this.mBrTransportBlockDataDescriptorId = source.getBrTransportBlockDataDescriptorId(); - this.mWaitForUuidsAfterBonding = source.getWaitForUuidsAfterBonding(); - this.mReceiveUuidsAndBondedEventBeforeClose = source - .getReceiveUuidsAndBondedEventBeforeClose(); - this.mRemoveBondTimeoutSeconds = source.getRemoveBondTimeoutSeconds(); - this.mRemoveBondSleepMillis = source.getRemoveBondSleepMillis(); - this.mCreateBondTimeoutSeconds = source.getCreateBondTimeoutSeconds(); - this.mHidCreateBondTimeoutSeconds = source.getHidCreateBondTimeoutSeconds(); - this.mProxyTimeoutSeconds = source.getProxyTimeoutSeconds(); - this.mRejectPhonebookAccess = source.getRejectPhonebookAccess(); - this.mRejectMessageAccess = source.getRejectMessageAccess(); - this.mRejectSimAccess = source.getRejectSimAccess(); - this.mWriteAccountKeySleepMillis = source.getWriteAccountKeySleepMillis(); - this.mSkipDisconnectingGattBeforeWritingAccountKey = source - .getSkipDisconnectingGattBeforeWritingAccountKey(); - this.mMoreEventLogForQuality = source.getMoreEventLogForQuality(); - this.mRetryGattConnectionAndSecretHandshake = source - .getRetryGattConnectionAndSecretHandshake(); - this.mGattConnectShortTimeoutMs = source.getGattConnectShortTimeoutMs(); - this.mGattConnectLongTimeoutMs = source.getGattConnectLongTimeoutMs(); - this.mGattConnectShortTimeoutRetryMaxSpentTimeMs = source - .getGattConnectShortTimeoutRetryMaxSpentTimeMs(); - this.mAddressRotateRetryMaxSpentTimeMs = source.getAddressRotateRetryMaxSpentTimeMs(); - this.mPairingRetryDelayMs = source.getPairingRetryDelayMs(); - this.mSecretHandshakeShortTimeoutMs = source.getSecretHandshakeShortTimeoutMs(); - this.mSecretHandshakeLongTimeoutMs = source.getSecretHandshakeLongTimeoutMs(); - this.mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs = source - .getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs(); - this.mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs = source - .getSecretHandshakeLongTimeoutRetryMaxSpentTimeMs(); - this.mSecretHandshakeRetryAttempts = source.getSecretHandshakeRetryAttempts(); - this.mSecretHandshakeRetryGattConnectionMaxSpentTimeMs = source - .getSecretHandshakeRetryGattConnectionMaxSpentTimeMs(); - this.mSignalLostRetryMaxSpentTimeMs = source.getSignalLostRetryMaxSpentTimeMs(); - this.mGattConnectionAndSecretHandshakeNoRetryGattError = source - .getGattConnectionAndSecretHandshakeNoRetryGattError(); - this.mRetrySecretHandshakeTimeout = source.getRetrySecretHandshakeTimeout(); - this.mLogUserManualRetry = source.getLogUserManualRetry(); - this.mPairFailureCounts = source.getPairFailureCounts(); - this.mCachedDeviceAddress = source.getCachedDeviceAddress(); - this.mPossibleCachedDeviceAddress = source.getPossibleCachedDeviceAddress(); - this.mSameModelIdPairedDeviceCount = source.getSameModelIdPairedDeviceCount(); - this.mIsDeviceFinishCheckAddressFromCache = source - .getIsDeviceFinishCheckAddressFromCache(); - this.mLogPairWithCachedModelId = source.getLogPairWithCachedModelId(); - this.mDirectConnectProfileIfModelIdInCache = source - .getDirectConnectProfileIfModelIdInCache(); - this.mAcceptPasskey = source.getAcceptPasskey(); - this.mSupportedProfileUuids = source.getSupportedProfileUuids(); - this.mProviderInitiatesBondingIfSupported = source - .getProviderInitiatesBondingIfSupported(); - this.mAttemptDirectConnectionWhenPreviouslyBonded = source - .getAttemptDirectConnectionWhenPreviouslyBonded(); - this.mAutomaticallyReconnectGattWhenNeeded = source - .getAutomaticallyReconnectGattWhenNeeded(); - this.mSkipConnectingProfiles = source.getSkipConnectingProfiles(); - this.mIgnoreUuidTimeoutAfterBonded = source.getIgnoreUuidTimeoutAfterBonded(); - this.mSpecifyCreateBondTransportType = source.getSpecifyCreateBondTransportType(); - this.mCreateBondTransportType = source.getCreateBondTransportType(); - this.mIncreaseIntentFilterPriority = source.getIncreaseIntentFilterPriority(); - this.mEvaluatePerformance = source.getEvaluatePerformance(); - this.mExtraLoggingInformation = source.getExtraLoggingInformation(); - this.mEnableNamingCharacteristic = source.getEnableNamingCharacteristic(); - this.mEnableFirmwareVersionCharacteristic = source - .getEnableFirmwareVersionCharacteristic(); - this.mKeepSameAccountKeyWrite = source.getKeepSameAccountKeyWrite(); - this.mIsRetroactivePairing = source.getIsRetroactivePairing(); - this.mNumSdpAttemptsAfterBonded = source.getNumSdpAttemptsAfterBonded(); - this.mSupportHidDevice = source.getSupportHidDevice(); - this.mEnablePairingWhileDirectlyConnecting = source - .getEnablePairingWhileDirectlyConnecting(); - this.mAcceptConsentForFastPairOne = source.getAcceptConsentForFastPairOne(); - this.mGattConnectRetryTimeoutMillis = source.getGattConnectRetryTimeoutMillis(); - this.mEnable128BitCustomGattCharacteristicsId = source - .getEnable128BitCustomGattCharacteristicsId(); - this.mEnableSendExceptionStepToValidator = source - .getEnableSendExceptionStepToValidator(); - this.mEnableAdditionalDataTypeWhenActionOverBle = source - .getEnableAdditionalDataTypeWhenActionOverBle(); - this.mCheckBondStateWhenSkipConnectingProfiles = source - .getCheckBondStateWhenSkipConnectingProfiles(); - this.mHandlePasskeyConfirmationByUi = source.getHandlePasskeyConfirmationByUi(); - this.mEnablePairFlowShowUiWithoutProfileConnection = source - .getEnablePairFlowShowUiWithoutProfileConnection(); - } - - /** - * Set gatt operation timeout. - */ - public Builder setGattOperationTimeoutSeconds(int value) { - this.mGattOperationTimeoutSeconds = value; - return this; - } - - /** - * Set gatt connection timeout. - */ - public Builder setGattConnectionTimeoutSeconds(int value) { - this.mGattConnectionTimeoutSeconds = value; - return this; - } - - /** - * Set bluetooth toggle timeout. - */ - public Builder setBluetoothToggleTimeoutSeconds(int value) { - this.mBluetoothToggleTimeoutSeconds = value; - return this; - } - - /** - * Set bluetooth toggle sleep time. - */ - public Builder setBluetoothToggleSleepSeconds(int value) { - this.mBluetoothToggleSleepSeconds = value; - return this; - } - - /** - * Set classic discovery timeout. - */ - public Builder setClassicDiscoveryTimeoutSeconds(int value) { - this.mClassicDiscoveryTimeoutSeconds = value; - return this; - } - - /** - * Set number of discover attempts allowed. - */ - public Builder setNumDiscoverAttempts(int value) { - this.mNumDiscoverAttempts = value; - return this; - } - - /** - * Set discovery retry sleep time. - */ - public Builder setDiscoveryRetrySleepSeconds(int value) { - this.mDiscoveryRetrySleepSeconds = value; - return this; - } - - /** - * Set whether to ignore discovery error. - */ - public Builder setIgnoreDiscoveryError(boolean value) { - this.mIgnoreDiscoveryError = value; - return this; - } - - /** - * Set sdp timeout. - */ - public Builder setSdpTimeoutSeconds(int value) { - this.mSdpTimeoutSeconds = value; - return this; - } - - /** - * Set number of sdp attempts allowed. - */ - public Builder setNumSdpAttempts(int value) { - this.mNumSdpAttempts = value; - return this; - } - - /** - * Set number of allowed attempts to create bond. - */ - public Builder setNumCreateBondAttempts(int value) { - this.mNumCreateBondAttempts = value; - return this; - } - - /** - * Set number of connect attempts allowed. - */ - public Builder setNumConnectAttempts(int value) { - this.mNumConnectAttempts = value; - return this; - } - - /** - * Set number of write account key attempts allowed. - */ - public Builder setNumWriteAccountKeyAttempts(int value) { - this.mNumWriteAccountKeyAttempts = value; - return this; - } - - /** - * Set whether to retry by bluetooth toggle on failure. - */ - public Builder setToggleBluetoothOnFailure(boolean value) { - this.mToggleBluetoothOnFailure = value; - return this; - } - - /** - * Set whether to use polling to set bluetooth status. - */ - public Builder setBluetoothStateUsesPolling(boolean value) { - this.mBluetoothStateUsesPolling = value; - return this; - } - - /** - * Set Bluetooth state polling timeout. - */ - public Builder setBluetoothStatePollingMillis(int value) { - this.mBluetoothStatePollingMillis = value; - return this; - } - - /** - * Set number of attempts. - */ - public Builder setNumAttempts(int value) { - this.mNumAttempts = value; - return this; - } - - /** - * Set whether to enable BrEdr handover. - */ - public Builder setEnableBrEdrHandover(boolean value) { - this.mEnableBrEdrHandover = value; - return this; - } - - /** - * Set Br handover data characteristic Id. - */ - public Builder setBrHandoverDataCharacteristicId(short value) { - this.mBrHandoverDataCharacteristicId = value; - return this; - } - - /** - * Set Bluetooth Sig data characteristic Id. - */ - public Builder setBluetoothSigDataCharacteristicId(short value) { - this.mBluetoothSigDataCharacteristicId = value; - return this; - } - - /** - * Set Firmware version characteristic id. - */ - public Builder setFirmwareVersionCharacteristicId(short value) { - this.mFirmwareVersionCharacteristicId = value; - return this; - } - - /** - * Set Br transport block data descriptor id. - */ - public Builder setBrTransportBlockDataDescriptorId(short value) { - this.mBrTransportBlockDataDescriptorId = value; - return this; - } - - /** - * Set whether to wait for Uuids after bonding. - */ - public Builder setWaitForUuidsAfterBonding(boolean value) { - this.mWaitForUuidsAfterBonding = value; - return this; - } - - /** - * Set whether to receive Uuids and bonded event before close. - */ - public Builder setReceiveUuidsAndBondedEventBeforeClose(boolean value) { - this.mReceiveUuidsAndBondedEventBeforeClose = value; - return this; - } - - /** - * Set remove bond timeout. - */ - public Builder setRemoveBondTimeoutSeconds(int value) { - this.mRemoveBondTimeoutSeconds = value; - return this; - } - - /** - * Set remove bound sleep time. - */ - public Builder setRemoveBondSleepMillis(int value) { - this.mRemoveBondSleepMillis = value; - return this; - } - - /** - * Set create bond timeout. - */ - public Builder setCreateBondTimeoutSeconds(int value) { - this.mCreateBondTimeoutSeconds = value; - return this; - } - - /** - * Set Hid create bond timeout. - */ - public Builder setHidCreateBondTimeoutSeconds(int value) { - this.mHidCreateBondTimeoutSeconds = value; - return this; - } - - /** - * Set proxy timeout. - */ - public Builder setProxyTimeoutSeconds(int value) { - this.mProxyTimeoutSeconds = value; - return this; - } - - /** - * Set whether to reject phone book access. - */ - public Builder setRejectPhonebookAccess(boolean value) { - this.mRejectPhonebookAccess = value; - return this; - } - - /** - * Set whether to reject message access. - */ - public Builder setRejectMessageAccess(boolean value) { - this.mRejectMessageAccess = value; - return this; - } - - /** - * Set whether to reject slim access. - */ - public Builder setRejectSimAccess(boolean value) { - this.mRejectSimAccess = value; - return this; - } - - /** - * Set whether to accept passkey. - */ - public Builder setAcceptPasskey(boolean value) { - this.mAcceptPasskey = value; - return this; - } - - /** - * Set supported profile Uuids. - */ - public Builder setSupportedProfileUuids(byte[] value) { - this.mSupportedProfileUuids = value; - return this; - } - - /** - * Set whether to collect more event log for quality. - */ - public Builder setMoreEventLogForQuality(boolean value) { - this.mMoreEventLogForQuality = value; - return this; - } - - /** - * Set supported profile Uuids. - */ - public Builder setSupportedProfileUuids(short... uuids) { - return setSupportedProfileUuids(Bytes.toBytes(ByteOrder.BIG_ENDIAN, uuids)); - } - - /** - * Set write account key sleep time. - */ - public Builder setWriteAccountKeySleepMillis(int value) { - this.mWriteAccountKeySleepMillis = value; - return this; - } - - /** - * Set whether to do provider initialized bonding if supported. - */ - public Builder setProviderInitiatesBondingIfSupported(boolean value) { - this.mProviderInitiatesBondingIfSupported = value; - return this; - } - - /** - * Set whether to try direct connection when the device is previously bonded. - */ - public Builder setAttemptDirectConnectionWhenPreviouslyBonded(boolean value) { - this.mAttemptDirectConnectionWhenPreviouslyBonded = value; - return this; - } - - /** - * Set whether to automatically reconnect gatt when needed. - */ - public Builder setAutomaticallyReconnectGattWhenNeeded(boolean value) { - this.mAutomaticallyReconnectGattWhenNeeded = value; - return this; - } - - /** - * Set whether to skip disconnecting gatt before writing account key. - */ - public Builder setSkipDisconnectingGattBeforeWritingAccountKey(boolean value) { - this.mSkipDisconnectingGattBeforeWritingAccountKey = value; - return this; - } - - /** - * Set whether to skip connecting profiles. - */ - public Builder setSkipConnectingProfiles(boolean value) { - this.mSkipConnectingProfiles = value; - return this; - } - - /** - * Set whether to ignore Uuid timeout after bonded. - */ - public Builder setIgnoreUuidTimeoutAfterBonded(boolean value) { - this.mIgnoreUuidTimeoutAfterBonded = value; - return this; - } - - /** - * Set whether to include transport type in create bound request. - */ - public Builder setSpecifyCreateBondTransportType(boolean value) { - this.mSpecifyCreateBondTransportType = value; - return this; - } - - /** - * Set transport type used in create bond request. - */ - public Builder setCreateBondTransportType(int value) { - this.mCreateBondTransportType = value; - return this; - } - - /** - * Set whether to increase intent filter priority. - */ - public Builder setIncreaseIntentFilterPriority(boolean value) { - this.mIncreaseIntentFilterPriority = value; - return this; - } - - /** - * Set whether to evaluate performance. - */ - public Builder setEvaluatePerformance(boolean value) { - this.mEvaluatePerformance = value; - return this; - } - - /** - * Set extra logging info. - */ - public Builder setExtraLoggingInformation(ExtraLoggingInformation value) { - this.mExtraLoggingInformation = value; - return this; - } - - /** - * Set whether to enable naming characteristic. - */ - public Builder setEnableNamingCharacteristic(boolean value) { - this.mEnableNamingCharacteristic = value; - return this; - } - - /** - * Set whether to keep writing the account key to the provider, that has already paired with - * the account. - */ - public Builder setKeepSameAccountKeyWrite(boolean value) { - this.mKeepSameAccountKeyWrite = value; - return this; - } - - /** - * Set whether to enable firmware version characteristic. - */ - public Builder setEnableFirmwareVersionCharacteristic(boolean value) { - this.mEnableFirmwareVersionCharacteristic = value; - return this; - } - - /** - * Set whether it is retroactive pairing. - */ - public Builder setIsRetroactivePairing(boolean value) { - this.mIsRetroactivePairing = value; - return this; - } - - /** - * Set number of allowed sdp attempts after bonded. - */ - public Builder setNumSdpAttemptsAfterBonded(int value) { - this.mNumSdpAttemptsAfterBonded = value; - return this; - } - - /** - * Set whether to support Hid device. - */ - public Builder setSupportHidDevice(boolean value) { - this.mSupportHidDevice = value; - return this; - } - - /** - * Set wehther to enable the pairing behavior to handle the state transition from - * BOND_BONDED to BOND_BONDING when directly connecting profiles. - */ - public Builder setEnablePairingWhileDirectlyConnecting(boolean value) { - this.mEnablePairingWhileDirectlyConnecting = value; - return this; - } - - /** - * Set whether to accept consent for fast pair one. - */ - public Builder setAcceptConsentForFastPairOne(boolean value) { - this.mAcceptConsentForFastPairOne = value; - return this; - } - - /** - * Set Gatt connect retry timeout. - */ - public Builder setGattConnectRetryTimeoutMillis(int value) { - this.mGattConnectRetryTimeoutMillis = value; - return this; - } - - /** - * Set whether to enable 128 bit custom gatt characteristic Id. - */ - public Builder setEnable128BitCustomGattCharacteristicsId(boolean value) { - this.mEnable128BitCustomGattCharacteristicsId = value; - return this; - } - - /** - * Set whether to send exception step to validator. - */ - public Builder setEnableSendExceptionStepToValidator(boolean value) { - this.mEnableSendExceptionStepToValidator = value; - return this; - } - - /** - * Set wehther to add the additional data type in the handshake when action over BLE. - */ - public Builder setEnableAdditionalDataTypeWhenActionOverBle(boolean value) { - this.mEnableAdditionalDataTypeWhenActionOverBle = value; - return this; - } - - /** - * Set whether to check bond state when skip connecting profiles. - */ - public Builder setCheckBondStateWhenSkipConnectingProfiles(boolean value) { - this.mCheckBondStateWhenSkipConnectingProfiles = value; - return this; - } - - /** - * Set whether to handle passkey confirmation by UI. - */ - public Builder setHandlePasskeyConfirmationByUi(boolean value) { - this.mHandlePasskeyConfirmationByUi = value; - return this; - } - - /** - * Set wehther to retry gatt connection and secret handshake. - */ - public Builder setRetryGattConnectionAndSecretHandshake(boolean value) { - this.mRetryGattConnectionAndSecretHandshake = value; - return this; - } - - /** - * Set gatt connect short timeout. - */ - public Builder setGattConnectShortTimeoutMs(long value) { - this.mGattConnectShortTimeoutMs = value; - return this; - } - - /** - * Set gatt connect long timeout. - */ - public Builder setGattConnectLongTimeoutMs(long value) { - this.mGattConnectLongTimeoutMs = value; - return this; - } - - /** - * Set gatt connection short timoutout, including retry. - */ - public Builder setGattConnectShortTimeoutRetryMaxSpentTimeMs(long value) { - this.mGattConnectShortTimeoutRetryMaxSpentTimeMs = value; - return this; - } - - /** - * Set address rotate timeout, including retry. - */ - public Builder setAddressRotateRetryMaxSpentTimeMs(long value) { - this.mAddressRotateRetryMaxSpentTimeMs = value; - return this; - } - - /** - * Set pairing retry delay time. - */ - public Builder setPairingRetryDelayMs(long value) { - this.mPairingRetryDelayMs = value; - return this; - } - - /** - * Set secret handshake short timeout. - */ - public Builder setSecretHandshakeShortTimeoutMs(long value) { - this.mSecretHandshakeShortTimeoutMs = value; - return this; - } - - /** - * Set secret handshake long timeout. - */ - public Builder setSecretHandshakeLongTimeoutMs(long value) { - this.mSecretHandshakeLongTimeoutMs = value; - return this; - } - - /** - * Set secret handshake short timeout retry max spent time. - */ - public Builder setSecretHandshakeShortTimeoutRetryMaxSpentTimeMs(long value) { - this.mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs = value; - return this; - } - - /** - * Set secret handshake long timeout retry max spent time. - */ - public Builder setSecretHandshakeLongTimeoutRetryMaxSpentTimeMs(long value) { - this.mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs = value; - return this; - } - - /** - * Set secret handshake retry attempts allowed. - */ - public Builder setSecretHandshakeRetryAttempts(long value) { - this.mSecretHandshakeRetryAttempts = value; - return this; - } - - /** - * Set secret handshake retry gatt connection max spent time. - */ - public Builder setSecretHandshakeRetryGattConnectionMaxSpentTimeMs(long value) { - this.mSecretHandshakeRetryGattConnectionMaxSpentTimeMs = value; - return this; - } - - /** - * Set signal loss retry max spent time. - */ - public Builder setSignalLostRetryMaxSpentTimeMs(long value) { - this.mSignalLostRetryMaxSpentTimeMs = value; - return this; - } - - /** - * Set gatt connection and secret handshake no retry gatt error. - */ - public Builder setGattConnectionAndSecretHandshakeNoRetryGattError( - ImmutableSet value) { - this.mGattConnectionAndSecretHandshakeNoRetryGattError = value; - return this; - } - - /** - * Set retry secret handshake timeout. - */ - public Builder setRetrySecretHandshakeTimeout(boolean value) { - this.mRetrySecretHandshakeTimeout = value; - return this; - } - - /** - * Set whether to log user manual retry. - */ - public Builder setLogUserManualRetry(boolean value) { - this.mLogUserManualRetry = value; - return this; - } - - /** - * Set pair falure counts. - */ - public Builder setPairFailureCounts(int counts) { - this.mPairFailureCounts = counts; - return this; - } - - /** - * Set whether to use pair flow to show ui when pairing is finished without connecting - * profile.. - */ - public Builder setEnablePairFlowShowUiWithoutProfileConnection(boolean value) { - this.mEnablePairFlowShowUiWithoutProfileConnection = value; - return this; - } - - /** - * Set whether to log pairing with cached module Id. - */ - public Builder setLogPairWithCachedModelId(boolean value) { - this.mLogPairWithCachedModelId = value; - return this; - } - - /** - * Set possible cached device address. - */ - public Builder setPossibleCachedDeviceAddress(String value) { - this.mPossibleCachedDeviceAddress = value; - return this; - } - - /** - * Set paired device count from the same module Id. - */ - public Builder setSameModelIdPairedDeviceCount(int value) { - this.mSameModelIdPairedDeviceCount = value; - return this; - } - - /** - * Set whether the bonded device address is from cache. - */ - public Builder setIsDeviceFinishCheckAddressFromCache(boolean value) { - this.mIsDeviceFinishCheckAddressFromCache = value; - return this; - } - - /** - * Set whether to directly connect profile if modelId is in cache. - */ - public Builder setDirectConnectProfileIfModelIdInCache(boolean value) { - this.mDirectConnectProfileIfModelIdInCache = value; - return this; - } - - /** - * Set cached device address. - */ - public Builder setCachedDeviceAddress(String value) { - this.mCachedDeviceAddress = value; - return this; - } - - /** - * Builds a Preferences instance. - */ - public Preferences build() { - return new Preferences( - this.mGattOperationTimeoutSeconds, - this.mGattConnectionTimeoutSeconds, - this.mBluetoothToggleTimeoutSeconds, - this.mBluetoothToggleSleepSeconds, - this.mClassicDiscoveryTimeoutSeconds, - this.mNumDiscoverAttempts, - this.mDiscoveryRetrySleepSeconds, - this.mIgnoreDiscoveryError, - this.mSdpTimeoutSeconds, - this.mNumSdpAttempts, - this.mNumCreateBondAttempts, - this.mNumConnectAttempts, - this.mNumWriteAccountKeyAttempts, - this.mToggleBluetoothOnFailure, - this.mBluetoothStateUsesPolling, - this.mBluetoothStatePollingMillis, - this.mNumAttempts, - this.mEnableBrEdrHandover, - this.mBrHandoverDataCharacteristicId, - this.mBluetoothSigDataCharacteristicId, - this.mFirmwareVersionCharacteristicId, - this.mBrTransportBlockDataDescriptorId, - this.mWaitForUuidsAfterBonding, - this.mReceiveUuidsAndBondedEventBeforeClose, - this.mRemoveBondTimeoutSeconds, - this.mRemoveBondSleepMillis, - this.mCreateBondTimeoutSeconds, - this.mHidCreateBondTimeoutSeconds, - this.mProxyTimeoutSeconds, - this.mRejectPhonebookAccess, - this.mRejectMessageAccess, - this.mRejectSimAccess, - this.mWriteAccountKeySleepMillis, - this.mSkipDisconnectingGattBeforeWritingAccountKey, - this.mMoreEventLogForQuality, - this.mRetryGattConnectionAndSecretHandshake, - this.mGattConnectShortTimeoutMs, - this.mGattConnectLongTimeoutMs, - this.mGattConnectShortTimeoutRetryMaxSpentTimeMs, - this.mAddressRotateRetryMaxSpentTimeMs, - this.mPairingRetryDelayMs, - this.mSecretHandshakeShortTimeoutMs, - this.mSecretHandshakeLongTimeoutMs, - this.mSecretHandshakeShortTimeoutRetryMaxSpentTimeMs, - this.mSecretHandshakeLongTimeoutRetryMaxSpentTimeMs, - this.mSecretHandshakeRetryAttempts, - this.mSecretHandshakeRetryGattConnectionMaxSpentTimeMs, - this.mSignalLostRetryMaxSpentTimeMs, - this.mGattConnectionAndSecretHandshakeNoRetryGattError, - this.mRetrySecretHandshakeTimeout, - this.mLogUserManualRetry, - this.mPairFailureCounts, - this.mCachedDeviceAddress, - this.mPossibleCachedDeviceAddress, - this.mSameModelIdPairedDeviceCount, - this.mIsDeviceFinishCheckAddressFromCache, - this.mLogPairWithCachedModelId, - this.mDirectConnectProfileIfModelIdInCache, - this.mAcceptPasskey, - this.mSupportedProfileUuids, - this.mProviderInitiatesBondingIfSupported, - this.mAttemptDirectConnectionWhenPreviouslyBonded, - this.mAutomaticallyReconnectGattWhenNeeded, - this.mSkipConnectingProfiles, - this.mIgnoreUuidTimeoutAfterBonded, - this.mSpecifyCreateBondTransportType, - this.mCreateBondTransportType, - this.mIncreaseIntentFilterPriority, - this.mEvaluatePerformance, - this.mExtraLoggingInformation, - this.mEnableNamingCharacteristic, - this.mEnableFirmwareVersionCharacteristic, - this.mKeepSameAccountKeyWrite, - this.mIsRetroactivePairing, - this.mNumSdpAttemptsAfterBonded, - this.mSupportHidDevice, - this.mEnablePairingWhileDirectlyConnecting, - this.mAcceptConsentForFastPairOne, - this.mGattConnectRetryTimeoutMillis, - this.mEnable128BitCustomGattCharacteristicsId, - this.mEnableSendExceptionStepToValidator, - this.mEnableAdditionalDataTypeWhenActionOverBle, - this.mCheckBondStateWhenSkipConnectingProfiles, - this.mHandlePasskeyConfirmationByUi, - this.mEnablePairFlowShowUiWithoutProfileConnection); - } - } - - /** - * Whether a given Uuid is supported. - */ - public boolean isSupportedProfile(short profileUuid) { - return Constants.PROFILES.containsKey(profileUuid) - && Shorts.contains( - Bytes.toShorts(ByteOrder.BIG_ENDIAN, getSupportedProfileUuids()), profileUuid); - } - - /** - * Information that will be used for logging. - */ - public static class ExtraLoggingInformation { - - private final String mModelId; - - private ExtraLoggingInformation(String modelId) { - this.mModelId = modelId; - } - - /** - * Returns model Id. - */ - public String getModelId() { - return mModelId; - } - - /** - * Converts an instance to a builder. - */ - public Builder toBuilder() { - return new Builder(this); - } - - /** - * Creates a builder for ExtraLoggingInformation. - */ - public static Builder builder() { - return new ExtraLoggingInformation.Builder(); - } - - @Override - public String toString() { - return "ExtraLoggingInformation{" + "modelId=" + mModelId + "}"; - } - - @Override - public boolean equals(@Nullable Object o) { - if (o == this) { - return true; - } - if (o instanceof ExtraLoggingInformation) { - Preferences.ExtraLoggingInformation that = (Preferences.ExtraLoggingInformation) o; - return this.mModelId.equals(that.getModelId()); - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(mModelId); - } - - /** - * Extra logging information builder. - */ - public static class Builder { - - private String mModelId; - - private Builder() { - } - - private Builder(ExtraLoggingInformation source) { - this.mModelId = source.getModelId(); - } - - /** - * Set model ID. - */ - public Builder setModelId(String modelId) { - this.mModelId = modelId; - return this; - } - - /** - * Builds extra logging information. - */ - public ExtraLoggingInformation build() { - return new ExtraLoggingInformation(mModelId); - } - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Reflect.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Reflect.java deleted file mode 100644 index a2603b5246c231323f7f30809c1d4020d1401c46..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Reflect.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -/** - * Utilities for calling methods using reflection. The main benefit of using this helper is to avoid - * complications around exception handling when calling methods reflectively. It's not safe to use - * Java 8's multicatch on such exceptions, because the java compiler converts multicatch into - * ReflectiveOperationException in some instances, which doesn't work on older sdk versions. - * Instead, use these utilities and catch ReflectionException. - * - *

    Example usage: - * - *

    {@code
    - * try {
    - *   Reflect.on(btAdapter)
    - *       .withMethod("setScanMode", int.class)
    - *       .invoke(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)
    - * } catch (ReflectionException e) { }
    - * }
    - */ -// TODO(b/202549655): remove existing Reflect usage. New usage is not allowed! No exception! -public final class Reflect { - private final Object mTargetObject; - - private Reflect(Object targetObject) { - this.mTargetObject = targetObject; - } - - /** Creates an instance of this helper to invoke methods on the given target object. */ - public static Reflect on(Object targetObject) { - return new Reflect(targetObject); - } - - /** Finds a method with the given name and parameter types. */ - public ReflectionMethod withMethod(String methodName, Class... paramTypes) - throws ReflectionException { - try { - return new ReflectionMethod(mTargetObject.getClass().getMethod(methodName, paramTypes)); - } catch (NoSuchMethodException e) { - throw new ReflectionException(e); - } - } - - /** Represents an invokable method found reflectively. */ - public final class ReflectionMethod { - private final Method mMethod; - - private ReflectionMethod(Method method) { - this.mMethod = method; - } - - /** - * Invokes this instance method with the given parameters. The called method does not return - * a value. - */ - public void invoke(Object... parameters) throws ReflectionException { - try { - mMethod.invoke(mTargetObject, parameters); - } catch (IllegalAccessException e) { - throw new ReflectionException(e); - } catch (InvocationTargetException e) { - throw new ReflectionException(e); - } - } - - /** - * Invokes this instance method with the given parameters. The called method returns a non - * null value. - */ - public Object get(Object... parameters) throws ReflectionException { - Object value; - try { - value = mMethod.invoke(mTargetObject, parameters); - } catch (IllegalAccessException e) { - throw new ReflectionException(e); - } catch (InvocationTargetException e) { - throw new ReflectionException(e); - } - if (value == null) { - throw new ReflectionException(new NullPointerException()); - } - return value; - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ReflectionException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ReflectionException.java deleted file mode 100644 index 1c20c550a425751f530d6038dd57d85f66ce9f40..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ReflectionException.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -/** - * An exception thrown during a reflection operation. Like ReflectiveOperationException, except - * compatible on older API versions. - */ -public final class ReflectionException extends Exception { - ReflectionException(Throwable cause) { - super(cause.getMessage(), cause); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SignalRotatedException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SignalRotatedException.java deleted file mode 100644 index d0d2a5d636fab799807b5b619dbfd8d04075158f..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SignalRotatedException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -/** Base class for fast pair signal rotated exceptions. */ -public class SignalRotatedException extends PairingException { - private final String mNewAddress; - - SignalRotatedException(String message, String newAddress, Exception e) { - super(message); - this.mNewAddress = newAddress; - initCause(e); - } - - /** Returns the new BLE address for the model ID. */ - public String getNewAddress() { - return mNewAddress; - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SimpleBroadcastReceiver.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SimpleBroadcastReceiver.java deleted file mode 100644 index 7f525a7b313cc3f117548bb523b0b95887f4ea63..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/SimpleBroadcastReceiver.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import android.annotation.TargetApi; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Build.VERSION_CODES; -import android.os.Handler; -import android.util.Log; - -import androidx.annotation.Nullable; - -import com.google.common.util.concurrent.SettableFuture; - -import java.util.Arrays; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * Like {@link BroadcastReceiver}, but: - * - *
      - *
    • Simpler to create and register, with a list of actions. - *
    • Implements AutoCloseable. If used as a resource in try-with-resources (available on - * KitKat+), unregisters itself automatically. - *
    • Lets you block waiting for your state transition with {@link #await}. - *
    - */ -// AutoCloseable only available on KitKat+. -@TargetApi(VERSION_CODES.KITKAT) -public abstract class SimpleBroadcastReceiver extends BroadcastReceiver implements AutoCloseable { - - private static final String TAG = SimpleBroadcastReceiver.class.getSimpleName(); - - /** - * Creates a one shot receiver. - */ - public static SimpleBroadcastReceiver oneShotReceiver( - Context context, Preferences preferences, String... actions) { - return new SimpleBroadcastReceiver(context, preferences, actions) { - @Override - protected void onReceive(Intent intent) { - close(); - } - }; - } - - private final Context mContext; - private final SettableFuture mIsClosedFuture = SettableFuture.create(); - private long mAwaitExtendSecond; - - // Nullness checker complains about 'this' being @UnderInitialization - @SuppressWarnings("nullness") - public SimpleBroadcastReceiver( - Context context, Preferences preferences, @Nullable Handler handler, - String... actions) { - Log.v(TAG, this + " listening for actions " + Arrays.toString(actions)); - this.mContext = context; - IntentFilter intentFilter = new IntentFilter(); - if (preferences.getIncreaseIntentFilterPriority()) { - intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - } - for (String action : actions) { - intentFilter.addAction(action); - } - context.registerReceiver(this, intentFilter, /* broadcastPermission= */ null, handler); - } - - public SimpleBroadcastReceiver(Context context, Preferences preferences, String... actions) { - this(context, preferences, /* handler= */ null, actions); - } - - /** - * Any exception thrown by this method will be delivered via {@link #await}. - */ - protected abstract void onReceive(Intent intent) throws Exception; - - @Override - public void onReceive(Context context, Intent intent) { - Log.v(TAG, "Got intent with action= " + intent.getAction()); - try { - onReceive(intent); - } catch (Exception e) { - closeWithError(e); - } - } - - @Override - public void close() { - closeWithError(null); - } - - void closeWithError(@Nullable Exception e) { - try { - mContext.unregisterReceiver(this); - } catch (IllegalArgumentException ignored) { - // Ignore. Happens if you unregister twice. - } - if (e == null) { - mIsClosedFuture.set(null); - } else { - mIsClosedFuture.setException(e); - } - } - - /** - * Extends the awaiting time. - */ - public void extendAwaitSecond(int awaitExtendSecond) { - this.mAwaitExtendSecond = awaitExtendSecond; - } - - /** - * Blocks until this receiver has closed (i.e. the state transition that this receiver is - * interested in has completed). Throws an exception on any error. - */ - public void await(long timeout, TimeUnit timeUnit) - throws InterruptedException, ExecutionException, TimeoutException { - Log.v(TAG, this + " waiting on future for " + timeout + " " + timeUnit); - try { - mIsClosedFuture.get(timeout, timeUnit); - } catch (TimeoutException e) { - if (mAwaitExtendSecond <= 0) { - throw e; - } - Log.i(TAG, "Extend timeout for " + mAwaitExtendSecond + " seconds"); - mIsClosedFuture.get(mAwaitExtendSecond, TimeUnit.SECONDS); - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/TdsException.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/TdsException.java deleted file mode 100644 index 7382ff371e4adfc92009122934e56cd300dfd3e2..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/TdsException.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import com.android.server.nearby.intdefs.FastPairEventIntDefs.BrEdrHandoverErrorCode; - -import com.google.errorprone.annotations.FormatMethod; - -/** - * Thrown when BR/EDR Handover fails. - */ -public class TdsException extends Exception { - - final @BrEdrHandoverErrorCode int mErrorCode; - - @FormatMethod - TdsException(@BrEdrHandoverErrorCode int errorCode, String format, Object... objects) { - super(String.format(format, objects)); - this.mErrorCode = errorCode; - } - - /** Returns error code. */ - public @BrEdrHandoverErrorCode int getErrorCode() { - return mErrorCode; - } -} - diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/TimingLogger.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/TimingLogger.java deleted file mode 100644 index 83ee3090267bf511d7ca7bf804a1a3019a442e1f..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/TimingLogger.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import android.os.SystemClock; -import android.util.Log; - -import androidx.annotation.VisibleForTesting; - -import java.util.ArrayDeque; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * A profiler for performance metrics. - * - *

    This class aim to break down the execution time for each steps of process to figure out the - * bottleneck. - */ -public class TimingLogger { - - private static final String TAG = TimingLogger.class.getSimpleName(); - - /** - * The name of this session. - */ - private final String mName; - - private final Preferences mPreference; - - /** - * The ordered timing sequence data. It's composed by a paired {@link Timing} generated from - * {@link #start} and {@link #end}. - */ - private final List mTimings; - - private final long mStartTimestampMs; - - /** Constructor. */ - public TimingLogger(String name, Preferences mPreference) { - this.mName = name; - this.mPreference = mPreference; - mTimings = new CopyOnWriteArrayList<>(); - mStartTimestampMs = SystemClock.elapsedRealtime(); - } - - @VisibleForTesting - List getTimings() { - return mTimings; - } - - /** - * Start a new paired timing. - * - * @param label The split name of paired timing. - */ - public void start(String label) { - if (mPreference.getEvaluatePerformance()) { - mTimings.add(new Timing(label)); - } - } - - /** - * End a paired timing. - */ - public void end() { - if (mPreference.getEvaluatePerformance()) { - mTimings.add(new Timing(Timing.END_LABEL)); - } - } - - /** - * Print out the timing data. - */ - public void dump() { - if (!mPreference.getEvaluatePerformance()) { - return; - } - - calculateTiming(); - Log.i(TAG, mName + "[Exclusive time] / [Total time] ([Timestamp])"); - int indentCount = 0; - for (Timing timing : mTimings) { - if (timing.isEndTiming()) { - indentCount--; - continue; - } - indentCount++; - if (timing.mExclusiveTime == timing.mTotalTime) { - Log.i(TAG, getIndentString(indentCount) + timing.mName + " " + timing.mExclusiveTime - + "ms (" + getRelativeTimestamp(timing.getTimestamp()) + ")"); - } else { - Log.i(TAG, getIndentString(indentCount) + timing.mName + " " + timing.mExclusiveTime - + "ms / " + timing.mTotalTime + "ms (" + getRelativeTimestamp( - timing.getTimestamp()) + ")"); - } - } - Log.i(TAG, mName + "end, " + getTotalTime() + "ms"); - } - - private void calculateTiming() { - ArrayDeque arrayDeque = new ArrayDeque<>(); - for (Timing timing : mTimings) { - if (timing.isStartTiming()) { - arrayDeque.addFirst(timing); - continue; - } - - Timing timingStart = arrayDeque.removeFirst(); - final long time = timing.mTimestamp - timingStart.mTimestamp; - timingStart.mExclusiveTime += time; - timingStart.mTotalTime += time; - if (!arrayDeque.isEmpty()) { - arrayDeque.peekFirst().mExclusiveTime -= time; - } - } - } - - private String getIndentString(int indentCount) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < indentCount; i++) { - sb.append(" "); - } - return sb.toString(); - } - - private long getRelativeTimestamp(long timestamp) { - return timestamp - mTimings.get(0).mTimestamp; - } - - @VisibleForTesting - long getTotalTime() { - return mTimings.get(mTimings.size() - 1).mTimestamp - mTimings.get(0).mTimestamp; - } - - /** - * Gets the current latency since this object was created. - */ - public long getLatencyMs() { - return SystemClock.elapsedRealtime() - mStartTimestampMs; - } - - @VisibleForTesting - static class Timing { - - private static final String END_LABEL = "END_LABEL"; - - /** - * The name of this paired timing. - */ - private final String mName; - - /** - * System uptime in millisecond. - */ - private final long mTimestamp; - - /** - * The execution time exclude inner split timings. - */ - private long mExclusiveTime; - - /** - * The execution time within a start and an end timing. - */ - private long mTotalTime; - - private Timing(String name) { - this.mName = name; - mTimestamp = SystemClock.elapsedRealtime(); - mExclusiveTime = 0; - mTotalTime = 0; - } - - @VisibleForTesting - String getName() { - return mName; - } - - @VisibleForTesting - long getTimestamp() { - return mTimestamp; - } - - @VisibleForTesting - long getExclusiveTime() { - return mExclusiveTime; - } - - @VisibleForTesting - long getTotalTime() { - return mTotalTime; - } - - @VisibleForTesting - boolean isStartTiming() { - return !isEndTiming(); - } - - @VisibleForTesting - boolean isEndTiming() { - return END_LABEL.equals(mName); - } - } - - /** - * This class ensures each split timing is paired with a start and an end timing. - */ - public static class ScopedTiming implements AutoCloseable { - - private final TimingLogger mTimingLogger; - - public ScopedTiming(TimingLogger logger, String label) { - mTimingLogger = logger; - mTimingLogger.start(label); - } - - @Override - public void close() { - mTimingLogger.end(); - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ToggleBluetoothTask.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ToggleBluetoothTask.java deleted file mode 100644 index 41ac9f512fbee66ec2b01e89f227f9c509fc91d5..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/ToggleBluetoothTask.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.fastpair; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; - -/** Task for toggling Bluetooth on and back off again. */ -interface ToggleBluetoothTask { - - /** - * Toggles the bluetooth adapter off and back on again to help improve connection reliability. - * - * @throws InterruptedException when waiting for the bluetooth adapter's state to be set has - * been interrupted. - * @throws ExecutionException when waiting for the bluetooth adapter's state to be set has - * failed. - * @throws TimeoutException when the bluetooth adapter's state fails to be set on or off. - */ - void toggleBluetooth() throws InterruptedException, ExecutionException, TimeoutException; -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattConnection.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattConnection.java deleted file mode 100644 index de131e4ee6ce4579b3964b20f5c33113e47bd101..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattConnection.java +++ /dev/null @@ -1,781 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.gatt; - -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; -import android.bluetooth.BluetoothGattService; -import android.bluetooth.BluetoothStatusCodes; -import android.util.Log; - -import androidx.annotation.VisibleForTesting; - -import com.android.server.nearby.common.bluetooth.BluetoothConsts; -import com.android.server.nearby.common.bluetooth.BluetoothException; -import com.android.server.nearby.common.bluetooth.BluetoothGattException; -import com.android.server.nearby.common.bluetooth.BluetoothTimeoutException; -import com.android.server.nearby.common.bluetooth.ReservedUuids; -import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattHelper.ConnectionOptions; -import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattHelper.OperationType; -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice; -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattWrapper; -import com.android.server.nearby.common.bluetooth.util.BluetoothGattUtils; -import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor; -import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.Operation; -import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.SynchronousOperation; - -import com.google.common.base.Preconditions; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.BlockingDeque; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nullable; -import javax.annotation.concurrent.GuardedBy; - -/** - * Gatt connection to a Bluetooth device. - */ -public class BluetoothGattConnection implements AutoCloseable { - - private static final String TAG = BluetoothGattConnection.class.getSimpleName(); - - @VisibleForTesting - static final long OPERATION_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(1); - @VisibleForTesting - static final long SLOW_OPERATION_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10); - - @VisibleForTesting - static final int GATT_INTERNAL_ERROR = 129; - @VisibleForTesting - static final int GATT_ERROR = 133; - - private final BluetoothGattWrapper mGatt; - private final BluetoothOperationExecutor mBluetoothOperationExecutor; - private final ConnectionOptions mConnectionOptions; - - private volatile boolean mServicesDiscovered = false; - - private volatile boolean mIsConnected = false; - - private volatile int mMtu = BluetoothConsts.DEFAULT_MTU; - - private final ConcurrentMap mChangeObservers = - new ConcurrentHashMap<>(); - - private final List mCloseListeners = new ArrayList<>(); - - private long mOperationTimeoutMillis = OPERATION_TIMEOUT_MILLIS; - - BluetoothGattConnection( - BluetoothGattWrapper gatt, - BluetoothOperationExecutor bluetoothOperationExecutor, - ConnectionOptions connectionOptions) { - mGatt = gatt; - mBluetoothOperationExecutor = bluetoothOperationExecutor; - mConnectionOptions = connectionOptions; - } - - /** - * Set operation timeout. - */ - public void setOperationTimeout(long timeoutMillis) { - Preconditions.checkArgument(timeoutMillis > 0, "invalid time out value"); - mOperationTimeoutMillis = timeoutMillis; - } - - /** - * Returns connected device. - */ - public BluetoothDevice getDevice() { - return mGatt.getDevice(); - } - - public ConnectionOptions getConnectionOptions() { - return mConnectionOptions; - } - - public boolean isConnected() { - return mIsConnected; - } - - /** - * Get service. - */ - public BluetoothGattService getService(UUID uuid) throws BluetoothException { - Log.d(TAG, String.format("Getting service %s.", uuid)); - if (!mServicesDiscovered) { - discoverServices(); - } - BluetoothGattService match = null; - for (BluetoothGattService service : mGatt.getServices()) { - if (service.getUuid().equals(uuid)) { - if (match != null) { - throw new BluetoothException( - String.format("More than one service %s found on device %s.", - uuid, - mGatt.getDevice())); - } - match = service; - } - } - if (match == null) { - throw new BluetoothException(String.format("Service %s not found on device %s.", - uuid, - mGatt.getDevice())); - } - Log.d(TAG, "Service found."); - return match; - } - - /** - * Returns a list of all characteristics under a given service UUID. - */ - private List getCharacteristics(UUID serviceUuid) - throws BluetoothException { - if (!mServicesDiscovered) { - discoverServices(); - } - ArrayList characteristics = new ArrayList<>(); - for (BluetoothGattService service : mGatt.getServices()) { - // Add all characteristics under this service if its service UUID matches. - if (service.getUuid().equals(serviceUuid)) { - characteristics.addAll(service.getCharacteristics()); - } - } - return characteristics; - } - - /** - * Get characteristic. - */ - public BluetoothGattCharacteristic getCharacteristic(UUID serviceUuid, - UUID characteristicUuid) throws BluetoothException { - Log.d(TAG, String.format("Getting characteristic %s on service %s.", characteristicUuid, - serviceUuid)); - BluetoothGattCharacteristic match = null; - for (BluetoothGattCharacteristic characteristic : getCharacteristics(serviceUuid)) { - if (characteristic.getUuid().equals(characteristicUuid)) { - if (match != null) { - throw new BluetoothException(String.format( - "More than one characteristic %s found on service %s on device %s.", - characteristicUuid, - serviceUuid, - mGatt.getDevice())); - } - match = characteristic; - } - } - if (match == null) { - throw new BluetoothException(String.format( - "Characteristic %s not found on service %s of device %s.", - characteristicUuid, - serviceUuid, - mGatt.getDevice())); - } - Log.d(TAG, "Characteristic found."); - return match; - } - - /** - * Get descriptor. - */ - public BluetoothGattDescriptor getDescriptor(UUID serviceUuid, - UUID characteristicUuid, UUID descriptorUuid) throws BluetoothException { - Log.d(TAG, String.format("Getting descriptor %s on characteristic %s on service %s.", - descriptorUuid, characteristicUuid, serviceUuid)); - BluetoothGattDescriptor match = null; - for (BluetoothGattDescriptor descriptor : - getCharacteristic(serviceUuid, characteristicUuid).getDescriptors()) { - if (descriptor.getUuid().equals(descriptorUuid)) { - if (match != null) { - throw new BluetoothException(String.format("More than one descriptor %s found " - + "on characteristic %s service %s on device %s.", - descriptorUuid, - characteristicUuid, - serviceUuid, - mGatt.getDevice())); - } - match = descriptor; - } - } - if (match == null) { - throw new BluetoothException(String.format( - "Descriptor %s not found on characteristic %s on service %s of device %s.", - descriptorUuid, - characteristicUuid, - serviceUuid, - mGatt.getDevice())); - } - Log.d(TAG, "Descriptor found."); - return match; - } - - /** - * Discover services. - */ - public void discoverServices() throws BluetoothException { - mBluetoothOperationExecutor.execute( - new SynchronousOperation(OperationType.DISCOVER_SERVICES) { - @Nullable - @Override - public Void call() throws BluetoothException { - if (mServicesDiscovered) { - return null; - } - boolean forceRefresh = false; - try { - discoverServicesInternal(); - } catch (BluetoothException e) { - if (!(e instanceof BluetoothGattException)) { - throw e; - } - int errorCode = ((BluetoothGattException) e).getGattErrorCode(); - if (errorCode != GATT_ERROR && errorCode != GATT_INTERNAL_ERROR) { - throw e; - } - Log.e(TAG, e.getMessage() - + "\n Ignore the gatt error for post MNC apis and force " - + "a refresh"); - forceRefresh = true; - } - - forceRefreshServiceCacheIfNeeded(forceRefresh); - - mServicesDiscovered = true; - - return null; - } - }); - } - - private void discoverServicesInternal() throws BluetoothException { - Log.i(TAG, "Starting services discovery."); - long startTimeMillis = System.currentTimeMillis(); - try { - mBluetoothOperationExecutor.execute( - new Operation(OperationType.DISCOVER_SERVICES_INTERNAL, mGatt) { - @Override - public void run() throws BluetoothException { - boolean success = mGatt.discoverServices(); - if (!success) { - throw new BluetoothException( - "gatt.discoverServices returned false."); - } - } - }, - SLOW_OPERATION_TIMEOUT_MILLIS); - Log.i(TAG, String.format("Services discovered successfully in %s ms.", - System.currentTimeMillis() - startTimeMillis)); - } catch (BluetoothException e) { - if (e instanceof BluetoothGattException) { - throw new BluetoothGattException(String.format( - "Failed to discover services on device: %s.", - mGatt.getDevice()), ((BluetoothGattException) e).getGattErrorCode(), e); - } else { - throw new BluetoothException(String.format( - "Failed to discover services on device: %s.", - mGatt.getDevice()), e); - } - } - } - - private boolean hasDynamicServices() { - BluetoothGattService gattService = - mGatt.getService(ReservedUuids.Services.GENERIC_ATTRIBUTE); - if (gattService != null) { - BluetoothGattCharacteristic serviceChange = - gattService.getCharacteristic(ReservedUuids.Characteristics.SERVICE_CHANGE); - if (serviceChange != null) { - return true; - } - } - - // Check whether the server contains a self defined service dynamic characteristic. - gattService = mGatt.getService(BluetoothConsts.SERVICE_DYNAMIC_SERVICE); - if (gattService != null) { - BluetoothGattCharacteristic serviceChange = - gattService.getCharacteristic(BluetoothConsts.SERVICE_DYNAMIC_CHARACTERISTIC); - if (serviceChange != null) { - return true; - } - } - - return false; - } - - private void forceRefreshServiceCacheIfNeeded(boolean forceRefresh) throws BluetoothException { - if (mGatt.getDevice().getBondState() != BluetoothDevice.BOND_BONDED) { - // Device is not bonded, so services should not have been cached. - return; - } - - if (!forceRefresh && !hasDynamicServices()) { - return; - } - Log.i(TAG, "Forcing a refresh of local cache of GATT services"); - boolean success = mGatt.refresh(); - if (!success) { - throw new BluetoothException("gatt.refresh returned false."); - } - discoverServicesInternal(); - } - - /** - * Read characteristic. - */ - public byte[] readCharacteristic(UUID serviceUuid, UUID characteristicUuid) - throws BluetoothException { - return readCharacteristic(getCharacteristic(serviceUuid, characteristicUuid)); - } - - /** - * Read characteristic. - */ - public byte[] readCharacteristic(final BluetoothGattCharacteristic characteristic) - throws BluetoothException { - try { - return mBluetoothOperationExecutor.executeNonnull( - new Operation(OperationType.READ_CHARACTERISTIC, mGatt, - characteristic) { - @Override - public void run() throws BluetoothException { - boolean success = mGatt.readCharacteristic(characteristic); - if (!success) { - throw new BluetoothException( - "gatt.readCharacteristic returned false."); - } - } - }, - mOperationTimeoutMillis); - } catch (BluetoothException e) { - throw new BluetoothException(String.format( - "Failed to read %s on device %s.", - BluetoothGattUtils.toString(characteristic), - mGatt.getDevice()), e); - } - } - - /** - * Writes Characteristic. - */ - public void writeCharacteristic(UUID serviceUuid, UUID characteristicUuid, byte[] value) - throws BluetoothException { - writeCharacteristic(getCharacteristic(serviceUuid, characteristicUuid), value); - } - - /** - * Writes Characteristic. - */ - public void writeCharacteristic(final BluetoothGattCharacteristic characteristic, - final byte[] value) throws BluetoothException { - Log.d(TAG, String.format("Writing %d bytes on %s on device %s.", - value.length, - BluetoothGattUtils.toString(characteristic), - mGatt.getDevice())); - if ((characteristic.getProperties() & (BluetoothGattCharacteristic.PROPERTY_WRITE - | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)) == 0) { - throw new BluetoothException(String.format("%s is not writable!", characteristic)); - } - try { - mBluetoothOperationExecutor.execute( - new Operation(OperationType.WRITE_CHARACTERISTIC, mGatt, characteristic) { - @Override - public void run() throws BluetoothException { - int writeCharacteristicResponseCode = mGatt.writeCharacteristic( - characteristic, value, characteristic.getWriteType()); - if (writeCharacteristicResponseCode != BluetoothStatusCodes.SUCCESS) { - throw new BluetoothException( - "gatt.writeCharacteristic returned " - + writeCharacteristicResponseCode); - } - } - }, - mOperationTimeoutMillis); - } catch (BluetoothException e) { - throw new BluetoothException(String.format( - "Failed to write %s on device %s.", - BluetoothGattUtils.toString(characteristic), - mGatt.getDevice()), e); - } - Log.d(TAG, "Writing characteristic done."); - } - - /** - * Reads descriptor. - */ - public byte[] readDescriptor(UUID serviceUuid, UUID characteristicUuid, UUID descriptorUuid) - throws BluetoothException { - return readDescriptor(getDescriptor(serviceUuid, characteristicUuid, descriptorUuid)); - } - - /** - * Reads descriptor. - */ - public byte[] readDescriptor(final BluetoothGattDescriptor descriptor) - throws BluetoothException { - try { - return mBluetoothOperationExecutor.executeNonnull( - new Operation(OperationType.READ_DESCRIPTOR, mGatt, descriptor) { - @Override - public void run() throws BluetoothException { - boolean success = mGatt.readDescriptor(descriptor); - if (!success) { - throw new BluetoothException("gatt.readDescriptor returned false."); - } - } - }, - mOperationTimeoutMillis); - } catch (BluetoothException e) { - throw new BluetoothException(String.format( - "Failed to read %s on %s on device %s.", - descriptor.getUuid(), - BluetoothGattUtils.toString(descriptor), - mGatt.getDevice()), e); - } - } - - /** - * Writes descriptor. - */ - public void writeDescriptor(UUID serviceUuid, UUID characteristicUuid, UUID descriptorUuid, - byte[] value) throws BluetoothException { - writeDescriptor(getDescriptor(serviceUuid, characteristicUuid, descriptorUuid), value); - } - - /** - * Writes descriptor. - */ - public void writeDescriptor(final BluetoothGattDescriptor descriptor, final byte[] value) - throws BluetoothException { - Log.d(TAG, String.format( - "Writing %d bytes on %s on device %s.", - value.length, - BluetoothGattUtils.toString(descriptor), - mGatt.getDevice())); - long startTimeMillis = System.currentTimeMillis(); - try { - mBluetoothOperationExecutor.execute( - new Operation(OperationType.WRITE_DESCRIPTOR, mGatt, descriptor) { - @Override - public void run() throws BluetoothException { - int writeDescriptorResponseCode = mGatt.writeDescriptor(descriptor, - value); - if (writeDescriptorResponseCode != BluetoothStatusCodes.SUCCESS) { - throw new BluetoothException( - "gatt.writeDescriptor returned " - + writeDescriptorResponseCode); - } - } - }, - mOperationTimeoutMillis); - Log.d(TAG, String.format("Writing descriptor done in %s ms.", - System.currentTimeMillis() - startTimeMillis)); - } catch (BluetoothException e) { - throw new BluetoothException(String.format( - "Failed to write %s on device %s.", - BluetoothGattUtils.toString(descriptor), - mGatt.getDevice()), e); - } - } - - /** - * Reads remote Rssi. - */ - public int readRemoteRssi() throws BluetoothException { - try { - return mBluetoothOperationExecutor.executeNonnull( - new Operation(OperationType.READ_RSSI, mGatt) { - @Override - public void run() throws BluetoothException { - boolean success = mGatt.readRemoteRssi(); - if (!success) { - throw new BluetoothException("gatt.readRemoteRssi returned false."); - } - } - }, - mOperationTimeoutMillis); - } catch (BluetoothException e) { - throw new BluetoothException( - String.format("Failed to read rssi on device %s.", mGatt.getDevice()), e); - } - } - - public int getMtu() { - return mMtu; - } - - /** - * Get max data packet size. - */ - public int getMaxDataPacketSize() { - // Per BT specs (3.2.9), only MTU - 3 bytes can be used to transmit data - return mMtu - 3; - } - - /** Set notification enabled or disabled. */ - @VisibleForTesting - public void setNotificationEnabled(BluetoothGattCharacteristic characteristic, boolean enabled) - throws BluetoothException { - boolean isIndication; - int properties = characteristic.getProperties(); - if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) { - isIndication = false; - } else if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) { - isIndication = true; - } else { - throw new BluetoothException(String.format( - "%s on device %s supports neither notifications nor indications.", - BluetoothGattUtils.toString(characteristic), - mGatt.getDevice())); - } - BluetoothGattDescriptor clientConfigDescriptor = - characteristic.getDescriptor( - ReservedUuids.Descriptors.CLIENT_CHARACTERISTIC_CONFIGURATION); - if (clientConfigDescriptor == null) { - throw new BluetoothException(String.format( - "%s on device %s is missing client config descriptor.", - BluetoothGattUtils.toString(characteristic), - mGatt.getDevice())); - } - long startTime = System.currentTimeMillis(); - Log.d(TAG, String.format("%s %s on characteristic %s.", enabled ? "Enabling" : "Disabling", - isIndication ? "indication" : "notification", characteristic.getUuid())); - - if (enabled) { - mGatt.setCharacteristicNotification(characteristic, enabled); - } - writeDescriptor(clientConfigDescriptor, - enabled - ? (isIndication - ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE : - BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) - : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); - if (!enabled) { - mGatt.setCharacteristicNotification(characteristic, enabled); - } - - Log.d(TAG, String.format("Done in %d ms.", System.currentTimeMillis() - startTime)); - } - - /** - * Enables notification. - */ - public ChangeObserver enableNotification(UUID serviceUuid, UUID characteristicUuid) - throws BluetoothException { - return enableNotification(getCharacteristic(serviceUuid, characteristicUuid)); - } - - /** - * Enables notification. - */ - public ChangeObserver enableNotification(final BluetoothGattCharacteristic characteristic) - throws BluetoothException { - return mBluetoothOperationExecutor.executeNonnull( - new SynchronousOperation( - OperationType.NOTIFICATION_CHANGE, - characteristic) { - @Override - public ChangeObserver call() throws BluetoothException { - ChangeObserver changeObserver = new ChangeObserver(); - mChangeObservers.put(characteristic, changeObserver); - setNotificationEnabled(characteristic, true); - return changeObserver; - } - }); - } - - /** - * Disables notification. - */ - public void disableNotification(UUID serviceUuid, UUID characteristicUuid) - throws BluetoothException { - disableNotification(getCharacteristic(serviceUuid, characteristicUuid)); - } - - /** - * Disables notification. - */ - public void disableNotification(final BluetoothGattCharacteristic characteristic) - throws BluetoothException { - mBluetoothOperationExecutor.execute( - new SynchronousOperation( - OperationType.NOTIFICATION_CHANGE, - characteristic) { - @Nullable - @Override - public Void call() throws BluetoothException { - setNotificationEnabled(characteristic, false); - mChangeObservers.remove(characteristic); - return null; - } - }); - } - - /** - * Adds a close listener. - */ - public void addCloseListener(ConnectionCloseListener listener) { - mCloseListeners.add(listener); - if (!mIsConnected) { - listener.onClose(); - return; - } - } - - /** - * Removes a close listener. - */ - public void removeCloseListener(ConnectionCloseListener listener) { - mCloseListeners.remove(listener); - } - - /** onCharacteristicChanged callback. */ - public void onCharacteristicChanged(BluetoothGattCharacteristic characteristic, byte[] value) { - ChangeObserver observer = mChangeObservers.get(characteristic); - if (observer == null) { - return; - } - observer.onValueChange(value); - } - - @Override - public void close() throws BluetoothException { - Log.d(TAG, "close"); - try { - if (!mIsConnected) { - // Don't call disconnect on a closed connection, since Android framework won't - // provide any feedback. - return; - } - mBluetoothOperationExecutor.execute( - new Operation(OperationType.DISCONNECT, mGatt.getDevice()) { - @Override - public void run() throws BluetoothException { - mGatt.disconnect(); - } - }, mOperationTimeoutMillis); - } finally { - mGatt.close(); - } - } - - /** onConnected callback. */ - public void onConnected() { - Log.d(TAG, "onConnected"); - mIsConnected = true; - } - - /** onClosed callback. */ - public void onClosed() { - Log.d(TAG, "onClosed"); - if (!mIsConnected) { - return; - } - mIsConnected = false; - for (ConnectionCloseListener listener : mCloseListeners) { - listener.onClose(); - } - mGatt.close(); - } - - /** - * Observer to wait or be called back when value change. - */ - public static class ChangeObserver { - - private final BlockingDeque mValues = new LinkedBlockingDeque(); - - @GuardedBy("mValues") - @Nullable - private volatile CharacteristicChangeListener mListener; - - /** - * Set listener. - */ - public void setListener(@Nullable CharacteristicChangeListener listener) { - synchronized (mValues) { - mListener = listener; - if (listener != null) { - byte[] value; - while ((value = mValues.poll()) != null) { - listener.onValueChange(value); - } - } - } - } - - /** - * onValueChange callback. - */ - public void onValueChange(byte[] newValue) { - synchronized (mValues) { - CharacteristicChangeListener listener = mListener; - if (listener == null) { - mValues.add(newValue); - } else { - listener.onValueChange(newValue); - } - } - } - - /** - * Waits for update for a given time. - */ - public byte[] waitForUpdate(long timeoutMillis) throws BluetoothException { - byte[] result; - try { - result = mValues.poll(timeoutMillis, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new BluetoothException("Operation interrupted."); - } - if (result == null) { - throw new BluetoothTimeoutException( - String.format("Operation timed out after %dms", timeoutMillis)); - } - return result; - } - } - - /** - * Listener for characteristic data changes over notifications or indications. - */ - public interface CharacteristicChangeListener { - - /** - * onValueChange callback. - */ - void onValueChange(byte[] newValue); - } - - /** - * Listener for connection close events. - */ - public interface ConnectionCloseListener { - - /** - * onClose callback. - */ - void onClose(); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattHelper.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattHelper.java deleted file mode 100644 index 18a9f5f3bbbef35ee98e34e9ab50f2693c4ed9ea..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/gatt/BluetoothGattHelper.java +++ /dev/null @@ -1,690 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.gatt; - -import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; -import android.bluetooth.le.ScanFilter; -import android.bluetooth.le.ScanSettings; -import android.content.Context; -import android.os.ParcelUuid; -import android.util.Log; - -import androidx.annotation.IntDef; -import androidx.annotation.VisibleForTesting; - -import com.android.server.nearby.common.bluetooth.BluetoothException; -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter; -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice; -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattCallback; -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattWrapper; -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.BluetoothLeScanner; -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.ScanCallback; -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.ScanResult; -import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor; -import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException; -import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.Operation; - -import com.google.common.base.Preconditions; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; -import java.util.Locale; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nullable; -import javax.annotation.concurrent.GuardedBy; - -/** - * Wrapper of {@link BluetoothGattWrapper} that provides blocking methods, errors and timeout - * handling. - */ -@SuppressWarnings("Guava") // java.util.Optional is not available until API 24 -public class BluetoothGattHelper { - - private static final String TAG = BluetoothGattHelper.class.getSimpleName(); - - @VisibleForTesting - static final long LOW_LATENCY_SCAN_MILLIS = TimeUnit.SECONDS.toMillis(5); - private static final long POLL_INTERVAL_MILLIS = 5L /* milliseconds */; - - /** - * BT operation types that can be in flight. - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - value = { - OperationType.SCAN, - OperationType.CONNECT, - OperationType.DISCOVER_SERVICES, - OperationType.DISCOVER_SERVICES_INTERNAL, - OperationType.NOTIFICATION_CHANGE, - OperationType.READ_CHARACTERISTIC, - OperationType.WRITE_CHARACTERISTIC, - OperationType.READ_DESCRIPTOR, - OperationType.WRITE_DESCRIPTOR, - OperationType.READ_RSSI, - OperationType.WRITE_RELIABLE, - OperationType.CHANGE_MTU, - OperationType.DISCONNECT, - }) - public @interface OperationType { - int SCAN = 0; - int CONNECT = 1; - int DISCOVER_SERVICES = 2; - int DISCOVER_SERVICES_INTERNAL = 3; - int NOTIFICATION_CHANGE = 4; - int READ_CHARACTERISTIC = 5; - int WRITE_CHARACTERISTIC = 6; - int READ_DESCRIPTOR = 7; - int WRITE_DESCRIPTOR = 8; - int READ_RSSI = 9; - int WRITE_RELIABLE = 10; - int CHANGE_MTU = 11; - int DISCONNECT = 12; - } - - @VisibleForTesting - final ScanCallback mScanCallback = new InternalScanCallback(); - @VisibleForTesting - final BluetoothGattCallback mBluetoothGattCallback = - new InternalBluetoothGattCallback(); - @VisibleForTesting - final ConcurrentMap mConnections = - new ConcurrentHashMap<>(); - - private final Context mApplicationContext; - private final BluetoothAdapter mBluetoothAdapter; - private final BluetoothOperationExecutor mBluetoothOperationExecutor; - - @VisibleForTesting - BluetoothGattHelper( - Context applicationContext, - BluetoothAdapter bluetoothAdapter, - BluetoothOperationExecutor bluetoothOperationExecutor) { - mApplicationContext = applicationContext; - mBluetoothAdapter = bluetoothAdapter; - mBluetoothOperationExecutor = bluetoothOperationExecutor; - } - - public BluetoothGattHelper(Context applicationContext, BluetoothAdapter bluetoothAdapter) { - this( - Preconditions.checkNotNull(applicationContext), - Preconditions.checkNotNull(bluetoothAdapter), - new BluetoothOperationExecutor(5)); - } - - /** - * Auto-connects a serice Uuid. - */ - public BluetoothGattConnection autoConnect(final UUID serviceUuid) throws BluetoothException { - Log.d(TAG, String.format("Starting autoconnection to a device advertising service %s.", - serviceUuid)); - BluetoothDevice device = null; - int retries = 3; - final BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); - if (scanner == null) { - throw new BluetoothException("Bluetooth is disabled or LE is not supported."); - } - final ScanFilter serviceFilter = new ScanFilter.Builder() - .setServiceUuid(new ParcelUuid(serviceUuid)) - .build(); - ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder() - .setReportDelay(0); - final ScanSettings scanSettingsLowLatency = scanSettingsBuilder - .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) - .build(); - final ScanSettings scanSettingsLowPower = scanSettingsBuilder - .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) - .build(); - while (true) { - long startTimeMillis = System.currentTimeMillis(); - try { - Log.d(TAG, "Starting low latency scanning."); - device = - mBluetoothOperationExecutor.executeNonnull( - new Operation(OperationType.SCAN) { - @Override - public void run() throws BluetoothException { - scanner.startScan(Arrays.asList(serviceFilter), - scanSettingsLowLatency, mScanCallback); - } - }, LOW_LATENCY_SCAN_MILLIS); - } catch (BluetoothOperationTimeoutException e) { - Log.d(TAG, String.format( - "Cannot find a nearby device in low latency scanning after %s ms.", - LOW_LATENCY_SCAN_MILLIS)); - } finally { - scanner.stopScan(mScanCallback); - } - if (device == null) { - Log.d(TAG, "Starting low power scanning."); - try { - device = mBluetoothOperationExecutor.executeNonnull( - new Operation(OperationType.SCAN) { - @Override - public void run() throws BluetoothException { - scanner.startScan(Arrays.asList(serviceFilter), - scanSettingsLowPower, mScanCallback); - } - }); - } finally { - scanner.stopScan(mScanCallback); - } - } - Log.d(TAG, String.format("Scanning done in %d ms. Found device %s.", - System.currentTimeMillis() - startTimeMillis, device)); - - try { - return connect(device); - } catch (BluetoothException e) { - retries--; - if (retries == 0) { - throw e; - } else { - Log.d(TAG, String.format( - "Connection failed: %s. Retrying %d more times.", e, retries)); - } - } - } - } - - /** - * Connects to a device using default connection options. - */ - public BluetoothGattConnection connect(BluetoothDevice bluetoothDevice) - throws BluetoothException { - return connect(bluetoothDevice, ConnectionOptions.builder().build()); - } - - /** - * Connects to a device using specifies connection options. - */ - public BluetoothGattConnection connect( - BluetoothDevice bluetoothDevice, ConnectionOptions options) throws BluetoothException { - Log.d(TAG, String.format("Connecting to device %s.", bluetoothDevice)); - long startTimeMillis = System.currentTimeMillis(); - - Operation connectOperation = - new Operation(OperationType.CONNECT, bluetoothDevice) { - private final Object mLock = new Object(); - - @GuardedBy("mLock") - private boolean mIsCanceled = false; - - @GuardedBy("mLock") - @Nullable(/* null before operation is executed */) - private BluetoothGattWrapper mBluetoothGatt; - - @Override - public void run() throws BluetoothException { - synchronized (mLock) { - if (mIsCanceled) { - return; - } - BluetoothGattWrapper bluetoothGattWrapper; - Log.d(TAG, "Use LE transport"); - bluetoothGattWrapper = - bluetoothDevice.connectGatt( - mApplicationContext, - options.autoConnect(), - mBluetoothGattCallback, - android.bluetooth.BluetoothDevice.TRANSPORT_LE); - if (bluetoothGattWrapper == null) { - throw new BluetoothException("connectGatt() returned null."); - } - - try { - // Set connection priority without waiting for connection callback. - // Per code, btif_gatt_client.c, when priority is set before - // connection, this sets preferred connection parameters that will - // be used during the connection establishment. - Optional connectionPriorityOption = - options.connectionPriority(); - if (connectionPriorityOption.isPresent()) { - // requestConnectionPriority can only be called when - // BluetoothGatt is connected to the system BluetoothGatt - // service (see android/bluetooth/BluetoothGatt.java code). - // However, there is no callback to the app to inform when this - // is done. requestConnectionPriority will returns false with no - // side-effect before the service is connected, so we just poll - // here until true is returned. - int connectionPriority = connectionPriorityOption.get(); - long startTimeMillis = System.currentTimeMillis(); - while (!bluetoothGattWrapper.requestConnectionPriority( - connectionPriority)) { - if (System.currentTimeMillis() - startTimeMillis - > options.connectionTimeoutMillis()) { - throw new BluetoothException( - String.format( - Locale.US, - "Failed to set connectionPriority " - + "after %dms.", - options.connectionTimeoutMillis())); - } - try { - Thread.sleep(POLL_INTERVAL_MILLIS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new BluetoothException( - "connect() operation interrupted."); - } - } - } - } catch (Exception e) { - // Make sure to clean connection. - bluetoothGattWrapper.disconnect(); - bluetoothGattWrapper.close(); - throw e; - } - - BluetoothGattConnection connection = new BluetoothGattConnection( - bluetoothGattWrapper, mBluetoothOperationExecutor, options); - mConnections.put(bluetoothGattWrapper, connection); - mBluetoothGatt = bluetoothGattWrapper; - } - } - - @Override - public void cancel() { - // Clean connection if connection times out. - synchronized (mLock) { - if (mIsCanceled) { - return; - } - mIsCanceled = true; - BluetoothGattWrapper bluetoothGattWrapper = mBluetoothGatt; - if (bluetoothGattWrapper == null) { - return; - } - mConnections.remove(bluetoothGattWrapper); - bluetoothGattWrapper.disconnect(); - bluetoothGattWrapper.close(); - } - } - }; - BluetoothGattConnection result; - if (options.autoConnect()) { - result = mBluetoothOperationExecutor.executeNonnull(connectOperation); - } else { - result = - mBluetoothOperationExecutor.executeNonnull( - connectOperation, options.connectionTimeoutMillis()); - } - Log.d(TAG, String.format("Connection success in %d ms.", - System.currentTimeMillis() - startTimeMillis)); - return result; - } - - private BluetoothGattConnection getConnectionByGatt(BluetoothGattWrapper gatt) - throws BluetoothException { - BluetoothGattConnection connection = mConnections.get(gatt); - if (connection == null) { - throw new BluetoothException("Receive callback on unexpected device: " + gatt); - } - return connection; - } - - private class InternalBluetoothGattCallback extends BluetoothGattCallback { - - @Override - public void onConnectionStateChange(BluetoothGattWrapper gatt, int status, int newState) { - BluetoothGattConnection connection; - BluetoothDevice device = gatt.getDevice(); - switch (newState) { - case BluetoothGatt.STATE_CONNECTED: { - connection = mConnections.get(gatt); - if (connection == null) { - Log.w(TAG, String.format( - "Received unexpected successful connection for dev %s! Ignoring.", - device)); - break; - } - - Operation operation = - new Operation<>(OperationType.CONNECT, device); - if (status != BluetoothGatt.GATT_SUCCESS) { - mConnections.remove(gatt); - gatt.disconnect(); - gatt.close(); - mBluetoothOperationExecutor.notifyCompletion(operation, status, null); - break; - } - - // Process connection options - ConnectionOptions options = connection.getConnectionOptions(); - Optional mtuOption = options.mtu(); - if (mtuOption.isPresent()) { - // Requesting MTU and waiting for MTU callback. - boolean success = gatt.requestMtu(mtuOption.get()); - if (!success) { - mBluetoothOperationExecutor.notifyFailure(operation, - new BluetoothException(String.format(Locale.US, - "Failed to request MTU of %d for dev %s: " - + "returned false.", - mtuOption.get(), device))); - // Make sure to clean connection. - mConnections.remove(gatt); - gatt.disconnect(); - gatt.close(); - } - break; - } - - // Connection successful - connection.onConnected(); - mBluetoothOperationExecutor.notifyCompletion(operation, status, connection); - break; - } - case BluetoothGatt.STATE_DISCONNECTED: { - connection = mConnections.remove(gatt); - if (connection == null) { - Log.w(TAG, String.format("Received unexpected disconnection" - + " for device %s! Ignoring.", device)); - break; - } - if (!connection.isConnected()) { - // This is a failed connection attempt - if (status == BluetoothGatt.GATT_SUCCESS) { - // This is weird... considering this as a failure - Log.w(TAG, String.format( - "Received a success for a failed connection " - + "attempt for device %s! Ignoring.", device)); - status = BluetoothGatt.GATT_FAILURE; - } - mBluetoothOperationExecutor - .notifyCompletion(new Operation( - OperationType.CONNECT, device), status, null); - // Clean Gatt object in every case. - gatt.disconnect(); - gatt.close(); - break; - } - connection.onClosed(); - mBluetoothOperationExecutor.notifyCompletion( - new Operation<>(OperationType.DISCONNECT, device), status); - break; - } - default: - Log.e(TAG, "Unexpected connection state: " + newState); - } - } - - @Override - public void onMtuChanged(BluetoothGattWrapper gatt, int mtu, int status) { - BluetoothGattConnection connection = mConnections.get(gatt); - BluetoothDevice device = gatt.getDevice(); - if (connection == null) { - Log.w(TAG, String.format( - "Received unexpected MTU change for device %s! Ignoring.", device)); - return; - } - if (connection.isConnected()) { - // This is the callback for the deprecated BluetoothGattConnection.requestMtu. - mBluetoothOperationExecutor.notifyCompletion( - new Operation<>(OperationType.CHANGE_MTU, gatt), status, mtu); - } else { - // This is the callback when requesting MTU right after connecting. - connection.onConnected(); - mBluetoothOperationExecutor.notifyCompletion( - new Operation<>(OperationType.CONNECT, device), status, connection); - if (status != BluetoothGatt.GATT_SUCCESS) { - Log.w(TAG, String.format( - "%s responds MTU change failed, status %s.", device, status)); - // Clean connection if it's failed. - mConnections.remove(gatt); - gatt.disconnect(); - gatt.close(); - return; - } - } - } - - @Override - public void onServicesDiscovered(BluetoothGattWrapper gatt, int status) { - mBluetoothOperationExecutor.notifyCompletion( - new Operation(OperationType.DISCOVER_SERVICES_INTERNAL, gatt), status); - } - - @Override - public void onCharacteristicRead(BluetoothGattWrapper gatt, - BluetoothGattCharacteristic characteristic, int status) { - mBluetoothOperationExecutor.notifyCompletion( - new Operation(OperationType.READ_CHARACTERISTIC, gatt, characteristic), - status, characteristic.getValue()); - } - - @Override - public void onCharacteristicWrite(BluetoothGattWrapper gatt, - BluetoothGattCharacteristic characteristic, int status) { - mBluetoothOperationExecutor.notifyCompletion(new Operation( - OperationType.WRITE_CHARACTERISTIC, gatt, characteristic), status); - } - - @Override - public void onDescriptorRead(BluetoothGattWrapper gatt, BluetoothGattDescriptor descriptor, - int status) { - mBluetoothOperationExecutor.notifyCompletion( - new Operation(OperationType.READ_DESCRIPTOR, gatt, descriptor), status, - descriptor.getValue()); - } - - @Override - public void onDescriptorWrite(BluetoothGattWrapper gatt, BluetoothGattDescriptor descriptor, - int status) { - Log.d(TAG, String.format("onDescriptorWrite %s, %s, %d", - gatt.getDevice(), descriptor.getUuid(), status)); - mBluetoothOperationExecutor.notifyCompletion( - new Operation(OperationType.WRITE_DESCRIPTOR, gatt, descriptor), status); - } - - @Override - public void onReadRemoteRssi(BluetoothGattWrapper gatt, int rssi, int status) { - mBluetoothOperationExecutor.notifyCompletion( - new Operation(OperationType.READ_RSSI, gatt), status, rssi); - } - - @Override - public void onReliableWriteCompleted(BluetoothGattWrapper gatt, int status) { - mBluetoothOperationExecutor.notifyCompletion( - new Operation(OperationType.WRITE_RELIABLE, gatt), status); - } - - @Override - public void onCharacteristicChanged(BluetoothGattWrapper gatt, - BluetoothGattCharacteristic characteristic) { - byte[] value = characteristic.getValue(); - if (value == null) { - // Value is not supposed to be null, but just to be safe... - value = new byte[0]; - } - Log.d(TAG, String.format("Characteristic %s changed, Gatt device: %s", - characteristic.getUuid(), gatt.getDevice())); - try { - getConnectionByGatt(gatt).onCharacteristicChanged(characteristic, value); - } catch (BluetoothException e) { - Log.e(TAG, "Error in onCharacteristicChanged", e); - } - } - } - - private class InternalScanCallback extends ScanCallback { - - @Override - public void onScanFailed(int errorCode) { - String errorMessage; - switch (errorCode) { - case ScanCallback.SCAN_FAILED_ALREADY_STARTED: - errorMessage = "SCAN_FAILED_ALREADY_STARTED"; - break; - case ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED: - errorMessage = "SCAN_FAILED_APPLICATION_REGISTRATION_FAILED"; - break; - case ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED: - errorMessage = "SCAN_FAILED_FEATURE_UNSUPPORTED"; - break; - case ScanCallback.SCAN_FAILED_INTERNAL_ERROR: - errorMessage = "SCAN_FAILED_INTERNAL_ERROR"; - break; - default: - errorMessage = "Unknown error code - " + errorCode; - } - mBluetoothOperationExecutor.notifyFailure( - new Operation(OperationType.SCAN), - new BluetoothException("Scan failed: " + errorMessage)); - } - - @Override - public void onScanResult(int callbackType, ScanResult result) { - mBluetoothOperationExecutor.notifySuccess( - new Operation(OperationType.SCAN), result.getDevice()); - } - } - - /** - * Options for {@link #connect}. - */ - public static class ConnectionOptions { - - private boolean mAutoConnect; - private long mConnectionTimeoutMillis; - private Optional mConnectionPriority; - private Optional mMtu; - - private ConnectionOptions(boolean autoConnect, long connectionTimeoutMillis, - Optional connectionPriority, - Optional mtu) { - this.mAutoConnect = autoConnect; - this.mConnectionTimeoutMillis = connectionTimeoutMillis; - this.mConnectionPriority = connectionPriority; - this.mMtu = mtu; - } - - boolean autoConnect() { - return mAutoConnect; - } - - long connectionTimeoutMillis() { - return mConnectionTimeoutMillis; - } - - Optional connectionPriority() { - return mConnectionPriority; - } - - Optional mtu() { - return mMtu; - } - - @Override - public String toString() { - return "ConnectionOptions{" - + "autoConnect=" + mAutoConnect + ", " - + "connectionTimeoutMillis=" + mConnectionTimeoutMillis + ", " - + "connectionPriority=" + mConnectionPriority + ", " - + "mtu=" + mMtu - + "}"; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o instanceof ConnectionOptions) { - ConnectionOptions that = (ConnectionOptions) o; - return this.mAutoConnect == that.autoConnect() - && this.mConnectionTimeoutMillis == that.connectionTimeoutMillis() - && this.mConnectionPriority.equals(that.connectionPriority()) - && this.mMtu.equals(that.mtu()); - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(mAutoConnect, mConnectionTimeoutMillis, mConnectionPriority, mMtu); - } - - /** - * Creates a builder of ConnectionOptions. - */ - public static Builder builder() { - return new ConnectionOptions.Builder() - .setAutoConnect(false) - .setConnectionTimeoutMillis(TimeUnit.SECONDS.toMillis(5)); - } - - /** - * Builder for {@link ConnectionOptions}. - */ - public static class Builder { - - private boolean mAutoConnect; - private long mConnectionTimeoutMillis; - private Optional mConnectionPriority = Optional.empty(); - private Optional mMtu = Optional.empty(); - - /** - * See {@link android.bluetooth.BluetoothDevice#connectGatt}. - */ - public Builder setAutoConnect(boolean autoConnect) { - this.mAutoConnect = autoConnect; - return this; - } - - /** - * See {@link android.bluetooth.BluetoothGatt#requestConnectionPriority(int)}. - */ - public Builder setConnectionPriority(int connectionPriority) { - this.mConnectionPriority = Optional.of(connectionPriority); - return this; - } - - /** - * See {@link android.bluetooth.BluetoothGatt#requestMtu(int)}. - */ - public Builder setMtu(int mtu) { - this.mMtu = Optional.of(mtu); - return this; - } - - /** - * Sets the timeout for the GATT connection. - */ - public Builder setConnectionTimeoutMillis(long connectionTimeoutMillis) { - this.mConnectionTimeoutMillis = connectionTimeoutMillis; - return this; - } - - /** - * Builds ConnectionOptions. - */ - public ConnectionOptions build() { - return new ConnectionOptions(mAutoConnect, mConnectionTimeoutMillis, - mConnectionPriority, mMtu); - } - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/Testability.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/Testability.java deleted file mode 100644 index 6cfdd784a490e12edcf66f12629953374f1ebf78..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/Testability.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.testability; - -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter; -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice; - -import javax.annotation.Nullable; - -/** Util class to convert from or to testable classes. */ -public class Testability { - /** Wraps a Bluetooth device. */ - public static BluetoothDevice wrap(android.bluetooth.BluetoothDevice bluetoothDevice) { - return BluetoothDevice.wrap(bluetoothDevice); - } - - /** Wraps a Bluetooth adapter. */ - @Nullable - public static BluetoothAdapter wrap( - @Nullable android.bluetooth.BluetoothAdapter bluetoothAdapter) { - return BluetoothAdapter.wrap(bluetoothAdapter); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothAdapter.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothAdapter.java deleted file mode 100644 index afa2a1bc031b444aec4070ce41ba6eaa7191cacb..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothAdapter.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.testability.android.bluetooth; - -import android.annotation.TargetApi; -import android.os.Build; - -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.BluetoothLeAdvertiser; -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.BluetoothLeScanner; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import javax.annotation.Nullable; - -/** - * Mockable wrapper of {@link android.bluetooth.BluetoothAdapter}. - */ -public class BluetoothAdapter { - /** See {@link android.bluetooth.BluetoothAdapter#ACTION_REQUEST_ENABLE}. */ - public static final String ACTION_REQUEST_ENABLE = - android.bluetooth.BluetoothAdapter.ACTION_REQUEST_ENABLE; - - /** See {@link android.bluetooth.BluetoothAdapter#ACTION_STATE_CHANGED}. */ - public static final String ACTION_STATE_CHANGED = - android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED; - - /** See {@link android.bluetooth.BluetoothAdapter#EXTRA_STATE}. */ - public static final String EXTRA_STATE = - android.bluetooth.BluetoothAdapter.EXTRA_STATE; - - /** See {@link android.bluetooth.BluetoothAdapter#STATE_OFF}. */ - public static final int STATE_OFF = - android.bluetooth.BluetoothAdapter.STATE_OFF; - - /** See {@link android.bluetooth.BluetoothAdapter#STATE_ON}. */ - public static final int STATE_ON = - android.bluetooth.BluetoothAdapter.STATE_ON; - - /** See {@link android.bluetooth.BluetoothAdapter#STATE_TURNING_OFF}. */ - public static final int STATE_TURNING_OFF = - android.bluetooth.BluetoothAdapter.STATE_TURNING_OFF; - - /** See {@link android.bluetooth.BluetoothAdapter#STATE_TURNING_ON}. */ - public static final int STATE_TURNING_ON = - android.bluetooth.BluetoothAdapter.STATE_TURNING_ON; - - private final android.bluetooth.BluetoothAdapter mWrappedBluetoothAdapter; - - private BluetoothAdapter(android.bluetooth.BluetoothAdapter bluetoothAdapter) { - mWrappedBluetoothAdapter = bluetoothAdapter; - } - - /** See {@link android.bluetooth.BluetoothAdapter#disable()}. */ - public boolean disable() { - return mWrappedBluetoothAdapter.disable(); - } - - /** See {@link android.bluetooth.BluetoothAdapter#enable()}. */ - public boolean enable() { - return mWrappedBluetoothAdapter.enable(); - } - - /** See {@link android.bluetooth.BluetoothAdapter#getBluetoothLeScanner}. */ - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Nullable - public BluetoothLeScanner getBluetoothLeScanner() { - return BluetoothLeScanner.wrap(mWrappedBluetoothAdapter.getBluetoothLeScanner()); - } - - /** See {@link android.bluetooth.BluetoothAdapter#getBluetoothLeAdvertiser()}. */ - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Nullable - public BluetoothLeAdvertiser getBluetoothLeAdvertiser() { - return BluetoothLeAdvertiser.wrap(mWrappedBluetoothAdapter.getBluetoothLeAdvertiser()); - } - - /** See {@link android.bluetooth.BluetoothAdapter#getBondedDevices()}. */ - @Nullable - public Set getBondedDevices() { - Set bondedDevices = - mWrappedBluetoothAdapter.getBondedDevices(); - if (bondedDevices == null) { - return null; - } - Set result = new HashSet(); - for (android.bluetooth.BluetoothDevice device : bondedDevices) { - if (device == null) { - continue; - } - result.add(BluetoothDevice.wrap(device)); - } - return Collections.unmodifiableSet(result); - } - - /** See {@link android.bluetooth.BluetoothAdapter#getRemoteDevice(byte[])}. */ - public BluetoothDevice getRemoteDevice(byte[] address) { - return BluetoothDevice.wrap(mWrappedBluetoothAdapter.getRemoteDevice(address)); - } - - /** See {@link android.bluetooth.BluetoothAdapter#getRemoteDevice(String)}. */ - public BluetoothDevice getRemoteDevice(String address) { - return BluetoothDevice.wrap(mWrappedBluetoothAdapter.getRemoteDevice(address)); - } - - /** See {@link android.bluetooth.BluetoothAdapter#isEnabled()}. */ - public boolean isEnabled() { - return mWrappedBluetoothAdapter.isEnabled(); - } - - /** See {@link android.bluetooth.BluetoothAdapter#isDiscovering()}. */ - public boolean isDiscovering() { - return mWrappedBluetoothAdapter.isDiscovering(); - } - - /** See {@link android.bluetooth.BluetoothAdapter#startDiscovery()}. */ - public boolean startDiscovery() { - return mWrappedBluetoothAdapter.startDiscovery(); - } - - /** See {@link android.bluetooth.BluetoothAdapter#cancelDiscovery()}. */ - public boolean cancelDiscovery() { - return mWrappedBluetoothAdapter.cancelDiscovery(); - } - - /** See {@link android.bluetooth.BluetoothAdapter#getDefaultAdapter()}. */ - @Nullable - public static BluetoothAdapter getDefaultAdapter() { - android.bluetooth.BluetoothAdapter adapter = - android.bluetooth.BluetoothAdapter.getDefaultAdapter(); - if (adapter == null) { - return null; - } - return new BluetoothAdapter(adapter); - } - - /** Wraps a Bluetooth adapter. */ - @Nullable - public static BluetoothAdapter wrap( - @Nullable android.bluetooth.BluetoothAdapter bluetoothAdapter) { - if (bluetoothAdapter == null) { - return null; - } - return new BluetoothAdapter(bluetoothAdapter); - } - - /** Unwraps a Bluetooth adapter. */ - public android.bluetooth.BluetoothAdapter unwrap() { - return mWrappedBluetoothAdapter; - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java deleted file mode 100644 index b2002c5c3094775341e826c6af9ea90228716716..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.testability.android.bluetooth; - -import android.annotation.TargetApi; -import android.bluetooth.BluetoothClass; -import android.bluetooth.BluetoothSocket; -import android.content.Context; -import android.os.ParcelUuid; - -import java.io.IOException; -import java.util.UUID; - -import javax.annotation.Nullable; - -/** - * Mockable wrapper of {@link android.bluetooth.BluetoothDevice}. - */ -@TargetApi(18) -public class BluetoothDevice { - /** See {@link android.bluetooth.BluetoothDevice#BOND_BONDED}. */ - public static final int BOND_BONDED = android.bluetooth.BluetoothDevice.BOND_BONDED; - - /** See {@link android.bluetooth.BluetoothDevice#BOND_BONDING}. */ - public static final int BOND_BONDING = android.bluetooth.BluetoothDevice.BOND_BONDING; - - /** See {@link android.bluetooth.BluetoothDevice#BOND_NONE}. */ - public static final int BOND_NONE = android.bluetooth.BluetoothDevice.BOND_NONE; - - /** See {@link android.bluetooth.BluetoothDevice#ACTION_ACL_CONNECTED}. */ - public static final String ACTION_ACL_CONNECTED = - android.bluetooth.BluetoothDevice.ACTION_ACL_CONNECTED; - - /** See {@link android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECT_REQUESTED}. */ - public static final String ACTION_ACL_DISCONNECT_REQUESTED = - android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED; - - /** See {@link android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECTED}. */ - public static final String ACTION_ACL_DISCONNECTED = - android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECTED; - - /** See {@link android.bluetooth.BluetoothDevice#ACTION_BOND_STATE_CHANGED}. */ - public static final String ACTION_BOND_STATE_CHANGED = - android.bluetooth.BluetoothDevice.ACTION_BOND_STATE_CHANGED; - - /** See {@link android.bluetooth.BluetoothDevice#ACTION_CLASS_CHANGED}. */ - public static final String ACTION_CLASS_CHANGED = - android.bluetooth.BluetoothDevice.ACTION_CLASS_CHANGED; - - /** See {@link android.bluetooth.BluetoothDevice#ACTION_FOUND}. */ - public static final String ACTION_FOUND = android.bluetooth.BluetoothDevice.ACTION_FOUND; - - /** See {@link android.bluetooth.BluetoothDevice#ACTION_NAME_CHANGED}. */ - public static final String ACTION_NAME_CHANGED = - android.bluetooth.BluetoothDevice.ACTION_NAME_CHANGED; - - /** See {@link android.bluetooth.BluetoothDevice#ACTION_PAIRING_REQUEST}. */ - // API 19 only - public static final String ACTION_PAIRING_REQUEST = - "android.bluetooth.device.action.PAIRING_REQUEST"; - - /** See {@link android.bluetooth.BluetoothDevice#ACTION_UUID}. */ - public static final String ACTION_UUID = android.bluetooth.BluetoothDevice.ACTION_UUID; - - /** See {@link android.bluetooth.BluetoothDevice#DEVICE_TYPE_CLASSIC}. */ - public static final int DEVICE_TYPE_CLASSIC = - android.bluetooth.BluetoothDevice.DEVICE_TYPE_CLASSIC; - - /** See {@link android.bluetooth.BluetoothDevice#DEVICE_TYPE_DUAL}. */ - public static final int DEVICE_TYPE_DUAL = android.bluetooth.BluetoothDevice.DEVICE_TYPE_DUAL; - - /** See {@link android.bluetooth.BluetoothDevice#DEVICE_TYPE_LE}. */ - public static final int DEVICE_TYPE_LE = android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE; - - /** See {@link android.bluetooth.BluetoothDevice#DEVICE_TYPE_UNKNOWN}. */ - public static final int DEVICE_TYPE_UNKNOWN = - android.bluetooth.BluetoothDevice.DEVICE_TYPE_UNKNOWN; - - /** See {@link android.bluetooth.BluetoothDevice#ERROR}. */ - public static final int ERROR = android.bluetooth.BluetoothDevice.ERROR; - - /** See {@link android.bluetooth.BluetoothDevice#EXTRA_BOND_STATE}. */ - public static final String EXTRA_BOND_STATE = - android.bluetooth.BluetoothDevice.EXTRA_BOND_STATE; - - /** See {@link android.bluetooth.BluetoothDevice#EXTRA_CLASS}. */ - public static final String EXTRA_CLASS = android.bluetooth.BluetoothDevice.EXTRA_CLASS; - - /** See {@link android.bluetooth.BluetoothDevice#EXTRA_DEVICE}. */ - public static final String EXTRA_DEVICE = android.bluetooth.BluetoothDevice.EXTRA_DEVICE; - - /** See {@link android.bluetooth.BluetoothDevice#EXTRA_NAME}. */ - public static final String EXTRA_NAME = android.bluetooth.BluetoothDevice.EXTRA_NAME; - - /** See {@link android.bluetooth.BluetoothDevice#EXTRA_PAIRING_KEY}. */ - // API 19 only - public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY"; - - /** See {@link android.bluetooth.BluetoothDevice#EXTRA_PAIRING_VARIANT}. */ - // API 19 only - public static final String EXTRA_PAIRING_VARIANT = - "android.bluetooth.device.extra.PAIRING_VARIANT"; - - /** See {@link android.bluetooth.BluetoothDevice#EXTRA_PREVIOUS_BOND_STATE}. */ - public static final String EXTRA_PREVIOUS_BOND_STATE = - android.bluetooth.BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE; - - /** See {@link android.bluetooth.BluetoothDevice#EXTRA_RSSI}. */ - public static final String EXTRA_RSSI = android.bluetooth.BluetoothDevice.EXTRA_RSSI; - - /** See {@link android.bluetooth.BluetoothDevice#EXTRA_UUID}. */ - public static final String EXTRA_UUID = android.bluetooth.BluetoothDevice.EXTRA_UUID; - - /** See {@link android.bluetooth.BluetoothDevice#PAIRING_VARIANT_PASSKEY_CONFIRMATION}. */ - // API 19 only - public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2; - - /** See {@link android.bluetooth.BluetoothDevice#PAIRING_VARIANT_PIN}. */ - // API 19 only - public static final int PAIRING_VARIANT_PIN = 0; - - private final android.bluetooth.BluetoothDevice mWrappedBluetoothDevice; - - private BluetoothDevice(android.bluetooth.BluetoothDevice bluetoothDevice) { - mWrappedBluetoothDevice = bluetoothDevice; - } - - /** - * See {@link android.bluetooth.BluetoothDevice#connectGatt(Context, boolean, - * android.bluetooth.BluetoothGattCallback)}. - */ - @Nullable(/* when bt service is not available */) - public BluetoothGattWrapper connectGatt(Context context, boolean autoConnect, - BluetoothGattCallback callback) { - android.bluetooth.BluetoothGatt gatt = - mWrappedBluetoothDevice.connectGatt(context, autoConnect, callback.unwrap()); - if (gatt == null) { - return null; - } - return BluetoothGattWrapper.wrap(gatt); - } - - /** - * See {@link android.bluetooth.BluetoothDevice#connectGatt(Context, boolean, - * android.bluetooth.BluetoothGattCallback, int)}. - */ - @TargetApi(23) - @Nullable(/* when bt service is not available */) - public BluetoothGattWrapper connectGatt(Context context, boolean autoConnect, - BluetoothGattCallback callback, int transport) { - android.bluetooth.BluetoothGatt gatt = - mWrappedBluetoothDevice.connectGatt( - context, autoConnect, callback.unwrap(), transport); - if (gatt == null) { - return null; - } - return BluetoothGattWrapper.wrap(gatt); - } - - - /** - * See {@link android.bluetooth.BluetoothDevice#createRfcommSocketToServiceRecord(UUID)}. - */ - public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException { - return mWrappedBluetoothDevice.createRfcommSocketToServiceRecord(uuid); - } - - /** - * See - * {@link android.bluetooth.BluetoothDevice#createInsecureRfcommSocketToServiceRecord(UUID)}. - */ - public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid) throws IOException { - return mWrappedBluetoothDevice.createInsecureRfcommSocketToServiceRecord(uuid); - } - - /** See {@link android.bluetooth.BluetoothDevice#setPairingConfirmation(boolean)}. */ - public boolean setPairingConfirmation(boolean confirm) { - return mWrappedBluetoothDevice.setPairingConfirmation(confirm); - } - - /** See {@link android.bluetooth.BluetoothDevice#fetchUuidsWithSdp()}. */ - public boolean fetchUuidsWithSdp() { - return mWrappedBluetoothDevice.fetchUuidsWithSdp(); - } - - /** See {@link android.bluetooth.BluetoothDevice#createBond()}. */ - public boolean createBond() { - return mWrappedBluetoothDevice.createBond(); - } - - /** See {@link android.bluetooth.BluetoothDevice#getUuids()}. */ - @Nullable(/* on error */) - public ParcelUuid[] getUuids() { - return mWrappedBluetoothDevice.getUuids(); - } - - /** See {@link android.bluetooth.BluetoothDevice#getBondState()}. */ - public int getBondState() { - return mWrappedBluetoothDevice.getBondState(); - } - - /** See {@link android.bluetooth.BluetoothDevice#getAddress()}. */ - public String getAddress() { - return mWrappedBluetoothDevice.getAddress(); - } - - /** See {@link android.bluetooth.BluetoothDevice#getBluetoothClass()}. */ - @Nullable(/* on error */) - public BluetoothClass getBluetoothClass() { - return mWrappedBluetoothDevice.getBluetoothClass(); - } - - /** See {@link android.bluetooth.BluetoothDevice#getType()}. */ - public int getType() { - return mWrappedBluetoothDevice.getType(); - } - - /** See {@link android.bluetooth.BluetoothDevice#getName()}. */ - @Nullable(/* on error */) - public String getName() { - return mWrappedBluetoothDevice.getName(); - } - - /** See {@link android.bluetooth.BluetoothDevice#toString()}. */ - @Override - public String toString() { - return mWrappedBluetoothDevice.toString(); - } - - /** See {@link android.bluetooth.BluetoothDevice#hashCode()}. */ - @Override - public int hashCode() { - return mWrappedBluetoothDevice.hashCode(); - } - - /** See {@link android.bluetooth.BluetoothDevice#equals(Object)}. */ - @Override - public boolean equals(@Nullable Object o) { - if (o == this) { - return true; - } - if (!(o instanceof BluetoothDevice)) { - return false; - } - return mWrappedBluetoothDevice.equals(((BluetoothDevice) o).unwrap()); - } - - /** Unwraps a Bluetooth device. */ - public android.bluetooth.BluetoothDevice unwrap() { - return mWrappedBluetoothDevice; - } - - /** Wraps a Bluetooth device. */ - public static BluetoothDevice wrap(android.bluetooth.BluetoothDevice bluetoothDevice) { - return new BluetoothDevice(bluetoothDevice); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallback.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallback.java deleted file mode 100644 index d36cfa251758b2b06d38db01ff3aa95717eebe87..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallback.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.testability.android.bluetooth; - -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; - -/** - * Wrapper of {@link android.bluetooth.BluetoothGattCallback} that uses mockable objects. - */ -public abstract class BluetoothGattCallback { - - private final android.bluetooth.BluetoothGattCallback mWrappedBluetoothGattCallback = - new InternalBluetoothGattCallback(); - - /** - * See {@link android.bluetooth.BluetoothGattCallback#onConnectionStateChange( - * android.bluetooth.BluetoothGatt, int, int)} - */ - public void onConnectionStateChange(BluetoothGattWrapper gatt, int status, int newState) {} - - /** - * See {@link android.bluetooth.BluetoothGattCallback#onServicesDiscovered( - * android.bluetooth.BluetoothGatt,int)} - */ - public void onServicesDiscovered(BluetoothGattWrapper gatt, int status) {} - - /** - * See {@link android.bluetooth.BluetoothGattCallback#onCharacteristicRead( - * android.bluetooth.BluetoothGatt, BluetoothGattCharacteristic, int)} - */ - public void onCharacteristicRead(BluetoothGattWrapper gatt, BluetoothGattCharacteristic - characteristic, int status) {} - - /** - * See {@link android.bluetooth.BluetoothGattCallback#onCharacteristicWrite( - * android.bluetooth.BluetoothGatt, BluetoothGattCharacteristic, int)} - */ - public void onCharacteristicWrite(BluetoothGattWrapper gatt, - BluetoothGattCharacteristic characteristic, int status) {} - - /** - * See {@link android.bluetooth.BluetoothGattCallback#onDescriptorRead( - * android.bluetooth.BluetoothGatt, BluetoothGattDescriptor, int)} - */ - public void onDescriptorRead( - BluetoothGattWrapper gatt, BluetoothGattDescriptor descriptor, int status) {} - - /** - * See {@link android.bluetooth.BluetoothGattCallback#onDescriptorWrite( - * android.bluetooth.BluetoothGatt, BluetoothGattDescriptor, int)} - */ - public void onDescriptorWrite(BluetoothGattWrapper gatt, BluetoothGattDescriptor descriptor, - int status) {} - - /** - * See {@link android.bluetooth.BluetoothGattCallback#onReadRemoteRssi( - * android.bluetooth.BluetoothGatt, int, int)} - */ - public void onReadRemoteRssi(BluetoothGattWrapper gatt, int rssi, int status) {} - - /** - * See {@link android.bluetooth.BluetoothGattCallback#onReliableWriteCompleted( - * android.bluetooth.BluetoothGatt, int)} - */ - public void onReliableWriteCompleted(BluetoothGattWrapper gatt, int status) {} - - /** - * See - * {@link android.bluetooth.BluetoothGattCallback#onMtuChanged(android.bluetooth.BluetoothGatt, - * int, int)} - */ - public void onMtuChanged(BluetoothGattWrapper gatt, int mtu, int status) {} - - /** - * See - * {@link android.bluetooth.BluetoothGattCallback#onCharacteristicChanged( - * android.bluetooth.BluetoothGatt, BluetoothGattCharacteristic)} - */ - public void onCharacteristicChanged(BluetoothGattWrapper gatt, - BluetoothGattCharacteristic characteristic) {} - - /** Unwraps a Bluetooth Gatt callback. */ - public android.bluetooth.BluetoothGattCallback unwrap() { - return mWrappedBluetoothGattCallback; - } - - /** Forward callback to testable instance. */ - private class InternalBluetoothGattCallback extends android.bluetooth.BluetoothGattCallback { - @Override - public void onConnectionStateChange(android.bluetooth.BluetoothGatt gatt, int status, - int newState) { - BluetoothGattCallback.this.onConnectionStateChange(BluetoothGattWrapper.wrap(gatt), - status, newState); - } - - @Override - public void onServicesDiscovered(android.bluetooth.BluetoothGatt gatt, int status) { - BluetoothGattCallback.this.onServicesDiscovered(BluetoothGattWrapper.wrap(gatt), - status); - } - - @Override - public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt, - BluetoothGattCharacteristic characteristic, int status) { - BluetoothGattCallback.this.onCharacteristicRead( - BluetoothGattWrapper.wrap(gatt), characteristic, status); - } - - @Override - public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt, - BluetoothGattCharacteristic characteristic, int status) { - BluetoothGattCallback.this.onCharacteristicWrite( - BluetoothGattWrapper.wrap(gatt), characteristic, status); - } - - @Override - public void onDescriptorRead(android.bluetooth.BluetoothGatt gatt, - BluetoothGattDescriptor descriptor, int status) { - BluetoothGattCallback.this.onDescriptorRead( - BluetoothGattWrapper.wrap(gatt), descriptor, status); - } - - @Override - public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt, - BluetoothGattDescriptor descriptor, int status) { - BluetoothGattCallback.this.onDescriptorWrite( - BluetoothGattWrapper.wrap(gatt), descriptor, status); - } - - @Override - public void onReadRemoteRssi(android.bluetooth.BluetoothGatt gatt, int rssi, int status) { - BluetoothGattCallback.this.onReadRemoteRssi(BluetoothGattWrapper.wrap(gatt), rssi, - status); - } - - @Override - public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt gatt, int status) { - BluetoothGattCallback.this.onReliableWriteCompleted(BluetoothGattWrapper.wrap(gatt), - status); - } - - @Override - public void onMtuChanged(android.bluetooth.BluetoothGatt gatt, int mtu, int status) { - BluetoothGattCallback.this.onMtuChanged(BluetoothGattWrapper.wrap(gatt), mtu, status); - } - - @Override - public void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt, - BluetoothGattCharacteristic characteristic) { - BluetoothGattCallback.this.onCharacteristicChanged( - BluetoothGattWrapper.wrap(gatt), characteristic); - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServer.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServer.java deleted file mode 100644 index d4873fd3077f79ff2bf1085dcc253819e28234f6..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServer.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.testability.android.bluetooth; - -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattService; - -import java.util.UUID; - -import javax.annotation.Nullable; - -/** - * Mockable wrapper of {@link android.bluetooth.BluetoothGattServer}. - */ -public class BluetoothGattServer { - - /** See {@link android.bluetooth.BluetoothGattServer#STATE_CONNECTED}. */ - public static final int STATE_CONNECTED = android.bluetooth.BluetoothGattServer.STATE_CONNECTED; - - /** See {@link android.bluetooth.BluetoothGattServer#STATE_DISCONNECTED}. */ - public static final int STATE_DISCONNECTED = - android.bluetooth.BluetoothGattServer.STATE_DISCONNECTED; - - private android.bluetooth.BluetoothGattServer mWrappedInstance; - - private BluetoothGattServer(android.bluetooth.BluetoothGattServer instance) { - mWrappedInstance = instance; - } - - /** Wraps a Bluetooth Gatt server. */ - @Nullable - public static BluetoothGattServer wrap( - @Nullable android.bluetooth.BluetoothGattServer instance) { - if (instance == null) { - return null; - } - return new BluetoothGattServer(instance); - } - - /** Unwraps a Bluetooth Gatt server. */ - public android.bluetooth.BluetoothGattServer unwrap() { - return mWrappedInstance; - } - - /** - * See {@link android.bluetooth.BluetoothGattServer#connect( - * android.bluetooth.BluetoothDevice, boolean)} - */ - public boolean connect(BluetoothDevice device, boolean autoConnect) { - return mWrappedInstance.connect(device.unwrap(), autoConnect); - } - - /** See {@link android.bluetooth.BluetoothGattServer#addService(BluetoothGattService)}. */ - public boolean addService(BluetoothGattService service) { - return mWrappedInstance.addService(service); - } - - /** See {@link android.bluetooth.BluetoothGattServer#clearServices()}. */ - public void clearServices() { - mWrappedInstance.clearServices(); - } - - /** See {@link android.bluetooth.BluetoothGattServer#close()}. */ - public void close() { - mWrappedInstance.close(); - } - - /** - * See {@link android.bluetooth.BluetoothGattServer#notifyCharacteristicChanged( - * android.bluetooth.BluetoothDevice, BluetoothGattCharacteristic, boolean)}. - */ - public boolean notifyCharacteristicChanged(BluetoothDevice device, - BluetoothGattCharacteristic characteristic, boolean confirm) { - return mWrappedInstance.notifyCharacteristicChanged( - device.unwrap(), characteristic, confirm); - } - - /** - * See {@link android.bluetooth.BluetoothGattServer#sendResponse( - * android.bluetooth.BluetoothDevice, int, int, int, byte[])}. - */ - public void sendResponse(BluetoothDevice device, int requestId, int status, int offset, - @Nullable byte[] value) { - mWrappedInstance.sendResponse(device.unwrap(), requestId, status, offset, value); - } - - /** - * See {@link android.bluetooth.BluetoothGattServer#cancelConnection( - * android.bluetooth.BluetoothDevice)}. - */ - public void cancelConnection(BluetoothDevice device) { - mWrappedInstance.cancelConnection(device.unwrap()); - } - - /** See {@link android.bluetooth.BluetoothGattServer#getService(UUID uuid)}. */ - public BluetoothGattService getService(UUID uuid) { - return mWrappedInstance.getService(uuid); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerCallback.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerCallback.java deleted file mode 100644 index 875dad562f5986857eae7fd845467654bce73e2a..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerCallback.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.testability.android.bluetooth; - -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; -import android.bluetooth.BluetoothGattService; - -/** - * Wrapper of {@link android.bluetooth.BluetoothGattServerCallback} that uses mockable objects. - */ -public abstract class BluetoothGattServerCallback { - - private final android.bluetooth.BluetoothGattServerCallback mWrappedInstance = - new InternalBluetoothGattServerCallback(); - - /** - * See {@link android.bluetooth.BluetoothGattServerCallback#onCharacteristicReadRequest( - * android.bluetooth.BluetoothDevice, int, int, BluetoothGattCharacteristic)} - */ - public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, - int offset, BluetoothGattCharacteristic characteristic) {} - - /** - * See {@link android.bluetooth.BluetoothGattServerCallback#onCharacteristicWriteRequest( - * android.bluetooth.BluetoothDevice, int, BluetoothGattCharacteristic, boolean, boolean, int, - * byte[])} - */ - public void onCharacteristicWriteRequest(BluetoothDevice device, - int requestId, - BluetoothGattCharacteristic characteristic, - boolean preparedWrite, - boolean responseNeeded, - int offset, - byte[] value) {} - - /** - * See {@link android.bluetooth.BluetoothGattServerCallback#onConnectionStateChange( - * android.bluetooth.BluetoothDevice, int, int)} - */ - public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {} - - /** - * See {@link android.bluetooth.BluetoothGattServerCallback#onDescriptorReadRequest( - * android.bluetooth.BluetoothDevice, int, int, BluetoothGattDescriptor)} - */ - public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, - BluetoothGattDescriptor descriptor) {} - - /** - * See {@link android.bluetooth.BluetoothGattServerCallback#onDescriptorWriteRequest( - * android.bluetooth.BluetoothDevice, int, BluetoothGattDescriptor, boolean, boolean, int, - * byte[])} - */ - public void onDescriptorWriteRequest(BluetoothDevice device, - int requestId, - BluetoothGattDescriptor descriptor, - boolean preparedWrite, - boolean responseNeeded, - int offset, - byte[] value) {} - - /** - * See {@link android.bluetooth.BluetoothGattServerCallback#onExecuteWrite( - * android.bluetooth.BluetoothDevice, int, boolean)} - */ - public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {} - - /** - * See {@link android.bluetooth.BluetoothGattServerCallback#onMtuChanged( - * android.bluetooth.BluetoothDevice, int)} - */ - public void onMtuChanged(BluetoothDevice device, int mtu) {} - - /** - * See {@link android.bluetooth.BluetoothGattServerCallback#onNotificationSent( - * android.bluetooth.BluetoothDevice, int)} - */ - public void onNotificationSent(BluetoothDevice device, int status) {} - - /** - * See {@link android.bluetooth.BluetoothGattServerCallback#onServiceAdded(int, - * BluetoothGattService)} - */ - public void onServiceAdded(int status, BluetoothGattService service) {} - - /** Unwraps a Bluetooth Gatt server callback. */ - public android.bluetooth.BluetoothGattServerCallback unwrap() { - return mWrappedInstance; - } - - /** Forward callback to testable instance. */ - private class InternalBluetoothGattServerCallback extends - android.bluetooth.BluetoothGattServerCallback { - @Override - public void onCharacteristicReadRequest(android.bluetooth.BluetoothDevice device, - int requestId, int offset, BluetoothGattCharacteristic characteristic) { - BluetoothGattServerCallback.this.onCharacteristicReadRequest( - BluetoothDevice.wrap(device), requestId, offset, characteristic); - } - - @Override - public void onCharacteristicWriteRequest(android.bluetooth.BluetoothDevice device, - int requestId, - BluetoothGattCharacteristic characteristic, - boolean preparedWrite, - boolean responseNeeded, - int offset, - byte[] value) { - BluetoothGattServerCallback.this.onCharacteristicWriteRequest( - BluetoothDevice.wrap(device), - requestId, - characteristic, - preparedWrite, - responseNeeded, - offset, - value); - } - - @Override - public void onConnectionStateChange(android.bluetooth.BluetoothDevice device, int status, - int newState) { - BluetoothGattServerCallback.this.onConnectionStateChange( - BluetoothDevice.wrap(device), status, newState); - } - - @Override - public void onDescriptorReadRequest(android.bluetooth.BluetoothDevice device, int requestId, - int offset, BluetoothGattDescriptor descriptor) { - BluetoothGattServerCallback.this.onDescriptorReadRequest(BluetoothDevice.wrap(device), - requestId, offset, descriptor); - } - - @Override - public void onDescriptorWriteRequest(android.bluetooth.BluetoothDevice device, - int requestId, - BluetoothGattDescriptor descriptor, - boolean preparedWrite, - boolean responseNeeded, - int offset, - byte[] value) { - BluetoothGattServerCallback.this.onDescriptorWriteRequest(BluetoothDevice.wrap(device), - requestId, - descriptor, - preparedWrite, - responseNeeded, - offset, - value); - } - - @Override - public void onExecuteWrite(android.bluetooth.BluetoothDevice device, int requestId, - boolean execute) { - BluetoothGattServerCallback.this.onExecuteWrite(BluetoothDevice.wrap(device), requestId, - execute); - } - - @Override - public void onMtuChanged(android.bluetooth.BluetoothDevice device, int mtu) { - BluetoothGattServerCallback.this.onMtuChanged(BluetoothDevice.wrap(device), mtu); - } - - @Override - public void onNotificationSent(android.bluetooth.BluetoothDevice device, int status) { - BluetoothGattServerCallback.this.onNotificationSent( - BluetoothDevice.wrap(device), status); - } - - @Override - public void onServiceAdded(int status, BluetoothGattService service) { - BluetoothGattServerCallback.this.onServiceAdded(status, service); - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattWrapper.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattWrapper.java deleted file mode 100644 index 453ee5d694bdb8629bd501d61c5b1b3704caee24..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattWrapper.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.testability.android.bluetooth; - -import android.annotation.TargetApi; -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; -import android.bluetooth.BluetoothGattService; -import android.os.Build; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.List; -import java.util.UUID; - -import javax.annotation.Nullable; - -/** Mockable wrapper of {@link android.bluetooth.BluetoothGatt}. */ -@TargetApi(Build.VERSION_CODES.TIRAMISU) -public class BluetoothGattWrapper { - private final android.bluetooth.BluetoothGatt mWrappedBluetoothGatt; - - private BluetoothGattWrapper(android.bluetooth.BluetoothGatt bluetoothGatt) { - mWrappedBluetoothGatt = bluetoothGatt; - } - - /** See {@link android.bluetooth.BluetoothGatt#getDevice()}. */ - public BluetoothDevice getDevice() { - return BluetoothDevice.wrap(mWrappedBluetoothGatt.getDevice()); - } - - /** See {@link android.bluetooth.BluetoothGatt#getServices()}. */ - public List getServices() { - return mWrappedBluetoothGatt.getServices(); - } - - /** See {@link android.bluetooth.BluetoothGatt#getService(UUID)}. */ - @Nullable(/* null if service is not found */) - public BluetoothGattService getService(UUID uuid) { - return mWrappedBluetoothGatt.getService(uuid); - } - - /** See {@link android.bluetooth.BluetoothGatt#discoverServices()}. */ - public boolean discoverServices() { - return mWrappedBluetoothGatt.discoverServices(); - } - - /** - * Hidden method. Clears the internal cache and forces a refresh of the services from the remote - * device. - */ - // TODO(b/201300471): remove refresh call using reflection. - public boolean refresh() { - try { - Method refreshMethod = android.bluetooth.BluetoothGatt.class.getMethod("refresh"); - return (Boolean) refreshMethod.invoke(mWrappedBluetoothGatt); - } catch (NoSuchMethodException - | IllegalAccessException - | IllegalArgumentException - | InvocationTargetException e) { - return false; - } - } - - /** - * See {@link android.bluetooth.BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)}. - */ - public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) { - return mWrappedBluetoothGatt.readCharacteristic(characteristic); - } - - /** - * See {@link android.bluetooth.BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, - * byte[], int)} . - */ - public int writeCharacteristic(BluetoothGattCharacteristic characteristic, byte[] value, - int writeType) { - return mWrappedBluetoothGatt.writeCharacteristic(characteristic, value, writeType); - } - - /** See {@link android.bluetooth.BluetoothGatt#readDescriptor(BluetoothGattDescriptor)}. */ - public boolean readDescriptor(BluetoothGattDescriptor descriptor) { - return mWrappedBluetoothGatt.readDescriptor(descriptor); - } - - /** - * See {@link android.bluetooth.BluetoothGatt#writeDescriptor(BluetoothGattDescriptor, - * byte[])}. - */ - public int writeDescriptor(BluetoothGattDescriptor descriptor, byte[] value) { - return mWrappedBluetoothGatt.writeDescriptor(descriptor, value); - } - - /** See {@link android.bluetooth.BluetoothGatt#readRemoteRssi()}. */ - public boolean readRemoteRssi() { - return mWrappedBluetoothGatt.readRemoteRssi(); - } - - /** See {@link android.bluetooth.BluetoothGatt#requestConnectionPriority(int)}. */ - public boolean requestConnectionPriority(int connectionPriority) { - return mWrappedBluetoothGatt.requestConnectionPriority(connectionPriority); - } - - /** See {@link android.bluetooth.BluetoothGatt#requestMtu(int)}. */ - public boolean requestMtu(int mtu) { - return mWrappedBluetoothGatt.requestMtu(mtu); - } - - /** See {@link android.bluetooth.BluetoothGatt#setCharacteristicNotification}. */ - public boolean setCharacteristicNotification( - BluetoothGattCharacteristic characteristic, boolean enable) { - return mWrappedBluetoothGatt.setCharacteristicNotification(characteristic, enable); - } - - /** See {@link android.bluetooth.BluetoothGatt#disconnect()}. */ - public void disconnect() { - mWrappedBluetoothGatt.disconnect(); - } - - /** See {@link android.bluetooth.BluetoothGatt#close()}. */ - public void close() { - mWrappedBluetoothGatt.close(); - } - - /** See {@link android.bluetooth.BluetoothGatt#hashCode()}. */ - @Override - public int hashCode() { - return mWrappedBluetoothGatt.hashCode(); - } - - /** See {@link android.bluetooth.BluetoothGatt#equals(Object)}. */ - @Override - public boolean equals(@Nullable Object o) { - if (o == this) { - return true; - } - if (!(o instanceof BluetoothGattWrapper)) { - return false; - } - return mWrappedBluetoothGatt.equals(((BluetoothGattWrapper) o).unwrap()); - } - - /** Unwraps a Bluetooth Gatt instance. */ - public android.bluetooth.BluetoothGatt unwrap() { - return mWrappedBluetoothGatt; - } - - /** Wraps a Bluetooth Gatt instance. */ - public static BluetoothGattWrapper wrap(android.bluetooth.BluetoothGatt gatt) { - return new BluetoothGattWrapper(gatt); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeAdvertiser.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeAdvertiser.java deleted file mode 100644 index b2c61ab3137739bc2274a3c1b644cd4da0895473..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeAdvertiser.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le; - -import android.annotation.TargetApi; -import android.bluetooth.le.AdvertiseCallback; -import android.bluetooth.le.AdvertiseData; -import android.bluetooth.le.AdvertiseSettings; -import android.os.Build; - -import javax.annotation.Nullable; - -/** - * Mockable wrapper of {@link android.bluetooth.le.BluetoothLeAdvertiser}. - */ -@TargetApi(Build.VERSION_CODES.LOLLIPOP) -public class BluetoothLeAdvertiser { - - private final android.bluetooth.le.BluetoothLeAdvertiser mWrappedInstance; - - private BluetoothLeAdvertiser( - android.bluetooth.le.BluetoothLeAdvertiser bluetoothLeAdvertiser) { - mWrappedInstance = bluetoothLeAdvertiser; - } - - /** - * See {@link android.bluetooth.le.BluetoothLeAdvertiser#startAdvertising(AdvertiseSettings, - * AdvertiseData, AdvertiseCallback)}. - */ - public void startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData, - AdvertiseCallback callback) { - mWrappedInstance.startAdvertising(settings, advertiseData, callback); - } - - /** - * See {@link android.bluetooth.le.BluetoothLeAdvertiser#startAdvertising(AdvertiseSettings, - * AdvertiseData, AdvertiseData, AdvertiseCallback)}. - */ - public void startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData, - AdvertiseData scanResponse, AdvertiseCallback callback) { - mWrappedInstance.startAdvertising(settings, advertiseData, scanResponse, callback); - } - - /** - * See {@link android.bluetooth.le.BluetoothLeAdvertiser#stopAdvertising(AdvertiseCallback)}. - */ - public void stopAdvertising(AdvertiseCallback callback) { - mWrappedInstance.stopAdvertising(callback); - } - - /** Wraps a Bluetooth LE advertiser. */ - @Nullable - public static BluetoothLeAdvertiser wrap( - @Nullable android.bluetooth.le.BluetoothLeAdvertiser bluetoothLeAdvertiser) { - if (bluetoothLeAdvertiser == null) { - return null; - } - return new BluetoothLeAdvertiser(bluetoothLeAdvertiser); - } - - /** Unwraps a Bluetooth LE advertiser. */ - @Nullable - public android.bluetooth.le.BluetoothLeAdvertiser unwrap() { - return mWrappedInstance; - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScanner.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScanner.java deleted file mode 100644 index 9b3447e557e4e4694f2415332ad3766489cdbcc0..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScanner.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le; - -import android.annotation.TargetApi; -import android.app.PendingIntent; -import android.bluetooth.le.ScanFilter; -import android.bluetooth.le.ScanSettings; -import android.os.Build; - -import java.util.List; - -import javax.annotation.Nullable; - -/** - * Mockable wrapper of {@link android.bluetooth.le.BluetoothLeScanner}. - */ -@TargetApi(Build.VERSION_CODES.LOLLIPOP) -public class BluetoothLeScanner { - - private final android.bluetooth.le.BluetoothLeScanner mWrappedBluetoothLeScanner; - - private BluetoothLeScanner(android.bluetooth.le.BluetoothLeScanner bluetoothLeScanner) { - mWrappedBluetoothLeScanner = bluetoothLeScanner; - } - - /** - * See {@link android.bluetooth.le.BluetoothLeScanner#startScan(List, ScanSettings, - * android.bluetooth.le.ScanCallback)}. - */ - public void startScan(List filters, ScanSettings settings, - ScanCallback callback) { - mWrappedBluetoothLeScanner.startScan(filters, settings, callback.unwrap()); - } - - /** - * See {@link android.bluetooth.le.BluetoothLeScanner#startScan(List, ScanSettings, - * PendingIntent)}. - */ - public void startScan( - List filters, ScanSettings settings, PendingIntent callbackIntent) { - mWrappedBluetoothLeScanner.startScan(filters, settings, callbackIntent); - } - - /** - * See {@link - * android.bluetooth.le.BluetoothLeScanner#startScan(android.bluetooth.le.ScanCallback)}. - */ - public void startScan(ScanCallback callback) { - mWrappedBluetoothLeScanner.startScan(callback.unwrap()); - } - - /** - * See - * {@link android.bluetooth.le.BluetoothLeScanner#stopScan(android.bluetooth.le.ScanCallback)}. - */ - public void stopScan(ScanCallback callback) { - mWrappedBluetoothLeScanner.stopScan(callback.unwrap()); - } - - /** See {@link android.bluetooth.le.BluetoothLeScanner#stopScan(PendingIntent)}. */ - public void stopScan(PendingIntent callbackIntent) { - mWrappedBluetoothLeScanner.stopScan(callbackIntent); - } - - /** Unwraps a Bluetooth LE scanner. */ - @Nullable - public android.bluetooth.le.BluetoothLeScanner unwrap() { - return mWrappedBluetoothLeScanner; - } - - /** Wraps a Bluetooth LE scanner. */ - @Nullable - public static BluetoothLeScanner wrap( - @Nullable android.bluetooth.le.BluetoothLeScanner bluetoothLeScanner) { - if (bluetoothLeScanner == null) { - return null; - } - return new BluetoothLeScanner(bluetoothLeScanner); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanCallback.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanCallback.java deleted file mode 100644 index 70926a77191481a665b34b89dc28a8103bffcbdf..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanCallback.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le; - -import android.annotation.TargetApi; -import android.os.Build; - -import java.util.ArrayList; -import java.util.List; - -/** - * Wrapper of {@link android.bluetooth.le.ScanCallback} that uses mockable objects. - */ -@TargetApi(Build.VERSION_CODES.LOLLIPOP) -public abstract class ScanCallback { - - /** See {@link android.bluetooth.le.ScanCallback#SCAN_FAILED_ALREADY_STARTED} */ - public static final int SCAN_FAILED_ALREADY_STARTED = - android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED; - - /** See {@link android.bluetooth.le.ScanCallback#SCAN_FAILED_APPLICATION_REGISTRATION_FAILED} */ - public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = - android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED; - - /** See {@link android.bluetooth.le.ScanCallback#SCAN_FAILED_FEATURE_UNSUPPORTED} */ - public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = - android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED; - - /** See {@link android.bluetooth.le.ScanCallback#SCAN_FAILED_INTERNAL_ERROR} */ - public static final int SCAN_FAILED_INTERNAL_ERROR = - android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR; - - private final android.bluetooth.le.ScanCallback mWrappedScanCallback = - new InternalScanCallback(); - - /** - * See {@link android.bluetooth.le.ScanCallback#onScanFailed(int)} - */ - public void onScanFailed(int errorCode) {} - - /** - * See - * {@link android.bluetooth.le.ScanCallback#onScanResult(int, android.bluetooth.le.ScanResult)}. - */ - public void onScanResult(int callbackType, ScanResult result) {} - - /** - * See {@link - * android.bluetooth.le.ScanCallback#onBatchScanResult(List)}. - */ - public void onBatchScanResults(List results) {} - - /** Unwraps scan callback. */ - public android.bluetooth.le.ScanCallback unwrap() { - return mWrappedScanCallback; - } - - /** Forward callback to testable instance. */ - private class InternalScanCallback extends android.bluetooth.le.ScanCallback { - @Override - public void onScanFailed(int errorCode) { - ScanCallback.this.onScanFailed(errorCode); - } - - @Override - public void onScanResult(int callbackType, android.bluetooth.le.ScanResult result) { - ScanCallback.this.onScanResult(callbackType, ScanResult.wrap(result)); - } - - @Override - public void onBatchScanResults(List results) { - List wrappedScanResults = new ArrayList<>(); - for (android.bluetooth.le.ScanResult result : results) { - wrappedScanResults.add(ScanResult.wrap(result)); - } - ScanCallback.this.onBatchScanResults(wrappedScanResults); - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanResult.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanResult.java deleted file mode 100644 index 1a6b7b304b6f72a1bbb7720701945ea8fae755d1..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanResult.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le; - -import android.annotation.TargetApi; -import android.bluetooth.le.ScanRecord; -import android.os.Build; - -import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice; - -import javax.annotation.Nullable; - -/** - * Mockable wrapper of {@link android.bluetooth.le.ScanResult}. - */ -@TargetApi(Build.VERSION_CODES.LOLLIPOP) -public class ScanResult { - - private final android.bluetooth.le.ScanResult mWrappedScanResult; - - private ScanResult(android.bluetooth.le.ScanResult scanResult) { - mWrappedScanResult = scanResult; - } - - /** See {@link android.bluetooth.le.ScanResult#getScanRecord()}. */ - @Nullable - public ScanRecord getScanRecord() { - return mWrappedScanResult.getScanRecord(); - } - - /** See {@link android.bluetooth.le.ScanResult#getRssi()}. */ - public int getRssi() { - return mWrappedScanResult.getRssi(); - } - - /** See {@link android.bluetooth.le.ScanResult#getTimestampNanos()}. */ - public long getTimestampNanos() { - return mWrappedScanResult.getTimestampNanos(); - } - - /** See {@link android.bluetooth.le.ScanResult#getDevice()}. */ - public BluetoothDevice getDevice() { - return BluetoothDevice.wrap(mWrappedScanResult.getDevice()); - } - - /** Creates a wrapper of scan result. */ - public static ScanResult wrap(android.bluetooth.le.ScanResult scanResult) { - return new ScanResult(scanResult); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtils.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtils.java deleted file mode 100644 index bb51920f18533fdda8c65e92774cb5c84f26a981..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtils.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.util; - -import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattCharacteristic; -import android.bluetooth.BluetoothGattDescriptor; -import android.bluetooth.BluetoothGattService; - -import javax.annotation.Nullable; - -/** - * Utils for Gatt profile. - */ -public class BluetoothGattUtils { - - /** - * Returns a string message for a BluetoothGatt status codes. - */ - public static String getMessageForStatusCode(int statusCode) { - switch (statusCode) { - case BluetoothGatt.GATT_SUCCESS: - return "GATT_SUCCESS"; - case BluetoothGatt.GATT_FAILURE: - return "GATT_FAILURE"; - case BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION: - return "GATT_INSUFFICIENT_AUTHENTICATION"; - case BluetoothGatt.GATT_INSUFFICIENT_AUTHORIZATION: - return "GATT_INSUFFICIENT_AUTHORIZATION"; - case BluetoothGatt.GATT_INSUFFICIENT_ENCRYPTION: - return "GATT_INSUFFICIENT_ENCRYPTION"; - case BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH: - return "GATT_INVALID_ATTRIBUTE_LENGTH"; - case BluetoothGatt.GATT_INVALID_OFFSET: - return "GATT_INVALID_OFFSET"; - case BluetoothGatt.GATT_READ_NOT_PERMITTED: - return "GATT_READ_NOT_PERMITTED"; - case BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED: - return "GATT_REQUEST_NOT_SUPPORTED"; - case BluetoothGatt.GATT_WRITE_NOT_PERMITTED: - return "GATT_WRITE_NOT_PERMITTED"; - case BluetoothGatt.GATT_CONNECTION_CONGESTED: - return "GATT_CONNECTION_CONGESTED"; - default: - return "Unknown error code"; - } - } - - /** Creates a user-readable string from a {@link BluetoothGattDescriptor}. */ - public static String toString(@Nullable BluetoothGattDescriptor descriptor) { - if (descriptor == null) { - return "null descriptor"; - } - return String.format("descriptor %s on %s", - descriptor.getUuid(), - toString(descriptor.getCharacteristic())); - } - - /** Creates a user-readable string from a {@link BluetoothGattCharacteristic}. */ - public static String toString(@Nullable BluetoothGattCharacteristic characteristic) { - if (characteristic == null) { - return "null characteristic"; - } - return String.format("characteristic %s on %s", - characteristic.getUuid(), - toString(characteristic.getService())); - } - - /** Creates a user-readable string from a {@link BluetoothGattService}. */ - public static String toString(@Nullable BluetoothGattService service) { - if (service == null) { - return "null service"; - } - return String.format("service %s", service.getUuid()); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/util/BluetoothOperationExecutor.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/util/BluetoothOperationExecutor.java deleted file mode 100644 index fecf48362425a3375e52d67b043d61eb7bb55666..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/bluetooth/util/BluetoothOperationExecutor.java +++ /dev/null @@ -1,548 +0,0 @@ -/* - * 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 com.android.server.nearby.common.bluetooth.util; - -import android.bluetooth.BluetoothGatt; -import android.util.Log; - -import com.android.server.nearby.common.bluetooth.BluetoothException; -import com.android.server.nearby.common.bluetooth.BluetoothGattException; -import com.android.server.nearby.common.bluetooth.testability.NonnullProvider; -import com.android.server.nearby.common.bluetooth.testability.TimeProvider; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Joiner; -import com.google.common.base.Objects; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Queue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import javax.annotation.Nullable; - -/** - * Scheduler to coordinate parallel bluetooth operations. - */ -public class BluetoothOperationExecutor { - - private static final String TAG = BluetoothOperationExecutor.class.getSimpleName(); - - /** - * Special value to indicate that the result is null (since {@link BlockingQueue} doesn't allow - * null elements). - */ - private static final Object NULL_RESULT = new Object(); - - /** - * Special value to indicate that there should be no timeout on the operation. - */ - private static final long NO_TIMEOUT = -1; - - private final NonnullProvider> mBlockingQueueProvider; - private final TimeProvider mTimeProvider; - @VisibleForTesting - final Map, Queue> mOperationResultQueues = new HashMap<>(); - private final Semaphore mOperationSemaphore; - - /** - * New instance that limits concurrent operations to maxConcurrentOperations. - */ - public BluetoothOperationExecutor(int maxConcurrentOperations) { - this( - new Semaphore(maxConcurrentOperations, true), - new TimeProvider(), - new NonnullProvider>() { - @Override - public BlockingQueue get() { - return new LinkedBlockingDeque(); - } - }); - } - - /** - * Constructor for unit tests. - */ - @VisibleForTesting - BluetoothOperationExecutor(Semaphore operationSemaphore, - TimeProvider timeProvider, - NonnullProvider> blockingQueueProvider) { - mOperationSemaphore = operationSemaphore; - mTimeProvider = timeProvider; - mBlockingQueueProvider = blockingQueueProvider; - } - - /** - * Executes the operation and waits for its completion. - */ - @Nullable - public T execute(Operation operation) throws BluetoothException { - return getResult(schedule(operation)); - } - - /** - * Executes the operation and waits for its completion and returns a non-null result. - */ - public T executeNonnull(Operation operation) throws BluetoothException { - T result = getResult(schedule(operation)); - if (result == null) { - throw new BluetoothException( - String.format(Locale.US, "Operation %s returned a null result.", operation)); - } - return result; - } - - /** - * Executes the operation and waits for its completion with a timeout. - */ - @Nullable - public T execute(Operation bluetoothOperation, long timeoutMillis) - throws BluetoothException, BluetoothOperationTimeoutException { - return getResult(schedule(bluetoothOperation), timeoutMillis); - } - - /** - * Executes the operation and waits for its completion with a timeout and returns a non-null - * result. - */ - public T executeNonnull(Operation bluetoothOperation, long timeoutMillis) - throws BluetoothException { - T result = getResult(schedule(bluetoothOperation), timeoutMillis); - if (result == null) { - throw new BluetoothException( - String.format(Locale.US, "Operation %s returned a null result.", - bluetoothOperation)); - } - return result; - } - - /** - * Schedules an operation and returns a {@link Future} that waits on operation completion and - * gets its result. - */ - public Future schedule(Operation bluetoothOperation) { - BlockingQueue resultQueue = mBlockingQueueProvider.get(); - mOperationResultQueues.put(bluetoothOperation, resultQueue); - - boolean semaphoreAcquired = mOperationSemaphore.tryAcquire(); - Log.d(TAG, String.format(Locale.US, - "Scheduling operation %s; %d permits available; Semaphore acquired: %b", - bluetoothOperation, - mOperationSemaphore.availablePermits(), - semaphoreAcquired)); - - if (semaphoreAcquired) { - bluetoothOperation.execute(this); - } - return new BluetoothOperationFuture(resultQueue, bluetoothOperation, semaphoreAcquired); - } - - /** - * Notifies that this operation has completed with success. - */ - public void notifySuccess(Operation bluetoothOperation) { - postResult(bluetoothOperation, null); - } - - /** - * Notifies that this operation has completed with success and with a result. - */ - public void notifySuccess(Operation bluetoothOperation, T result) { - postResult(bluetoothOperation, result); - } - - /** - * Notifies that this operation has completed with the given BluetoothGatt status code (which - * may indicate success or failure). - */ - public void notifyCompletion(Operation bluetoothOperation, int status) { - notifyCompletion(bluetoothOperation, status, null); - } - - /** - * Notifies that this operation has completed with the given BluetoothGatt status code (which - * may indicate success or failure) and with a result. - */ - public void notifyCompletion(Operation bluetoothOperation, int status, - @Nullable T result) { - if (status != BluetoothGatt.GATT_SUCCESS) { - notifyFailure(bluetoothOperation, new BluetoothGattException( - String.format(Locale.US, - "Operation %s failed: %d - %s.", bluetoothOperation, status, - BluetoothGattUtils.getMessageForStatusCode(status)), - status)); - return; - } - postResult(bluetoothOperation, result); - } - - /** - * Notifies that this operation has completed with failure. - */ - public void notifyFailure(Operation bluetoothOperation, BluetoothException exception) { - postResult(bluetoothOperation, exception); - } - - private void postResult(Operation bluetoothOperation, @Nullable Object result) { - Queue resultQueue = mOperationResultQueues.get(bluetoothOperation); - if (resultQueue == null) { - Log.e(TAG, String.format(Locale.US, - "Receive completion for unexpected operation: %s.", bluetoothOperation)); - return; - } - resultQueue.add(result == null ? NULL_RESULT : result); - mOperationResultQueues.remove(bluetoothOperation); - mOperationSemaphore.release(); - Log.d(TAG, String.format(Locale.US, - "Released semaphore for operation %s. There are %d permits left", - bluetoothOperation, mOperationSemaphore.availablePermits())); - } - - /** - * Waits for all future on the list to complete, ignoring the results. - */ - public void waitFor(List> futures) throws BluetoothException { - for (Future future : futures) { - if (future == null) { - continue; - } - getResult(future); - } - } - - /** - * Waits with timeout for all future on the list to complete, ignoring the results. - */ - public void waitFor(List> futures, long timeoutMillis) - throws BluetoothException { - long startTime = mTimeProvider.getTimeMillis(); - for (Future future : futures) { - if (future == null) { - continue; - } - getResult(future, - timeoutMillis - (mTimeProvider.getTimeMillis() - startTime)); - } - } - - /** - * Waits for a future to complete and returns the result. - */ - @Nullable - public static T getResult(Future future) throws BluetoothException { - return getResultInternal(future, NO_TIMEOUT); - } - - /** - * Waits for a future to complete and returns the result with timeout. - */ - @Nullable - public static T getResult(Future future, long timeoutMillis) throws BluetoothException { - return getResultInternal(future, Math.max(0, timeoutMillis)); - } - - @Nullable - private static T getResultInternal(Future future, long timeoutMillis) - throws BluetoothException { - try { - if (timeoutMillis == NO_TIMEOUT) { - return future.get(); - } else { - return future.get(timeoutMillis, TimeUnit.MILLISECONDS); - } - } catch (InterruptedException e) { - try { - boolean cancelSuccess = future.cancel(true); - if (!cancelSuccess && future.isDone()) { - // Operation has succeeded before we send cancel to it. - return getResultInternal(future, NO_TIMEOUT); - } - } finally { - // Re-interrupt the thread last since we're recursively calling getResultInternal. - // We know the future is done, so there's no need to be interrupted while we call. - Thread.currentThread().interrupt(); - } - throw new BluetoothException("Wait interrupted"); - } catch (ExecutionException e) { - Throwable cause = e.getCause(); - if (cause instanceof BluetoothException) { - throw (BluetoothException) cause; - } - throw new RuntimeException(e); - } catch (TimeoutException e) { - boolean cancelSuccess = future.cancel(true); - if (!cancelSuccess && future.isDone()) { - // Operation has succeeded before we send cancel to it. - return getResultInternal(future, NO_TIMEOUT); - } - throw new BluetoothOperationTimeoutException( - String.format(Locale.US, "Wait timed out after %s ms.", timeoutMillis), e); - } - } - - /** - * Asynchronous bluetooth operation to schedule. - * - *

    An instance that doesn't implemented run() can be used to notify operation result. - * - * @param Type of provided instance. - */ - public static class Operation { - - private Object[] mElements; - - public Operation(Object... elements) { - mElements = elements; - } - - /** - * Executes operation using executor. - */ - public void execute(BluetoothOperationExecutor executor) { - try { - run(); - } catch (BluetoothException e) { - executor.postResult(this, e); - } - } - - /** - * Run function. Not supported. - */ - @SuppressWarnings("unused") - public void run() throws BluetoothException { - throw new RuntimeException("Not implemented"); - } - - /** - * Try to cancel operation when a timeout occurs. - */ - public void cancel() { - } - - @Override - public boolean equals(@Nullable Object o) { - if (o == null) { - return false; - } - if (!Operation.class.isInstance(o)) { - return false; - } - Operation other = (Operation) o; - return Arrays.equals(mElements, other.mElements); - } - - @Override - public int hashCode() { - return Objects.hashCode(mElements); - } - - @Override - public String toString() { - return Joiner.on('-').join(mElements); - } - } - - /** - * Synchronous bluetooth operation to schedule. - * - * @param Type of provided instance. - */ - public static class SynchronousOperation extends Operation { - - public SynchronousOperation(Object... elements) { - super(elements); - } - - @Override - public void execute(BluetoothOperationExecutor executor) { - try { - Object result = call(); - if (result == null) { - result = NULL_RESULT; - } - executor.postResult(this, result); - } catch (BluetoothException e) { - executor.postResult(this, e); - } - } - - /** - * Call function. Not supported. - */ - @SuppressWarnings("unused") - @Nullable - public T call() throws BluetoothException { - throw new RuntimeException("Not implemented"); - } - } - - /** - * {@link Future} to wait / get result of an operation. - * - *

  • Waits for operation to complete - *
  • Handles timeouts if needed - *
  • Queues identical Bluetooth operations - *
  • Unwraps Exceptions and null values - */ - private class BluetoothOperationFuture implements Future { - - private final Object mLock = new Object(); - - /** - * Queue that will be used to store the result. It should normally contains one element - * maximum, but using a queue avoid some race conditions. - */ - private final BlockingQueue mResultQueue; - private final Operation mBluetoothOperation; - private final boolean mOperationExecuted; - private boolean mIsCancelled = false; - private boolean mIsDone = false; - - BluetoothOperationFuture(BlockingQueue resultQueue, - Operation bluetoothOperation, boolean operationExecuted) { - mResultQueue = resultQueue; - mBluetoothOperation = bluetoothOperation; - mOperationExecuted = operationExecuted; - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - synchronized (mLock) { - if (mIsDone) { - return false; - } - if (mIsCancelled) { - return true; - } - mBluetoothOperation.cancel(); - mIsCancelled = true; - notifyFailure(mBluetoothOperation, new BluetoothException("Operation cancelled.")); - return true; - } - } - - @Override - public boolean isCancelled() { - synchronized (mLock) { - return mIsCancelled; - } - } - - @Override - public boolean isDone() { - synchronized (mLock) { - return mIsDone; - } - } - - @Override - @Nullable - public T get() throws InterruptedException, ExecutionException { - try { - return getInternal(NO_TIMEOUT, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - throw new RuntimeException(e); // This is not supposed to be thrown - } - } - - @Override - @Nullable - public T get(long timeoutMillis, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return getInternal(Math.max(0, timeoutMillis), unit); - } - - @SuppressWarnings("unchecked") - @Nullable - private T getInternal(long timeoutMillis, TimeUnit unit) - throws ExecutionException, InterruptedException, TimeoutException { - // Prevent parallel executions of this method. - long startTime = mTimeProvider.getTimeMillis(); - synchronized (this) { - synchronized (mLock) { - if (mIsDone) { - throw new ExecutionException( - new BluetoothException("get() called twice...")); - } - } - if (!mOperationExecuted) { - if (timeoutMillis == NO_TIMEOUT) { - mOperationSemaphore.acquire(); - } else { - if (!mOperationSemaphore.tryAcquire(timeoutMillis - - (mTimeProvider.getTimeMillis() - startTime), unit)) { - throw new TimeoutException(String.format(Locale.US, - "A timeout occurred when processing %s after %s %s.", - mBluetoothOperation, timeoutMillis, unit)); - } - } - mBluetoothOperation.execute(BluetoothOperationExecutor.this); - } - Object result; - - if (timeoutMillis == NO_TIMEOUT) { - result = mResultQueue.take(); - } else { - result = mResultQueue.poll( - timeoutMillis - (mTimeProvider.getTimeMillis() - startTime), unit); - } - - if (result == null) { - throw new TimeoutException(String.format(Locale.US, - "A timeout occurred when processing %s after %s ms.", - mBluetoothOperation, timeoutMillis)); - } - synchronized (mLock) { - mIsDone = true; - } - if (result instanceof BluetoothException) { - throw new ExecutionException((BluetoothException) result); - } - if (result == NULL_RESULT) { - result = null; - } - return (T) result; - } - } - } - - /** - * Exception thrown when an operation execution times out. Since state of the system is unknown - * afterward (operation may still complete or not), it is recommended to disconnect and - * reconnect. - */ - public static class BluetoothOperationTimeoutException extends BluetoothException { - - public BluetoothOperationTimeoutException(String message) { - super(message); - } - - public BluetoothOperationTimeoutException(String message, Throwable cause) { - super(message, cause); - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/eventloop/Annotations.java b/nearby/service/java/com/android/server/nearby/common/eventloop/Annotations.java deleted file mode 100644 index 44c9422203268364e478865ecaa7bfeca21f8003..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/eventloop/Annotations.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 com.android.server.nearby.common.eventloop; - -import static java.lang.annotation.ElementType.CONSTRUCTOR; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.CLASS; - -import androidx.annotation.AnyThread; -import androidx.annotation.BinderThread; -import androidx.annotation.UiThread; -import androidx.annotation.WorkerThread; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -/** - * A collection of threading annotations relating to EventLoop. These should be used in conjunction - * with {@link UiThread}, {@link BinderThread}, {@link WorkerThread}, and {@link AnyThread}. - */ -public class Annotations { - - /** - * Denotes that the annotated method or constructor should only be called on the EventLoop - * thread. - */ - @Retention(CLASS) - @Target({METHOD, CONSTRUCTOR, TYPE}) - public @interface EventThread { - } - - /** Denotes that the annotated method or constructor should only be called on a Network - * thread. */ - @Retention(CLASS) - @Target({METHOD, CONSTRUCTOR, TYPE}) - public @interface NetworkThread { - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/eventloop/EventLoop.java b/nearby/service/java/com/android/server/nearby/common/eventloop/EventLoop.java deleted file mode 100644 index c89366f3bb3ddf91ff2d5d2e65fd0bc01bd2b23e..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/eventloop/EventLoop.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * 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 com.android.server.nearby.common.eventloop; - -import android.annotation.Nullable; -import android.os.Handler; -import android.os.Looper; - -/** - * Handles executing runnables on a background thread. - * - *

    Nearby services follow an event loop model where events can be queued and delivered in the - * future. All code that is run in this EventLoop is guaranteed to be run on this thread. The main - * advantage of this model is that all modules don't have to deal with synchronization and race - * conditions, while making it easy to handle the several asynchronous tasks that are expected to be - * needed for this type of provider (such as starting a WiFi scan and waiting for the result, - * starting BLE scans, doing a server request and waiting for the response etc.). - * - *

    Code that needs to wait for an event should not spawn a new thread nor sleep. It should simply - * deliver a new message to the event queue when the reply of the event happens. - */ -// TODO(b/177675274): Resolve nullness suppression. -@SuppressWarnings("nullness") -public class EventLoop { - - private final Interface mImpl; - - private EventLoop(Interface impl) { - this.mImpl = impl; - } - - protected EventLoop(String name) { - this(new HandlerEventLoopImpl(name)); - } - - /** Creates an EventLoop. */ - public static EventLoop newInstance(String name) { - return new EventLoop(name); - } - - /** Creates an EventLoop. */ - public static EventLoop newInstance(String name, Looper looper) { - return new EventLoop(new HandlerEventLoopImpl(name, looper)); - } - - /** Marks the EventLoop as destroyed. Any further messages received will be ignored. */ - public void destroy() { - mImpl.destroy(); - } - - /** - * Posts a runnable to this event loop, blocking until the runnable has been executed. This - * should - * be used rarely. It could be useful, for example, for a runnable that initializes the system - * and - * must block the posting of all other runnables. - * - * @param runnable a Runnable to post. This method will not return until the run() method of the - * given runnable has executed on the background thread. - */ - public void postAndWait(final NamedRunnable runnable) throws InterruptedException { - mImpl.postAndWait(runnable); - } - - /** - * Posts a runnable to this to the front of the event loop, blocking until the runnable has been - * executed. This should be used rarely, as it can starve the event loop. - * - * @param runnable a Runnable to post. This method will not return until the run() method of the - * given runnable has executed on the background thread. - */ - public void postToFrontAndWait(final NamedRunnable runnable) throws InterruptedException { - mImpl.postToFrontAndWait(runnable); - } - - /** Checks if there are any pending posts of the Runnable in the queue. */ - public boolean isPosted(NamedRunnable runnable) { - return mImpl.isPosted(runnable); - } - - /** - * Run code on the event loop thread. - * - * @param runnable the runnable to execute. - */ - public void postRunnable(NamedRunnable runnable) { - mImpl.postRunnable(runnable); - } - - /** - * Run code to be executed when there is no runnable scheduled. - * - * @param runnable last runnable to execute. - */ - public void postEmptyQueueRunnable(final NamedRunnable runnable) { - mImpl.postEmptyQueueRunnable(runnable); - } - - /** - * Run code on the event loop thread after delayedMillis. - * - * @param runnable the runnable to execute. - * @param delayedMillis the number of milliseconds before executing the runnable. - */ - public void postRunnableDelayed(NamedRunnable runnable, long delayedMillis) { - mImpl.postRunnableDelayed(runnable, delayedMillis); - } - - /** - * Removes and cancels the specified {@code runnable} if it had not posted/started yet. Calling - * with null does nothing. - */ - public void removeRunnable(@Nullable NamedRunnable runnable) { - mImpl.removeRunnable(runnable); - } - - /** Asserts that the current operation is being executed in the Event Loop's thread. */ - public void checkThread() { - mImpl.checkThread(); - } - - public Handler getHandler() { - return mImpl.getHandler(); - } - - interface Interface { - void destroy(); - - void postAndWait(NamedRunnable runnable) throws InterruptedException; - - void postToFrontAndWait(NamedRunnable runnable) throws InterruptedException; - - boolean isPosted(NamedRunnable runnable); - - void postRunnable(NamedRunnable runnable); - - void postEmptyQueueRunnable(NamedRunnable runnable); - - void postRunnableDelayed(NamedRunnable runnable, long delayedMillis); - - void removeRunnable(NamedRunnable runnable); - - void checkThread(); - - Handler getHandler(); - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/eventloop/HandlerEventLoopImpl.java b/nearby/service/java/com/android/server/nearby/common/eventloop/HandlerEventLoopImpl.java deleted file mode 100644 index 018dcdb1ba0573cee286ab15f27f274bd4fcb9b7..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/eventloop/HandlerEventLoopImpl.java +++ /dev/null @@ -1,304 +0,0 @@ -/* - * 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 com.android.server.nearby.common.eventloop; - -import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.os.MessageQueue; -import android.os.Process; -import android.os.SystemClock; -import android.util.Log; - -import java.text.SimpleDateFormat; -import java.util.Locale; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - - -/** - * Handles executing runnables on a background thread. - * - *

    Nearby services follow an event loop model where events can be queued and delivered in the - * future. All code that is run in this package is guaranteed to be run on this thread. The main - * advantage of this model is that all modules don't have to deal with synchronization and race - * conditions, while making it easy to handle the several asynchronous tasks that are expected to be - * needed for this type of provider (such as starting a WiFi scan and waiting for the result, - * starting BLE scans, doing a server request and waiting for the response etc.). - * - *

    Code that needs to wait for an event should not spawn a new thread nor sleep. It should simply - * deliver a new message to the event queue when the reply of the event happens. - * - *

    - */ -// TODO(b/203471261) use executor instead of handler -// TODO(b/177675274): Resolve nullness suppression. -@SuppressWarnings("nullness") -final class HandlerEventLoopImpl implements EventLoop.Interface { - /** The {@link Message#what} code for all messages that we post to the EventLoop. */ - private static final int WHAT = 0; - - private static final long ELAPSED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(5); - private static final long RUNNABLE_DELAY_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(2); - private static final String TAG = HandlerEventLoopImpl.class.getSimpleName(); - private final MyHandler mHandler; - - private volatile boolean mIsDestroyed = false; - - /** Constructs an EventLoop. */ - HandlerEventLoopImpl(String name) { - this(name, createHandlerThread(name)); - } - - HandlerEventLoopImpl(String name, Looper looper) { - - mHandler = new MyHandler(looper); - Log.d(TAG, - "Created EventLoop for thread '" + looper.getThread().getName() - + "(id: " + looper.getThread().getId() + ")'"); - } - - private static Looper createHandlerThread(String name) { - HandlerThread handlerThread = new HandlerThread(name, Process.THREAD_PRIORITY_BACKGROUND); - handlerThread.start(); - - return handlerThread.getLooper(); - } - - /** - * Wrapper to satisfy Android Lint. {@link Looper#getQueue()} is public and available since ICS, - * but was marked @hide until Marshmallow. Tested that this code doesn't crash pre-Marshmallow. - * /aosp-ics/frameworks/base/core/java/android/os/Looper.java?l=218 - */ - @SuppressLint("NewApi") - private static MessageQueue getQueue(Handler handler) { - return handler.getLooper().getQueue(); - } - - /** Marks the EventLoop as destroyed. Any further messages received will be ignored. */ - @Override - public void destroy() { - Looper looper = mHandler.getLooper(); - Log.d(TAG, - "Destroying EventLoop for thread " + looper.getThread().getName() - + " (id: " + looper.getThread().getId() + ")"); - looper.quit(); - mIsDestroyed = true; - } - - /** - * Posts a runnable to this event loop, blocking until the runnable has been executed. This - * should - * be used rarely. It could be useful, for example, for a runnable that initializes the system - * and - * must block the posting of all other runnables. - * - * @param runnable a Runnable to post. This method will not return until the run() method of the - * given runnable has executed on the background thread. - */ - @Override - public void postAndWait(final NamedRunnable runnable) throws InterruptedException { - internalPostAndWait(runnable, false); - } - - @Override - public void postToFrontAndWait(final NamedRunnable runnable) throws InterruptedException { - internalPostAndWait(runnable, true); - } - - /** Checks if there are any pending posts of the Runnable in the queue. */ - @Override - public boolean isPosted(NamedRunnable runnable) { - return mHandler.hasMessages(WHAT, runnable); - } - - /** - * Run code on the event loop thread. - * - * @param runnable the runnable to execute. - */ - @Override - public void postRunnable(NamedRunnable runnable) { - Log.d(TAG, "Posting " + runnable); - mHandler.post(runnable, 0L, false); - } - - /** - * Run code to be executed when there is no runnable scheduled. - * - * @param runnable last runnable to execute. - */ - @Override - public void postEmptyQueueRunnable(final NamedRunnable runnable) { - mHandler.post( - () -> - getQueue(mHandler) - .addIdleHandler( - () -> { - if (mHandler.hasMessages(WHAT)) { - return true; - } else { - // Only stop if start has not been called since - // this was queued - runnable.run(); - return false; - } - })); - } - - /** - * Run code on the event loop thread after delayedMillis. - * - * @param runnable the runnable to execute. - * @param delayedMillis the number of milliseconds before executing the runnable. - */ - @Override - public void postRunnableDelayed(NamedRunnable runnable, long delayedMillis) { - Log.d(TAG, "Posting " + runnable + " [delay " + delayedMillis + "]"); - mHandler.post(runnable, delayedMillis, false); - } - - /** - * Removes and cancels the specified {@code runnable} if it had not posted/started yet. Calling - * with null does nothing. - */ - @Override - public void removeRunnable(@Nullable NamedRunnable runnable) { - if (runnable != null) { - // Removes any pending sent messages where what=WHAT and obj=runnable. We can't use - // removeCallbacks(runnable) because we're not posting the runnable directly, we're - // sending a Message with the runnable as its obj. - mHandler.removeMessages(WHAT, runnable); - } - } - - /** Asserts that the current operation is being executed in the Event Loop's thread. */ - @Override - public void checkThread() { - - Thread currentThread = Looper.myLooper().getThread(); - Thread expectedThread = mHandler.getLooper().getThread(); - if (currentThread.getId() != expectedThread.getId()) { - throw new IllegalStateException( - String.format( - "This method must run in the EventLoop thread '%s (id: %s)'. " - + "Was called from thread '%s (id: %s)'.", - expectedThread.getName(), - expectedThread.getId(), - currentThread.getName(), - currentThread.getId())); - } - - } - - @Override - public Handler getHandler() { - return mHandler; - } - - private void internalPostAndWait(final NamedRunnable runnable, boolean postToFront) - throws InterruptedException { - final CountDownLatch latch = new CountDownLatch(1); - NamedRunnable delegate = - new NamedRunnable(runnable.name) { - @Override - public void run() { - try { - runnable.run(); - } finally { - latch.countDown(); - } - } - }; - - Log.d(TAG, "Posting " + delegate + " and wait"); - if (!mHandler.post(delegate, 0L, postToFront)) { - // Do not wait if delegate is not posted. - Log.d(TAG, delegate + " not posted"); - latch.countDown(); - } - latch.await(); - } - - /** Handler that executes code on a private event loop thread. */ - private class MyHandler extends Handler { - - MyHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - NamedRunnable runnable = (NamedRunnable) msg.obj; - - if (mIsDestroyed) { - Log.w(TAG, "Runnable " + runnable - + " attempted to run after the EventLoop was destroyed. Ignoring"); - return; - } - Log.i(TAG, "Executing " + runnable); - - // Did this runnable start much later than we expected it to? If so, then log. - long expectedStartTime = (long) msg.arg1 << 32 | (msg.arg2 & 0xFFFFFFFFL); - logIfExceedsThreshold( - RUNNABLE_DELAY_THRESHOLD_MS, expectedStartTime, runnable, "was delayed for"); - - long startTimeMillis = SystemClock.elapsedRealtime(); - try { - runnable.run(); - } catch (Exception t) { - Log.e(TAG, runnable + "crashed."); - throw t; - } finally { - logIfExceedsThreshold(ELAPSED_THRESHOLD_MS, startTimeMillis, runnable, "ran for"); - } - } - - private boolean post(NamedRunnable runnable, long delayedMillis, boolean postToFront) { - if (mIsDestroyed) { - Log.w(TAG, runnable + " not posted since EventLoop is destroyed"); - return false; - } - long expectedStartTime = SystemClock.elapsedRealtime() + delayedMillis; - int arg1 = (int) (expectedStartTime >> 32); - int arg2 = (int) expectedStartTime; - Message message = obtainMessage(WHAT, arg1, arg2, runnable /* obj */); - boolean sent = - postToFront - ? sendMessageAtFrontOfQueue(message) - : sendMessageDelayed(message, delayedMillis); - if (!sent) { - Log.w(TAG, runnable + "not posted since looper is exiting"); - } - return sent; - } - - private void logIfExceedsThreshold( - long thresholdMillis, long startTimeMillis, NamedRunnable runnable, - String message) { - long elapsedMillis = SystemClock.elapsedRealtime() - startTimeMillis; - if (elapsedMillis > thresholdMillis) { - String elapsedFormatted = - new SimpleDateFormat("mm:ss.SSS", Locale.US).format(elapsedMillis); - Log.w(TAG, runnable + " " + message + " " + elapsedFormatted); - } - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/fastpair/IconUtils.java b/nearby/service/java/com/android/server/nearby/common/fastpair/IconUtils.java deleted file mode 100644 index 35a1a9fa8398fba57ce3a24d9571818b6cb56bbd..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/fastpair/IconUtils.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.server.nearby.common.fastpair; - -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; - -import androidx.annotation.VisibleForTesting; -import androidx.core.graphics.ColorUtils; - -/** Utility methods for icon size verification. */ -public class IconUtils { - private static final int MIN_ICON_SIZE = 16; - private static final int DESIRED_ICON_SIZE = 32; - private static final double NOTIFICATION_BACKGROUND_PADDING_PERCENTAGE = 0.125; - private static final double NOTIFICATION_BACKGROUND_ALPHA = 0.7; - - /** - * Verify that the icon is non null and falls in the small bucket. Just because an icon isn't - * small doesn't guarantee it is large or exists. - */ - @VisibleForTesting - static boolean isIconSizedSmall(@Nullable Bitmap bitmap) { - if (bitmap == null) { - return false; - } - int min = MIN_ICON_SIZE; - int desired = DESIRED_ICON_SIZE; - return bitmap.getWidth() >= min - && bitmap.getWidth() < desired - && bitmap.getHeight() >= min - && bitmap.getHeight() < desired; - } - - /** - * Verify that the icon is non null and falls in the regular / default size bucket. Doesn't - * guarantee if not regular then it is small. - */ - @VisibleForTesting - static boolean isIconSizedRegular(@Nullable Bitmap bitmap) { - if (bitmap == null) { - return false; - } - return bitmap.getWidth() >= DESIRED_ICON_SIZE - && bitmap.getHeight() >= DESIRED_ICON_SIZE; - } - - // All icons that are sized correctly (larger than the min icon size) are resize on the server - // to the desired icon size so that they appear correct in notifications. - - /** - * All icons that are sized correctly (larger than the min icon size) are resize on the server - * to the desired icon size so that they appear correct in notifications. - */ - public static boolean isIconSizeCorrect(@Nullable Bitmap bitmap) { - if (bitmap == null) { - return false; - } - return isIconSizedSmall(bitmap) || isIconSizedRegular(bitmap); - } - - /** Adds a circular, white background to the bitmap. */ - @Nullable - public static Bitmap addWhiteCircleBackground(Context context, @Nullable Bitmap bitmap) { - if (bitmap == null) { - return null; - } - - if (bitmap.getWidth() != bitmap.getHeight()) { - return bitmap; - } - - int padding = (int) (bitmap.getWidth() * NOTIFICATION_BACKGROUND_PADDING_PERCENTAGE); - Bitmap bitmapWithBackground = - Bitmap.createBitmap( - bitmap.getWidth() + (2 * padding), - bitmap.getHeight() + (2 * padding), - bitmap.getConfig()); - Canvas canvas = new Canvas(bitmapWithBackground); - Paint paint = new Paint(); - paint.setColor( - ColorUtils.setAlphaComponent( - Color.WHITE, (int) (255 * NOTIFICATION_BACKGROUND_ALPHA))); - paint.setStyle(Paint.Style.FILL); - paint.setAntiAlias(true); - canvas.drawCircle( - bitmapWithBackground.getWidth() / 2f, - bitmapWithBackground.getHeight() / 2f, - bitmapWithBackground.getWidth() / 2f, - paint); - canvas.drawBitmap(bitmap, padding, padding, null); - return bitmapWithBackground; - } -} - diff --git a/nearby/service/java/com/android/server/nearby/common/locator/Locator.java b/nearby/service/java/com/android/server/nearby/common/locator/Locator.java deleted file mode 100644 index 2003335b7516b74b87d360209fad03b1b9bee3e6..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/locator/Locator.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * 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.server.nearby.common.locator; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.ContextWrapper; -import android.util.Log; - -import androidx.annotation.VisibleForTesting; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -/** Collection of bindings that map service types to their respective implementation(s). */ -public class Locator { - private static final Object UNBOUND = new Object(); - private final Context mContext; - @Nullable - private Locator mParent; - private final String mTag; // For debugging - private final Map, Object> mBindings = new HashMap<>(); - private final ArrayList mModules = new ArrayList<>(); - - /** Thrown upon attempt to bind an interface twice. */ - public static class DuplicateBindingException extends RuntimeException { - DuplicateBindingException(String msg) { - super(msg); - } - } - - /** Constructor with a null parent. */ - public Locator(Context context) { - this(context, null); - } - - /** - * Constructor. Supply a valid context and the Locator's parent. - * - *

    To find a suitable parent you may want to use findLocator. - */ - public Locator(Context context, @Nullable Locator parent) { - this.mContext = context; - this.mParent = parent; - this.mTag = context.getClass().getName(); - } - - /** Attaches the parent to the locator. */ - public void attachParent(Locator parent) { - this.mParent = parent; - } - - /** Associates the specified type with the supplied instance. */ - public Locator bind(Class type, T instance) { - bindKeyValue(type, instance); - return this; - } - - /** For tests only. Disassociates the specified type from any instance. */ - @VisibleForTesting - public Locator overrideBindingForTest(Class type, T instance) { - mBindings.remove(type); - return bind(type, instance); - } - - /** For tests only. Force Locator to return null when try to get an instance. */ - @VisibleForTesting - public Locator removeBindingForTest(Class type) { - Locator locator = this; - do { - locator.mBindings.put(type, UNBOUND); - locator = locator.mParent; - } while (locator != null); - return this; - } - - /** Binds a module. */ - public synchronized Locator bind(Module module) { - mModules.add(module); - return this; - } - - /** - * Searches the chain of locators for a binding for the given type. - * - * @throws IllegalStateException if no binding is found. - */ - public T get(Class type) { - T instance = getOptional(type); - if (instance != null) { - return instance; - } - - String errorMessage = getUnboundErrorMessage(type); - throw new IllegalStateException(errorMessage); - } - - @VisibleForTesting - String getUnboundErrorMessage(Class type) { - StringBuilder sb = new StringBuilder(); - sb.append("Unbound type: ").append(type.getName()).append("\n").append( - "Searched locators:\n"); - Locator locator = this; - while (true) { - sb.append(locator.mTag); - locator = locator.mParent; - if (locator == null) { - break; - } - sb.append(" ->\n"); - } - return sb.toString(); - } - - /** - * Searches the chain of locators for a binding for the given type. Returns null if no locator - * was - * found. - */ - @Nullable - public T getOptional(Class type) { - Locator locator = this; - do { - T instance = locator.getInstance(type); - if (instance != null) { - return instance; - } - locator = locator.mParent; - } while (locator != null); - return null; - } - - private synchronized void bindKeyValue(Class key, T value) { - Object boundInstance = mBindings.get(key); - if (boundInstance != null) { - if (boundInstance == UNBOUND) { - Log.w(mTag, "Bind call too late - someone already tried to get: " + key); - } else { - throw new DuplicateBindingException("Duplicate binding: " + key); - } - } - mBindings.put(key, value); - } - - // Suppress warning of cast from Object -> T - @SuppressWarnings("unchecked") - @Nullable - private synchronized T getInstance(Class type) { - if (mContext == null) { - throw new IllegalStateException("Locator not initialized yet."); - } - - T instance = (T) mBindings.get(type); - if (instance != null) { - return instance != UNBOUND ? instance : null; - } - - // Ask modules to supply a binding - int moduleCount = mModules.size(); - for (int i = 0; i < moduleCount; i++) { - mModules.get(i).configure(mContext, type, this); - } - - instance = (T) mBindings.get(type); - if (instance == null) { - mBindings.put(type, UNBOUND); - } - return instance; - } - - /** - * Iterates over all bound objects and gives the modules a chance to clean up the objects they - * have created. - */ - public synchronized void destroy() { - for (Class type : mBindings.keySet()) { - Object instance = mBindings.get(type); - if (instance == UNBOUND) { - continue; - } - - for (Module module : mModules) { - module.destroy(mContext, type, instance); - } - } - mBindings.clear(); - } - - /** Returns true if there are no bindings. */ - public boolean isEmpty() { - return mBindings.isEmpty(); - } - - /** Returns the parent locator or null if no parent. */ - @Nullable - public Locator getParent() { - return mParent; - } - - /** - * Finds the first locator, then searches the chain of locators for a binding for the given - * type. - * - * @throws IllegalStateException if no binding is found. - */ - public static T get(Context context, Class type) { - Locator locator = findLocator(context); - if (locator == null) { - throw new IllegalStateException("No locator found in context " + context); - } - return locator.get(type); - } - - /** - * Find the first locator from the context wrapper. - */ - public static T getFromContextWrapper(LocatorContextWrapper wrapper, Class type) { - Locator locator = wrapper.getLocator(); - if (locator == null) { - throw new IllegalStateException("No locator found in context wrapper"); - } - return locator.get(type); - } - - /** - * Finds the first locator, then searches the chain of locators for a binding for the given - * type. - * Returns null if no binding was found. - */ - @Nullable - public static T getOptional(Context context, Class type) { - Locator locator = findLocator(context); - if (locator == null) { - return null; - } - return locator.getOptional(type); - } - - /** Finds the first locator in the context hierarchy. */ - @Nullable - public static Locator findLocator(Context context) { - Context applicationContext = context.getApplicationContext(); - boolean applicationContextVisited = false; - - Context searchContext = context; - do { - Locator locator = tryGetLocator(searchContext); - if (locator != null) { - return locator; - } - - applicationContextVisited |= (searchContext == applicationContext); - - if (searchContext instanceof ContextWrapper) { - searchContext = ((ContextWrapper) context).getBaseContext(); - - if (searchContext == null) { - throw new IllegalStateException( - "Invalid ContextWrapper -- If this is a Robolectric test, " - + "have you called ActivityController.create()?"); - } - } else if (!applicationContextVisited) { - searchContext = applicationContext; - } else { - searchContext = null; - } - } while (searchContext != null); - - return null; - } - - @Nullable - private static Locator tryGetLocator(Object object) { - if (object instanceof LocatorContext) { - Locator locator = ((LocatorContext) object).getLocator(); - if (locator == null) { - throw new IllegalStateException( - "LocatorContext must not return null Locator: " + object); - } - return locator; - } - return null; - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/locator/LocatorContext.java b/nearby/service/java/com/android/server/nearby/common/locator/LocatorContext.java deleted file mode 100644 index 06eef8aacb37a54d4422cdd23c77adc1e5618b0d..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/locator/LocatorContext.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.server.nearby.common.locator; - -/** - * An object that has a {@link Locator}. The locator can be used to resolve service types to their - * respective implementation(s). - */ -public interface LocatorContext { - /** Returns the locator. May not return null. */ - Locator getLocator(); -} diff --git a/nearby/service/java/com/android/server/nearby/common/locator/LocatorContextWrapper.java b/nearby/service/java/com/android/server/nearby/common/locator/LocatorContextWrapper.java deleted file mode 100644 index 03df33f843b4dcbfba83368838ddd1d4ec237945..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/locator/LocatorContextWrapper.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.server.nearby.common.locator; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.ContextWrapper; - -/** - * Wraps a Context and associates it with a Locator, optionally linking it with a parent locator. - */ -public class LocatorContextWrapper extends ContextWrapper implements LocatorContext { - private final Locator mLocator; - private final Context mContext; - /** Constructs a context wrapper with a Locator linked to the passed locator. */ - public LocatorContextWrapper(Context context, @Nullable Locator parentLocator) { - super(context); - mContext = context; - // Assigning under initialization object, but it's safe, since locator is used lazily. - this.mLocator = new Locator(this, parentLocator); - } - - /** - * Constructs a context wrapper. - * - *

    Uses the Locator associated with the passed context as the parent. - */ - public LocatorContextWrapper(Context context) { - this(context, Locator.findLocator(context)); - } - - /** - * Get the context of the context wrapper. - */ - public Context getContext() { - return mContext; - } - - @Override - public Locator getLocator() { - return mLocator; - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/locator/Module.java b/nearby/service/java/com/android/server/nearby/common/locator/Module.java deleted file mode 100644 index 0131c44c07ba19773cf6088b235e0471ef464c8c..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/locator/Module.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.server.nearby.common.locator; - -import android.content.Context; - -/** Configures late bindings of service types to their concrete implementations. */ -public abstract class Module { - /** - * Configures the binding between the {@code type} and its implementation by calling methods on - * the {@code locator}, for example: - * - *

    {@code
    -     * void configure(Context context, Class type, Locator locator) {
    -     *   if (type == MyService.class) {
    -     *     locator.bind(MyService.class, new MyImplementation(context));
    -     *   }
    -     * }
    -     * }
    - * - *

    If the module does not recognize the specified type, the method does not have to do - * anything. - */ - public abstract void configure(Context context, Class type, Locator locator); - - /** - * Notifies you that a binding of class {@code type} is no longer needed and can now release - * everything it was holding on to, such as a database connection. - * - *

    {@code
    -     * void destroy(Context context, Class type, Object instance) {
    -     *   if (type == MyService.class) {
    -     *     ((MyService) instance).destroy();
    -     *   }
    -     * }
    -     * }
    - * - *

    If the module does not recognize the specified type, the method does not have to do - * anything. - */ - public void destroy(Context context, Class type, Object instance) {} -} - diff --git a/nearby/service/java/com/android/server/nearby/common/servicemonitor/CurrentUserServiceProvider.java b/nearby/service/java/com/android/server/nearby/common/servicemonitor/CurrentUserServiceProvider.java deleted file mode 100644 index 80248e8bfca9288e7af4a66721c02f9b61966b48..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/servicemonitor/CurrentUserServiceProvider.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * 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.server.nearby.common.servicemonitor; - -import static android.content.pm.PackageManager.GET_META_DATA; -import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO; -import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; -import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; -import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; - -import android.app.ActivityManager; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ResolveInfo; -import android.os.UserHandle; - -import com.android.internal.util.Preconditions; -import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceChangedListener; -import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceProvider; - -import java.util.Comparator; -import java.util.List; - -/** - * This is mostly borrowed from frameworks CurrentUserServiceSupplier. - * Provides services based on the current active user and version as defined in the service - * manifest. This implementation uses {@link android.content.pm.PackageManager#MATCH_SYSTEM_ONLY} to - * ensure only system (ie, privileged) services are matched. It also handles services that are not - * direct boot aware, and will automatically pick the best service as the user's direct boot state - * changes. - */ -public final class CurrentUserServiceProvider extends BroadcastReceiver implements - ServiceProvider { - - private static final String TAG = "CurrentUserServiceProvider"; - - private static final String EXTRA_SERVICE_VERSION = "serviceVersion"; - - // This is equal to the hidden Intent.ACTION_USER_SWITCHED. - private static final String ACTION_USER_SWITCHED = "android.intent.action.USER_SWITCHED"; - // This is equal to the hidden Intent.EXTRA_USER_HANDLE. - private static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle"; - // This is equal to the hidden UserHandle.USER_NULL. - private static final int USER_NULL = -10000; - - private static final Comparator sBoundServiceInfoComparator = (o1, o2) -> { - if (o1 == o2) { - return 0; - } else if (o1 == null) { - return -1; - } else if (o2 == null) { - return 1; - } - - // ServiceInfos with higher version numbers always win. - return Integer.compare(o1.getVersion(), o2.getVersion()); - }; - - /** Bound service information with version information. */ - public static class BoundServiceInfo extends ServiceMonitor.BoundServiceInfo { - - private static int parseUid(ResolveInfo resolveInfo) { - return resolveInfo.serviceInfo.applicationInfo.uid; - } - - private static int parseVersion(ResolveInfo resolveInfo) { - int version = Integer.MIN_VALUE; - if (resolveInfo.serviceInfo.metaData != null) { - version = resolveInfo.serviceInfo.metaData.getInt(EXTRA_SERVICE_VERSION, version); - } - return version; - } - - private final int mVersion; - - protected BoundServiceInfo(String action, ResolveInfo resolveInfo) { - this( - action, - parseUid(resolveInfo), - new ComponentName( - resolveInfo.serviceInfo.packageName, - resolveInfo.serviceInfo.name), - parseVersion(resolveInfo)); - } - - protected BoundServiceInfo(String action, int uid, ComponentName componentName, - int version) { - super(action, uid, componentName); - mVersion = version; - } - - public int getVersion() { - return mVersion; - } - - @Override - public String toString() { - return super.toString() + "@" + mVersion; - } - } - - /** - * Creates an instance with the specific service details. - * - * @param context the context the provider is to use - * @param action the action the service must declare in its intent-filter - */ - public static CurrentUserServiceProvider create(Context context, String action) { - return new CurrentUserServiceProvider(context, action); - } - - private final Context mContext; - private final Intent mIntent; - private volatile ServiceChangedListener mListener; - - private CurrentUserServiceProvider(Context context, String action) { - mContext = context; - mIntent = new Intent(action); - } - - @Override - public boolean hasMatchingService() { - int intentQueryFlags = - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY; - List resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser( - mIntent, intentQueryFlags, UserHandle.SYSTEM); - return !resolveInfos.isEmpty(); - } - - @Override - public void register(ServiceChangedListener listener) { - Preconditions.checkState(mListener == null); - - mListener = listener; - - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(ACTION_USER_SWITCHED); - intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); - mContext.registerReceiverForAllUsers(this, intentFilter, null, - ForegroundThread.getHandler()); - } - - @Override - public void unregister() { - Preconditions.checkArgument(mListener != null); - - mListener = null; - mContext.unregisterReceiver(this); - } - - @Override - public BoundServiceInfo getServiceInfo() { - BoundServiceInfo bestServiceInfo = null; - - // only allow services in the correct direct boot state to match - int intentQueryFlags = MATCH_DIRECT_BOOT_AUTO | GET_META_DATA | MATCH_SYSTEM_ONLY; - List resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser( - mIntent, intentQueryFlags, UserHandle.of(ActivityManager.getCurrentUser())); - for (ResolveInfo resolveInfo : resolveInfos) { - BoundServiceInfo serviceInfo = - new BoundServiceInfo(mIntent.getAction(), resolveInfo); - - if (sBoundServiceInfoComparator.compare(serviceInfo, bestServiceInfo) > 0) { - bestServiceInfo = serviceInfo; - } - } - - return bestServiceInfo; - } - - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action == null) { - return; - } - int userId = intent.getIntExtra(EXTRA_USER_HANDLE, USER_NULL); - if (userId == USER_NULL) { - return; - } - ServiceChangedListener listener = mListener; - if (listener == null) { - return; - } - - switch (action) { - case ACTION_USER_SWITCHED: - listener.onServiceChanged(); - break; - case Intent.ACTION_USER_UNLOCKED: - // user unlocked implies direct boot mode may have changed - if (userId == ActivityManager.getCurrentUser()) { - listener.onServiceChanged(); - } - break; - default: - break; - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/servicemonitor/ForegroundThread.java b/nearby/service/java/com/android/server/nearby/common/servicemonitor/ForegroundThread.java deleted file mode 100644 index 2c363f89d5d30133b00e6c4ebc3fd6cca524ad4c..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/servicemonitor/ForegroundThread.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.server.nearby.common.servicemonitor; - -import android.os.Handler; -import android.os.HandlerThread; - -import com.android.modules.utils.HandlerExecutor; - -import java.util.concurrent.Executor; - -/** - * Thread for asynchronous event processing. This thread is configured as - * {@link android.os.Process#THREAD_PRIORITY_FOREGROUND}, which means more CPU - * resources will be dedicated to it, and it will be treated like "a user - * interface that the user is interacting with." - *

    - * This thread is best suited for tasks that the user is actively waiting for, - * or for tasks that the user expects to be executed immediately. - * - */ -public final class ForegroundThread extends HandlerThread { - private static ForegroundThread sInstance; - private static Handler sHandler; - private static HandlerExecutor sHandlerExecutor; - - private ForegroundThread() { - super("nearbyfg", android.os.Process.THREAD_PRIORITY_FOREGROUND); - } - - private static void ensureThreadLocked() { - if (sInstance == null) { - sInstance = new ForegroundThread(); - sInstance.start(); - sHandler = new Handler(sInstance.getLooper()); - sHandlerExecutor = new HandlerExecutor(sHandler); - } - } - - /** Get ForegroundThread singleton instance. */ - public static ForegroundThread get() { - synchronized (ForegroundThread.class) { - ensureThreadLocked(); - return sInstance; - } - } - - /** Get ForegroundThread singleton handler. */ - public static Handler getHandler() { - synchronized (ForegroundThread.class) { - ensureThreadLocked(); - return sHandler; - } - } - - /** Get ForegroundThread singleton executor. */ - public static Executor getExecutor() { - synchronized (ForegroundThread.class) { - ensureThreadLocked(); - return sHandlerExecutor; - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/servicemonitor/PackageWatcher.java b/nearby/service/java/com/android/server/nearby/common/servicemonitor/PackageWatcher.java deleted file mode 100644 index 7d1db5781f43d67148f8348cc01941ab2d5d5118..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/servicemonitor/PackageWatcher.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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.server.nearby.common.servicemonitor; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; - -import com.android.modules.utils.BackgroundThread; - -import java.util.Objects; - -/** - * This is mostly from frameworks PackageMonitor. - * Helper class for watching somePackagesChanged. - */ -public abstract class PackageWatcher extends BroadcastReceiver { - static final String TAG = "PackageWatcher"; - static final IntentFilter sPackageFilt = new IntentFilter(); - static final IntentFilter sNonDataFilt = new IntentFilter(); - static final IntentFilter sExternalFilt = new IntentFilter(); - - static { - sPackageFilt.addAction(Intent.ACTION_PACKAGE_ADDED); - sPackageFilt.addAction(Intent.ACTION_PACKAGE_REMOVED); - sPackageFilt.addAction(Intent.ACTION_PACKAGE_CHANGED); - sPackageFilt.addDataScheme("package"); - sNonDataFilt.addAction(Intent.ACTION_PACKAGES_SUSPENDED); - sNonDataFilt.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED); - sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); - sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - } - - Context mRegisteredContext; - Handler mRegisteredHandler; - boolean mSomePackagesChanged; - - public PackageWatcher() { - } - - void register(Context context, Looper thread, boolean externalStorage) { - register(context, externalStorage, - (thread == null) ? BackgroundThread.getHandler() : new Handler(thread)); - } - - void register(Context context, boolean externalStorage, Handler handler) { - if (mRegisteredContext != null) { - throw new IllegalStateException("Already registered"); - } - mRegisteredContext = context; - mRegisteredHandler = Objects.requireNonNull(handler); - context.registerReceiverForAllUsers(this, sPackageFilt, null, mRegisteredHandler); - context.registerReceiverForAllUsers(this, sNonDataFilt, null, mRegisteredHandler); - if (externalStorage) { - context.registerReceiverForAllUsers(this, sExternalFilt, null, mRegisteredHandler); - } - } - - void unregister() { - if (mRegisteredContext == null) { - throw new IllegalStateException("Not registered"); - } - mRegisteredContext.unregisterReceiver(this); - mRegisteredContext = null; - } - - // Called when some package has been changed. - abstract void onSomePackagesChanged(); - - String getPackageName(Intent intent) { - Uri uri = intent.getData(); - String pkg = uri != null ? uri.getSchemeSpecificPart() : null; - return pkg; - } - - @Override - public void onReceive(Context context, Intent intent) { - mSomePackagesChanged = false; - - String action = intent.getAction(); - if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { - // We consider something to have changed regardless of whether - // this is just an update, because the update is now finished - // and the contents of the package may have changed. - mSomePackagesChanged = true; - } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { - String pkg = getPackageName(intent); - if (pkg != null) { - if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { - mSomePackagesChanged = true; - } - } - } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { - String pkg = getPackageName(intent); - if (pkg != null) { - mSomePackagesChanged = true; - } - } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { - mSomePackagesChanged = true; - } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { - mSomePackagesChanged = true; - } else if (Intent.ACTION_PACKAGES_SUSPENDED.equals(action)) { - mSomePackagesChanged = true; - } else if (Intent.ACTION_PACKAGES_UNSUSPENDED.equals(action)) { - mSomePackagesChanged = true; - } - - if (mSomePackagesChanged) { - onSomePackagesChanged(); - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitor.java b/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitor.java deleted file mode 100644 index a86af85512afdfc84680e8fa69cb17c19ef1475c..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitor.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - * 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.server.nearby.common.servicemonitor; - -import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.Context; -import android.os.Handler; -import android.os.IBinder; -import android.os.RemoteException; - -import java.io.PrintWriter; -import java.util.Objects; -import java.util.concurrent.Executor; - -/** - * This is exported from frameworks ServiceWatcher. - * A ServiceMonitor is responsible for continuously maintaining an active binding to a service - * selected by it's {@link ServiceProvider}. The {@link ServiceProvider} may change the service it - * selects over time, and the currently bound service may crash, restart, have a user change, have - * changes made to its package, and so on and so forth. The ServiceMonitor is responsible for - * maintaining the binding across all these changes. - * - *

    Clients may invoke {@link BinderOperation}s on the ServiceMonitor, and it will make a best - * effort to run these on the currently bound service, but individual operations may fail (if there - * is no service currently bound for instance). In order to help clients maintain the correct state, - * clients may supply a {@link ServiceListener}, which is informed when the ServiceMonitor connects - * and disconnects from a service. This allows clients to bring a bound service back into a known - * state on connection, and then run binder operations from there. In order to help clients - * accomplish this, ServiceMonitor guarantees that {@link BinderOperation}s and the - * {@link ServiceListener} will always be run on the same thread, so that strong ordering guarantees - * can be established between them. - * - * There is never any guarantee of whether a ServiceMonitor is currently connected to a service, and - * whether any particular {@link BinderOperation} will succeed. Clients must ensure they do not rely - * on this, and instead use {@link ServiceListener} notifications as necessary to recover from - * failures. - */ -public interface ServiceMonitor { - - /** - * Operation to run on a binder interface. All operations will be run on the thread used by the - * ServiceMonitor this is run with. - */ - interface BinderOperation { - /** Invoked to run the operation. Run on the ServiceMonitor thread. */ - void run(IBinder binder) throws RemoteException; - - /** - * Invoked if {@link #run(IBinder)} could not be invoked because there was no current - * binding, or if {@link #run(IBinder)} threw an exception ({@link RemoteException} or - * {@link RuntimeException}). This callback is only intended for resource deallocation and - * cleanup in response to a single binder operation, it should not be used to propagate - * errors further. Run on the ServiceMonitor thread. - */ - default void onError() {} - } - - /** - * Listener for bind and unbind events. All operations will be run on the thread used by the - * ServiceMonitor this is run with. - * - * @param type of bound service - */ - interface ServiceListener { - /** Invoked when a service is bound. Run on the ServiceMonitor thread. */ - void onBind(IBinder binder, TBoundServiceInfo service) throws RemoteException; - - /** Invoked when a service is unbound. Run on the ServiceMonitor thread. */ - void onUnbind(); - } - - /** - * A listener for when a {@link ServiceProvider} decides that the current service has changed. - */ - interface ServiceChangedListener { - /** - * Should be invoked when the current service may have changed. - */ - void onServiceChanged(); - } - - /** - * This provider encapsulates the logic of deciding what service a {@link ServiceMonitor} should - * be bound to at any given moment. - * - * @param type of bound service - */ - interface ServiceProvider { - /** - * Should return true if there exists at least one service capable of meeting the criteria - * of this provider. This does not imply that {@link #getServiceInfo()} will always return a - * non-null result, as any service may be disqualified for various reasons at any point in - * time. May be invoked at any time from any thread and thus should generally not have any - * dependency on the other methods in this interface. - */ - boolean hasMatchingService(); - - /** - * Invoked when the provider should start monitoring for any changes that could result in a - * different service selection, and should invoke - * {@link ServiceChangedListener#onServiceChanged()} in that case. {@link #getServiceInfo()} - * may be invoked after this method is called. - */ - void register(ServiceChangedListener listener); - - /** - * Invoked when the provider should stop monitoring for any changes that could result in a - * different service selection, should no longer invoke - * {@link ServiceChangedListener#onServiceChanged()}. {@link #getServiceInfo()} will not be - * invoked after this method is called. - */ - void unregister(); - - /** - * Must be implemented to return the current service selected by this provider. May return - * null if no service currently meets the criteria. Only invoked while registered. - */ - @Nullable TBoundServiceInfo getServiceInfo(); - } - - /** - * Information on the service selected as the best option for binding. - */ - class BoundServiceInfo { - - protected final @Nullable String mAction; - protected final int mUid; - protected final ComponentName mComponentName; - - protected BoundServiceInfo(String action, int uid, ComponentName componentName) { - mAction = action; - mUid = uid; - mComponentName = Objects.requireNonNull(componentName); - } - - /** Returns the action associated with this bound service. */ - public @Nullable String getAction() { - return mAction; - } - - /** Returns the component of this bound service. */ - public ComponentName getComponentName() { - return mComponentName; - } - - @Override - public final boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof BoundServiceInfo)) { - return false; - } - - BoundServiceInfo that = (BoundServiceInfo) o; - return mUid == that.mUid - && Objects.equals(mAction, that.mAction) - && mComponentName.equals(that.mComponentName); - } - - @Override - public final int hashCode() { - return Objects.hash(mAction, mUid, mComponentName); - } - - @Override - public String toString() { - if (mComponentName == null) { - return "none"; - } else { - return mUid + "/" + mComponentName.flattenToShortString(); - } - } - } - - /** - * Creates a new ServiceMonitor instance. - */ - static ServiceMonitor create( - Context context, - String tag, - ServiceProvider serviceProvider, - @Nullable ServiceListener serviceListener) { - return create(context, ForegroundThread.getHandler(), ForegroundThread.getExecutor(), tag, - serviceProvider, serviceListener); - } - - /** - * Creates a new ServiceMonitor instance that runs on the given handler. - */ - static ServiceMonitor create( - Context context, - Handler handler, - Executor executor, - String tag, - ServiceProvider serviceProvider, - @Nullable ServiceListener serviceListener) { - return new ServiceMonitorImpl<>(context, handler, executor, tag, serviceProvider, - serviceListener); - } - - /** - * Returns true if there is at least one service that the ServiceMonitor could hypothetically - * bind to, as selected by the {@link ServiceProvider}. - */ - boolean checkServiceResolves(); - - /** - * Registers the ServiceMonitor, so that it will begin maintaining an active binding to the - * service selected by {@link ServiceProvider}, until {@link #unregister()} is called. - */ - void register(); - - /** - * Unregisters the ServiceMonitor, so that it will release any active bindings. If the - * ServiceMonitor is currently bound, this will result in one final - * {@link ServiceListener#onUnbind()} invocation, which may happen after this method completes - * (but which is guaranteed to occur before any further - * {@link ServiceListener#onBind(IBinder, BoundServiceInfo)} invocation in response to a later - * call to {@link #register()}). - */ - void unregister(); - - /** - * Runs the given binder operation on the currently bound service (if available). The operation - * will always fail if the ServiceMonitor is not currently registered. - */ - void runOnBinder(BinderOperation operation); - - /** - * Dumps ServiceMonitor information. - */ - void dump(PrintWriter pw); -} diff --git a/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitorImpl.java b/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitorImpl.java deleted file mode 100644 index d0d6c3b7a7cd153f828b75a0cfeebf4d07b5594a..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/common/servicemonitor/ServiceMonitorImpl.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * 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.server.nearby.common.servicemonitor; - -import static android.content.Context.BIND_AUTO_CREATE; -import static android.content.Context.BIND_NOT_FOREGROUND; - -import android.annotation.Nullable; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.RemoteException; -import android.util.Log; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.Preconditions; -import com.android.server.nearby.common.servicemonitor.ServiceMonitor.BoundServiceInfo; -import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceChangedListener; - -import java.io.PrintWriter; -import java.util.Objects; -import java.util.concurrent.Executor; - -/** - * Implementation of ServiceMonitor. Keeping the implementation separate from the interface allows - * us to store the generic relationship between the service provider and the service listener, while - * hiding the generics from clients, simplifying the API. - */ -class ServiceMonitorImpl implements ServiceMonitor, - ServiceChangedListener { - - private static final String TAG = "ServiceMonitor"; - private static final boolean D = Log.isLoggable(TAG, Log.DEBUG); - private static final long RETRY_DELAY_MS = 15 * 1000; - - // This is the same as Context.BIND_NOT_VISIBLE. - private static final int BIND_NOT_VISIBLE = 0x40000000; - - final Context mContext; - final Handler mHandler; - final Executor mExecutor; - final String mTag; - final ServiceProvider mServiceProvider; - final @Nullable ServiceListener mServiceListener; - - private final PackageWatcher mPackageWatcher = new PackageWatcher() { - @Override - public void onSomePackagesChanged() { - onServiceChanged(false); - } - }; - - @GuardedBy("this") - private boolean mRegistered = false; - @GuardedBy("this") - private MyServiceConnection mServiceConnection = new MyServiceConnection(null); - - ServiceMonitorImpl(Context context, Handler handler, Executor executor, String tag, - ServiceProvider serviceProvider, - ServiceListener serviceListener) { - mContext = context; - mExecutor = executor; - mHandler = handler; - mTag = tag; - mServiceProvider = serviceProvider; - mServiceListener = serviceListener; - } - - @Override - public boolean checkServiceResolves() { - return mServiceProvider.hasMatchingService(); - } - - @Override - public synchronized void register() { - Preconditions.checkState(!mRegistered); - - mRegistered = true; - mPackageWatcher.register(mContext, /*externalStorage=*/ true, mHandler); - mServiceProvider.register(this); - - onServiceChanged(false); - } - - @Override - public synchronized void unregister() { - Preconditions.checkState(mRegistered); - - mServiceProvider.unregister(); - mPackageWatcher.unregister(); - mRegistered = false; - - onServiceChanged(false); - } - - @Override - public synchronized void onServiceChanged() { - onServiceChanged(false); - } - - @Override - public synchronized void runOnBinder(BinderOperation operation) { - MyServiceConnection serviceConnection = mServiceConnection; - mHandler.post(() -> serviceConnection.runOnBinder(operation)); - } - - synchronized void onServiceChanged(boolean forceRebind) { - TBoundServiceInfo newBoundServiceInfo; - if (mRegistered) { - newBoundServiceInfo = mServiceProvider.getServiceInfo(); - } else { - newBoundServiceInfo = null; - } - - if (forceRebind || !Objects.equals(mServiceConnection.getBoundServiceInfo(), - newBoundServiceInfo)) { - Log.i(TAG, "[" + mTag + "] chose new implementation " + newBoundServiceInfo); - MyServiceConnection oldServiceConnection = mServiceConnection; - MyServiceConnection newServiceConnection = new MyServiceConnection(newBoundServiceInfo); - mServiceConnection = newServiceConnection; - mHandler.post(() -> { - oldServiceConnection.unbind(); - newServiceConnection.bind(); - }); - } - } - - @Override - public String toString() { - MyServiceConnection serviceConnection; - synchronized (this) { - serviceConnection = mServiceConnection; - } - - return serviceConnection.getBoundServiceInfo().toString(); - } - - @Override - public void dump(PrintWriter pw) { - MyServiceConnection serviceConnection; - synchronized (this) { - serviceConnection = mServiceConnection; - } - - pw.println("target service=" + serviceConnection.getBoundServiceInfo()); - pw.println("connected=" + serviceConnection.isConnected()); - } - - // runs on the handler thread, and expects most of its methods to be called from that thread - private class MyServiceConnection implements ServiceConnection { - - private final @Nullable TBoundServiceInfo mBoundServiceInfo; - - // volatile so that isConnected can be called from any thread easily - private volatile @Nullable IBinder mBinder; - private @Nullable Runnable mRebinder; - - MyServiceConnection(@Nullable TBoundServiceInfo boundServiceInfo) { - mBoundServiceInfo = boundServiceInfo; - } - - // may be called from any thread - @Nullable TBoundServiceInfo getBoundServiceInfo() { - return mBoundServiceInfo; - } - - // may be called from any thread - boolean isConnected() { - return mBinder != null; - } - - void bind() { - Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - - if (mBoundServiceInfo == null) { - return; - } - - if (D) { - Log.d(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo); - } - - Intent bindIntent = new Intent(mBoundServiceInfo.getAction()) - .setComponent(mBoundServiceInfo.getComponentName()); - if (!mContext.bindService(bindIntent, - BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE, - mExecutor, this)) { - Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later"); - mRebinder = this::bind; - mHandler.postDelayed(mRebinder, RETRY_DELAY_MS); - } else { - mRebinder = null; - } - } - - void unbind() { - Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - - if (mBoundServiceInfo == null) { - return; - } - - if (D) { - Log.d(TAG, "[" + mTag + "] unbinding from " + mBoundServiceInfo); - } - - if (mRebinder != null) { - mHandler.removeCallbacks(mRebinder); - mRebinder = null; - } else { - mContext.unbindService(this); - } - - onServiceDisconnected(mBoundServiceInfo.getComponentName()); - } - - void runOnBinder(BinderOperation operation) { - Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - - if (mBinder == null) { - operation.onError(); - return; - } - - try { - operation.run(mBinder); - } catch (RuntimeException | RemoteException e) { - // binders may propagate some specific non-RemoteExceptions from the other side - // through the binder as well - we cannot allow those to crash the system server - Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e); - operation.onError(); - } - } - - @Override - public final void onServiceConnected(ComponentName component, IBinder binder) { - Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - Preconditions.checkState(mBinder == null); - - Log.i(TAG, "[" + mTag + "] connected to " + component.toShortString()); - - mBinder = binder; - - if (mServiceListener != null) { - try { - mServiceListener.onBind(binder, mBoundServiceInfo); - } catch (RuntimeException | RemoteException e) { - // binders may propagate some specific non-RemoteExceptions from the other side - // through the binder as well - we cannot allow those to crash the system server - Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e); - } - } - } - - @Override - public final void onServiceDisconnected(ComponentName component) { - Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - - if (mBinder == null) { - return; - } - - Log.i(TAG, "[" + mTag + "] disconnected from " + mBoundServiceInfo); - - mBinder = null; - if (mServiceListener != null) { - mServiceListener.onUnbind(); - } - } - - @Override - public final void onBindingDied(ComponentName component) { - Preconditions.checkState(Looper.myLooper() == mHandler.getLooper()); - - Log.w(TAG, "[" + mTag + "] " + mBoundServiceInfo + " died"); - - // introduce a small delay to prevent spamming binding over and over, since the likely - // cause of a binding dying is some package event that may take time to recover from - mHandler.postDelayed(() -> onServiceChanged(true), 500); - } - - @Override - public final void onNullBinding(ComponentName component) { - Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " has null binding"); - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java deleted file mode 100644 index 412b7381aedb6b1365e28209142011b99c9d755b..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.fastpair; - -import static com.android.server.nearby.fastpair.Constant.TAG; - -import static com.google.common.primitives.Bytes.concat; - -import android.accounts.Account; -import android.annotation.Nullable; -import android.content.Context; -import android.nearby.FastPairDevice; -import android.nearby.NearbyDevice; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.nearby.common.ble.decode.FastPairDecoder; -import com.android.server.nearby.common.ble.util.RangingUtils; -import com.android.server.nearby.common.bloomfilter.BloomFilter; -import com.android.server.nearby.common.bloomfilter.FastPairBloomFilterHasher; -import com.android.server.nearby.common.locator.Locator; -import com.android.server.nearby.fastpair.cache.DiscoveryItem; -import com.android.server.nearby.fastpair.cache.FastPairCacheManager; -import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager; -import com.android.server.nearby.fastpair.notification.FastPairNotificationManager; -import com.android.server.nearby.provider.FastPairDataProvider; -import com.android.server.nearby.util.ArrayUtils; -import com.android.server.nearby.util.DataUtils; -import com.android.server.nearby.util.Hex; - -import java.util.List; - -import service.proto.Cache; -import service.proto.Data; -import service.proto.Rpcs; - -/** - * Handler that handle fast pair related broadcast. - */ -public class FastPairAdvHandler { - Context mContext; - String mBleAddress; - // TODO(b/247152236): Need to confirm the usage - // and deleted this after notification manager in use. - private boolean mIsFirst = true; - private FastPairDataProvider mPairDataProvider; - private static final double NEARBY_DISTANCE_THRESHOLD = 0.6; - // The byte, 0bLLLLTTTT, for battery length and type. - // Bit 0 - 3: type, 0b0011 (show UI indication) or 0b0100 (hide UI indication). - // Bit 4 - 7: length. - // https://developers.google.com/nearby/fast-pair/specifications/extensions/batterynotification - private static final byte SHOW_UI_INDICATION = 0b0011; - private static final byte HIDE_UI_INDICATION = 0b0100; - private static final int LENGTH_ADVERTISEMENT_TYPE_BIT = 4; - - /** The types about how the bloomfilter is processed. */ - public enum ProcessBloomFilterType { - IGNORE, // The bloomfilter is not handled. e.g. distance is too far away. - CACHE, // The bloomfilter is recognized in the local cache. - FOOTPRINT, // Need to check the bloomfilter from the footprints. - ACCOUNT_KEY_HIT // The specified account key was hit the bloom filter. - } - - /** - * Constructor function. - */ - public FastPairAdvHandler(Context context) { - mContext = context; - } - - @VisibleForTesting - FastPairAdvHandler(Context context, FastPairDataProvider dataProvider) { - mContext = context; - mPairDataProvider = dataProvider; - } - - /** - * Handles all of the scanner result. Fast Pair will handle model id broadcast bloomfilter - * broadcast and battery level broadcast. - */ - public void handleBroadcast(NearbyDevice device) { - FastPairDevice fastPairDevice = (FastPairDevice) device; - mBleAddress = fastPairDevice.getBluetoothAddress(); - if (mPairDataProvider == null) { - mPairDataProvider = FastPairDataProvider.getInstance(); - } - if (mPairDataProvider == null) { - return; - } - - if (FastPairDecoder.checkModelId(fastPairDevice.getData())) { - byte[] model = FastPairDecoder.getModelId(fastPairDevice.getData()); - Log.v(TAG, "On discovery model id " + Hex.bytesToStringLowercase(model)); - // Use api to get anti spoofing key from model id. - try { - List accountList = mPairDataProvider.loadFastPairEligibleAccounts(); - Rpcs.GetObservedDeviceResponse response = - mPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(model); - if (response == null) { - Log.e(TAG, "server does not have model id " - + Hex.bytesToStringLowercase(model)); - return; - } - // Check the distance of the device if the distance is larger than the threshold - // do not show half sheet. - if (!isNearby(fastPairDevice.getRssi(), - response.getDevice().getBleTxPower() == 0 ? fastPairDevice.getTxPower() - : response.getDevice().getBleTxPower())) { - return; - } - Locator.get(mContext, FastPairHalfSheetManager.class).showHalfSheet( - DataUtils.toScanFastPairStoreItem( - response, mBleAddress, Hex.bytesToStringLowercase(model), - accountList.isEmpty() ? null : accountList.get(0).name)); - } catch (IllegalStateException e) { - Log.e(TAG, "OEM does not construct fast pair data proxy correctly"); - } - } else { - // Start to process bloom filter. Yet to finish. - try { - subsequentPair(fastPairDevice); - } catch (IllegalStateException e) { - Log.e(TAG, "handleBroadcast: subsequent pair failed", e); - } - } - } - - @Nullable - @VisibleForTesting - static byte[] getBloomFilterBytes(byte[] data) { - byte[] bloomFilterBytes = FastPairDecoder.getBloomFilter(data); - if (bloomFilterBytes == null) { - bloomFilterBytes = FastPairDecoder.getBloomFilterNoNotification(data); - } - if (ArrayUtils.isEmpty(bloomFilterBytes)) { - Log.d(TAG, "subsequentPair: bloomFilterByteArray empty"); - return null; - } - return bloomFilterBytes; - } - - private int getTxPower(FastPairDevice scannedDevice, - Data.FastPairDeviceWithAccountKey recognizedDevice) { - return recognizedDevice.getDiscoveryItem().getTxPower() == 0 - ? scannedDevice.getTxPower() - : recognizedDevice.getDiscoveryItem().getTxPower(); - } - - private void subsequentPair(FastPairDevice scannedDevice) { - byte[] data = scannedDevice.getData(); - - if (ArrayUtils.isEmpty(data)) { - Log.d(TAG, "subsequentPair: no valid data"); - return; - } - - byte[] bloomFilterBytes = getBloomFilterBytes(data); - if (ArrayUtils.isEmpty(bloomFilterBytes)) { - Log.d(TAG, "subsequentPair: no valid bloom filter"); - return; - } - - byte[] salt = FastPairDecoder.getBloomFilterSalt(data); - if (ArrayUtils.isEmpty(salt)) { - Log.d(TAG, "subsequentPair: no valid salt"); - return; - } - byte[] saltWithData = concat(salt, generateBatteryData(data)); - - List accountList = mPairDataProvider.loadFastPairEligibleAccounts(); - for (Account account : accountList) { - List devices = - mPairDataProvider.loadFastPairDeviceWithAccountKey(account); - Data.FastPairDeviceWithAccountKey recognizedDevice = - findRecognizedDevice(devices, - new BloomFilter(bloomFilterBytes, - new FastPairBloomFilterHasher()), saltWithData); - if (recognizedDevice == null) { - Log.v(TAG, "subsequentPair: recognizedDevice is null"); - continue; - } - - // Check the distance of the device if the distance is larger than the - // threshold - if (!isNearby(scannedDevice.getRssi(), getTxPower(scannedDevice, recognizedDevice))) { - Log.v(TAG, - "subsequentPair: the distance of the device is larger than the threshold"); - return; - } - - // Check if the device is already paired - List storedFastPairItemList = - Locator.get(mContext, FastPairCacheManager.class) - .getAllSavedStoredFastPairItem(); - Cache.StoredFastPairItem recognizedStoredFastPairItem = - findRecognizedDeviceFromCachedItem(storedFastPairItemList, - new BloomFilter(bloomFilterBytes, - new FastPairBloomFilterHasher()), saltWithData); - if (recognizedStoredFastPairItem != null) { - // The bloomfilter is recognized in the cache so the device is paired - // before - Log.d(TAG, "bloom filter is recognized in the cache"); - continue; - } - showSubsequentNotification(account, scannedDevice, recognizedDevice); - } - } - - private void showSubsequentNotification(Account account, FastPairDevice scannedDevice, - Data.FastPairDeviceWithAccountKey recognizedDevice) { - // Get full info from api the initial request will only return - // part of the info due to size limit. - List devicesWithAccountKeys = - mPairDataProvider.loadFastPairDeviceWithAccountKey(account, - List.of(recognizedDevice.getAccountKey().toByteArray())); - if (devicesWithAccountKeys == null || devicesWithAccountKeys.isEmpty()) { - Log.d(TAG, "No fast pair device with account key is found."); - return; - } - - // Saved device from footprint does not have ble address. - // We need to fill ble address with current scan result. - Cache.StoredDiscoveryItem storedDiscoveryItem = - devicesWithAccountKeys.get(0).getDiscoveryItem().toBuilder() - .setMacAddress( - scannedDevice.getBluetoothAddress()) - .build(); - // Show notification - FastPairNotificationManager fastPairNotificationManager = - Locator.get(mContext, FastPairNotificationManager.class); - DiscoveryItem item = new DiscoveryItem(mContext, storedDiscoveryItem); - Locator.get(mContext, FastPairCacheManager.class).saveDiscoveryItem(item); - fastPairNotificationManager.showDiscoveryNotification(item, - devicesWithAccountKeys.get(0).getAccountKey().toByteArray()); - } - - // Battery advertisement format: - // Byte 0: Battery length and type, Bit 0 - 3: type, Bit 4 - 7: length. - // Byte 1 - 3: Battery values. - // Reference: - // https://developers.google.com/nearby/fast-pair/specifications/extensions/batterynotification - @VisibleForTesting - static byte[] generateBatteryData(byte[] data) { - byte[] batteryLevelNoNotification = FastPairDecoder.getBatteryLevelNoNotification(data); - boolean suppressBatteryNotification = - (batteryLevelNoNotification != null && batteryLevelNoNotification.length > 0); - byte[] batteryValues = - suppressBatteryNotification - ? batteryLevelNoNotification - : FastPairDecoder.getBatteryLevel(data); - if (ArrayUtils.isEmpty(batteryValues)) { - return new byte[0]; - } - return generateBatteryData(suppressBatteryNotification, batteryValues); - } - - @VisibleForTesting - static byte[] generateBatteryData(boolean suppressBatteryNotification, byte[] batteryValues) { - return concat( - new byte[] { - (byte) - (batteryValues.length << LENGTH_ADVERTISEMENT_TYPE_BIT - | (suppressBatteryNotification - ? HIDE_UI_INDICATION : SHOW_UI_INDICATION)) - }, - batteryValues); - } - - /** - * Checks the bloom filter to see if any of the devices are recognized and should have a - * notification displayed for them. A device is recognized if the account key + salt combination - * is inside the bloom filter. - */ - @Nullable - @VisibleForTesting - static Data.FastPairDeviceWithAccountKey findRecognizedDevice( - List devices, BloomFilter bloomFilter, byte[] salt) { - for (Data.FastPairDeviceWithAccountKey device : devices) { - if (device.getAccountKey().toByteArray() == null || salt == null) { - return null; - } - byte[] rotatedKey = concat(device.getAccountKey().toByteArray(), salt); - - StringBuilder sb = new StringBuilder(); - for (byte b : rotatedKey) { - sb.append(b); - } - - if (bloomFilter.possiblyContains(rotatedKey)) { - return device; - } - } - return null; - } - - @Nullable - static Cache.StoredFastPairItem findRecognizedDeviceFromCachedItem( - List devices, BloomFilter bloomFilter, byte[] salt) { - for (Cache.StoredFastPairItem device : devices) { - if (device.getAccountKey().toByteArray() == null || salt == null) { - return null; - } - byte[] rotatedKey = concat(device.getAccountKey().toByteArray(), salt); - if (bloomFilter.possiblyContains(rotatedKey)) { - return device; - } - } - return null; - } - - /** - * Check the device distance for certain rssi value. - */ - boolean isNearby(int rssi, int txPower) { - return RangingUtils.distanceFromRssiAndTxPower(rssi, txPower) < NEARBY_DISTANCE_THRESHOLD; - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java deleted file mode 100644 index 447d19984f20f4adf6b0c1ccf1a99e426000de56..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * 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.server.nearby.fastpair; - -import static com.google.common.primitives.Bytes.concat; - -import android.accounts.Account; -import android.annotation.Nullable; -import android.content.Context; -import android.nearby.FastPairDevice; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.WorkerThread; - -import com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress; -import com.android.server.nearby.common.eventloop.Annotations; -import com.android.server.nearby.common.eventloop.EventLoop; -import com.android.server.nearby.common.eventloop.NamedRunnable; -import com.android.server.nearby.common.locator.Locator; -import com.android.server.nearby.fastpair.cache.DiscoveryItem; -import com.android.server.nearby.fastpair.cache.FastPairCacheManager; -import com.android.server.nearby.fastpair.footprint.FastPairUploadInfo; -import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager; -import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager; -import com.android.server.nearby.fastpair.notification.FastPairNotificationManager; -import com.android.server.nearby.fastpair.pairinghandler.PairingProgressHandlerBase; -import com.android.server.nearby.provider.FastPairDataProvider; - -import com.google.common.hash.Hashing; -import com.google.protobuf.ByteString; -import com.google.protobuf.InvalidProtocolBufferException; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import service.proto.Cache; - -/** - * FastPair controller after get the info from intent handler Fast Pair controller is responsible - * for pairing control. - */ -public class FastPairController { - private static final String TAG = "FastPairController"; - private final Context mContext; - private final EventLoop mEventLoop; - private final FastPairCacheManager mFastPairCacheManager; - private final FootprintsDeviceManager mFootprintsDeviceManager; - private boolean mIsFastPairing = false; - // boolean flag whether upload to footprint or not. - private boolean mShouldUpload = false; - @Nullable - private Callback mCallback; - - public FastPairController(Context context) { - mContext = context; - mEventLoop = Locator.get(mContext, EventLoop.class); - mFastPairCacheManager = Locator.get(mContext, FastPairCacheManager.class); - mFootprintsDeviceManager = Locator.get(mContext, FootprintsDeviceManager.class); - } - - /** - * Should be called on create lifecycle. - */ - @WorkerThread - public void onCreate() { - mEventLoop.postRunnable(new NamedRunnable("FastPairController::InitializeScanner") { - @Override - public void run() { - // init scanner here and start scan. - } - }); - } - - /** - * Should be called on destroy lifecycle. - */ - @WorkerThread - public void onDestroy() { - mEventLoop.postRunnable(new NamedRunnable("FastPairController::DestroyScanner") { - @Override - public void run() { - // Unregister scanner from here - } - }); - } - - /** - * Pairing function. - */ - public void pair(FastPairDevice fastPairDevice) { - byte[] discoveryItem = fastPairDevice.getData(); - String modelId = fastPairDevice.getModelId(); - - Log.v(TAG, "pair: fastPairDevice " + fastPairDevice); - mEventLoop.postRunnable( - new NamedRunnable("fastPairWith=" + modelId) { - @Override - public void run() { - try { - DiscoveryItem item = new DiscoveryItem(mContext, - Cache.StoredDiscoveryItem.parseFrom(discoveryItem)); - if (TextUtils.isEmpty(item.getMacAddress())) { - Log.w(TAG, "There is no mac address in the DiscoveryItem," - + " ignore pairing"); - return; - } - // Check enabled state to prevent multiple pair attempts if we get the - // intent more than once (this can happen due to an Android platform - // bug - b/31459521). - if (item.getState() - != Cache.StoredDiscoveryItem.State.STATE_ENABLED) { - Log.d(TAG, "Incorrect state, ignore pairing"); - return; - } - FastPairNotificationManager fastPairNotificationManager = - Locator.get(mContext, FastPairNotificationManager.class); - FastPairHalfSheetManager fastPairHalfSheetManager = - Locator.get(mContext, FastPairHalfSheetManager.class); - mFastPairCacheManager.saveDiscoveryItem(item); - - PairingProgressHandlerBase pairingProgressHandlerBase = - PairingProgressHandlerBase.create( - mContext, - item, - /* companionApp= */ null, - /* accountKey= */ null, - mFootprintsDeviceManager, - fastPairNotificationManager, - fastPairHalfSheetManager, - /* isRetroactivePair= */ false); - - pair(item, - /* accountKey= */ null, - /* companionApp= */ null, - pairingProgressHandlerBase); - } catch (InvalidProtocolBufferException e) { - Log.w(TAG, - "Error parsing serialized discovery item with size " - + discoveryItem.length); - } - } - }); - } - - /** - * Subsequent pairing entry. - */ - public void pair(DiscoveryItem item, - @Nullable byte[] accountKey, - @Nullable String companionApp) { - FastPairNotificationManager fastPairNotificationManager = - Locator.get(mContext, FastPairNotificationManager.class); - FastPairHalfSheetManager fastPairHalfSheetManager = - Locator.get(mContext, FastPairHalfSheetManager.class); - PairingProgressHandlerBase pairingProgressHandlerBase = - PairingProgressHandlerBase.create( - mContext, - item, - /* companionApp= */ null, - /* accountKey= */ accountKey, - mFootprintsDeviceManager, - fastPairNotificationManager, - fastPairHalfSheetManager, - /* isRetroactivePair= */ false); - pair(item, accountKey, companionApp, pairingProgressHandlerBase); - } - /** - * Pairing function - */ - @Annotations.EventThread - public void pair( - DiscoveryItem item, - @Nullable byte[] accountKey, - @Nullable String companionApp, - PairingProgressHandlerBase pairingProgressHandlerBase) { - if (mIsFastPairing) { - Log.d(TAG, "FastPair: fastpairing, skip pair request"); - return; - } - mIsFastPairing = true; - Log.d(TAG, "FastPair: start pair"); - - // Hide all "tap to pair" notifications until after the flow completes. - mEventLoop.removeRunnable(mReEnableAllDeviceItemsRunnable); - if (mCallback != null) { - mCallback.fastPairUpdateDeviceItemsEnabled(false); - } - - Future task = - FastPairManager.pair( - Executors.newSingleThreadExecutor(), - mContext, - item, - accountKey, - companionApp, - mFootprintsDeviceManager, - pairingProgressHandlerBase); - mIsFastPairing = false; - } - - /** Fixes a companion app package name with extra spaces. */ - private static String trimCompanionApp(String companionApp) { - return companionApp == null ? null : companionApp.trim(); - } - - /** - * Function to handle when scanner find bloomfilter. - */ - @Annotations.EventThread - public FastPairAdvHandler.ProcessBloomFilterType onBloomFilterDetect(FastPairAdvHandler handler, - boolean advertiseInRange) { - if (mIsFastPairing) { - return FastPairAdvHandler.ProcessBloomFilterType.IGNORE; - } - // Check if the device is in the cache or footprint. - return FastPairAdvHandler.ProcessBloomFilterType.CACHE; - } - - /** - * Add newly paired device info to footprint - */ - @WorkerThread - public void addDeviceToFootprint(String publicAddress, byte[] accountKey, - DiscoveryItem discoveryItem) { - if (!mShouldUpload) { - return; - } - Log.d(TAG, "upload device to footprint"); - FastPairManager.processBackgroundTask(() -> { - Cache.StoredDiscoveryItem storedDiscoveryItem = - prepareStoredDiscoveryItemForFootprints(discoveryItem); - byte[] hashValue = - Hashing.sha256() - .hashBytes( - concat(accountKey, BluetoothAddress.decode(publicAddress))) - .asBytes(); - FastPairUploadInfo uploadInfo = - new FastPairUploadInfo(storedDiscoveryItem, ByteString.copyFrom(accountKey), - ByteString.copyFrom(hashValue)); - // account data place holder here - try { - FastPairDataProvider fastPairDataProvider = FastPairDataProvider.getInstance(); - if (fastPairDataProvider == null) { - return; - } - List accountList = fastPairDataProvider.loadFastPairEligibleAccounts(); - if (accountList.size() > 0) { - fastPairDataProvider.optIn(accountList.get(0)); - fastPairDataProvider.upload(accountList.get(0), uploadInfo); - } - } catch (IllegalStateException e) { - Log.e(TAG, "OEM does not construct fast pair data proxy correctly"); - } - }); - } - - @Nullable - private Cache.StoredDiscoveryItem getStoredDiscoveryItemFromAddressForFootprints( - String bleAddress) { - - List discoveryItems = new ArrayList<>(); - //cacheManager.getAllDiscoveryItems(); - for (DiscoveryItem discoveryItem : discoveryItems) { - if (bleAddress.equals(discoveryItem.getMacAddress())) { - return prepareStoredDiscoveryItemForFootprints(discoveryItem); - } - } - return null; - } - - static Cache.StoredDiscoveryItem prepareStoredDiscoveryItemForFootprints( - DiscoveryItem discoveryItem) { - Cache.StoredDiscoveryItem.Builder storedDiscoveryItem = - discoveryItem.getCopyOfStoredItem().toBuilder(); - // Strip the mac address so we aren't storing it in the cloud and ensure the item always - // starts as enabled and in a good state. - storedDiscoveryItem.clearMacAddress(); - - return storedDiscoveryItem.build(); - } - - /** - * FastPairConnection will check whether write account key result if the account key is - * generated change the parameter. - */ - public void setShouldUpload(boolean shouldUpload) { - mShouldUpload = shouldUpload; - } - - private final NamedRunnable mReEnableAllDeviceItemsRunnable = - new NamedRunnable("reEnableAllDeviceItems") { - @Override - public void run() { - if (mCallback != null) { - mCallback.fastPairUpdateDeviceItemsEnabled(true); - } - } - }; - - interface Callback { - void fastPairUpdateDeviceItemsEnabled(boolean enabled); - } -} \ No newline at end of file diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java deleted file mode 100644 index 8a9614ed71c38ee13638fa56272dc933c7b21463..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java +++ /dev/null @@ -1,511 +0,0 @@ -/* - * 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.server.nearby.fastpair; - -import static com.android.nearby.halfsheet.constants.Constant.ACTION_FAST_PAIR; -import static com.android.nearby.halfsheet.constants.Constant.ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET; -import static com.android.nearby.halfsheet.constants.Constant.ACTION_FAST_PAIR_HALF_SHEET_CANCEL; -import static com.android.nearby.halfsheet.constants.Constant.ACTION_HALF_SHEET_FOREGROUND_STATE; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_FOREGROUND; -import static com.android.nearby.halfsheet.constants.FastPairConstants.EXTRA_MODEL_ID; -import static com.android.server.nearby.fastpair.Constant.TAG; - -import static com.google.common.io.BaseEncoding.base16; - -import android.annotation.Nullable; -import android.annotation.WorkerThread; -import android.app.KeyguardManager; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.database.ContentObserver; -import android.nearby.FastPairDevice; -import android.nearby.NearbyDevice; -import android.nearby.NearbyManager; -import android.nearby.ScanCallback; -import android.nearby.ScanRequest; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.android.server.nearby.common.ble.decode.FastPairDecoder; -import com.android.server.nearby.common.bluetooth.BluetoothException; -import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection; -import com.android.server.nearby.common.bluetooth.fastpair.FastPairDualConnection; -import com.android.server.nearby.common.bluetooth.fastpair.PairingException; -import com.android.server.nearby.common.bluetooth.fastpair.Preferences; -import com.android.server.nearby.common.bluetooth.fastpair.ReflectionException; -import com.android.server.nearby.common.bluetooth.fastpair.SimpleBroadcastReceiver; -import com.android.server.nearby.common.eventloop.Annotations; -import com.android.server.nearby.common.eventloop.EventLoop; -import com.android.server.nearby.common.eventloop.NamedRunnable; -import com.android.server.nearby.common.locator.Locator; -import com.android.server.nearby.common.locator.LocatorContextWrapper; -import com.android.server.nearby.fastpair.cache.DiscoveryItem; -import com.android.server.nearby.fastpair.cache.FastPairCacheManager; -import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager; -import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager; -import com.android.server.nearby.fastpair.pairinghandler.PairingProgressHandlerBase; -import com.android.server.nearby.util.ForegroundThread; -import com.android.server.nearby.util.Hex; - -import com.google.common.collect.ImmutableList; -import com.google.protobuf.ByteString; - -import java.security.GeneralSecurityException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import service.proto.Cache; -import service.proto.Rpcs; - -/** - * FastPairManager is the class initiated in nearby service to handle Fast Pair related - * work. - */ - -public class FastPairManager { - - private static final String ACTION_PREFIX = UserActionHandler.PREFIX; - private static final int WAIT_FOR_UNLOCK_MILLIS = 5000; - - /** A notification ID which should be dismissed */ - public static final String EXTRA_NOTIFICATION_ID = ACTION_PREFIX + "EXTRA_NOTIFICATION_ID"; - - private static Executor sFastPairExecutor; - - private ContentObserver mFastPairScanChangeContentObserver = null; - - final LocatorContextWrapper mLocatorContextWrapper; - final IntentFilter mIntentFilter; - final Locator mLocator; - private boolean mScanEnabled = false; - private final FastPairCacheManager mFastPairCacheManager; - - private final BroadcastReceiver mScreenBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - switch (action) { - case Intent.ACTION_SCREEN_ON: - Log.d(TAG, "onReceive: ACTION_SCREEN_ON"); - invalidateScan(); - break; - case Intent.ACTION_BOOT_COMPLETED: - Log.d(TAG, "onReceive: ACTION_BOOT_COMPLETED."); - invalidateScan(); - break; - case BluetoothDevice.ACTION_BOND_STATE_CHANGED: - Log.d(TAG, "onReceive: ACTION_BOND_STATE_CHANGED"); - processBluetoothConnectionEvent(intent); - break; - case ACTION_HALF_SHEET_FOREGROUND_STATE: - boolean state = intent.getBooleanExtra(EXTRA_HALF_SHEET_FOREGROUND, false); - Log.d(TAG, "halfsheet report foreground state: " + state); - Locator.get(mLocatorContextWrapper, FastPairHalfSheetManager.class) - .setHalfSheetForeground(state); - break; - case ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET: - Log.d(TAG, "onReceive: ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET"); - String deviceModelId = intent.getStringExtra(EXTRA_MODEL_ID); - if (deviceModelId == null) { - Log.d(TAG, "HalfSheetManager reset device ban state skipped, " - + "deviceModelId not found"); - break; - } - Locator.get(mLocatorContextWrapper, FastPairHalfSheetManager.class) - .resetBanState(deviceModelId); - break; - case ACTION_FAST_PAIR_HALF_SHEET_CANCEL: - Log.d(TAG, "onReceive: ACTION_FAST_PAIR_HALF_SHEET_CANCEL"); - String modelId = intent.getStringExtra(EXTRA_MODEL_ID); - if (modelId == null) { - Log.d(TAG, "skip half sheet cancel action, model id not found"); - break; - } - Locator.get(mLocatorContextWrapper, FastPairHalfSheetManager.class) - .dismiss(modelId); - break; - case ACTION_FAST_PAIR: - Log.d(TAG, "onReceive: ACTION_FAST_PAIR"); - String itemId = intent.getStringExtra(UserActionHandler.EXTRA_ITEM_ID); - String accountKeyString = intent - .getStringExtra(UserActionHandler.EXTRA_FAST_PAIR_SECRET); - if (itemId == null || accountKeyString == null) { - Log.d(TAG, "skip pair action, item id " - + "or fast pair account key not found"); - break; - } - try { - FastPairController controller = - Locator.getFromContextWrapper(mLocatorContextWrapper, - FastPairController.class); - if (mFastPairCacheManager != null) { - controller.pair(mFastPairCacheManager.getDiscoveryItem(itemId), - base16().decode(accountKeyString), /* companionApp= */ null); - } - } catch (IllegalStateException e) { - Log.e(TAG, "Cannot find FastPairController class", e); - } - } - } - }; - - public FastPairManager(LocatorContextWrapper contextWrapper) { - mLocatorContextWrapper = contextWrapper; - mIntentFilter = new IntentFilter(); - mLocator = mLocatorContextWrapper.getLocator(); - mLocator.bind(new FastPairModule()); - Rpcs.GetObservedDeviceResponse getObservedDeviceResponse = - Rpcs.GetObservedDeviceResponse.newBuilder().build(); - mFastPairCacheManager = - Locator.getFromContextWrapper(mLocatorContextWrapper, FastPairCacheManager.class); - } - - final ScanCallback mScanCallback = new ScanCallback() { - @Override - public void onDiscovered(@NonNull NearbyDevice device) { - Locator.get(mLocatorContextWrapper, FastPairAdvHandler.class).handleBroadcast(device); - } - - @Override - public void onUpdated(@NonNull NearbyDevice device) { - FastPairDevice fastPairDevice = (FastPairDevice) device; - byte[] modelArray = FastPairDecoder.getModelId(fastPairDevice.getData()); - Log.d(TAG, "update model id" + Hex.bytesToStringLowercase(modelArray)); - } - - @Override - public void onLost(@NonNull NearbyDevice device) { - FastPairDevice fastPairDevice = (FastPairDevice) device; - byte[] modelArray = FastPairDecoder.getModelId(fastPairDevice.getData()); - Log.d(TAG, "lost model id" + Hex.bytesToStringLowercase(modelArray)); - } - - @Override - public void onError(int errorCode) { - Log.w(TAG, "[FastPairManager] Scan error is " + errorCode); - } - }; - - /** - * Function called when nearby service start. - */ - public void initiate() { - mIntentFilter.addAction(Intent.ACTION_SCREEN_ON); - mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF); - mIntentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); - mIntentFilter.addAction(Intent.ACTION_BOOT_COMPLETED); - mIntentFilter.addAction(ACTION_FAST_PAIR_HALF_SHEET_CANCEL); - mIntentFilter.addAction(ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET); - mIntentFilter.addAction(ACTION_HALF_SHEET_FOREGROUND_STATE); - mIntentFilter.addAction(ACTION_FAST_PAIR); - - mLocatorContextWrapper.getContext().registerReceiver(mScreenBroadcastReceiver, - mIntentFilter, Context.RECEIVER_EXPORTED); - } - - /** - * Function to free up fast pair resource. - */ - public void cleanUp() { - mLocatorContextWrapper.getContext().unregisterReceiver(mScreenBroadcastReceiver); - if (mFastPairScanChangeContentObserver != null) { - mLocatorContextWrapper.getContentResolver().unregisterContentObserver( - mFastPairScanChangeContentObserver); - } - } - - /** - * Starts fast pair process. - */ - @Annotations.EventThread - public static Future pair( - ExecutorService executor, - Context context, - DiscoveryItem item, - @Nullable byte[] accountKey, - @Nullable String companionApp, - FootprintsDeviceManager footprints, - PairingProgressHandlerBase pairingProgressHandlerBase) { - return executor.submit( - () -> pairInternal(context, item, companionApp, accountKey, footprints, - pairingProgressHandlerBase), /* result= */ null); - } - - /** - * Starts fast pair - */ - @WorkerThread - public static void pairInternal( - Context context, - DiscoveryItem item, - @Nullable String companionApp, - @Nullable byte[] accountKey, - FootprintsDeviceManager footprints, - PairingProgressHandlerBase pairingProgressHandlerBase) { - FastPairHalfSheetManager fastPairHalfSheetManager = - Locator.get(context, FastPairHalfSheetManager.class); - try { - pairingProgressHandlerBase.onPairingStarted(); - if (pairingProgressHandlerBase.skipWaitingScreenUnlock()) { - // Do nothing due to we are not showing the status notification in some pairing - // types, e.g. the retroactive pairing. - } else { - // If the screen is locked when the user taps to pair, the screen will unlock. We - // must wait for the unlock to complete before showing the status notification, or - // it won't be heads-up. - pairingProgressHandlerBase.onWaitForScreenUnlock(); - waitUntilScreenIsUnlocked(context); - pairingProgressHandlerBase.onScreenUnlocked(); - } - BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(context); - - boolean isBluetoothEnabled = bluetoothAdapter != null && bluetoothAdapter.isEnabled(); - if (!isBluetoothEnabled) { - if (bluetoothAdapter == null || !bluetoothAdapter.enable()) { - Log.d(TAG, "FastPair: Failed to enable bluetooth"); - return; - } - Log.v(TAG, "FastPair: Enabling bluetooth for fast pair"); - - Locator.get(context, EventLoop.class) - .postRunnable( - new NamedRunnable("enableBluetoothToast") { - @Override - public void run() { - Log.d(TAG, "Enable bluetooth toast test"); - } - }); - // Set up call back to call this function again once bluetooth has been - // enabled; this does not seem to be a problem as the device connects without a - // problem, but in theory the timeout also includes turning on bluetooth now. - } - - pairingProgressHandlerBase.onReadyToPair(); - - String modelId = item.getTriggerId(); - Preferences.Builder prefsBuilder = - Preferences.builder() - .setEnableBrEdrHandover(false) - .setIgnoreDiscoveryError(true); - pairingProgressHandlerBase.onSetupPreferencesBuilder(prefsBuilder); - if (item.getFastPairInformation() != null) { - prefsBuilder.setSkipConnectingProfiles( - item.getFastPairInformation().getDataOnlyConnection()); - } - // When add watch and auto device needs to change the config - prefsBuilder.setRejectMessageAccess(true); - prefsBuilder.setRejectPhonebookAccess(true); - prefsBuilder.setHandlePasskeyConfirmationByUi(false); - - FastPairConnection connection = new FastPairDualConnection( - context, item.getMacAddress(), - prefsBuilder.build(), - null); - connection.setOnPairedCallback( - address -> { - Log.v(TAG, "connection on paired callback;"); - // TODO(b/259150992) add fill Bluetooth metadata values logic - pairingProgressHandlerBase.onPairedCallbackCalled( - connection, accountKey, footprints, address); - }); - pairingProgressHandlerBase.onPairingSetupCompleted(); - - FastPairConnection.SharedSecret sharedSecret; - if ((accountKey != null || item.getAuthenticationPublicKeySecp256R1() != null)) { - sharedSecret = - connection.pair( - accountKey != null ? accountKey - : item.getAuthenticationPublicKeySecp256R1()); - if (accountKey == null) { - // Account key is null so it is initial pairing - if (sharedSecret != null) { - Locator.get(context, FastPairController.class).addDeviceToFootprint( - connection.getPublicAddress(), sharedSecret.getKey(), item); - cacheFastPairDevice(context, connection.getPublicAddress(), - sharedSecret.getKey(), item); - } - } - } else { - // Fast Pair one - connection.pair(); - } - // TODO(b/213373051): Merge logic with pairingProgressHandlerBase or delete the - // pairingProgressHandlerBase class. - fastPairHalfSheetManager.showPairingSuccessHalfSheet(connection.getPublicAddress()); - pairingProgressHandlerBase.onPairingSuccess(connection.getPublicAddress()); - } catch (BluetoothException - | InterruptedException - | ReflectionException - | TimeoutException - | ExecutionException - | PairingException - | GeneralSecurityException e) { - Log.e(TAG, "Failed to pair.", e); - - // TODO(b/213373051): Merge logic with pairingProgressHandlerBase or delete the - // pairingProgressHandlerBase class. - fastPairHalfSheetManager.showPairingFailed(); - pairingProgressHandlerBase.onPairingFailed(e); - } - } - - private static void cacheFastPairDevice(Context context, String publicAddress, byte[] key, - DiscoveryItem item) { - try { - Locator.get(context, EventLoop.class).postAndWait( - new NamedRunnable("FastPairCacheDevice") { - @Override - public void run() { - Cache.StoredFastPairItem storedFastPairItem = - Cache.StoredFastPairItem.newBuilder() - .setMacAddress(publicAddress) - .setAccountKey(ByteString.copyFrom(key)) - .setModelId(item.getTriggerId()) - .addAllFeatures(item.getFastPairInformation() == null - ? ImmutableList.of() : - item.getFastPairInformation().getFeaturesList()) - .setDiscoveryItem(item.getCopyOfStoredItem()) - .build(); - Locator.get(context, FastPairCacheManager.class) - .putStoredFastPairItem(storedFastPairItem); - } - } - ); - } catch (InterruptedException e) { - Log.e(TAG, "Fail to insert paired device into cache"); - } - } - - /** Checks if the pairing is initial pairing with fast pair 2.0 design. */ - public static boolean isThroughFastPair2InitialPairing( - DiscoveryItem item, @Nullable byte[] accountKey) { - return accountKey == null && item.getAuthenticationPublicKeySecp256R1() != null; - } - - private static void waitUntilScreenIsUnlocked(Context context) - throws InterruptedException, ExecutionException, TimeoutException { - KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class); - - // KeyguardManager's isScreenLocked() counterintuitively returns false when the lock screen - // is showing if the user has set "swipe to unlock" (i.e. no required password, PIN, or - // pattern) So we use this method instead, which returns true when on the lock screen - // regardless. - if (keyguardManager.isKeyguardLocked()) { - Log.v(TAG, "FastPair: Screen is locked, waiting until unlocked " - + "to show status notifications."); - try (SimpleBroadcastReceiver isUnlockedReceiver = - SimpleBroadcastReceiver.oneShotReceiver( - context, FlagUtils.getPreferencesBuilder().build(), - Intent.ACTION_USER_PRESENT)) { - isUnlockedReceiver.await(WAIT_FOR_UNLOCK_MILLIS, TimeUnit.MILLISECONDS); - } - } - } - - /** - * Processed task in a background thread - */ - @Annotations.EventThread - public static void processBackgroundTask(Runnable runnable) { - getExecutor().execute(runnable); - } - - /** - * This function should only be called on main thread since there is no lock - */ - private static Executor getExecutor() { - if (sFastPairExecutor != null) { - return sFastPairExecutor; - } - sFastPairExecutor = Executors.newSingleThreadExecutor(); - return sFastPairExecutor; - } - - /** - * Null when the Nearby Service is not available. - */ - @Nullable - private NearbyManager getNearbyManager() { - return (NearbyManager) mLocatorContextWrapper - .getApplicationContext().getSystemService(Context.NEARBY_SERVICE); - } - - /** - * Starts or stops scanning according to mAllowScan value. - */ - private void invalidateScan() { - NearbyManager nearbyManager = getNearbyManager(); - if (nearbyManager == null) { - Log.w(TAG, "invalidateScan: " - + "failed to start or stop scanning because NearbyManager is null."); - return; - } - if (mScanEnabled) { - Log.v(TAG, "invalidateScan: scan is enabled"); - nearbyManager.startScan(new ScanRequest.Builder() - .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR).build(), - ForegroundThread.getExecutor(), - mScanCallback); - } else { - Log.v(TAG, "invalidateScan: scan is disabled"); - nearbyManager.stopScan(mScanCallback); - } - } - - /** - * When certain device is forgotten we need to remove the info from database because the info - * is no longer useful. - */ - private void processBluetoothConnectionEvent(Intent intent) { - int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, - BluetoothDevice.ERROR); - if (bondState == BluetoothDevice.BOND_NONE) { - BluetoothDevice device = - intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (device != null) { - Log.d("FastPairService", "Forget device detect"); - processBackgroundTask(new Runnable() { - @Override - public void run() { - mFastPairCacheManager.removeStoredFastPairItem(device.getAddress()); - } - }); - } - - } - } - - /** - * Helper function to get bluetooth adapter. - */ - @Nullable - public static BluetoothAdapter getBluetoothAdapter(Context context) { - BluetoothManager manager = context.getSystemService(BluetoothManager.class); - return manager == null ? null : manager.getAdapter(); - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java deleted file mode 100644 index 1df4723e6f2587f3241b81c046834e2923cab91b..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.server.nearby.fastpair; - -import android.content.Context; - -import com.android.server.nearby.common.eventloop.EventLoop; -import com.android.server.nearby.common.locator.Locator; -import com.android.server.nearby.common.locator.Module; -import com.android.server.nearby.fastpair.cache.FastPairCacheManager; -import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager; -import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager; -import com.android.server.nearby.fastpair.notification.FastPairNotificationManager; - -import java.time.Clock; -import java.time.Instant; -import java.time.ZoneId; - -/** - * Module that associates all of the fast pair related singleton class - */ -public class FastPairModule extends Module { - /** - * Initiate the class that needs to be singleton. - */ - @Override - public void configure(Context context, Class type, Locator locator) { - if (type.equals(FastPairCacheManager.class)) { - locator.bind(FastPairCacheManager.class, new FastPairCacheManager(context)); - } else if (type.equals(FootprintsDeviceManager.class)) { - locator.bind(FootprintsDeviceManager.class, new FootprintsDeviceManager()); - } else if (type.equals(EventLoop.class)) { - locator.bind(EventLoop.class, EventLoop.newInstance("NearbyFastPair")); - } else if (type.equals(FastPairController.class)) { - locator.bind(FastPairController.class, new FastPairController(context)); - } else if (type.equals(FastPairCacheManager.class)) { - locator.bind(FastPairCacheManager.class, new FastPairCacheManager(context)); - } else if (type.equals(FastPairHalfSheetManager.class)) { - locator.bind(FastPairHalfSheetManager.class, new FastPairHalfSheetManager(context)); - } else if (type.equals(FastPairAdvHandler.class)) { - locator.bind(FastPairAdvHandler.class, new FastPairAdvHandler(context)); - } else if (type.equals(FastPairNotificationManager.class)) { - locator.bind(FastPairNotificationManager.class, - new FastPairNotificationManager(context)); - } else if (type.equals(Clock.class)) { - locator.bind(Clock.class, new Clock() { - @Override - public ZoneId getZone() { - return null; - } - - @Override - public Clock withZone(ZoneId zone) { - return null; - } - - @Override - public Instant instant() { - return null; - } - }); - } - - } - - /** - * Clean up the singleton classes. - */ - @Override - public void destroy(Context context, Class type, Object instance) { - super.destroy(context, type, instance); - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FlagUtils.java b/nearby/service/java/com/android/server/nearby/fastpair/FlagUtils.java deleted file mode 100644 index 883a1f895f9fecbec9862dd048aeee3c88d8400a..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/FlagUtils.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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.server.nearby.fastpair; - -import android.text.TextUtils; - -import com.android.server.nearby.common.bluetooth.fastpair.Preferences; - -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableSet; - -/** - * This is fast pair connection preference - */ -public class FlagUtils { - private static final int GATT_OPERATION_TIME_OUT_SECOND = 10; - private static final int GATT_CONNECTION_TIME_OUT_SECOND = 15; - private static final int BLUETOOTH_TOGGLE_TIME_OUT_SECOND = 10; - private static final int BLUETOOTH_TOGGLE_SLEEP_TIME_OUT_SECOND = 2; - private static final int CLASSIC_DISCOVERY_TIME_OUT_SECOND = 13; - private static final int NUM_DISCOVER_ATTEMPTS = 3; - private static final int DISCOVERY_RETRY_SLEEP_SECONDS = 1; - private static final int SDP_TIME_OUT_SECONDS = 10; - private static final int NUM_SDP_ATTEMPTS = 0; - private static final int NUM_CREATED_BOND_ATTEMPTS = 3; - private static final int NUM_CONNECT_ATTEMPT = 2; - private static final int NUM_WRITE_ACCOUNT_KEY_ATTEMPT = 3; - private static final boolean TOGGLE_BLUETOOTH_ON_FAILURE = false; - private static final boolean BLUETOOTH_STATE_POOLING = true; - private static final int BLUETOOTH_STATE_POOLING_MILLIS = 1000; - private static final int NUM_ATTEMPTS = 2; - private static final short BREDR_HANDOVER_DATA_CHARACTERISTIC_ID = 11265; // 0x2c01 - private static final short BLUETOOTH_SIG_DATA_CHARACTERISTIC_ID = 11266; // 0x2c02 - private static final short TRANSPORT_BLOCK_DATA_CHARACTERISTIC_ID = 11267; // 0x2c03 - private static final boolean WAIT_FOR_UUID_AFTER_BONDING = true; - private static final boolean RECEIVE_UUID_AND_BONDED_EVENT_BEFORE_CLOSE = true; - private static final int REMOVE_BOND_TIME_OUT_SECONDS = 5; - private static final int REMOVE_BOND_SLEEP_MILLIS = 1000; - private static final int CREATE_BOND_TIME_OUT_SECONDS = 15; - private static final int HIDE_CREATED_BOND_TIME_OUT_SECONDS = 40; - private static final int PROXY_TIME_OUT_SECONDS = 2; - private static final boolean REJECT_ACCESS = false; - private static final boolean ACCEPT_PASSKEY = true; - private static final int WRITE_ACCOUNT_KEY_SLEEP_MILLIS = 2000; - private static final boolean PROVIDER_INITIATE_BONDING = false; - private static final boolean SPECIFY_CREATE_BOND_TRANSPORT_TYPE = false; - private static final int CREATE_BOND_TRANSPORT_TYPE = 0; - private static final boolean KEEP_SAME_ACCOUNT_KEY_WRITE = true; - private static final boolean ENABLE_NAMING_CHARACTERISTIC = true; - private static final boolean CHECK_FIRMWARE_VERSION = true; - private static final int SDP_ATTEMPTS_AFTER_BONDED = 1; - private static final boolean SUPPORT_HID = false; - private static final boolean ENABLE_PAIRING_WHILE_DIRECTLY_CONNECTING = true; - private static final boolean ACCEPT_CONSENT_FOR_FP_ONE = true; - private static final int GATT_CONNECT_RETRY_TIMEOUT_MILLIS = 18000; - private static final boolean ENABLE_128BIT_CUSTOM_GATT_CHARACTERISTIC = true; - private static final boolean ENABLE_SEND_EXCEPTION_STEP_TO_VALIDATOR = true; - private static final boolean ENABLE_ADDITIONAL_DATA_TYPE_WHEN_ACTION_OVER_BLE = true; - private static final boolean CHECK_BOND_STATE_WHEN_SKIP_CONNECTING_PROFILE = true; - private static final boolean MORE_LOG_FOR_QUALITY = true; - private static final boolean RETRY_GATT_CONNECTION_AND_SECRET_HANDSHAKE = true; - private static final int GATT_CONNECT_SHORT_TIMEOUT_MS = 7000; - private static final int GATT_CONNECTION_LONG_TIME_OUT_MS = 15000; - private static final int GATT_CONNECT_SHORT_TIMEOUT_RETRY_MAX_SPENT_TIME_MS = 1000; - private static final int ADDRESS_ROTATE_RETRY_MAX_SPENT_TIME_MS = 15000; - private static final int PAIRING_RETRY_DELAY_MS = 100; - private static final int HANDSHAKE_SHORT_TIMEOUT_MS = 3000; - private static final int HANDSHAKE_LONG_TIMEOUT_MS = 1000; - private static final int SECRET_HANDSHAKE_SHORT_TIMEOUT_RETRY_MAX_SPENT_TIME_MS = 5000; - private static final int SECRET_HANDSHAKE_LONG_TIMEOUT_RETRY_MAX_SPENT_TIME_MS = 7000; - private static final int SECRET_HANDSHAKE_RETRY_ATTEMPTS = 3; - private static final int SECRET_HANDSHAKE_RETRY_GATT_CONNECTION_MAX_SPENT_TIME_MS = 15000; - private static final int SIGNAL_LOST_RETRY_MAX_SPENT_TIME_MS = 15000; - private static final boolean RETRY_SECRET_HANDSHAKE_TIMEOUT = false; - private static final boolean LOG_USER_MANUAL_RETRY = true; - private static final boolean ENABLE_PAIR_FLOW_SHOW_UI_WITHOUT_PROFILE_CONNECTION = false; - private static final boolean LOG_USER_MANUAL_CITY = true; - private static final boolean LOG_PAIR_WITH_CACHED_MODEL_ID = true; - private static final boolean DIRECT_CONNECT_PROFILE_IF_MODEL_ID_IN_CACHE = false; - - public static Preferences.Builder getPreferencesBuilder() { - return Preferences.builder() - .setGattOperationTimeoutSeconds(GATT_OPERATION_TIME_OUT_SECOND) - .setGattConnectionTimeoutSeconds(GATT_CONNECTION_TIME_OUT_SECOND) - .setBluetoothToggleTimeoutSeconds(BLUETOOTH_TOGGLE_TIME_OUT_SECOND) - .setBluetoothToggleSleepSeconds(BLUETOOTH_TOGGLE_SLEEP_TIME_OUT_SECOND) - .setClassicDiscoveryTimeoutSeconds(CLASSIC_DISCOVERY_TIME_OUT_SECOND) - .setNumDiscoverAttempts(NUM_DISCOVER_ATTEMPTS) - .setDiscoveryRetrySleepSeconds(DISCOVERY_RETRY_SLEEP_SECONDS) - .setSdpTimeoutSeconds(SDP_TIME_OUT_SECONDS) - .setNumSdpAttempts(NUM_SDP_ATTEMPTS) - .setNumCreateBondAttempts(NUM_CREATED_BOND_ATTEMPTS) - .setNumConnectAttempts(NUM_CONNECT_ATTEMPT) - .setNumWriteAccountKeyAttempts(NUM_WRITE_ACCOUNT_KEY_ATTEMPT) - .setToggleBluetoothOnFailure(TOGGLE_BLUETOOTH_ON_FAILURE) - .setBluetoothStateUsesPolling(BLUETOOTH_STATE_POOLING) - .setBluetoothStatePollingMillis(BLUETOOTH_STATE_POOLING_MILLIS) - .setNumAttempts(NUM_ATTEMPTS) - .setBrHandoverDataCharacteristicId(BREDR_HANDOVER_DATA_CHARACTERISTIC_ID) - .setBluetoothSigDataCharacteristicId(BLUETOOTH_SIG_DATA_CHARACTERISTIC_ID) - .setBrTransportBlockDataDescriptorId(TRANSPORT_BLOCK_DATA_CHARACTERISTIC_ID) - .setWaitForUuidsAfterBonding(WAIT_FOR_UUID_AFTER_BONDING) - .setReceiveUuidsAndBondedEventBeforeClose( - RECEIVE_UUID_AND_BONDED_EVENT_BEFORE_CLOSE) - .setRemoveBondTimeoutSeconds(REMOVE_BOND_TIME_OUT_SECONDS) - .setRemoveBondSleepMillis(REMOVE_BOND_SLEEP_MILLIS) - .setCreateBondTimeoutSeconds(CREATE_BOND_TIME_OUT_SECONDS) - .setHidCreateBondTimeoutSeconds(HIDE_CREATED_BOND_TIME_OUT_SECONDS) - .setProxyTimeoutSeconds(PROXY_TIME_OUT_SECONDS) - .setRejectPhonebookAccess(REJECT_ACCESS) - .setRejectMessageAccess(REJECT_ACCESS) - .setRejectSimAccess(REJECT_ACCESS) - .setAcceptPasskey(ACCEPT_PASSKEY) - .setWriteAccountKeySleepMillis(WRITE_ACCOUNT_KEY_SLEEP_MILLIS) - .setProviderInitiatesBondingIfSupported(PROVIDER_INITIATE_BONDING) - .setAttemptDirectConnectionWhenPreviouslyBonded(true) - .setAutomaticallyReconnectGattWhenNeeded(true) - .setSkipDisconnectingGattBeforeWritingAccountKey(true) - .setIgnoreUuidTimeoutAfterBonded(true) - .setSpecifyCreateBondTransportType(SPECIFY_CREATE_BOND_TRANSPORT_TYPE) - .setCreateBondTransportType(CREATE_BOND_TRANSPORT_TYPE) - .setIncreaseIntentFilterPriority(true) - .setEvaluatePerformance(false) - .setKeepSameAccountKeyWrite(KEEP_SAME_ACCOUNT_KEY_WRITE) - .setEnableNamingCharacteristic(ENABLE_NAMING_CHARACTERISTIC) - .setEnableFirmwareVersionCharacteristic(CHECK_FIRMWARE_VERSION) - .setNumSdpAttemptsAfterBonded(SDP_ATTEMPTS_AFTER_BONDED) - .setSupportHidDevice(SUPPORT_HID) - .setEnablePairingWhileDirectlyConnecting( - ENABLE_PAIRING_WHILE_DIRECTLY_CONNECTING) - .setAcceptConsentForFastPairOne(ACCEPT_CONSENT_FOR_FP_ONE) - .setGattConnectRetryTimeoutMillis(GATT_CONNECT_RETRY_TIMEOUT_MILLIS) - .setEnable128BitCustomGattCharacteristicsId( - ENABLE_128BIT_CUSTOM_GATT_CHARACTERISTIC) - .setEnableSendExceptionStepToValidator(ENABLE_SEND_EXCEPTION_STEP_TO_VALIDATOR) - .setEnableAdditionalDataTypeWhenActionOverBle( - ENABLE_ADDITIONAL_DATA_TYPE_WHEN_ACTION_OVER_BLE) - .setCheckBondStateWhenSkipConnectingProfiles( - CHECK_BOND_STATE_WHEN_SKIP_CONNECTING_PROFILE) - .setMoreEventLogForQuality(MORE_LOG_FOR_QUALITY) - .setRetryGattConnectionAndSecretHandshake( - RETRY_GATT_CONNECTION_AND_SECRET_HANDSHAKE) - .setGattConnectShortTimeoutMs(GATT_CONNECT_SHORT_TIMEOUT_MS) - .setGattConnectLongTimeoutMs(GATT_CONNECTION_LONG_TIME_OUT_MS) - .setGattConnectShortTimeoutRetryMaxSpentTimeMs( - GATT_CONNECT_SHORT_TIMEOUT_RETRY_MAX_SPENT_TIME_MS) - .setAddressRotateRetryMaxSpentTimeMs(ADDRESS_ROTATE_RETRY_MAX_SPENT_TIME_MS) - .setPairingRetryDelayMs(PAIRING_RETRY_DELAY_MS) - .setSecretHandshakeShortTimeoutMs(HANDSHAKE_SHORT_TIMEOUT_MS) - .setSecretHandshakeLongTimeoutMs(HANDSHAKE_LONG_TIMEOUT_MS) - .setSecretHandshakeShortTimeoutRetryMaxSpentTimeMs( - SECRET_HANDSHAKE_SHORT_TIMEOUT_RETRY_MAX_SPENT_TIME_MS) - .setSecretHandshakeLongTimeoutRetryMaxSpentTimeMs( - SECRET_HANDSHAKE_LONG_TIMEOUT_RETRY_MAX_SPENT_TIME_MS) - .setSecretHandshakeRetryAttempts(SECRET_HANDSHAKE_RETRY_ATTEMPTS) - .setSecretHandshakeRetryGattConnectionMaxSpentTimeMs( - SECRET_HANDSHAKE_RETRY_GATT_CONNECTION_MAX_SPENT_TIME_MS) - .setSignalLostRetryMaxSpentTimeMs(SIGNAL_LOST_RETRY_MAX_SPENT_TIME_MS) - .setGattConnectionAndSecretHandshakeNoRetryGattError( - getGattConnectionAndSecretHandshakeNoRetryGattError()) - .setRetrySecretHandshakeTimeout(RETRY_SECRET_HANDSHAKE_TIMEOUT) - .setLogUserManualRetry(LOG_USER_MANUAL_RETRY) - .setEnablePairFlowShowUiWithoutProfileConnection( - ENABLE_PAIR_FLOW_SHOW_UI_WITHOUT_PROFILE_CONNECTION) - .setLogUserManualRetry(LOG_USER_MANUAL_CITY) - .setLogPairWithCachedModelId(LOG_PAIR_WITH_CACHED_MODEL_ID) - .setDirectConnectProfileIfModelIdInCache( - DIRECT_CONNECT_PROFILE_IF_MODEL_ID_IN_CACHE); - } - - private static ImmutableSet getGattConnectionAndSecretHandshakeNoRetryGattError() { - ImmutableSet.Builder noRetryGattErrorsBuilder = ImmutableSet.builder(); - // When GATT connection fail we will not retry on error code 257 - for (String errorCode : - Splitter.on(",").split("257,")) { - if (!TextUtils.isDigitsOnly(errorCode)) { - continue; - } - - try { - noRetryGattErrorsBuilder.add(Integer.parseInt(errorCode)); - } catch (NumberFormatException e) { - // Ignore - } - } - return noRetryGattErrorsBuilder.build(); - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/HalfSheetResources.java b/nearby/service/java/com/android/server/nearby/fastpair/HalfSheetResources.java deleted file mode 100644 index 86dd44dbdc99989ac3da9d04f62859206766a810..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/HalfSheetResources.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.fastpair; - -import static com.android.server.nearby.fastpair.Constant.TAG; - -import android.annotation.ColorInt; -import android.annotation.ColorRes; -import android.annotation.DrawableRes; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.StringRes; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; - -/** - * Utility to obtain the {@link com.android.nearby.halfsheet} {@link Resources}, in the - * HalfSheetUX APK. - * @hide - */ -public class HalfSheetResources { - @NonNull - private final Context mContext; - - @Nullable - private Context mResourcesContext = null; - - @Nullable - private static Context sTestResourcesContext = null; - - public HalfSheetResources(Context context) { - mContext = context; - } - - /** - * Convenience method to mock all resources for the duration of a test. - * - * Call with a null context to reset after the test. - */ - @VisibleForTesting - public static void setResourcesContextForTest(@Nullable Context testContext) { - sTestResourcesContext = testContext; - } - - /** - * Get the {@link Context} of the resources package. - */ - @Nullable - public synchronized Context getResourcesContext() { - if (sTestResourcesContext != null) { - return sTestResourcesContext; - } - - if (mResourcesContext != null) { - return mResourcesContext; - } - - String packageName = PackageUtils.getHalfSheetApkPkgName(mContext); - if (packageName == null) { - Log.e(TAG, "Resolved package not found"); - return null; - } - final Context pkgContext; - try { - pkgContext = mContext.createPackageContext(packageName, 0 /* flags */); - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Resolved package not found"); - return null; - } - - mResourcesContext = pkgContext; - return pkgContext; - } - - /** - * Get the {@link Resources} of the ServiceConnectivityResources APK. - */ - public Resources get() { - return getResourcesContext().getResources(); - } - - /** - * Gets the {@code String} with given resource Id. - */ - public String getString(@StringRes int id) { - return get().getString(id); - } - - /** - * Gets the {@code String} with given resource Id and formatted arguments. - */ - public String getString(@StringRes int id, Object... formatArgs) { - return get().getString(id, formatArgs); - } - - /** - * Gets the {@link Drawable} with given resource Id. - */ - public Drawable getDrawable(@DrawableRes int id) { - return get().getDrawable(id, getResourcesContext().getTheme()); - } - - /** - * Gets a themed color integer associated with a particular resource ID. - */ - @ColorInt - public int getColor(@ColorRes int id) { - return get().getColor(id, getResourcesContext().getTheme()); - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/PackageUtils.java b/nearby/service/java/com/android/server/nearby/fastpair/PackageUtils.java deleted file mode 100644 index 0ff8caf1af25abeca5d28de873eeabf77202484c..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/PackageUtils.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.fastpair; - -import static com.android.nearby.halfsheet.constants.Constant.ACTION_RESOURCES_APK; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.util.Log; - -import com.android.server.nearby.util.Environment; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * Helper class for package related methods. - */ -public class PackageUtils { - - /** - * Gets the package name of HalfSheet.apk - */ - @Nullable - public static String getHalfSheetApkPkgName(Context context) { - List resolveInfos = context - .getPackageManager().queryIntentActivities( - new Intent(ACTION_RESOURCES_APK), - PackageManager.MATCH_SYSTEM_ONLY); - - // remove apps that don't live in the nearby apex - resolveInfos.removeIf(info -> - !Environment.isAppInNearbyApex(info.activityInfo.applicationInfo)); - - if (resolveInfos.isEmpty()) { - // Resource APK not loaded yet, print a stack trace to see where this is called from - Log.e("FastPairManager", "Attempted to fetch resources before halfsheet " - + " APK is installed or package manager can't resolve correctly!", - new IllegalStateException()); - return null; - } - - if (resolveInfos.size() > 1) { - // multiple apps found, log a warning, but continue - Log.w("FastPairManager", "Found > 1 APK that can resolve halfsheet APK intent: " - + resolveInfos.stream() - .map(info -> info.activityInfo.applicationInfo.packageName) - .collect(Collectors.joining(", "))); - } - - // Assume the first ResolveInfo is the one we're looking for - ResolveInfo info = resolveInfos.get(0); - return info.activityInfo.applicationInfo.packageName; - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/blocklist/Blocklist.java b/nearby/service/java/com/android/server/nearby/fastpair/blocklist/Blocklist.java deleted file mode 100644 index d8091ba11663265e588edb82879ac149e687c2f7..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/blocklist/Blocklist.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.fastpair.blocklist; - - -/** - * Skeletal implementation of Blocklist - * - *

    Controls the frequency to show the available device to users. - */ -public interface Blocklist { - - /** Checks certain item is blocked within durationSeconds. */ - boolean isBlocklisted(int id, int durationSeconds); - - /** Updates the HalfSheet blocklist state for a given id. */ - boolean updateState(int id, BlocklistState state); - - /** Removes the HalfSheet blocklist. */ - boolean removeBlocklist(int id); - - /** Resets certain device ban state to active. */ - void resetBlockState(int id); - - /** - * Used for indicate what state is the blocklist item. - * - *

    The different states have differing priorities and higher priority states will override - * lower one. - * More details and state transition diagram, - * see: https://docs.google.com/document/d/1wzE5CHXTkzKJY-2AltSrxOVteom2Nebc1sbjw1Tt7BQ/edit?usp=sharing&resourcekey=0-L-wUz3Hw5gZPThm5VPwHOQ - */ - enum BlocklistState { - UNKNOWN(0), - ACTIVE(1), - DISMISSED(2), - PAIRING(3), - PAIRED(4), - DO_NOT_SHOW_AGAIN(5), - DO_NOT_SHOW_AGAIN_LONG(6); - - private final int mValue; - - BlocklistState(final int value) { - this.mValue = value; - } - - public boolean hasHigherPriorityThan(BlocklistState otherState) { - return this.mValue > otherState.mValue; - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/blocklist/BlocklistElement.java b/nearby/service/java/com/android/server/nearby/fastpair/blocklist/BlocklistElement.java deleted file mode 100644 index d058d5822f5dbce83f89f008326bdf26fcad44e5..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/blocklist/BlocklistElement.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.fastpair.blocklist; - -import com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState; -import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetBlocklist; - -/** Element in the {@link FastPairHalfSheetBlocklist} */ -public class BlocklistElement { - private final long mTimeStamp; - private final BlocklistState mState; - - public BlocklistElement(BlocklistState state, long timeStamp) { - this.mState = state; - this.mTimeStamp = timeStamp; - } - - public Long getTimeStamp() { - return mTimeStamp; - } - - public BlocklistState getState() { - return mState; - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java deleted file mode 100644 index 5ce4488b55c40d52df97242768a71c8d9ce823a2..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java +++ /dev/null @@ -1,463 +0,0 @@ -/* - * 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.server.nearby.fastpair.cache; - -import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress; -import static com.android.server.nearby.fastpair.UserActionHandler.EXTRA_FAST_PAIR_SECRET; - -import android.annotation.IntDef; -import android.annotation.Nullable; -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.text.TextUtils; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.nearby.common.ble.util.RangingUtils; -import com.android.server.nearby.common.fastpair.IconUtils; -import com.android.server.nearby.common.locator.Locator; -import com.android.server.nearby.common.locator.LocatorContextWrapper; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.net.URISyntaxException; -import java.time.Clock; -import java.util.Objects; - -import service.proto.Cache; - -/** - * Wrapper class around StoredDiscoveryItem. A centralized place for methods related to - * updating/parsing StoredDiscoveryItem. - */ -public class DiscoveryItem implements Comparable { - - private static final String ACTION_FAST_PAIR = - "com.android.server.nearby:ACTION_FAST_PAIR"; - private static final int BEACON_STALENESS_MILLIS = 120000; - private static final int ITEM_EXPIRATION_MILLIS = 20000; - private static final int APP_INSTALL_EXPIRATION_MILLIS = 600000; - private static final int ITEM_DELETABLE_MILLIS = 15000; - - private final FastPairCacheManager mFastPairCacheManager; - private final Clock mClock; - - private Cache.StoredDiscoveryItem mStoredDiscoveryItem; - - /** IntDef for StoredDiscoveryItem.State */ - @IntDef({ - Cache.StoredDiscoveryItem.State.STATE_ENABLED_VALUE, - Cache.StoredDiscoveryItem.State.STATE_MUTED_VALUE, - Cache.StoredDiscoveryItem.State.STATE_DISABLED_BY_SYSTEM_VALUE - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ItemState { - } - - public DiscoveryItem(LocatorContextWrapper locatorContextWrapper, - Cache.StoredDiscoveryItem mStoredDiscoveryItem) { - this.mFastPairCacheManager = - locatorContextWrapper.getLocator().get(FastPairCacheManager.class); - this.mClock = - locatorContextWrapper.getLocator().get(Clock.class); - this.mStoredDiscoveryItem = mStoredDiscoveryItem; - } - - public DiscoveryItem(Context context, Cache.StoredDiscoveryItem mStoredDiscoveryItem) { - this.mFastPairCacheManager = Locator.get(context, FastPairCacheManager.class); - this.mClock = Locator.get(context, Clock.class); - this.mStoredDiscoveryItem = mStoredDiscoveryItem; - } - - /** @return A new StoredDiscoveryItem with state fields set to their defaults. */ - public static Cache.StoredDiscoveryItem newStoredDiscoveryItem() { - Cache.StoredDiscoveryItem.Builder storedDiscoveryItem = - Cache.StoredDiscoveryItem.newBuilder(); - storedDiscoveryItem.setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED); - return storedDiscoveryItem.build(); - } - - /** - * Checks if store discovery item support fast pair or not. - */ - public boolean isFastPair() { - Intent intent = parseIntentScheme(mStoredDiscoveryItem.getActionUrl()); - if (intent == null) { - Log.w("FastPairDiscovery", "FastPair: fail to parse action url" - + mStoredDiscoveryItem.getActionUrl()); - return false; - } - return ACTION_FAST_PAIR.equals(intent.getAction()); - } - - /** - * Checks if the item is expired. Expired items are those over getItemExpirationMillis() eg. 2 - * minutes - */ - public static boolean isExpired( - long currentTimestampMillis, @Nullable Long lastObservationTimestampMillis) { - if (lastObservationTimestampMillis == null) { - return true; - } - return (currentTimestampMillis - lastObservationTimestampMillis) - >= ITEM_EXPIRATION_MILLIS; - } - - /** - * Checks if the item is deletable for saving disk space. Deletable items are those over - * getItemDeletableMillis eg. over 25 hrs. - */ - public static boolean isDeletable( - long currentTimestampMillis, @Nullable Long lastObservationTimestampMillis) { - if (lastObservationTimestampMillis == null) { - return true; - } - return currentTimestampMillis - lastObservationTimestampMillis - >= ITEM_DELETABLE_MILLIS; - } - - /** Checks if the item has a pending app install */ - public boolean isPendingAppInstallValid() { - return isPendingAppInstallValid(mClock.millis()); - } - - /** - * Checks if pending app valid. - */ - public boolean isPendingAppInstallValid(long appInstallMillis) { - return isPendingAppInstallValid(appInstallMillis, mStoredDiscoveryItem); - } - - /** - * Checks if the app install time expired. - */ - public static boolean isPendingAppInstallValid( - long currentMillis, Cache.StoredDiscoveryItem storedItem) { - return currentMillis - storedItem.getPendingAppInstallTimestampMillis() - < APP_INSTALL_EXPIRATION_MILLIS; - } - - - /** Checks if the item has enough data to be shown */ - public boolean isReadyForDisplay() { - boolean hasUrlOrPopularApp = !mStoredDiscoveryItem.getActionUrl().isEmpty(); - - return !TextUtils.isEmpty(mStoredDiscoveryItem.getTitle()) && hasUrlOrPopularApp; - } - - /** Checks if the action url is app install */ - public boolean isApp() { - return mStoredDiscoveryItem.getActionUrlType() == Cache.ResolvedUrlType.APP; - } - - /** Returns true if an item is muted, or if state is unavailable. */ - public boolean isMuted() { - return mStoredDiscoveryItem.getState() != Cache.StoredDiscoveryItem.State.STATE_ENABLED; - } - - /** - * Returns the state of store discovery item. - */ - public Cache.StoredDiscoveryItem.State getState() { - return mStoredDiscoveryItem.getState(); - } - - /** Checks if it's device item. e.g. Chromecast / Wear */ - public static boolean isDeviceType(Cache.NearbyType type) { - return type == Cache.NearbyType.NEARBY_CHROMECAST - || type == Cache.NearbyType.NEARBY_WEAR - || type == Cache.NearbyType.NEARBY_DEVICE; - } - - /** - * Check if the type is supported. - */ - public static boolean isTypeEnabled(Cache.NearbyType type) { - switch (type) { - case NEARBY_WEAR: - case NEARBY_CHROMECAST: - case NEARBY_DEVICE: - return true; - default: - Log.e("FastPairDiscoveryItem", "Invalid item type " + type.name()); - return false; - } - } - - /** Gets hash code of UI related data so we can collapse identical items. */ - public int getUiHashCode() { - return Objects.hash( - mStoredDiscoveryItem.getTitle(), - mStoredDiscoveryItem.getDescription(), - mStoredDiscoveryItem.getAppName(), - mStoredDiscoveryItem.getDisplayUrl(), - mStoredDiscoveryItem.getMacAddress()); - } - - // Getters below - - /** - * Returns the id of store discovery item. - */ - @Nullable - public String getId() { - return mStoredDiscoveryItem.getId(); - } - - /** - * Returns the title of discovery item. - */ - @Nullable - public String getTitle() { - return mStoredDiscoveryItem.getTitle(); - } - - /** - * Returns the description of discovery item. - */ - @Nullable - public String getDescription() { - return mStoredDiscoveryItem.getDescription(); - } - - /** - * Returns the mac address of discovery item. - */ - @Nullable - public String getMacAddress() { - return mStoredDiscoveryItem.getMacAddress(); - } - - /** - * Returns the display url of discovery item. - */ - @Nullable - public String getDisplayUrl() { - return mStoredDiscoveryItem.getDisplayUrl(); - } - - /** - * Returns the public key of discovery item. - */ - @Nullable - public byte[] getAuthenticationPublicKeySecp256R1() { - return mStoredDiscoveryItem.getAuthenticationPublicKeySecp256R1().toByteArray(); - } - - /** - * Returns the pairing secret. - */ - @Nullable - public String getFastPairSecretKey() { - Intent intent = parseIntentScheme(mStoredDiscoveryItem.getActionUrl()); - if (intent == null) { - Log.d("FastPairDiscoveryItem", "FastPair: fail to parse action url " - + mStoredDiscoveryItem.getActionUrl()); - return null; - } - return intent.getStringExtra(EXTRA_FAST_PAIR_SECRET); - } - - /** - * Returns the fast pair info of discovery item. - */ - @Nullable - public Cache.FastPairInformation getFastPairInformation() { - return mStoredDiscoveryItem.hasFastPairInformation() - ? mStoredDiscoveryItem.getFastPairInformation() : null; - } - - /** - * Returns the app name of discovery item. - */ - @Nullable - @VisibleForTesting - protected String getAppName() { - return mStoredDiscoveryItem.getAppName(); - } - - /** - * Returns the package name of discovery item. - */ - @Nullable - public String getAppPackageName() { - return mStoredDiscoveryItem.getPackageName(); - } - - /** - * Returns the action url of discovery item. - */ - @Nullable - public String getActionUrl() { - return mStoredDiscoveryItem.getActionUrl(); - } - - /** - * Returns the rssi value of discovery item. - */ - @Nullable - public Integer getRssi() { - return mStoredDiscoveryItem.getRssi(); - } - - /** - * Returns the TX power of discovery item. - */ - @Nullable - public Integer getTxPower() { - return mStoredDiscoveryItem.getTxPower(); - } - - /** - * Returns the first observed time stamp of discovery item. - */ - @Nullable - public Long getFirstObservationTimestampMillis() { - return mStoredDiscoveryItem.getFirstObservationTimestampMillis(); - } - - /** - * Returns the last observed time stamp of discovery item. - */ - @Nullable - public Long getLastObservationTimestampMillis() { - return mStoredDiscoveryItem.getLastObservationTimestampMillis(); - } - - /** - * Calculates an estimated distance for the item, computed from the TX power (at 1m) and RSSI. - * - * @return estimated distance, or null if there is no RSSI or no TX power. - */ - @Nullable - public Double getEstimatedDistance() { - // In the future, we may want to do a foreground subscription to leverage onDistanceChanged. - return RangingUtils.distanceFromRssiAndTxPower(mStoredDiscoveryItem.getRssi(), - mStoredDiscoveryItem.getTxPower()); - } - - /** - * Gets icon Bitmap from icon store. - * - * @return null if no icon or icon size is incorrect. - */ - @Nullable - public Bitmap getIcon() { - Bitmap icon = - BitmapFactory.decodeByteArray( - mStoredDiscoveryItem.getIconPng().toByteArray(), - 0 /* offset */, mStoredDiscoveryItem.getIconPng().size()); - if (IconUtils.isIconSizeCorrect(icon)) { - return icon; - } else { - return null; - } - } - - /** Gets a FIFE URL of the icon. */ - @Nullable - public String getIconFifeUrl() { - return mStoredDiscoveryItem.getIconFifeUrl(); - } - - /** - * Compares this object to the specified object: 1. By device type. Device setups are 'greater - * than' beacons. 2. By relevance. More relevant items are 'greater than' less relevant items. - * 3.By distance. Nearer items are 'greater than' further items. - * - *

    In the list view, we sort in descending order, i.e. we put the most relevant items first. - */ - @Override - public int compareTo(DiscoveryItem another) { - // For items of the same relevance, compare distance. - Double distance1 = getEstimatedDistance(); - Double distance2 = another.getEstimatedDistance(); - distance1 = distance1 != null ? distance1 : Double.MAX_VALUE; - distance2 = distance2 != null ? distance2 : Double.MAX_VALUE; - // Negate because closer items are better ("greater than") further items. - return -distance1.compareTo(distance2); - } - - @Nullable - public String getTriggerId() { - return mStoredDiscoveryItem.getTriggerId(); - } - - @Override - public boolean equals(Object another) { - if (another instanceof DiscoveryItem) { - return ((DiscoveryItem) another).mStoredDiscoveryItem.equals(mStoredDiscoveryItem); - } - return false; - } - - @Override - public int hashCode() { - return mStoredDiscoveryItem.hashCode(); - } - - @Override - public String toString() { - return String.format( - "[triggerId=%s], [id=%s], [title=%s], [url=%s], [ready=%s], [macAddress=%s]", - getTriggerId(), - getId(), - getTitle(), - getActionUrl(), - isReadyForDisplay(), - maskBluetoothAddress(getMacAddress())); - } - - /** - * Gets a copy of the StoredDiscoveryItem proto backing this DiscoveryItem. Currently needed for - * Fast Pair 2.0: We store the item in the cloud associated with a user's account, to enable - * pairing with other devices owned by the user. - */ - public Cache.StoredDiscoveryItem getCopyOfStoredItem() { - return mStoredDiscoveryItem; - } - - /** - * Gets the StoredDiscoveryItem represented by this DiscoveryItem. This lets tests manipulate - * values that production code should not manipulate. - */ - - public Cache.StoredDiscoveryItem getStoredItemForTest() { - return mStoredDiscoveryItem; - } - - /** - * Sets the StoredDiscoveryItem represented by this DiscoveryItem. This lets tests manipulate - * values that production code should not manipulate. - */ - public void setStoredItemForTest(Cache.StoredDiscoveryItem s) { - mStoredDiscoveryItem = s; - } - - /** - * Parse the intent from item url. - */ - public static Intent parseIntentScheme(String uri) { - try { - return Intent.parseUri(uri, Intent.URI_INTENT_SCHEME); - } catch (URISyntaxException e) { - return null; - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java deleted file mode 100644 index c6134f5db25cccf86ebca17a4c28608ec4c8722a..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * 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.server.nearby.fastpair.cache; - -import android.bluetooth.le.ScanResult; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.util.Log; - -import com.android.server.nearby.common.eventloop.Annotations; - -import com.google.protobuf.InvalidProtocolBufferException; - -import java.util.ArrayList; -import java.util.List; - -import service.proto.Cache; -import service.proto.Rpcs; - - -/** - * Save FastPair device info to database to avoid multiple requesting. - */ -public class FastPairCacheManager { - private final Context mContext; - private final FastPairDbHelper mFastPairDbHelper; - - public FastPairCacheManager(Context context) { - mContext = context; - mFastPairDbHelper = new FastPairDbHelper(context); - } - - /** - * Clean up function to release db - */ - public void cleanUp() { - mFastPairDbHelper.close(); - } - - /** - * Saves the response to the db - */ - private void saveDevice() { - } - - Cache.ServerResponseDbItem getDeviceFromScanResult(ScanResult scanResult) { - return Cache.ServerResponseDbItem.newBuilder().build(); - } - - /** - * Save discovery item into database. Discovery item is item that discovered through Ble before - * pairing success. - */ - public boolean saveDiscoveryItem(DiscoveryItem item) { - - SQLiteDatabase db = mFastPairDbHelper.getWritableDatabase(); - ContentValues values = new ContentValues(); - values.put(DiscoveryItemContract.DiscoveryItemEntry.COLUMN_MODEL_ID, item.getTriggerId()); - values.put(DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE, - item.getCopyOfStoredItem().toByteArray()); - db.insert(DiscoveryItemContract.DiscoveryItemEntry.TABLE_NAME, null, values); - return true; - } - - - @Annotations.EventThread - private Rpcs.GetObservedDeviceResponse getObservedDeviceInfo(ScanResult scanResult) { - return Rpcs.GetObservedDeviceResponse.getDefaultInstance(); - } - - /** - * Get discovery item from item id. - */ - public DiscoveryItem getDiscoveryItem(String itemId) { - return new DiscoveryItem(mContext, getStoredDiscoveryItem(itemId)); - } - - /** - * Get discovery item from item id. - */ - public Cache.StoredDiscoveryItem getStoredDiscoveryItem(String itemId) { - SQLiteDatabase db = mFastPairDbHelper.getReadableDatabase(); - String[] projection = { - DiscoveryItemContract.DiscoveryItemEntry.COLUMN_MODEL_ID, - DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE - }; - String selection = DiscoveryItemContract.DiscoveryItemEntry.COLUMN_MODEL_ID + " =? "; - String[] selectionArgs = {itemId}; - Cursor cursor = db.query( - DiscoveryItemContract.DiscoveryItemEntry.TABLE_NAME, - projection, - selection, - selectionArgs, - null, - null, - null - ); - - if (cursor.moveToNext()) { - byte[] res = cursor.getBlob(cursor.getColumnIndexOrThrow( - DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE)); - try { - Cache.StoredDiscoveryItem item = Cache.StoredDiscoveryItem.parseFrom(res); - return item; - } catch (InvalidProtocolBufferException e) { - Log.e("FastPairCacheManager", "storediscovery has error"); - } - } - cursor.close(); - return Cache.StoredDiscoveryItem.getDefaultInstance(); - } - - /** - * Get all of the discovery item related info in the cache. - */ - public List getAllSavedStoreDiscoveryItem() { - List storedDiscoveryItemList = new ArrayList<>(); - SQLiteDatabase db = mFastPairDbHelper.getReadableDatabase(); - String[] projection = { - DiscoveryItemContract.DiscoveryItemEntry.COLUMN_MODEL_ID, - DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE - }; - Cursor cursor = db.query( - DiscoveryItemContract.DiscoveryItemEntry.TABLE_NAME, - projection, - null, - null, - null, - null, - null - ); - - while (cursor.moveToNext()) { - byte[] res = cursor.getBlob(cursor.getColumnIndexOrThrow( - DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE)); - try { - Cache.StoredDiscoveryItem item = Cache.StoredDiscoveryItem.parseFrom(res); - storedDiscoveryItemList.add(item); - } catch (InvalidProtocolBufferException e) { - Log.e("FastPairCacheManager", "storediscovery has error"); - } - - } - cursor.close(); - return storedDiscoveryItemList; - } - - /** - * Get scan result from local database use model id - */ - public Cache.StoredScanResult getStoredScanResult(String modelId) { - return Cache.StoredScanResult.getDefaultInstance(); - } - - /** - * Gets the paired Fast Pair item that paired to the phone through mac address. - */ - public Cache.StoredFastPairItem getStoredFastPairItemFromMacAddress(String macAddress) { - SQLiteDatabase db = mFastPairDbHelper.getReadableDatabase(); - String[] projection = { - StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_ACCOUNT_KEY, - StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS, - StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_STORED_FAST_PAIR_BYTE - }; - String selection = - StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS + " =? "; - String[] selectionArgs = {macAddress}; - Cursor cursor = db.query( - StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME, - projection, - selection, - selectionArgs, - null, - null, - null - ); - - if (cursor.moveToNext()) { - byte[] res = cursor.getBlob(cursor.getColumnIndexOrThrow( - StoredFastPairItemContract.StoredFastPairItemEntry - .COLUMN_STORED_FAST_PAIR_BYTE)); - try { - Cache.StoredFastPairItem item = Cache.StoredFastPairItem.parseFrom(res); - return item; - } catch (InvalidProtocolBufferException e) { - Log.e("FastPairCacheManager", "storediscovery has error"); - } - } - cursor.close(); - return Cache.StoredFastPairItem.getDefaultInstance(); - } - - /** - * Save paired fast pair item into the database. - */ - public boolean putStoredFastPairItem(Cache.StoredFastPairItem storedFastPairItem) { - SQLiteDatabase db = mFastPairDbHelper.getWritableDatabase(); - ContentValues values = new ContentValues(); - values.put(StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS, - storedFastPairItem.getMacAddress()); - values.put(StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_ACCOUNT_KEY, - storedFastPairItem.getAccountKey().toString()); - values.put(StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_STORED_FAST_PAIR_BYTE, - storedFastPairItem.toByteArray()); - db.insert(StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME, null, values); - return true; - - } - - /** - * Removes certain storedFastPairItem so that it can update timely. - */ - public void removeStoredFastPairItem(String macAddress) { - SQLiteDatabase db = mFastPairDbHelper.getWritableDatabase(); - int res = db.delete(StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME, - StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS + "=?", - new String[]{macAddress}); - - } - - /** - * Get all of the store fast pair item related info in the cache. - */ - public List getAllSavedStoredFastPairItem() { - List storedFastPairItemList = new ArrayList<>(); - SQLiteDatabase db = mFastPairDbHelper.getReadableDatabase(); - String[] projection = { - StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS, - StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_ACCOUNT_KEY, - StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_STORED_FAST_PAIR_BYTE - }; - Cursor cursor = db.query( - StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME, - projection, - null, - null, - null, - null, - null - ); - - while (cursor.moveToNext()) { - byte[] res = cursor.getBlob(cursor.getColumnIndexOrThrow(StoredFastPairItemContract - .StoredFastPairItemEntry.COLUMN_STORED_FAST_PAIR_BYTE)); - try { - Cache.StoredFastPairItem item = Cache.StoredFastPairItem.parseFrom(res); - storedFastPairItemList.add(item); - } catch (InvalidProtocolBufferException e) { - Log.e("FastPairCacheManager", "storediscovery has error"); - } - - } - cursor.close(); - return storedFastPairItemList; - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairDbHelper.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairDbHelper.java deleted file mode 100644 index d950d8d9a613df01c17855e8629f2f9ec5627c89..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairDbHelper.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.server.nearby.fastpair.cache; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; - -/** - * Fast Pair db helper handle all of the db actions related Fast Pair. - */ -public class FastPairDbHelper extends SQLiteOpenHelper { - - public static final int DATABASE_VERSION = 1; - public static final String DATABASE_NAME = "FastPair.db"; - private static final String SQL_CREATE_DISCOVERY_ITEM_DB = - "CREATE TABLE IF NOT EXISTS " + DiscoveryItemContract.DiscoveryItemEntry.TABLE_NAME - + " (" + DiscoveryItemContract.DiscoveryItemEntry._ID - + "INTEGER PRIMARY KEY," - + DiscoveryItemContract.DiscoveryItemEntry.COLUMN_MODEL_ID - + " TEXT," + DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE - + " BLOB)"; - private static final String SQL_DELETE_DISCOVERY_ITEM_DB = - "DROP TABLE IF EXISTS " + DiscoveryItemContract.DiscoveryItemEntry.TABLE_NAME; - private static final String SQL_CREATE_FAST_PAIR_ITEM_DB = - "CREATE TABLE IF NOT EXISTS " - + StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME - + " (" + StoredFastPairItemContract.StoredFastPairItemEntry._ID - + "INTEGER PRIMARY KEY," - + StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS - + " TEXT," - + StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_ACCOUNT_KEY - + " TEXT," - + StoredFastPairItemContract - .StoredFastPairItemEntry.COLUMN_STORED_FAST_PAIR_BYTE - + " BLOB)"; - private static final String SQL_DELETE_FAST_PAIR_ITEM_DB = - "DROP TABLE IF EXISTS " + StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME; - - public FastPairDbHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL(SQL_CREATE_DISCOVERY_ITEM_DB); - db.execSQL(SQL_CREATE_FAST_PAIR_ITEM_DB); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - // Since the outdated data has no value so just remove the data. - db.execSQL(SQL_DELETE_DISCOVERY_ITEM_DB); - db.execSQL(SQL_DELETE_FAST_PAIR_ITEM_DB); - onCreate(db); - } - - @Override - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - super.onDowngrade(db, oldVersion, newVersion); - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/StoredFastPairItemContract.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/StoredFastPairItemContract.java deleted file mode 100644 index 99805654aec4243adae3d0dde9b1f29f414facac..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/cache/StoredFastPairItemContract.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.fastpair.cache; - -import android.provider.BaseColumns; - -/** - * Defines fast pair item database schema. - */ -public class StoredFastPairItemContract { - private StoredFastPairItemContract() {} - - /** - * StoredFastPairItem entry related info. - */ - public static class StoredFastPairItemEntry implements BaseColumns { - public static final String TABLE_NAME = "STORED_FAST_PAIR_ITEM"; - public static final String COLUMN_MAC_ADDRESS = "MAC_ADDRESS"; - public static final String COLUMN_ACCOUNT_KEY = "ACCOUNT_KEY"; - - public static final String COLUMN_STORED_FAST_PAIR_BYTE = "STORED_FAST_PAIR_BYTE"; - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/footprint/FastPairUploadInfo.java b/nearby/service/java/com/android/server/nearby/fastpair/footprint/FastPairUploadInfo.java deleted file mode 100644 index 6c9aff03460ee82b74e16ae974761ce24e1c228e..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/footprint/FastPairUploadInfo.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.fastpair.footprint; - - -import com.google.protobuf.ByteString; - -import service.proto.Cache; - -/** - * Wrapper class that upload the pair info to the footprint. - */ -public class FastPairUploadInfo { - - private Cache.StoredDiscoveryItem mStoredDiscoveryItem; - - private ByteString mAccountKey; - - private ByteString mSha256AccountKeyPublicAddress; - - - public FastPairUploadInfo(Cache.StoredDiscoveryItem storedDiscoveryItem, ByteString accountKey, - ByteString sha256AccountKeyPublicAddress) { - mStoredDiscoveryItem = storedDiscoveryItem; - mAccountKey = accountKey; - mSha256AccountKeyPublicAddress = sha256AccountKeyPublicAddress; - } - - public Cache.StoredDiscoveryItem getStoredDiscoveryItem() { - return mStoredDiscoveryItem; - } - - public ByteString getAccountKey() { - return mAccountKey; - } - - - public ByteString getSha256AccountKeyPublicAddress() { - return mSha256AccountKeyPublicAddress; - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/footprint/FootprintsDeviceManager.java b/nearby/service/java/com/android/server/nearby/fastpair/footprint/FootprintsDeviceManager.java deleted file mode 100644 index 68217c1edf5f45a868533a08d5a7bec90a1c5918..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/footprint/FootprintsDeviceManager.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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.server.nearby.fastpair.footprint; - -/** - * FootprintDeviceManager is responsible for all of the foot print operation. Footprint will - * store all of device info that already paired with certain account. This class will call AOSP - * api to let OEM save certain device. - */ -public class FootprintsDeviceManager { -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetBlocklist.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetBlocklist.java deleted file mode 100644 index 146b97a06d17fbd03c798b5eaf6caa8cc14e5265..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetBlocklist.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.fastpair.halfsheet; - - -import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.ACTIVE; -import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.DISMISSED; -import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN; -import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN_LONG; - -import android.util.Log; -import android.util.LruCache; - -import androidx.annotation.VisibleForTesting; - -import com.android.server.nearby.fastpair.blocklist.Blocklist; -import com.android.server.nearby.fastpair.blocklist.BlocklistElement; -import com.android.server.nearby.util.Clock; -import com.android.server.nearby.util.DefaultClock; - - -/** - * Maintains a list of half sheet id to tell whether the half sheet should be suppressed or not. - * - *

    When user cancel half sheet, the ble address related half sheet should be in block list and - * after certain duration of time half sheet can show again. - */ -public class FastPairHalfSheetBlocklist extends LruCache - implements Blocklist { - private static final String TAG = "HalfSheetBlocklist"; - // Number of entries in the FastPair blocklist - private static final int FAST_PAIR_BLOCKLIST_CACHE_SIZE = 16; - // Duration between first half sheet dismiss and second half sheet shows: 2 seconds - private static final int FAST_PAIR_HALF_SHEET_DISMISS_COOL_DOWN_MILLIS = 2000; - // The timeout to ban half sheet after user trigger the ban logic even number of time : 1 day - private static final int DURATION_RESURFACE_HALFSHEET_EVEN_NUMBER_BAN_MILLI_SECONDS = 86400000; - // Timeout for DISMISSED entries in the blocklist to expire : 1 min - private static final int FAST_PAIR_BLOCKLIST_DISMISSED_HALF_SHEET_TIMEOUT_MILLIS = 60000; - // The timeout for entries in the blocklist to expire : 1 day - private static final int STATE_EXPIRATION_MILLI_SECONDS = 86400000; - private long mEndTimeBanAllItems; - private final Clock mClock; - - - public FastPairHalfSheetBlocklist() { - // Reuses the size limit from notification cache. - // Number of entries in the FastPair blocklist - super(FAST_PAIR_BLOCKLIST_CACHE_SIZE); - mClock = new DefaultClock(); - } - - @VisibleForTesting - FastPairHalfSheetBlocklist(int size, Clock clock) { - super(size); - mClock = clock; - } - - /** - * Checks whether need to show HalfSheet or not. - * - *

    When the HalfSheet {@link BlocklistState} is DISMISS, there is a little cool down period - * to allow half sheet to reshow. - * If the HalfSheet {@link BlocklistState} is DO_NOT_SHOW_AGAIN, within durationMilliSeconds - * from banned start time, the function will return true - * otherwise it will return false if the status is expired - * If the HalfSheet {@link BlocklistState} is DO_NOT_SHOW_AGAIN_LONG, the half sheet will be - * baned for a longer duration. - * - * @param id {@link com.android.nearby.halfsheet.HalfSheetActivity} id - * @param durationMilliSeconds the time duration from item is banned to now - * @return whether the HalfSheet is blocked to show - */ - @Override - public boolean isBlocklisted(int id, int durationMilliSeconds) { - if (shouldBanAllItem()) { - return true; - } - BlocklistElement entry = get(id); - if (entry == null) { - return false; - } - if (entry.getState().equals(DO_NOT_SHOW_AGAIN)) { - Log.d(TAG, "BlocklistState: DO_NOT_SHOW_AGAIN"); - return mClock.elapsedRealtime() < entry.getTimeStamp() + durationMilliSeconds; - } - if (entry.getState().equals(DO_NOT_SHOW_AGAIN_LONG)) { - Log.d(TAG, "BlocklistState: DO_NOT_SHOW_AGAIN_LONG "); - return mClock.elapsedRealtime() - < entry.getTimeStamp() - + DURATION_RESURFACE_HALFSHEET_EVEN_NUMBER_BAN_MILLI_SECONDS; - } - - if (entry.getState().equals(ACTIVE)) { - Log.d(TAG, "BlocklistState: ACTIVE"); - return false; - } - // Get some cool down period for dismiss state - if (entry.getState().equals(DISMISSED)) { - Log.d(TAG, "BlocklistState: DISMISSED"); - return mClock.elapsedRealtime() - < entry.getTimeStamp() + FAST_PAIR_HALF_SHEET_DISMISS_COOL_DOWN_MILLIS; - } - if (dismissStateHasExpired(entry)) { - Log.d(TAG, "stateHasExpired: True"); - return false; - } - return true; - } - - @Override - public boolean removeBlocklist(int id) { - BlocklistElement oldValue = remove(id); - return oldValue != null; - } - - /** - * Updates the HalfSheet blocklist state - * - *

    When the new {@link BlocklistState} has higher priority then old {@link BlocklistState} or - * the old {@link BlocklistState} status is expired,the function will update the status. - * - * @param id HalfSheet id - * @param state Blocklist state - * @return update status successful or not - */ - @Override - public boolean updateState(int id, BlocklistState state) { - BlocklistElement entry = get(id); - if (entry == null || state.hasHigherPriorityThan(entry.getState()) - || dismissStateHasExpired(entry)) { - Log.d(TAG, "updateState: " + state); - put(id, new BlocklistElement(state, mClock.elapsedRealtime())); - return true; - } - return false; - } - - /** Enables lower state to override the higher value state. */ - public void forceUpdateState(int id, BlocklistState state) { - put(id, new BlocklistElement(state, mClock.elapsedRealtime())); - } - - /** Resets certain device ban state to active. */ - @Override - public void resetBlockState(int id) { - BlocklistElement entry = get(id); - if (entry != null) { - put(id, new BlocklistElement(ACTIVE, mClock.elapsedRealtime())); - } - } - - /** Checks whether certain device state has expired. */ - public boolean isStateExpired(int id) { - BlocklistElement entry = get(id); - if (entry != null) { - return mClock.elapsedRealtime() > entry.getTimeStamp() + STATE_EXPIRATION_MILLI_SECONDS; - } - return false; - } - - private boolean dismissStateHasExpired(BlocklistElement entry) { - return mClock.elapsedRealtime() - > entry.getTimeStamp() + FAST_PAIR_BLOCKLIST_DISMISSED_HALF_SHEET_TIMEOUT_MILLIS; - } - - /** - * Updates the end time that all half sheet will be banned. - */ - void banAllItem(long banDurationTimeMillis) { - long endTime = mClock.elapsedRealtime() + banDurationTimeMillis; - if (endTime > mEndTimeBanAllItems) { - mEndTimeBanAllItems = endTime; - } - } - - private boolean shouldBanAllItem() { - return mClock.elapsedRealtime() < mEndTimeBanAllItems; - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java deleted file mode 100644 index 7b266a73a03397ea66123e3063d50095bedcabb2..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java +++ /dev/null @@ -1,571 +0,0 @@ -/* - * 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.server.nearby.fastpair.halfsheet; - -import static com.android.nearby.halfsheet.constants.Constant.DEVICE_PAIRING_FRAGMENT_TYPE; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_BINDER; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_BUNDLE; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_CONTENT; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_INFO; -import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_TYPE; -import static com.android.nearby.halfsheet.constants.Constant.FAST_PAIR_HALF_SHEET_HELP_URL; -import static com.android.nearby.halfsheet.constants.Constant.RESULT_FAIL; - -import static java.util.concurrent.TimeUnit.SECONDS; - -import android.annotation.UiThread; -import android.app.ActivityManager; -import android.app.KeyguardManager; -import android.bluetooth.BluetoothDevice; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.nearby.FastPairDevice; -import android.nearby.FastPairStatusCallback; -import android.nearby.PairStatusMetadata; -import android.os.Bundle; -import android.os.UserHandle; -import android.util.Log; -import android.util.LruCache; -import android.widget.Toast; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.nearby.halfsheet.R; -import com.android.server.nearby.common.eventloop.Annotations; -import com.android.server.nearby.common.eventloop.EventLoop; -import com.android.server.nearby.common.eventloop.NamedRunnable; -import com.android.server.nearby.common.locator.Locator; -import com.android.server.nearby.common.locator.LocatorContextWrapper; -import com.android.server.nearby.fastpair.FastPairController; -import com.android.server.nearby.fastpair.PackageUtils; -import com.android.server.nearby.fastpair.blocklist.Blocklist; -import com.android.server.nearby.fastpair.cache.DiscoveryItem; - -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -import service.proto.Cache; - -/** - * Fast Pair ux manager for half sheet. - */ -public class FastPairHalfSheetManager { - private static final String ACTIVITY_INTENT_ACTION = "android.nearby.SHOW_HALFSHEET"; - private static final String HALF_SHEET_CLASS_NAME = - "com.android.nearby.halfsheet.HalfSheetActivity"; - private static final String TAG = "FPHalfSheetManager"; - public static final String FINISHED_STATE = "FINISHED_STATE"; - @VisibleForTesting static final String DISMISS_HALFSHEET_RUNNABLE_NAME = "DismissHalfSheet"; - @VisibleForTesting static final String SHOW_TOAST_RUNNABLE_NAME = "SuccessPairingToast"; - - // The timeout to ban half sheet after user trigger the ban logic odd number of time: 5 mins - private static final int DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS = 300000; - // Number of seconds half sheet will show after the advertisement is no longer seen. - private static final int HALF_SHEET_TIME_OUT_SECONDS = 12; - - static final int HALFSHEET_ID_SEED = "new_fast_pair_half_sheet".hashCode(); - - private String mHalfSheetApkPkgName; - private boolean mIsHalfSheetForeground = false; - private boolean mIsActivePairing = false; - private Cache.ScanFastPairStoreItem mCurrentScanFastPairStoreItem = null; - private final LocatorContextWrapper mLocatorContextWrapper; - private final AtomicInteger mNotificationIds = new AtomicInteger(HALFSHEET_ID_SEED); - private FastPairHalfSheetBlocklist mHalfSheetBlocklist; - // Todo: Make "16" a flag, which can be updated from the server side. - final LruCache mModelIdMap = new LruCache<>(16); - HalfSheetDismissState mHalfSheetDismissState = HalfSheetDismissState.ACTIVE; - // Ban count map track the number of ban happens to certain model id - // If the model id is baned by the odd number of time it is banned for 5 mins - // if the model id is banned even number of time ban 24 hours. - private final Map mBanCountMap = new HashMap<>(); - - FastPairUiServiceImpl mFastPairUiService; - private NamedRunnable mDismissRunnable; - - /** - * Half sheet state default is active. If user dismiss half sheet once controller will mark half - * sheet as dismiss state. If user dismiss half sheet twice controller will mark half sheet as - * ban state for certain period of time. - */ - enum HalfSheetDismissState { - ACTIVE, - DISMISS, - BAN - } - - public FastPairHalfSheetManager(Context context) { - this(new LocatorContextWrapper(context)); - mHalfSheetBlocklist = new FastPairHalfSheetBlocklist(); - } - - @VisibleForTesting - public FastPairHalfSheetManager(LocatorContextWrapper locatorContextWrapper) { - mLocatorContextWrapper = locatorContextWrapper; - mFastPairUiService = new FastPairUiServiceImpl(); - mHalfSheetBlocklist = new FastPairHalfSheetBlocklist(); - } - - /** - * Invokes half sheet in the other apk. This function can only be called in Nearby because other - * app can't get the correct component name. - */ - public void showHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem) { - String modelId = scanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT); - if (modelId == null) { - Log.d(TAG, "model id not found"); - return; - } - - synchronized (mModelIdMap) { - if (mModelIdMap.get(modelId) == null) { - mModelIdMap.put(modelId, createNewHalfSheetId()); - } - } - int halfSheetId = mModelIdMap.get(modelId); - - if (!allowedToShowHalfSheet(halfSheetId)) { - Log.d(TAG, "Not allow to show initial Half sheet"); - return; - } - - // If currently half sheet UI is in the foreground, - // DO NOT request start-activity to avoid unnecessary memory usage - if (mIsHalfSheetForeground) { - updateForegroundHalfSheet(scanFastPairStoreItem); - return; - } else { - // If the half sheet is not in foreground but the system is still pairing - // with the same device, mark as duplicate request and skip. - if (mCurrentScanFastPairStoreItem != null && mIsActivePairing - && mCurrentScanFastPairStoreItem.getAddress().toLowerCase(Locale.ROOT) - .equals(scanFastPairStoreItem.getAddress().toLowerCase(Locale.ROOT))) { - Log.d(TAG, "Same device is pairing."); - return; - } - } - - try { - if (mLocatorContextWrapper != null) { - String packageName = getHalfSheetApkPkgName(); - if (packageName == null) { - Log.e(TAG, "package name is null"); - return; - } - mFastPairUiService.setFastPairController( - mLocatorContextWrapper.getLocator().get(FastPairController.class)); - Bundle bundle = new Bundle(); - bundle.putBinder(EXTRA_BINDER, mFastPairUiService); - mLocatorContextWrapper - .startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION) - .putExtra(EXTRA_HALF_SHEET_INFO, - scanFastPairStoreItem.toByteArray()) - .putExtra(EXTRA_HALF_SHEET_TYPE, - DEVICE_PAIRING_FRAGMENT_TYPE) - .putExtra(EXTRA_BUNDLE, bundle) - .setComponent(new ComponentName(packageName, - HALF_SHEET_CLASS_NAME)), - UserHandle.CURRENT); - mHalfSheetBlocklist.updateState(halfSheetId, Blocklist.BlocklistState.ACTIVE); - } - } catch (IllegalStateException e) { - Log.e(TAG, "Can't resolve package that contains half sheet"); - } - Log.d(TAG, "show initial half sheet."); - mCurrentScanFastPairStoreItem = scanFastPairStoreItem; - mIsHalfSheetForeground = true; - enableAutoDismiss(scanFastPairStoreItem.getAddress(), HALF_SHEET_TIME_OUT_SECONDS); - } - - /** - * Auto dismiss half sheet after timeout - */ - @VisibleForTesting - void enableAutoDismiss(String address, long timeoutDuration) { - if (mDismissRunnable == null - || !mDismissRunnable.name.equals(DISMISS_HALFSHEET_RUNNABLE_NAME)) { - mDismissRunnable = - new NamedRunnable(DISMISS_HALFSHEET_RUNNABLE_NAME) { - @Override - public void run() { - Log.d(TAG, "Dismiss the half sheet after " - + timeoutDuration + " seconds"); - // BMW car kit will advertise even after pairing start, - // to avoid the half sheet be dismissed during active pairing, - // If the half sheet is in the pairing state, disable the auto dismiss. - // See b/182396106 - if (mIsActivePairing) { - return; - } - mIsHalfSheetForeground = false; - FastPairStatusCallback pairStatusCallback = - mFastPairUiService.getPairStatusCallback(); - if (pairStatusCallback != null) { - pairStatusCallback.onPairUpdate(new FastPairDevice.Builder() - .setBluetoothAddress(address).build(), - new PairStatusMetadata(PairStatusMetadata.Status.DISMISS)); - } else { - Log.w(TAG, "pairStatusCallback is null," - + " failed to enable auto dismiss "); - } - } - }; - } - if (Locator.get(mLocatorContextWrapper, EventLoop.class).isPosted(mDismissRunnable)) { - disableDismissRunnable(); - } - Locator.get(mLocatorContextWrapper, EventLoop.class) - .postRunnableDelayed(mDismissRunnable, SECONDS.toMillis(timeoutDuration)); - } - - private void updateForegroundHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem) { - if (mCurrentScanFastPairStoreItem == null) { - return; - } - if (mCurrentScanFastPairStoreItem.getAddress().toLowerCase(Locale.ROOT) - .equals(scanFastPairStoreItem.getAddress().toLowerCase(Locale.ROOT))) { - // If current address is the same, reset the timeout. - Log.d(TAG, "same Address device, reset the auto dismiss timeout"); - enableAutoDismiss(scanFastPairStoreItem.getAddress(), HALF_SHEET_TIME_OUT_SECONDS); - } else { - // If current address is different, not reset timeout - // wait for half sheet auto dismiss or manually dismiss to start new pair. - if (mCurrentScanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT) - .equals(scanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT))) { - Log.d(TAG, "same model id device is also nearby"); - } - Log.d(TAG, "showInitialHalfsheet: address changed, from " - + mCurrentScanFastPairStoreItem.getAddress() - + " to " + scanFastPairStoreItem.getAddress()); - } - } - - /** - * Show passkey confirmation info on half sheet - */ - public void showPasskeyConfirmation(BluetoothDevice device, int passkey) { - } - - /** - * This function will handle pairing steps for half sheet. - */ - public void showPairingHalfSheet(DiscoveryItem item) { - Log.d(TAG, "show pairing half sheet"); - } - - /** - * Shows pairing success info. - * If the half sheet is not shown, show toast to remind user. - */ - public void showPairingSuccessHalfSheet(String address) { - resetPairingStateDisableAutoDismiss(); - if (mIsHalfSheetForeground) { - FastPairStatusCallback pairStatusCallback = mFastPairUiService.getPairStatusCallback(); - if (pairStatusCallback == null) { - Log.w(TAG, "FastPairHalfSheetManager failed to show success half sheet because " - + "the pairStatusCallback is null"); - return; - } - Log.d(TAG, "showPairingSuccess: pairStatusCallback not NULL"); - pairStatusCallback.onPairUpdate( - new FastPairDevice.Builder().setBluetoothAddress(address).build(), - new PairStatusMetadata(PairStatusMetadata.Status.SUCCESS)); - } else { - Locator.get(mLocatorContextWrapper, EventLoop.class) - .postRunnable( - new NamedRunnable(SHOW_TOAST_RUNNABLE_NAME) { - @Override - public void run() { - try { - Toast.makeText(mLocatorContextWrapper, - mLocatorContextWrapper - .getPackageManager() - .getResourcesForApplication( - getHalfSheetApkPkgName()) - .getString(R.string.fast_pair_device_ready), - Toast.LENGTH_LONG).show(); - } catch (PackageManager.NameNotFoundException e) { - Log.d(TAG, "showPairingSuccess fail:" - + " package name cannot be found "); - e.printStackTrace(); - } - } - }); - } - } - - /** - * Shows pairing fail half sheet. - * If the half sheet is not shown, create a new half sheet to help user go to Setting - * to manually pair with the device. - */ - public void showPairingFailed() { - resetPairingStateDisableAutoDismiss(); - if (mCurrentScanFastPairStoreItem == null) { - return; - } - if (mIsHalfSheetForeground) { - FastPairStatusCallback pairStatusCallback = mFastPairUiService.getPairStatusCallback(); - if (pairStatusCallback != null) { - Log.v(TAG, "showPairingFailed: pairStatusCallback not NULL"); - pairStatusCallback.onPairUpdate( - new FastPairDevice.Builder() - .setBluetoothAddress(mCurrentScanFastPairStoreItem.getAddress()) - .build(), - new PairStatusMetadata(PairStatusMetadata.Status.FAIL)); - } else { - Log.w(TAG, "FastPairHalfSheetManager failed to show fail half sheet because " - + "the pairStatusCallback is null"); - } - } else { - String packageName = getHalfSheetApkPkgName(); - if (packageName == null) { - Log.e(TAG, "package name is null"); - return; - } - Bundle bundle = new Bundle(); - bundle.putBinder(EXTRA_BINDER, mFastPairUiService); - mLocatorContextWrapper - .startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION) - .putExtra(EXTRA_HALF_SHEET_INFO, - mCurrentScanFastPairStoreItem.toByteArray()) - .putExtra(EXTRA_HALF_SHEET_TYPE, - DEVICE_PAIRING_FRAGMENT_TYPE) - .putExtra(EXTRA_HALF_SHEET_CONTENT, RESULT_FAIL) - .putExtra(EXTRA_BUNDLE, bundle) - .setComponent(new ComponentName(packageName, - HALF_SHEET_CLASS_NAME)), - UserHandle.CURRENT); - Log.d(TAG, "Starts a new half sheet to showPairingFailed"); - String modelId = mCurrentScanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT); - if (modelId == null || mModelIdMap.get(modelId) == null) { - Log.d(TAG, "info not enough"); - return; - } - int halfSheetId = mModelIdMap.get(modelId); - mHalfSheetBlocklist.updateState(halfSheetId, Blocklist.BlocklistState.ACTIVE); - } - } - - /** - * Removes dismiss half sheet runnable. When half sheet shows, there is timer for half sheet to - * dismiss. But when user is pairing, half sheet should not dismiss. - * So this function disable the runnable. - */ - public void disableDismissRunnable() { - if (mDismissRunnable == null) { - return; - } - Log.d(TAG, "remove dismiss runnable"); - Locator.get(mLocatorContextWrapper, EventLoop.class).removeRunnable(mDismissRunnable); - } - - /** - * When user first click back button or click the empty space in half sheet the half sheet will - * be banned for certain short period of time for that device model id. When user click cancel - * or dismiss half sheet for the second time the half sheet related item should be added to - * blocklist so the half sheet will not show again to interrupt user. - * - * @param modelId half sheet display item modelId. - */ - @Annotations.EventThread - public void dismiss(String modelId) { - Log.d(TAG, "HalfSheetManager report dismiss device modelId: " + modelId); - mIsHalfSheetForeground = false; - Integer halfSheetId = mModelIdMap.get(modelId); - if (mDismissRunnable != null - && Locator.get(mLocatorContextWrapper, EventLoop.class) - .isPosted(mDismissRunnable)) { - disableDismissRunnable(); - } - if (halfSheetId != null) { - Log.d(TAG, "id: " + halfSheetId + " half sheet is dismissed"); - boolean isDontShowAgain = - !mHalfSheetBlocklist.updateState(halfSheetId, - Blocklist.BlocklistState.DISMISSED); - if (isDontShowAgain) { - if (!mBanCountMap.containsKey(halfSheetId)) { - mBanCountMap.put(halfSheetId, 0); - } - int dismissCountTrack = mBanCountMap.get(halfSheetId) + 1; - mBanCountMap.put(halfSheetId, dismissCountTrack); - if (dismissCountTrack % 2 == 1) { - Log.d(TAG, "id: " + halfSheetId + " half sheet is short time banned"); - mHalfSheetBlocklist.forceUpdateState(halfSheetId, - Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN); - } else { - Log.d(TAG, "id: " + halfSheetId + " half sheet is long time banned"); - mHalfSheetBlocklist.updateState(halfSheetId, - Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN_LONG); - } - } - } - } - - /** - * Changes the half sheet ban state to active. - */ - @UiThread - public void resetBanState(String modelId) { - Log.d(TAG, "HalfSheetManager reset device ban state modelId: " + modelId); - Integer halfSheetId = mModelIdMap.get(modelId); - if (halfSheetId == null) { - Log.d(TAG, "halfSheetId not found."); - return; - } - mHalfSheetBlocklist.resetBlockState(halfSheetId); - } - - // Invokes this method to reset some states when showing the pairing result. - private void resetPairingStateDisableAutoDismiss() { - mIsActivePairing = false; - if (mDismissRunnable != null && Locator.get(mLocatorContextWrapper, EventLoop.class) - .isPosted(mDismissRunnable)) { - disableDismissRunnable(); - } - } - - /** - * When the device pairing finished should remove the suppression for the model id - * so the user canntry twice if the user want to. - */ - public void reportDonePairing(int halfSheetId) { - mHalfSheetBlocklist.removeBlocklist(halfSheetId); - } - - @VisibleForTesting - public FastPairHalfSheetBlocklist getHalfSheetBlocklist() { - return mHalfSheetBlocklist; - } - - /** - * Destroys the bluetooth pairing controller. - */ - public void destroyBluetoothPairController() { - } - - /** - * Notifies manager the pairing has finished. - */ - public void notifyPairingProcessDone(boolean success, String address, DiscoveryItem item) { - mCurrentScanFastPairStoreItem = null; - mIsHalfSheetForeground = false; - } - - private boolean allowedToShowHalfSheet(int halfSheetId) { - // Half Sheet will not show when the screen is locked so disable half sheet - KeyguardManager keyguardManager = - mLocatorContextWrapper.getSystemService(KeyguardManager.class); - if (keyguardManager != null && keyguardManager.isKeyguardLocked()) { - Log.d(TAG, "device is locked"); - return false; - } - - // Check whether the blocklist state has expired - if (mHalfSheetBlocklist.isStateExpired(halfSheetId)) { - mHalfSheetBlocklist.removeBlocklist(halfSheetId); - mBanCountMap.remove(halfSheetId); - } - - // Half Sheet will not show when the model id is banned - if (mHalfSheetBlocklist.isBlocklisted( - halfSheetId, DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)) { - Log.d(TAG, "id: " + halfSheetId + " is blocked"); - return false; - } - return !isHelpPageForeground(); - } - - /** - * Checks if the user already open the info page, return true to suppress half sheet. - * ActivityManager#getRunningTasks to get the most recent task and check the baseIntent's - * url to see if we should suppress half sheet. - */ - private boolean isHelpPageForeground() { - ActivityManager activityManager = - mLocatorContextWrapper.getSystemService(ActivityManager.class); - if (activityManager == null) { - Log.d(TAG, "ActivityManager is null"); - return false; - } - try { - List taskInfos = activityManager.getRunningTasks(1); - if (taskInfos.isEmpty()) { - Log.d(TAG, "Empty running tasks"); - return false; - } - String url = taskInfos.get(0).baseIntent.getDataString(); - Log.d(TAG, "Info page url:" + url); - if (FAST_PAIR_HALF_SHEET_HELP_URL.equals(url)) { - return true; - } - } catch (SecurityException e) { - Log.d(TAG, "Unable to get running tasks"); - } - return false; - } - - /** Report actively pairing when the Fast Pair starts. */ - public void reportActivelyPairing() { - mIsActivePairing = true; - } - - - private Integer createNewHalfSheetId() { - return mNotificationIds.getAndIncrement(); - } - - /** Gets the half sheet status whether it is foreground or dismissed */ - public boolean getHalfSheetForeground() { - return mIsHalfSheetForeground; - } - - /** Sets whether the half sheet is at the foreground or not. */ - public void setHalfSheetForeground(boolean state) { - mIsHalfSheetForeground = state; - } - - /** Returns whether the fast pair is actively pairing . */ - @VisibleForTesting - public boolean isActivePairing() { - return mIsActivePairing; - } - - /** Sets fast pair to be active pairing or not, used for testing. */ - @VisibleForTesting - public void setIsActivePairing(boolean isActivePairing) { - mIsActivePairing = isActivePairing; - } - - /** - * Gets the package name of HalfSheet.apk - * getHalfSheetApkPkgName may invoke PackageManager multiple times and it does not have - * race condition check. Since there is no lock for mHalfSheetApkPkgName. - */ - private String getHalfSheetApkPkgName() { - if (mHalfSheetApkPkgName != null) { - return mHalfSheetApkPkgName; - } - mHalfSheetApkPkgName = PackageUtils.getHalfSheetApkPkgName(mLocatorContextWrapper); - Log.v(TAG, "Found halfsheet APK at: " + mHalfSheetApkPkgName); - return mHalfSheetApkPkgName; - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairUiServiceImpl.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairUiServiceImpl.java deleted file mode 100644 index eb1fb8562b935d7e5bd12e598c47863b1978d782..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairUiServiceImpl.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.fastpair.halfsheet; - -import static com.android.server.nearby.fastpair.Constant.TAG; - -import android.nearby.FastPairDevice; -import android.nearby.FastPairStatusCallback; -import android.nearby.PairStatusMetadata; -import android.nearby.aidl.IFastPairStatusCallback; -import android.nearby.aidl.IFastPairUiService; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.nearby.fastpair.FastPairController; - -/** - * Service implementing Fast Pair functionality. - * - * @hide - */ -public class FastPairUiServiceImpl extends IFastPairUiService.Stub { - - private IBinder mStatusCallbackProxy; - private FastPairController mFastPairController; - private FastPairStatusCallback mFastPairStatusCallback; - - /** - * Registers the Binder call back in the server notifies the proxy when there is an update - * in the server. - */ - @Override - public void registerCallback(IFastPairStatusCallback iFastPairStatusCallback) { - mStatusCallbackProxy = iFastPairStatusCallback.asBinder(); - mFastPairStatusCallback = new FastPairStatusCallback() { - @Override - public void onPairUpdate(FastPairDevice fastPairDevice, - PairStatusMetadata pairStatusMetadata) { - try { - iFastPairStatusCallback.onPairUpdate(fastPairDevice, pairStatusMetadata); - } catch (RemoteException e) { - Log.w(TAG, "Failed to update pair status.", e); - } - } - }; - } - - /** - * Unregisters the Binder call back in the server. - */ - @Override - public void unregisterCallback(IFastPairStatusCallback iFastPairStatusCallback) { - mStatusCallbackProxy = null; - mFastPairStatusCallback = null; - } - - /** - * Asks the Fast Pair service to pair the device. initial pairing. - */ - @Override - public void connect(FastPairDevice fastPairDevice) { - if (mFastPairController != null) { - mFastPairController.pair(fastPairDevice); - } else { - Log.w(TAG, "Failed to connect because there is no FastPairController."); - } - } - - /** - * Cancels Fast Pair connection and dismisses half sheet. - */ - @Override - public void cancel(FastPairDevice fastPairDevice) { - } - - public FastPairStatusCallback getPairStatusCallback() { - return mFastPairStatusCallback; - } - - /** - * Sets fastPairStatusCallback. - */ - @VisibleForTesting - public void setFastPairStatusCallback(FastPairStatusCallback fastPairStatusCallback) { - mFastPairStatusCallback = fastPairStatusCallback; - } - - /** - * Sets function for Fast Pair controller. - */ - public void setFastPairController(FastPairController fastPairController) { - mFastPairController = fastPairController; - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationBuilder.java b/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationBuilder.java deleted file mode 100644 index 4260235fd007ba710fb353e6187aeb78b761a1fa..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationBuilder.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.fastpair.notification; - -import android.app.Notification; -import android.content.Context; -import android.os.Bundle; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.nearby.halfsheet.R; -import com.android.server.nearby.fastpair.HalfSheetResources; -import com.android.server.nearby.fastpair.PackageUtils; - -/** Wrapper class for Fast Pair specific logic for notification builder. */ -public class FastPairNotificationBuilder extends Notification.Builder { - - @VisibleForTesting - static final String NOTIFICATION_OVERRIDE_NAME_EXTRA = "android.substName"; - final String mPackageName; - final Context mContext; - final HalfSheetResources mResources; - - public FastPairNotificationBuilder(Context context, String channelId) { - super(context, channelId); - this.mContext = context; - this.mPackageName = PackageUtils.getHalfSheetApkPkgName(context); - this.mResources = new HalfSheetResources(context); - } - - /** - * If the flag is enabled, all the devices notification should use "Devices" as the source name, - * and links/Apps uses "Nearby". If the flag is not enabled, all notifications use "Nearby" as - * source name. - */ - public FastPairNotificationBuilder setIsDevice(boolean isDevice) { - Bundle extras = new Bundle(); - String notificationOverrideName = - isDevice - ? mResources.get().getString(R.string.common_devices) - : mResources.get().getString(R.string.common_nearby_title); - extras.putString(NOTIFICATION_OVERRIDE_NAME_EXTRA, notificationOverrideName); - addExtras(extras); - return this; - } - - /** Set the "ticker" text which is sent to accessibility services. */ - public FastPairNotificationBuilder setTickerForAccessibility(String tickerText) { - // On Lollipop and above, setTicker() tells Accessibility what to say about the notification - // (e.g. this is what gets announced when a HUN appears). - setTicker(tickerText); - return this; - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationManager.java b/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationManager.java deleted file mode 100644 index c74249c20c1e9c5bb42b91e704efb1d8f9debbd7..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotificationManager.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * 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.server.nearby.fastpair.notification; - -import static com.android.server.nearby.fastpair.Constant.TAG; - -import android.annotation.Nullable; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationChannelGroup; -import android.app.NotificationManager; -import android.content.Context; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.nearby.halfsheet.R; -import com.android.server.nearby.fastpair.HalfSheetResources; -import com.android.server.nearby.fastpair.cache.DiscoveryItem; - -import com.google.common.base.Objects; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; - -import java.util.concurrent.TimeUnit; - -/** - * Responsible for show notification logic. - */ -public class FastPairNotificationManager { - - private static int sInstanceId = 0; - // Notification channel group ID for Devices notification channels. - private static final String DEVICES_CHANNEL_GROUP_ID = "DEVICES_CHANNEL_GROUP_ID"; - // These channels are rebranded string because they are migrated from different channel ID they - // should not be changed. - // Channel ID for channel "Devices within reach". - static final String DEVICES_WITHIN_REACH_CHANNEL_ID = "DEVICES_WITHIN_REACH_REBRANDED"; - // Channel ID for channel "Devices". - static final String DEVICES_CHANNEL_ID = "DEVICES_REBRANDED"; - // Channel ID for channel "Devices with your account". - public static final String DEVICES_WITH_YOUR_ACCOUNT_CHANNEL_ID = "DEVICES_WITH_YOUR_ACCOUNT"; - - // Default channel importance for channel "Devices within reach". - private static final int DEFAULT_DEVICES_WITHIN_REACH_CHANNEL_IMPORTANCE = - NotificationManager.IMPORTANCE_HIGH; - // Default channel importance for channel "Devices". - private static final int DEFAULT_DEVICES_CHANNEL_IMPORTANCE = - NotificationManager.IMPORTANCE_LOW; - // Default channel importance for channel "Devices with your account". - private static final int DEFAULT_DEVICES_WITH_YOUR_ACCOUNT_CHANNEL_IMPORTANCE = - NotificationManager.IMPORTANCE_MIN; - - /** Fixed notification ID that won't duplicated with {@code notificationId}. */ - private static final int MAGIC_PAIR_NOTIFICATION_ID = "magic_pair_notification_id".hashCode(); - /** Fixed notification ID that won't duplicated with {@code mNotificationId}. */ - @VisibleForTesting - static final int PAIR_SUCCESS_NOTIFICATION_ID = MAGIC_PAIR_NOTIFICATION_ID - 1; - /** Fixed notification ID for showing the pairing failure notification. */ - @VisibleForTesting static final int PAIR_FAILURE_NOTIFICATION_ID = - MAGIC_PAIR_NOTIFICATION_ID - 3; - - /** - * The amount of delay enforced between notifications. The system only allows 10 notifications / - * second, but delays in the binder IPC can cause overlap. - */ - private static final long MIN_NOTIFICATION_DELAY_MILLIS = 300; - - // To avoid a (really unlikely) race where the user pairs and succeeds quickly more than once, - // use a unique ID per session, so we can delay cancellation without worrying. - // This is for connecting related notifications only. Discovery notification will use item id - // as notification id. - @VisibleForTesting - final int mNotificationId; - private HalfSheetResources mResources; - private final FastPairNotifications mNotifications; - private boolean mDiscoveryNotificationEnable = true; - // A static cache that remembers all recently shown notifications. We use this to throttle - // ourselves from showing notifications too rapidly. If we attempt to show a notification faster - // than once every 100ms, the later notifications will be dropped and we'll show stale state. - // Maps from Key -> Uptime Millis - private final Cache mNotificationCache = - CacheBuilder.newBuilder() - .maximumSize(100) - .expireAfterWrite(MIN_NOTIFICATION_DELAY_MILLIS, TimeUnit.MILLISECONDS) - .build(); - private NotificationManager mNotificationManager; - - /** - * FastPair notification manager that handle notification ui for fast pair. - */ - @VisibleForTesting - public FastPairNotificationManager(Context context, int notificationId, - NotificationManager notificationManager, HalfSheetResources resources) { - mNotificationId = notificationId; - mNotificationManager = notificationManager; - mResources = resources; - mNotifications = new FastPairNotifications(context, mResources); - - configureDevicesNotificationChannels(); - } - - /** - * FastPair notification manager that handle notification ui for fast pair. - */ - public FastPairNotificationManager(Context context, int notificationId) { - this(context, notificationId, context.getSystemService(NotificationManager.class), - new HalfSheetResources(context)); - } - - /** - * FastPair notification manager that handle notification ui for fast pair. - */ - public FastPairNotificationManager(Context context) { - this(context, /* notificationId= */ MAGIC_PAIR_NOTIFICATION_ID + sInstanceId); - - sInstanceId++; - } - - /** - * Shows the notification when found saved device. A notification will be like - * "Your saved device is available." - * This uses item id as notification Id. This should be disabled when connecting starts. - */ - public void showDiscoveryNotification(DiscoveryItem item, byte[] accountKey) { - if (mDiscoveryNotificationEnable) { - Log.v(TAG, "the discovery notification is disabled"); - return; - } - - show(item.getId().hashCode(), mNotifications.discoveryNotification(item, accountKey)); - } - - /** - * Shows pairing in progress notification. - */ - public void showConnectingNotification(DiscoveryItem item) { - disableShowDiscoveryNotification(); - cancel(PAIR_FAILURE_NOTIFICATION_ID); - show(mNotificationId, mNotifications.progressNotification(item)); - } - - /** - * Shows when Fast Pair successfully pairs the headset. - */ - public void showPairingSucceededNotification( - DiscoveryItem item, - int batteryLevel, - @Nullable String deviceName) { - enableShowDiscoveryNotification(); - cancel(mNotificationId); - show(PAIR_SUCCESS_NOTIFICATION_ID, - mNotifications - .pairingSucceededNotification( - batteryLevel, deviceName, item.getTitle(), item)); - } - - /** - * Shows failed notification. - */ - public synchronized void showPairingFailedNotification(DiscoveryItem item, byte[] accountKey) { - enableShowDiscoveryNotification(); - cancel(mNotificationId); - show(PAIR_FAILURE_NOTIFICATION_ID, - mNotifications.showPairingFailedNotification(item, accountKey)); - } - - /** - * Notify the pairing process is done. - */ - public void notifyPairingProcessDone(boolean success, boolean forceNotify, - String privateAddress, String publicAddress) {} - - /** Enables the discovery notification when pairing is in progress */ - public void enableShowDiscoveryNotification() { - Log.v(TAG, "enabling discovery notification"); - mDiscoveryNotificationEnable = true; - } - - /** Disables the discovery notification when pairing is in progress */ - public synchronized void disableShowDiscoveryNotification() { - Log.v(TAG, "disabling discovery notification"); - mDiscoveryNotificationEnable = false; - } - - private void show(int id, Notification notification) { - mNotificationManager.notify(id, notification); - } - - /** - * Configures devices related notification channels, including "Devices" and "Devices within - * reach" channels. - */ - private void configureDevicesNotificationChannels() { - mNotificationManager.createNotificationChannelGroup( - new NotificationChannelGroup( - DEVICES_CHANNEL_GROUP_ID, - mResources.get().getString(R.string.common_devices))); - mNotificationManager.createNotificationChannel( - createNotificationChannel( - DEVICES_WITHIN_REACH_CHANNEL_ID, - mResources.get().getString(R.string.devices_within_reach_channel_name), - DEFAULT_DEVICES_WITHIN_REACH_CHANNEL_IMPORTANCE, - DEVICES_CHANNEL_GROUP_ID)); - mNotificationManager.createNotificationChannel( - createNotificationChannel( - DEVICES_CHANNEL_ID, - mResources.get().getString(R.string.common_devices), - DEFAULT_DEVICES_CHANNEL_IMPORTANCE, - DEVICES_CHANNEL_GROUP_ID)); - mNotificationManager.createNotificationChannel( - createNotificationChannel( - DEVICES_WITH_YOUR_ACCOUNT_CHANNEL_ID, - mResources.get().getString(R.string.devices_with_your_account_channel_name), - DEFAULT_DEVICES_WITH_YOUR_ACCOUNT_CHANNEL_IMPORTANCE, - DEVICES_CHANNEL_GROUP_ID)); - } - - private NotificationChannel createNotificationChannel( - String channelId, String channelName, int channelImportance, String channelGroupId) { - NotificationChannel channel = - new NotificationChannel(channelId, channelName, channelImportance); - channel.setGroup(channelGroupId); - if (channelImportance >= NotificationManager.IMPORTANCE_HIGH) { - channel.setSound(/* sound= */ null, /* audioAttributes= */ null); - // Disable vibration. Otherwise, the silent sound triggers a vibration if your - // ring volume is set to vibrate (aka turned down all the way). - channel.enableVibration(false); - } - - return channel; - } - - /** Cancel a previously shown notification. */ - public void cancel(int id) { - try { - mNotificationManager.cancel(id); - } catch (SecurityException e) { - Log.e(TAG, "Failed to cancel notification " + id, e); - } - mNotificationCache.invalidate(new Key(id)); - } - - private static final class Key { - @Nullable final String mTag; - final int mId; - - Key(int id) { - this.mTag = null; - this.mId = id; - } - - @Override - public boolean equals(@Nullable Object o) { - if (o instanceof Key) { - Key that = (Key) o; - return Objects.equal(mTag, that.mTag) && (mId == that.mId); - } - return false; - } - - @Override - public int hashCode() { - return Objects.hashCode(mTag == null ? 0 : mTag, mId); - } - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotifications.java b/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotifications.java deleted file mode 100644 index 3b4eef8a5fa09190b794d8857c2c1191b3ff6acd..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/notification/FastPairNotifications.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.fastpair.notification; - -import static com.android.nearby.halfsheet.constants.Constant.ACTION_FAST_PAIR; -import static com.android.server.nearby.fastpair.UserActionHandler.EXTRA_FAST_PAIR_SECRET; -import static com.android.server.nearby.fastpair.UserActionHandler.EXTRA_ITEM_ID; -import static com.android.server.nearby.fastpair.notification.FastPairNotificationManager.DEVICES_WITHIN_REACH_CHANNEL_ID; - -import static com.google.common.io.BaseEncoding.base16; - -import android.annotation.Nullable; -import android.app.Notification; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.graphics.drawable.Icon; -import android.os.SystemClock; -import android.provider.Settings; - -import com.android.nearby.halfsheet.R; -import com.android.server.nearby.common.fastpair.IconUtils; -import com.android.server.nearby.fastpair.HalfSheetResources; -import com.android.server.nearby.fastpair.cache.DiscoveryItem; - -import service.proto.Cache; - -/** - * Collection of utilities to create {@link Notification} objects that are displayed through {@link - * FastPairNotificationManager}. - */ -public class FastPairNotifications { - - private final Context mContext; - private final HalfSheetResources mResources; - /** - * Note: Idea copied from Google. - * - *

    Request code used for notification pending intents (executed on tap, dismiss). - * - *

    Android only keeps one PendingIntent instance if it thinks multiple pending intents match. - * As comparing PendingIntents/Intents does not inspect the data in the extras, multiple pending - * intents can conflict. This can have surprising consequences (see b/68702692#comment8). - * - *

    We also need to avoid conflicts with notifications started by an earlier launch of the app - * so use the truncated uptime of when the class was instantiated. The uptime will only overflow - * every ~50 days, and even then chances of conflict will be rare. - */ - private static int sRequestCode = (int) SystemClock.elapsedRealtime(); - - public FastPairNotifications(Context context, HalfSheetResources resources) { - this.mContext = context; - this.mResources = resources; - } - - /** - * Creates the initial "Your saved device is available" notification when subsequent pairing - * is available. - * @param item discovered item which contains title and item id - * @param accountKey used for generating intent for pairing - */ - public Notification discoveryNotification(DiscoveryItem item, byte[] accountKey) { - Notification.Builder builder = - newBaseBuilder(item) - .setContentTitle(mResources.getString(R.string.fast_pair_your_device)) - .setContentText(item.getTitle()) - .setContentIntent(getPairIntent(item.getCopyOfStoredItem(), accountKey)) - .setCategory(Notification.CATEGORY_RECOMMENDATION) - .setAutoCancel(false); - return builder.build(); - } - - /** - * Creates the in progress "Connecting" notification when the device and headset are paring. - */ - public Notification progressNotification(DiscoveryItem item) { - String summary = mResources.getString(R.string.common_connecting); - Notification.Builder builder = - newBaseBuilder(item) - .setTickerForAccessibility(summary) - .setCategory(Notification.CATEGORY_PROGRESS) - .setContentTitle(mResources.getString(R.string.fast_pair_your_device)) - .setContentText(summary) - // Intermediate progress bar. - .setProgress(0, 0, true) - // Tapping does not dismiss this. - .setAutoCancel(false); - - return builder.build(); - } - - /** - * Creates paring failed notification. - */ - public Notification showPairingFailedNotification(DiscoveryItem item, byte[] accountKey) { - String couldNotPair = mResources.getString(R.string.fast_pair_unable_to_connect); - String notificationContent; - if (accountKey != null) { - notificationContent = mResources.getString( - R.string.fast_pair_turn_on_bt_device_pairing_mode); - } else { - notificationContent = - mResources.getString(R.string.fast_pair_unable_to_connect_description); - } - Notification.Builder builder = - newBaseBuilder(item) - .setTickerForAccessibility(couldNotPair) - .setCategory(Notification.CATEGORY_ERROR) - .setContentTitle(couldNotPair) - .setContentText(notificationContent) - .setContentIntent(getBluetoothSettingsIntent()) - // Dismissing completes the attempt. - .setDeleteIntent(getBluetoothSettingsIntent()); - return builder.build(); - - } - - /** - * Creates paring successfully notification. - */ - public Notification pairingSucceededNotification( - int batteryLevel, - @Nullable String deviceName, - String modelName, - DiscoveryItem item) { - final String contentText; - StringBuilder contentTextBuilder = new StringBuilder(); - contentTextBuilder.append(modelName); - if (batteryLevel >= 0 && batteryLevel <= 100) { - contentTextBuilder - .append("\n") - .append(mResources.getString(R.string.common_battery_level, batteryLevel)); - } - String pairingComplete = - deviceName == null - ? mResources.getString(R.string.fast_pair_device_ready) - : mResources.getString( - R.string.fast_pair_device_ready_with_device_name, deviceName); - contentText = contentTextBuilder.toString(); - Notification.Builder builder = - newBaseBuilder(item) - .setTickerForAccessibility(pairingComplete) - .setCategory(Notification.CATEGORY_STATUS) - .setContentTitle(pairingComplete) - .setContentText(contentText); - - return builder.build(); - } - - private PendingIntent getPairIntent(Cache.StoredDiscoveryItem item, byte[] accountKey) { - Intent intent = - new Intent(ACTION_FAST_PAIR) - .putExtra(EXTRA_ITEM_ID, item.getId()) - // Encode account key as a string instead of bytes so that it can be passed - // to the string representation of the intent. - .putExtra(EXTRA_FAST_PAIR_SECRET, base16().encode(accountKey)) - .setPackage(mContext.getPackageName()); - return PendingIntent.getBroadcast(mContext, sRequestCode++, intent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); - } - - private PendingIntent getBluetoothSettingsIntent() { - Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS); - return PendingIntent.getActivity(mContext, sRequestCode++, intent, - PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); - } - - private LargeHeadsUpNotificationBuilder newBaseBuilder(DiscoveryItem item) { - LargeHeadsUpNotificationBuilder builder = - (LargeHeadsUpNotificationBuilder) - (new LargeHeadsUpNotificationBuilder( - mContext, - DEVICES_WITHIN_REACH_CHANNEL_ID, - /* largeIcon= */ true) - .setIsDevice(true) - // Tapping does not dismiss this. - .setSmallIcon(Icon.createWithResource( - mResources.getResourcesContext(), - R.drawable.quantum_ic_devices_other_vd_theme_24))) - .setLargeIcon(IconUtils.addWhiteCircleBackground( - mResources.getResourcesContext(), item.getIcon())) - // Dismissible. - .setOngoing(false) - // Timestamp is not relevant, hide it. - .setShowWhen(false) - .setColor(mResources.getColor(R.color.discovery_activity_accent)) - .setLocalOnly(true) - // don't show these notifications on wear devices - .setAutoCancel(true); - - return builder; - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/notification/LargeHeadsUpNotificationBuilder.java b/nearby/service/java/com/android/server/nearby/fastpair/notification/LargeHeadsUpNotificationBuilder.java deleted file mode 100644 index ec41d765d043f69bfdf766e08044002138dc2c42..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/notification/LargeHeadsUpNotificationBuilder.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.fastpair.notification; - -import static com.android.server.nearby.fastpair.Constant.TAG; - -import android.annotation.LayoutRes; -import android.annotation.Nullable; -import android.app.Notification; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.util.Log; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.RemoteViews; - -import com.android.nearby.halfsheet.R; - -/** Wrapper class for creating larger heads up notifications. */ -public class LargeHeadsUpNotificationBuilder extends FastPairNotificationBuilder { - private final boolean mLargeIcon; - private final RemoteViews mNotification; - private final RemoteViews mNotificationCollapsed; - - @Nullable private Runnable mLargeIconAction; - @Nullable private Runnable mProgressAction; - - public LargeHeadsUpNotificationBuilder(Context context, String channelId, boolean largeIcon) { - super(context, channelId); - - this.mLargeIcon = largeIcon; - this.mNotification = getRemoteViews(R.layout.fast_pair_heads_up_notification); - this.mNotificationCollapsed = getRemoteViews(R.layout.fast_pair_heads_up_notification); - - if (mNotification != null) { - mNotificationCollapsed.setViewVisibility(android.R.id.secondaryProgress, View.GONE); - } - } - - /** - * Create a new RemoteViews object that will display the views contained - * fast_pair_heads_up_notification layout. - */ - @Nullable - public RemoteViews getRemoteViews(@LayoutRes int layoutId) { - return new RemoteViews(mPackageName, layoutId); - } - - @Override - public Notification.Builder setContentTitle(@Nullable CharSequence title) { - if (mNotification != null) { - mNotification.setTextViewText(android.R.id.title, title); - } - if (mNotificationCollapsed != null) { - mNotificationCollapsed.setTextViewText(android.R.id.title, title); - // Collapsed mode does not need additional lines. - mNotificationCollapsed.setInt(android.R.id.title, "setMaxLines", 1); - } - return super.setContentTitle(title); - } - - @Override - public Notification.Builder setContentText(@Nullable CharSequence text) { - if (mNotification != null) { - mNotification.setTextViewText(android.R.id.text1, text); - } - if (mNotificationCollapsed != null) { - mNotificationCollapsed.setTextViewText(android.R.id.text1, text); - // Collapsed mode does not need additional lines. - mNotificationCollapsed.setInt(android.R.id.text1, "setMaxLines", 1); - } - - return super.setContentText(text); - } - - @Override - public Notification.Builder setSubText(@Nullable CharSequence subText) { - if (mNotification != null) { - mNotification.setTextViewText(android.R.id.text2, subText); - } - if (mNotificationCollapsed != null) { - mNotificationCollapsed.setTextViewText(android.R.id.text2, subText); - } - return super.setSubText(subText); - } - - @Override - public Notification.Builder setLargeIcon(@Nullable Bitmap bitmap) { - RemoteViews image = - getRemoteViews( - useLargeIcon() - ? R.layout.fast_pair_heads_up_notification_large_image - : R.layout.fast_pair_heads_up_notification_small_image); - if (image == null) { - return super.setLargeIcon(bitmap); - } - image.setImageViewBitmap(android.R.id.icon, bitmap); - - if (mNotification != null) { - mNotification.removeAllViews(android.R.id.icon1); - mNotification.addView(android.R.id.icon1, image); - } - if (mNotificationCollapsed != null) { - mNotificationCollapsed.removeAllViews(android.R.id.icon1); - mNotificationCollapsed.addView(android.R.id.icon1, image); - // In Android S, if super.setLargeIcon() is called, there will be an extra icon on - // top-right. - // But we still need to store this setting for the default UI when something wrong. - mLargeIconAction = () -> super.setLargeIcon(bitmap); - return this; - } - return super.setLargeIcon(bitmap); - } - - @Override - public Notification.Builder setProgress(int max, int progress, boolean indeterminate) { - if (mNotification != null) { - mNotification.setViewVisibility(android.R.id.secondaryProgress, View.VISIBLE); - mNotification.setProgressBar(android.R.id.progress, max, progress, indeterminate); - } - if (mNotificationCollapsed != null) { - mNotificationCollapsed.setViewVisibility(android.R.id.secondaryProgress, View.VISIBLE); - mNotificationCollapsed.setProgressBar(android.R.id.progress, max, progress, - indeterminate); - // In Android S, if super.setProgress() is called, there will be an extra progress bar. - // But we still need to store this setting for the default UI when something wrong. - mProgressAction = () -> super.setProgress(max, progress, indeterminate); - return this; - } - return super.setProgress(max, progress, indeterminate); - } - - @Override - public Notification build() { - if (mNotification != null) { - boolean buildSuccess = false; - try { - // Attempt to apply the remote views. This verifies that all of the resources are - // correctly available. - // If it fails, fall back to a non-custom notification. - mNotification.apply(mContext, new LinearLayout(mContext)); - if (mNotificationCollapsed != null) { - mNotificationCollapsed.apply(mContext, new LinearLayout(mContext)); - } - buildSuccess = true; - } catch (Resources.NotFoundException e) { - Log.w(TAG, "Failed to build notification, not setting custom view.", e); - } - - if (buildSuccess) { - if (mNotificationCollapsed != null) { - setStyle(new Notification.DecoratedCustomViewStyle()); - setCustomContentView(mNotificationCollapsed); - setCustomBigContentView(mNotification); - } else { - setCustomHeadsUpContentView(mNotification); - } - } else { - if (mLargeIconAction != null) { - mLargeIconAction.run(); - } - if (mProgressAction != null) { - mProgressAction.run(); - } - } - } - return super.build(); - } - - private boolean useLargeIcon() { - return mLargeIcon; - } -} diff --git a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandler.java deleted file mode 100644 index e56f1ea02a85beac41508aa6182dd818278a23e7..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandler.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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.server.nearby.fastpair.pairinghandler; - - -import android.annotation.Nullable; -import android.bluetooth.BluetoothDevice; -import android.content.Context; - -import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection; -import com.android.server.nearby.common.locator.Locator; -import com.android.server.nearby.fastpair.cache.DiscoveryItem; -import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager; -import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager; -import com.android.server.nearby.intdefs.NearbyEventIntDefs; - -/** Pairing progress handler that handle pairing come from half sheet. */ -public final class HalfSheetPairingProgressHandler extends PairingProgressHandlerBase { - - private final FastPairHalfSheetManager mFastPairHalfSheetManager; - private final boolean mIsSubsequentPair; - private final DiscoveryItem mItemResurface; - - HalfSheetPairingProgressHandler( - Context context, - DiscoveryItem item, - @Nullable String companionApp, - @Nullable byte[] accountKey) { - super(context, item); - this.mFastPairHalfSheetManager = Locator.get(context, FastPairHalfSheetManager.class); - this.mIsSubsequentPair = - item.getAuthenticationPublicKeySecp256R1() != null && accountKey != null; - this.mItemResurface = item; - } - - @Override - protected int getPairStartEventCode() { - return mIsSubsequentPair ? NearbyEventIntDefs.EventCode.SUBSEQUENT_PAIR_START - : NearbyEventIntDefs.EventCode.MAGIC_PAIR_START; - } - - @Override - protected int getPairEndEventCode() { - return mIsSubsequentPair ? NearbyEventIntDefs.EventCode.SUBSEQUENT_PAIR_END - : NearbyEventIntDefs.EventCode.MAGIC_PAIR_END; - } - - @Override - public void onPairingStarted() { - super.onPairingStarted(); - // Half sheet is not in the foreground reshow half sheet, also avoid showing HalfSheet on TV - if (!mFastPairHalfSheetManager.getHalfSheetForeground()) { - mFastPairHalfSheetManager.showPairingHalfSheet(mItemResurface); - } - mFastPairHalfSheetManager.reportActivelyPairing(); - mFastPairHalfSheetManager.disableDismissRunnable(); - } - - @Override - public void onHandlePasskeyConfirmation(BluetoothDevice device, int passkey) { - super.onHandlePasskeyConfirmation(device, passkey); - mFastPairHalfSheetManager.showPasskeyConfirmation(device, passkey); - } - - @Nullable - @Override - public String onPairedCallbackCalled( - FastPairConnection connection, - byte[] accountKey, - FootprintsDeviceManager footprints, - String address) { - String deviceName = super.onPairedCallbackCalled(connection, accountKey, - footprints, address); - mFastPairHalfSheetManager.showPairingSuccessHalfSheet(address); - mFastPairHalfSheetManager.disableDismissRunnable(); - return deviceName; - } - - @Override - public void onPairingFailed(Throwable throwable) { - super.onPairingFailed(throwable); - mFastPairHalfSheetManager.disableDismissRunnable(); - mFastPairHalfSheetManager.showPairingFailed(); - mFastPairHalfSheetManager.notifyPairingProcessDone( - /* success= */ false, /* publicAddress= */ null, mItem); - // fix auto rebond issue - mFastPairHalfSheetManager.destroyBluetoothPairController(); - } - - @Override - public void onPairingSuccess(String address) { - super.onPairingSuccess(address); - mFastPairHalfSheetManager.disableDismissRunnable(); - mFastPairHalfSheetManager - .notifyPairingProcessDone(/* success= */ true, address, mItem); - mFastPairHalfSheetManager.destroyBluetoothPairController(); - } -} - diff --git a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandler.java deleted file mode 100644 index 5317673ccd69a6b5c83ca4f8dd55b661ea5a80b9..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandler.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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.server.nearby.fastpair.pairinghandler; - -import android.annotation.Nullable; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothManager; -import android.content.Context; -import android.util.Log; - -import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection; -import com.android.server.nearby.fastpair.cache.DiscoveryItem; -import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager; -import com.android.server.nearby.fastpair.notification.FastPairNotificationManager; -import com.android.server.nearby.intdefs.NearbyEventIntDefs; - -/** Pairing progress handler for pairing coming from notifications. */ -@SuppressWarnings("nullness") -public class NotificationPairingProgressHandler extends PairingProgressHandlerBase { - private final FastPairNotificationManager mFastPairNotificationManager; - @Nullable - private final String mCompanionApp; - @Nullable - private final byte[] mAccountKey; - private final boolean mIsSubsequentPair; - - NotificationPairingProgressHandler( - Context context, - DiscoveryItem item, - @Nullable String companionApp, - @Nullable byte[] accountKey, - FastPairNotificationManager mFastPairNotificationManager) { - super(context, item); - this.mFastPairNotificationManager = mFastPairNotificationManager; - this.mCompanionApp = companionApp; - this.mAccountKey = accountKey; - this.mIsSubsequentPair = - item.getAuthenticationPublicKeySecp256R1() != null && accountKey != null; - } - - @Override - public int getPairStartEventCode() { - return mIsSubsequentPair ? NearbyEventIntDefs.EventCode.SUBSEQUENT_PAIR_START - : NearbyEventIntDefs.EventCode.MAGIC_PAIR_START; - } - - @Override - public int getPairEndEventCode() { - return mIsSubsequentPair ? NearbyEventIntDefs.EventCode.SUBSEQUENT_PAIR_END - : NearbyEventIntDefs.EventCode.MAGIC_PAIR_END; - } - - @Override - public void onReadyToPair() { - super.onReadyToPair(); - mFastPairNotificationManager.showConnectingNotification(mItem); - } - - @Override - public String onPairedCallbackCalled( - FastPairConnection connection, - byte[] accountKey, - FootprintsDeviceManager footprints, - String address) { - String deviceName = super.onPairedCallbackCalled(connection, accountKey, footprints, - address); - int batteryLevel = -1; - - BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class); - BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); - if (address != null && bluetoothAdapter != null) { - batteryLevel = bluetoothAdapter.getRemoteDevice(address).getBatteryLevel(); - } else { - Log.v(TAG, "onPairedCallbackCalled getBatteryLevel failed"); - } - mFastPairNotificationManager - .showPairingSucceededNotification(mItem, batteryLevel, deviceName); - return deviceName; - } - - @Override - public void onPairingFailed(Throwable throwable) { - super.onPairingFailed(throwable); - mFastPairNotificationManager.showPairingFailedNotification(mItem, mAccountKey); - mFastPairNotificationManager.notifyPairingProcessDone( - /* success= */ false, - /* forceNotify= */ false, - /* privateAddress= */ mItem.getMacAddress(), - /* publicAddress= */ null); - } - - @Override - public void onPairingSuccess(String address) { - super.onPairingSuccess(address); - int batteryLevel = -1; - - BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class); - BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); - String deviceName = null; - if (address != null && bluetoothAdapter != null) { - deviceName = bluetoothAdapter.getRemoteDevice(address).getName(); - batteryLevel = bluetoothAdapter.getRemoteDevice(address).getBatteryLevel(); - } else { - Log.v(TAG, "onPairedCallbackCalled getBatteryLevel failed"); - } - mFastPairNotificationManager - .showPairingSucceededNotification(mItem, batteryLevel, deviceName); - mFastPairNotificationManager.notifyPairingProcessDone( - /* success= */ true, - /* forceNotify= */ false, - /* privateAddress= */ mItem.getMacAddress(), - /* publicAddress= */ address); - } -} - diff --git a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java deleted file mode 100644 index 6f2dc40fe2605569545e3b3ceaaebbe6f478b240..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * 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.server.nearby.fastpair.pairinghandler; - -import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress; -import static com.android.server.nearby.fastpair.FastPairManager.isThroughFastPair2InitialPairing; - -import android.annotation.Nullable; -import android.bluetooth.BluetoothDevice; -import android.content.Context; -import android.util.Log; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection; -import com.android.server.nearby.common.bluetooth.fastpair.Preferences; -import com.android.server.nearby.fastpair.cache.DiscoveryItem; -import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager; -import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager; -import com.android.server.nearby.fastpair.notification.FastPairNotificationManager; -import com.android.server.nearby.intdefs.FastPairEventIntDefs; - -/** Base class for pairing progress handler. */ -public abstract class PairingProgressHandlerBase { - protected static final String TAG = "FPPairingHandler"; - protected final Context mContext; - protected final DiscoveryItem mItem; - @Nullable - private FastPairEventIntDefs.ErrorCode mRescueFromError; - - protected abstract int getPairStartEventCode(); - - protected abstract int getPairEndEventCode(); - - protected PairingProgressHandlerBase(Context context, DiscoveryItem item) { - this.mContext = context; - this.mItem = item; - } - - /** - * Pairing progress init function. - */ - public static PairingProgressHandlerBase create( - Context context, - DiscoveryItem item, - @Nullable String companionApp, - @Nullable byte[] accountKey, - FootprintsDeviceManager footprints, - FastPairNotificationManager notificationManager, - FastPairHalfSheetManager fastPairHalfSheetManager, - boolean isRetroactivePair) { - PairingProgressHandlerBase pairingProgressHandlerBase; - // Disable half sheet on subsequent pairing - if (item.getAuthenticationPublicKeySecp256R1() != null - && accountKey != null) { - // Subsequent pairing - pairingProgressHandlerBase = - new NotificationPairingProgressHandler( - context, item, companionApp, accountKey, notificationManager); - } else { - pairingProgressHandlerBase = - new HalfSheetPairingProgressHandler(context, item, companionApp, accountKey); - } - - Log.v(TAG, "PairingProgressHandler:Create " + item.getMacAddress() + " for pairing"); - return pairingProgressHandlerBase; - } - - /** - * Function calls when pairing start. - */ - public void onPairingStarted() { - Log.v(TAG, "PairingProgressHandler:onPairingStarted"); - } - - /** - * Waits for screen to unlock. - */ - public void onWaitForScreenUnlock() { - Log.v(TAG, "PairingProgressHandler:onWaitForScreenUnlock"); - } - - /** - * Function calls when screen unlock. - */ - public void onScreenUnlocked() { - Log.v(TAG, "PairingProgressHandler:onScreenUnlocked"); - } - - /** - * Calls when the handler is ready to pair. - */ - public void onReadyToPair() { - Log.v(TAG, "PairingProgressHandler:onReadyToPair"); - } - - /** - * Helps to set up pairing preference. - */ - public void onSetupPreferencesBuilder(Preferences.Builder builder) { - Log.v(TAG, "PairingProgressHandler:onSetupPreferencesBuilder"); - } - - /** - * Calls when pairing setup complete. - */ - public void onPairingSetupCompleted() { - Log.v(TAG, "PairingProgressHandler:onPairingSetupCompleted"); - } - - /** Called while pairing if needs to handle the passkey confirmation by Ui. */ - public void onHandlePasskeyConfirmation(BluetoothDevice device, int passkey) { - Log.v(TAG, "PairingProgressHandler:onHandlePasskeyConfirmation"); - } - - /** - * In this callback, we know if it is a real initial pairing by existing account key, and do - * following things: - *

  • 1, optIn footprint for initial pairing. - *
  • 2, write the device name to provider - *
  • 2.1, generate default personalized name for initial pairing or get the personalized name - * from footprint for subsequent pairing. - *
  • 2.2, set alias name for the bluetooth device. - *
  • 2.3, update the device name for connection to write into provider for initial pair. - *
  • 3, suppress battery notifications until oobe finishes. - * - * @return display name of the pairing device - */ - @Nullable - public String onPairedCallbackCalled( - FastPairConnection connection, - byte[] accountKey, - FootprintsDeviceManager footprints, - String address) { - Log.v(TAG, - "PairingProgressHandler:onPairedCallbackCalled with address: " - + address); - - byte[] existingAccountKey = connection.getExistingAccountKey(); - optInFootprintsForInitialPairing(footprints, mItem, accountKey, existingAccountKey); - // Add support for naming the device - return null; - } - - /** - * Gets the related info from db use account key. - */ - @Nullable - public byte[] getKeyForLocalCache( - byte[] accountKey, FastPairConnection connection, - FastPairConnection.SharedSecret sharedSecret) { - Log.v(TAG, "PairingProgressHandler:getKeyForLocalCache"); - return accountKey != null ? accountKey : connection.getExistingAccountKey(); - } - - /** - * Function handles pairing fail. - */ - public void onPairingFailed(Throwable throwable) { - Log.w(TAG, "PairingProgressHandler:onPairingFailed"); - } - - /** - * Function handles pairing success. - */ - public void onPairingSuccess(String address) { - Log.v(TAG, "PairingProgressHandler:onPairingSuccess with address: " - + maskBluetoothAddress(address)); - } - - @VisibleForTesting - static void optInFootprintsForInitialPairing( - FootprintsDeviceManager footprints, - DiscoveryItem item, - byte[] accountKey, - @Nullable byte[] existingAccountKey) { - if (isThroughFastPair2InitialPairing(item, accountKey) && existingAccountKey == null) { - // enable the save to footprint - Log.v(TAG, "footprint should call opt in here"); - } - } - - /** - * Returns {@code true} if the PairingProgressHandler is running at the background. - * - *

    In order to keep the following status notification shows as a heads up, we must wait for - * the screen unlocked to continue. - */ - public boolean skipWaitingScreenUnlock() { - return false; - } -} - diff --git a/nearby/service/java/com/android/server/nearby/intdefs/FastPairEventIntDefs.java b/nearby/service/java/com/android/server/nearby/intdefs/FastPairEventIntDefs.java deleted file mode 100644 index 8bb7980dd20546646b95a79892525d3cf33c21fe..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/intdefs/FastPairEventIntDefs.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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 com.android.server.nearby.intdefs; - -import androidx.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** Holds integer definitions for FastPair. */ -public class FastPairEventIntDefs { - - /** Fast Pair Bond State. */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - value = { - BondState.UNKNOWN_BOND_STATE, - BondState.NONE, - BondState.BONDING, - BondState.BONDED, - }) - public @interface BondState { - int UNKNOWN_BOND_STATE = 0; - int NONE = 10; - int BONDING = 11; - int BONDED = 12; - } - - /** Fast Pair error code. */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - value = { - ErrorCode.UNKNOWN_ERROR_CODE, - ErrorCode.OTHER_ERROR, - ErrorCode.TIMEOUT, - ErrorCode.INTERRUPTED, - ErrorCode.REFLECTIVE_OPERATION_EXCEPTION, - ErrorCode.EXECUTION_EXCEPTION, - ErrorCode.PARSE_EXCEPTION, - ErrorCode.MDH_REMOTE_EXCEPTION, - ErrorCode.SUCCESS_RETRY_GATT_ERROR, - ErrorCode.SUCCESS_RETRY_GATT_TIMEOUT, - ErrorCode.SUCCESS_RETRY_SECRET_HANDSHAKE_ERROR, - ErrorCode.SUCCESS_RETRY_SECRET_HANDSHAKE_TIMEOUT, - ErrorCode.SUCCESS_SECRET_HANDSHAKE_RECONNECT, - ErrorCode.SUCCESS_ADDRESS_ROTATE, - ErrorCode.SUCCESS_SIGNAL_LOST, - }) - public @interface ErrorCode { - int UNKNOWN_ERROR_CODE = 0; - - // Check the other fields for a more specific error code. - int OTHER_ERROR = 1; - - // The operation timed out. - int TIMEOUT = 2; - - // The thread was interrupted. - int INTERRUPTED = 3; - - // Some reflective call failed (should never happen). - int REFLECTIVE_OPERATION_EXCEPTION = 4; - - // A Future threw an exception (should never happen). - int EXECUTION_EXCEPTION = 5; - - // Parsing something (e.g. BR/EDR Handover data) failed. - int PARSE_EXCEPTION = 6; - - // A failure at MDH. - int MDH_REMOTE_EXCEPTION = 7; - - // For errors on GATT connection and retry success - int SUCCESS_RETRY_GATT_ERROR = 8; - - // For timeout on GATT connection and retry success - int SUCCESS_RETRY_GATT_TIMEOUT = 9; - - // For errors on secret handshake and retry success - int SUCCESS_RETRY_SECRET_HANDSHAKE_ERROR = 10; - - // For timeout on secret handshake and retry success - int SUCCESS_RETRY_SECRET_HANDSHAKE_TIMEOUT = 11; - - // For secret handshake fail and restart GATT connection success - int SUCCESS_SECRET_HANDSHAKE_RECONNECT = 12; - - // For address rotate and retry with new address success - int SUCCESS_ADDRESS_ROTATE = 13; - - // For signal lost and retry with old address still success - int SUCCESS_SIGNAL_LOST = 14; - } - - /** Fast Pair BrEdrHandover Error Code. */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - value = { - BrEdrHandoverErrorCode.UNKNOWN_BR_EDR_HANDOVER_ERROR_CODE, - BrEdrHandoverErrorCode.CONTROL_POINT_RESULT_CODE_NOT_SUCCESS, - BrEdrHandoverErrorCode.BLUETOOTH_MAC_INVALID, - BrEdrHandoverErrorCode.TRANSPORT_BLOCK_INVALID, - }) - public @interface BrEdrHandoverErrorCode { - int UNKNOWN_BR_EDR_HANDOVER_ERROR_CODE = 0; - int CONTROL_POINT_RESULT_CODE_NOT_SUCCESS = 1; - int BLUETOOTH_MAC_INVALID = 2; - int TRANSPORT_BLOCK_INVALID = 3; - } - - /** Fast Pair CreateBound Error Code. */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - value = { - CreateBondErrorCode.UNKNOWN_BOND_ERROR_CODE, - CreateBondErrorCode.BOND_BROKEN, - CreateBondErrorCode.POSSIBLE_MITM, - CreateBondErrorCode.NO_PERMISSION, - CreateBondErrorCode.INCORRECT_VARIANT, - CreateBondErrorCode.FAILED_BUT_ALREADY_RECEIVE_PASS_KEY, - }) - public @interface CreateBondErrorCode { - int UNKNOWN_BOND_ERROR_CODE = 0; - int BOND_BROKEN = 1; - int POSSIBLE_MITM = 2; - int NO_PERMISSION = 3; - int INCORRECT_VARIANT = 4; - int FAILED_BUT_ALREADY_RECEIVE_PASS_KEY = 5; - } - - /** Fast Pair Connect Error Code. */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - value = { - ConnectErrorCode.UNKNOWN_CONNECT_ERROR_CODE, - ConnectErrorCode.UNSUPPORTED_PROFILE, - ConnectErrorCode.GET_PROFILE_PROXY_FAILED, - ConnectErrorCode.DISCONNECTED, - ConnectErrorCode.LINK_KEY_CLEARED, - ConnectErrorCode.FAIL_TO_DISCOVERY, - ConnectErrorCode.DISCOVERY_NOT_FINISHED, - }) - public @interface ConnectErrorCode { - int UNKNOWN_CONNECT_ERROR_CODE = 0; - int UNSUPPORTED_PROFILE = 1; - int GET_PROFILE_PROXY_FAILED = 2; - int DISCONNECTED = 3; - int LINK_KEY_CLEARED = 4; - int FAIL_TO_DISCOVERY = 5; - int DISCOVERY_NOT_FINISHED = 6; - } - - private FastPairEventIntDefs() {} -} diff --git a/nearby/service/java/com/android/server/nearby/intdefs/NearbyEventIntDefs.java b/nearby/service/java/com/android/server/nearby/intdefs/NearbyEventIntDefs.java deleted file mode 100644 index 91bf49a6a9f4f72f88f3c19e57c829423e785ac1..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/intdefs/NearbyEventIntDefs.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * 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 com.android.server.nearby.intdefs; - -import androidx.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** Holds integer definitions for NearbyEvent. */ -public class NearbyEventIntDefs { - - /** NearbyEvent Code. */ - @Retention(RetentionPolicy.SOURCE) - @IntDef( - value = { - EventCode.UNKNOWN_EVENT_TYPE, - EventCode.MAGIC_PAIR_START, - EventCode.WAIT_FOR_SCREEN_UNLOCK, - EventCode.GATT_CONNECT, - EventCode.BR_EDR_HANDOVER_WRITE_CONTROL_POINT_REQUEST, - EventCode.BR_EDR_HANDOVER_READ_BLUETOOTH_MAC, - EventCode.BR_EDR_HANDOVER_READ_TRANSPORT_BLOCK, - EventCode.GET_PROFILES_VIA_SDP, - EventCode.DISCOVER_DEVICE, - EventCode.CANCEL_DISCOVERY, - EventCode.REMOVE_BOND, - EventCode.CANCEL_BOND, - EventCode.CREATE_BOND, - EventCode.CONNECT_PROFILE, - EventCode.DISABLE_BLUETOOTH, - EventCode.ENABLE_BLUETOOTH, - EventCode.MAGIC_PAIR_END, - EventCode.SECRET_HANDSHAKE, - EventCode.WRITE_ACCOUNT_KEY, - EventCode.WRITE_TO_FOOTPRINTS, - EventCode.PASSKEY_EXCHANGE, - EventCode.DEVICE_RECOGNIZED, - EventCode.GET_LOCAL_PUBLIC_ADDRESS, - EventCode.DIRECTLY_CONNECTED_TO_PROFILE, - EventCode.DEVICE_ALIAS_CHANGED, - EventCode.WRITE_DEVICE_NAME, - EventCode.UPDATE_PROVIDER_NAME_START, - EventCode.UPDATE_PROVIDER_NAME_END, - EventCode.READ_FIRMWARE_VERSION, - EventCode.RETROACTIVE_PAIR_START, - EventCode.RETROACTIVE_PAIR_END, - EventCode.SUBSEQUENT_PAIR_START, - EventCode.SUBSEQUENT_PAIR_END, - EventCode.BISTO_PAIR_START, - EventCode.BISTO_PAIR_END, - EventCode.REMOTE_PAIR_START, - EventCode.REMOTE_PAIR_END, - EventCode.BEFORE_CREATE_BOND, - EventCode.BEFORE_CREATE_BOND_BONDING, - EventCode.BEFORE_CREATE_BOND_BONDED, - EventCode.BEFORE_CONNECT_PROFILE, - EventCode.HANDLE_PAIRING_REQUEST, - EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION, - EventCode.GATT_CONNECTION_AND_SECRET_HANDSHAKE, - EventCode.CHECK_SIGNAL_AFTER_HANDSHAKE, - EventCode.RECOVER_BY_RETRY_GATT, - EventCode.RECOVER_BY_RETRY_HANDSHAKE, - EventCode.RECOVER_BY_RETRY_HANDSHAKE_RECONNECT, - EventCode.GATT_HANDSHAKE_MANUAL_RETRY_ATTEMPTS, - EventCode.PAIR_WITH_CACHED_MODEL_ID, - EventCode.DIRECTLY_CONNECT_PROFILE_WITH_CACHED_ADDRESS, - EventCode.PAIR_WITH_NEW_MODEL, - }) - public @interface EventCode { - int UNKNOWN_EVENT_TYPE = 0; - - // Codes for Magic Pair. - // Starting at 1000 to not conflict with other existing codes (e.g. - // DiscoveryEvent) that may be migrated to become official Event Codes. - int MAGIC_PAIR_START = 1010; - int WAIT_FOR_SCREEN_UNLOCK = 1020; - int GATT_CONNECT = 1030; - int BR_EDR_HANDOVER_WRITE_CONTROL_POINT_REQUEST = 1040; - int BR_EDR_HANDOVER_READ_BLUETOOTH_MAC = 1050; - int BR_EDR_HANDOVER_READ_TRANSPORT_BLOCK = 1060; - int GET_PROFILES_VIA_SDP = 1070; - int DISCOVER_DEVICE = 1080; - int CANCEL_DISCOVERY = 1090; - int REMOVE_BOND = 1100; - int CANCEL_BOND = 1110; - int CREATE_BOND = 1120; - int CONNECT_PROFILE = 1130; - int DISABLE_BLUETOOTH = 1140; - int ENABLE_BLUETOOTH = 1150; - int MAGIC_PAIR_END = 1160; - int SECRET_HANDSHAKE = 1170; - int WRITE_ACCOUNT_KEY = 1180; - int WRITE_TO_FOOTPRINTS = 1190; - int PASSKEY_EXCHANGE = 1200; - int DEVICE_RECOGNIZED = 1210; - int GET_LOCAL_PUBLIC_ADDRESS = 1220; - int DIRECTLY_CONNECTED_TO_PROFILE = 1230; - int DEVICE_ALIAS_CHANGED = 1240; - int WRITE_DEVICE_NAME = 1250; - int UPDATE_PROVIDER_NAME_START = 1260; - int UPDATE_PROVIDER_NAME_END = 1270; - int READ_FIRMWARE_VERSION = 1280; - int RETROACTIVE_PAIR_START = 1290; - int RETROACTIVE_PAIR_END = 1300; - int SUBSEQUENT_PAIR_START = 1310; - int SUBSEQUENT_PAIR_END = 1320; - int BISTO_PAIR_START = 1330; - int BISTO_PAIR_END = 1340; - int REMOTE_PAIR_START = 1350; - int REMOTE_PAIR_END = 1360; - int BEFORE_CREATE_BOND = 1370; - int BEFORE_CREATE_BOND_BONDING = 1380; - int BEFORE_CREATE_BOND_BONDED = 1390; - int BEFORE_CONNECT_PROFILE = 1400; - int HANDLE_PAIRING_REQUEST = 1410; - int SECRET_HANDSHAKE_GATT_COMMUNICATION = 1420; - int GATT_CONNECTION_AND_SECRET_HANDSHAKE = 1430; - int CHECK_SIGNAL_AFTER_HANDSHAKE = 1440; - int RECOVER_BY_RETRY_GATT = 1450; - int RECOVER_BY_RETRY_HANDSHAKE = 1460; - int RECOVER_BY_RETRY_HANDSHAKE_RECONNECT = 1470; - int GATT_HANDSHAKE_MANUAL_RETRY_ATTEMPTS = 1480; - int PAIR_WITH_CACHED_MODEL_ID = 1490; - int DIRECTLY_CONNECT_PROFILE_WITH_CACHED_ADDRESS = 1500; - int PAIR_WITH_NEW_MODEL = 1510; - } - - private NearbyEventIntDefs() {} -} diff --git a/nearby/service/java/com/android/server/nearby/managers/BroadcastProviderManager.java b/nearby/service/java/com/android/server/nearby/managers/BroadcastProviderManager.java index 024bff80d25c3a56d1bec771eb3f9a6285368a26..28a33fa1c03c85311f8a0198f557899c144002ad 100644 --- a/nearby/service/java/com/android/server/nearby/managers/BroadcastProviderManager.java +++ b/nearby/service/java/com/android/server/nearby/managers/BroadcastProviderManager.java @@ -22,6 +22,7 @@ import android.nearby.BroadcastCallback; import android.nearby.BroadcastRequest; import android.nearby.IBroadcastListener; import android.nearby.PresenceBroadcastRequest; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -49,6 +50,10 @@ public class BroadcastProviderManager implements BleBroadcastProvider.BroadcastL private final NearbyConfiguration mNearbyConfiguration; private IBroadcastListener mBroadcastListener; + // Used with mBroadcastListener. Now we only support single client, for multi clients, a map + // between live binder to the information over the binder is needed. + // TODO: Finish multi-client logic for broadcast. + private BroadcastListenerDeathRecipient mDeathRecipient; public BroadcastProviderManager(Context context, Injector injector) { this(ForegroundThread.getExecutor(), @@ -62,6 +67,7 @@ public class BroadcastProviderManager implements BleBroadcastProvider.BroadcastL mLock = new Object(); mNearbyConfiguration = new NearbyConfiguration(); mBroadcastListener = null; + mDeathRecipient = null; } /** @@ -70,6 +76,15 @@ public class BroadcastProviderManager implements BleBroadcastProvider.BroadcastL public void startBroadcast(BroadcastRequest broadcastRequest, IBroadcastListener listener) { synchronized (mLock) { mExecutor.execute(() -> { + if (listener == null) { + return; + } + if (mBroadcastListener != null) { + Log.i(TAG, "We do not support multi clients yet," + + " please stop previous broadcast first."); + reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE); + return; + } if (!mNearbyConfiguration.isTestAppSupported()) { NearbyConfiguration configuration = new NearbyConfiguration(); if (!configuration.isPresenceBroadcastLegacyEnabled()) { @@ -89,7 +104,17 @@ public class BroadcastProviderManager implements BleBroadcastProvider.BroadcastL reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE); return; } + BroadcastListenerDeathRecipient deathRecipient = + new BroadcastListenerDeathRecipient(listener); + try { + listener.asBinder().linkToDeath(deathRecipient, 0); + } catch (RemoteException e) { + // This binder has already died, so call the DeathRecipient as if we had + // called linkToDeath in time. + deathRecipient.binderDied(); + } mBroadcastListener = listener; + mDeathRecipient = deathRecipient; mBleBroadcastProvider.start(presenceBroadcastRequest.getVersion(), advertisement.toBytes(), this); }); @@ -113,13 +138,19 @@ public class BroadcastProviderManager implements BleBroadcastProvider.BroadcastL */ public void stopBroadcast(IBroadcastListener listener) { synchronized (mLock) { - if (!mNearbyConfiguration.isTestAppSupported() - && !mNearbyConfiguration.isPresenceBroadcastLegacyEnabled()) { - reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE); - return; + if (listener != null) { + if (!mNearbyConfiguration.isTestAppSupported() + && !mNearbyConfiguration.isPresenceBroadcastLegacyEnabled()) { + reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE); + return; + } + if (mDeathRecipient != null) { + listener.asBinder().unlinkToDeath(mDeathRecipient, 0); + } } mBroadcastListener = null; - mExecutor.execute(() -> mBleBroadcastProvider.stop()); + mDeathRecipient = null; + mExecutor.execute(mBleBroadcastProvider::stop); } } @@ -142,4 +173,21 @@ public class BroadcastProviderManager implements BleBroadcastProvider.BroadcastL Log.e(TAG, "remote exception when reporting status"); } } + + /** + * Class to make listener unregister after the binder is dead. + */ + public class BroadcastListenerDeathRecipient implements IBinder.DeathRecipient { + public IBroadcastListener mListener; + + BroadcastListenerDeathRecipient(IBroadcastListener listener) { + mListener = listener; + } + + @Override + public void binderDied() { + Log.d(TAG, "Binder is dead - stop broadcast listener"); + stopBroadcast(mListener); + } + } } diff --git a/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManager.java b/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManager.java index 0c4142657c346d5f4bb079152bb68654047a01f2..89952320250fb4795ca1593d35958f7cbcaa6990 100644 --- a/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManager.java +++ b/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManager.java @@ -21,6 +21,7 @@ import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE; import static com.android.server.nearby.NearbyService.TAG; import android.annotation.Nullable; +import android.bluetooth.BluetoothAdapter; import android.content.Context; import android.nearby.DataElement; import android.nearby.IScanListener; @@ -35,6 +36,7 @@ import android.util.Log; import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.nearby.NearbyConfiguration; import com.android.server.nearby.injector.Injector; import com.android.server.nearby.managers.registration.DiscoveryRegistration; import com.android.server.nearby.provider.AbstractDiscoveryProvider; @@ -66,6 +68,7 @@ public class DiscoveryProviderManager extends private final BleDiscoveryProvider mBleDiscoveryProvider; private final Injector mInjector; private final Executor mExecutor; + private final NearbyConfiguration mNearbyConfiguration; public DiscoveryProviderManager(Context context, Injector injector) { Log.v(TAG, "DiscoveryProviderManager: "); @@ -75,6 +78,7 @@ public class DiscoveryProviderManager extends mChreDiscoveryProvider = new ChreDiscoveryProvider(mContext, new ChreCommunication(injector, mContext, mExecutor), mExecutor); mInjector = injector; + mNearbyConfiguration = new NearbyConfiguration(); } @VisibleForTesting @@ -86,6 +90,7 @@ public class DiscoveryProviderManager extends mInjector = injector; mBleDiscoveryProvider = bleDiscoveryProvider; mChreDiscoveryProvider = chreDiscoveryProvider; + mNearbyConfiguration = new NearbyConfiguration(); } private static boolean isChreOnly(Set scanFilters) { @@ -141,6 +146,10 @@ public class DiscoveryProviderManager extends /** Called after boot completed. */ public void init() { + // Register BLE only scan when Bluetooth is turned off + if (mNearbyConfiguration.enableBleInInit()) { + setBleScanEnabled(); + } if (mInjector.getContextHubManager() != null) { mChreDiscoveryProvider.init(); } @@ -242,7 +251,7 @@ public class DiscoveryProviderManager extends @GuardedBy("mMultiplexerLock") private void startBleProvider(Set scanFilters) { if (!mBleDiscoveryProvider.getController().isStarted()) { - Log.d(TAG, "DiscoveryProviderManager starts Ble scanning."); + Log.d(TAG, "DiscoveryProviderManager starts BLE scanning."); mBleDiscoveryProvider.getController().setListener(this); mBleDiscoveryProvider.getController().setProviderScanMode(mMerged.getScanMode()); mBleDiscoveryProvider.getController().setProviderScanFilters( @@ -313,4 +322,29 @@ public class DiscoveryProviderManager extends public void onMergedRegistrationsUpdated() { invalidateProviderScanMode(); } + + /** + * Registers Nearby service to Ble scan if Bluetooth is off. (Even when Bluetooth is off) + * @return {@code true} when Nearby currently can scan through Bluetooth or Ble or successfully + * registers Nearby service to Ble scan when Blutooth is off. + */ + public boolean setBleScanEnabled() { + BluetoothAdapter adapter = mInjector.getBluetoothAdapter(); + if (adapter == null) { + Log.e(TAG, "BluetoothAdapter is null."); + return false; + } + if (adapter.isEnabled() || adapter.isLeEnabled()) { + return true; + } + if (!adapter.isBleScanAlwaysAvailable()) { + Log.v(TAG, "Ble always on scan is disabled."); + return false; + } + if (!adapter.enableBLE()) { + Log.e(TAG, "Failed to register Ble scan."); + return false; + } + return true; + } } diff --git a/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManagerLegacy.java b/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManagerLegacy.java index 4b76eba8888958a4dadd0257ec006f46a605b3bb..3ef8fb79e0c480a095fcdd1cff10918478a0f570 100644 --- a/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManagerLegacy.java +++ b/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManagerLegacy.java @@ -22,6 +22,7 @@ import static com.android.server.nearby.NearbyService.TAG; import android.annotation.Nullable; import android.app.AppOpsManager; +import android.bluetooth.BluetoothAdapter; import android.content.Context; import android.nearby.DataElement; import android.nearby.IScanListener; @@ -38,6 +39,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.nearby.NearbyConfiguration; import com.android.server.nearby.injector.Injector; import com.android.server.nearby.metrics.NearbyMetrics; import com.android.server.nearby.presence.PresenceDiscoveryResult; @@ -69,6 +71,7 @@ public class DiscoveryProviderManagerLegacy implements AbstractDiscoveryProvider private final Context mContext; private final BleDiscoveryProvider mBleDiscoveryProvider; private final Injector mInjector; + private final NearbyConfiguration mNearbyConfiguration; @ScanRequest.ScanMode private int mScanMode; @GuardedBy("mLock") @@ -83,6 +86,7 @@ public class DiscoveryProviderManagerLegacy implements AbstractDiscoveryProvider mContext, new ChreCommunication(injector, mContext, executor), executor); mScanTypeScanListenerRecordMap = new HashMap<>(); mInjector = injector; + mNearbyConfiguration = new NearbyConfiguration(); Log.v(TAG, "DiscoveryProviderManagerLegacy: "); } @@ -96,6 +100,7 @@ public class DiscoveryProviderManagerLegacy implements AbstractDiscoveryProvider mBleDiscoveryProvider = bleDiscoveryProvider; mChreDiscoveryProvider = chreDiscoveryProvider; mScanTypeScanListenerRecordMap = scanTypeScanListenerRecordMap; + mNearbyConfiguration = new NearbyConfiguration(); } private static boolean isChreOnly(List scanFilters) { @@ -142,18 +147,18 @@ public class DiscoveryProviderManagerLegacy implements AbstractDiscoveryProvider for (IBinder listenerBinder : mScanTypeScanListenerRecordMap.keySet()) { ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listenerBinder); if (record == null) { - Log.w(TAG, "DiscoveryProviderManager cannot find the scan record."); + Log.w(TAG, "DiscoveryProviderManagerLegacy cannot find the scan record."); continue; } CallerIdentity callerIdentity = record.getCallerIdentity(); if (!DiscoveryPermissions.noteDiscoveryResultDelivery( appOpsManager, callerIdentity)) { - Log.w(TAG, "[DiscoveryProviderManager] scan permission revoked " + Log.w(TAG, "[DiscoveryProviderManagerLegacy] scan permission revoked " + "- not forwarding results"); try { record.getScanListener().onError(ScanCallback.ERROR_PERMISSION_DENIED); } catch (RemoteException e) { - Log.w(TAG, "DiscoveryProviderManager failed to report error.", e); + Log.w(TAG, "DiscoveryProviderManagerLegacy failed to report error.", e); } return; } @@ -180,7 +185,7 @@ public class DiscoveryProviderManagerLegacy implements AbstractDiscoveryProvider NearbyMetrics.logScanDeviceDiscovered( record.hashCode(), record.getScanRequest(), nearbyDevice); } catch (RemoteException e) { - Log.w(TAG, "DiscoveryProviderManager failed to report onDiscovered.", e); + Log.w(TAG, "DiscoveryProviderManagerLegacy failed to report onDiscovered.", e); } } } @@ -193,18 +198,18 @@ public class DiscoveryProviderManagerLegacy implements AbstractDiscoveryProvider for (IBinder listenerBinder : mScanTypeScanListenerRecordMap.keySet()) { ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listenerBinder); if (record == null) { - Log.w(TAG, "DiscoveryProviderManager cannot find the scan record."); + Log.w(TAG, "DiscoveryProviderManagerLegacy cannot find the scan record."); continue; } CallerIdentity callerIdentity = record.getCallerIdentity(); if (!DiscoveryPermissions.noteDiscoveryResultDelivery( appOpsManager, callerIdentity)) { - Log.w(TAG, "[DiscoveryProviderManager] scan permission revoked " + Log.w(TAG, "[DiscoveryProviderManagerLegacy] scan permission revoked " + "- not forwarding results"); try { record.getScanListener().onError(ScanCallback.ERROR_PERMISSION_DENIED); } catch (RemoteException e) { - Log.w(TAG, "DiscoveryProviderManager failed to report error.", e); + Log.w(TAG, "DiscoveryProviderManagerLegacy failed to report error.", e); } return; } @@ -212,7 +217,7 @@ public class DiscoveryProviderManagerLegacy implements AbstractDiscoveryProvider try { record.getScanListener().onError(errorCode); } catch (RemoteException e) { - Log.w(TAG, "DiscoveryProviderManager failed to report onError.", e); + Log.w(TAG, "DiscoveryProviderManagerLegacy failed to report onError.", e); } } } @@ -220,6 +225,10 @@ public class DiscoveryProviderManagerLegacy implements AbstractDiscoveryProvider /** Called after boot completed. */ public void init() { + // Register BLE only scan when Bluetooth is turned off + if (mNearbyConfiguration.enableBleInInit()) { + setBleScanEnabled(); + } if (mInjector.getContextHubManager() != null) { mChreDiscoveryProvider.init(); } @@ -293,10 +302,10 @@ public class DiscoveryProviderManagerLegacy implements AbstractDiscoveryProvider if (listenerBinder != null && deathRecipient != null) { listenerBinder.unlinkToDeath(removedRecord.getDeathRecipient(), 0); } - Log.v(TAG, "DiscoveryProviderManager unregistered scan listener."); + Log.v(TAG, "DiscoveryProviderManagerLegacy unregistered scan listener."); NearbyMetrics.logScanStopped(removedRecord.hashCode(), removedRecord.getScanRequest()); if (mScanTypeScanListenerRecordMap.isEmpty()) { - Log.v(TAG, "DiscoveryProviderManager stops provider because there is no " + Log.v(TAG, "DiscoveryProviderManagerLegacy stops provider because there is no " + "scan listener registered."); stopProviders(); return; @@ -306,8 +315,8 @@ public class DiscoveryProviderManagerLegacy implements AbstractDiscoveryProvider // Removes current highest scan mode requested and sets the next highest scan mode. if (removedRecord.getScanRequest().getScanMode() == mScanMode) { - Log.v(TAG, "DiscoveryProviderManager starts to find the new highest scan mode " - + "because the highest scan mode listener was unregistered."); + Log.v(TAG, "DiscoveryProviderManagerLegacy starts to find the new highest " + + "scan mode because the highest scan mode listener was unregistered."); @ScanRequest.ScanMode int highestScanModeRequested = ScanRequest.SCAN_MODE_NO_POWER; // find the next highest scan mode; for (ScanListenerRecord record : mScanTypeScanListenerRecordMap.values()) { @@ -377,7 +386,7 @@ public class DiscoveryProviderManagerLegacy implements AbstractDiscoveryProvider private void startBleProvider(List scanFilters) { if (!mBleDiscoveryProvider.getController().isStarted()) { - Log.d(TAG, "DiscoveryProviderManager starts Ble scanning."); + Log.d(TAG, "DiscoveryProviderManagerLegacy starts BLE scanning."); mBleDiscoveryProvider.getController().setListener(this); mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode); mBleDiscoveryProvider.getController().setProviderScanFilters(scanFilters); @@ -387,7 +396,7 @@ public class DiscoveryProviderManagerLegacy implements AbstractDiscoveryProvider @VisibleForTesting void startChreProvider(List scanFilters) { - Log.d(TAG, "DiscoveryProviderManager starts CHRE scanning."); + Log.d(TAG, "DiscoveryProviderManagerLegacy starts CHRE scanning."); mChreDiscoveryProvider.getController().setProviderScanFilters(scanFilters); mChreDiscoveryProvider.getController().setProviderScanMode(mScanMode); mChreDiscoveryProvider.getController().start(); @@ -503,4 +512,29 @@ public class DiscoveryProviderManagerLegacy implements AbstractDiscoveryProvider unregisterScanListener(listener); } } + + /** + * Registers Nearby service to Ble scan if Bluetooth is off. (Even when Bluetooth is off) + * @return {@code true} when Nearby currently can scan through Bluetooth or Ble or successfully + * registers Nearby service to Ble scan when Blutooth is off. + */ + public boolean setBleScanEnabled() { + BluetoothAdapter adapter = mInjector.getBluetoothAdapter(); + if (adapter == null) { + Log.e(TAG, "BluetoothAdapter is null."); + return false; + } + if (adapter.isEnabled() || adapter.isLeEnabled()) { + return true; + } + if (!adapter.isBleScanAlwaysAvailable()) { + Log.v(TAG, "Ble always on scan is disabled."); + return false; + } + if (!adapter.enableBLE()) { + Log.e(TAG, "Failed to register Ble scan."); + return false; + } + return true; + } } diff --git a/nearby/service/java/com/android/server/nearby/presence/EncryptionInfo.java b/nearby/service/java/com/android/server/nearby/presence/EncryptionInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..ac1e18ff477328dcd775e9fe4bc08ce71af3dac0 --- /dev/null +++ b/nearby/service/java/com/android/server/nearby/presence/EncryptionInfo.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2023 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.server.nearby.presence; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.nearby.DataElement; + +import androidx.annotation.NonNull; + +import com.android.internal.util.Preconditions; +import com.android.server.nearby.util.ArrayUtils; + +import java.util.Arrays; + +/** + * Data Element that indicates the presence of extended 16 bytes salt instead of 2 byte salt and to + * indicate which encoding scheme (MIC tag or Signature) is used for a section. If present, it must + * be the first DE in a section. + */ +public class EncryptionInfo { + + @IntDef({EncodingScheme.MIC, EncodingScheme.SIGNATURE}) + public @interface EncodingScheme { + int MIC = 0; + int SIGNATURE = 1; + } + + // 1st byte : encryption scheme + // 2nd to 17th bytes: salt + public static final int ENCRYPTION_INFO_LENGTH = 17; + private static final int ENCODING_SCHEME_MASK = 0b01111000; + private static final int ENCODING_SCHEME_OFFSET = 3; + @EncodingScheme + private final int mEncodingScheme; + private final byte[] mSalt; + + /** + * Constructs a {@link DataElement}. + */ + public EncryptionInfo(@NonNull byte[] value) { + Preconditions.checkArgument(value.length == ENCRYPTION_INFO_LENGTH); + mEncodingScheme = (value[0] & ENCODING_SCHEME_MASK) >> ENCODING_SCHEME_OFFSET; + Preconditions.checkArgument(isValidEncodingScheme(mEncodingScheme)); + mSalt = Arrays.copyOfRange(value, 1, ENCRYPTION_INFO_LENGTH); + } + + private boolean isValidEncodingScheme(int scheme) { + return scheme == EncodingScheme.MIC || scheme == EncodingScheme.SIGNATURE; + } + + @EncodingScheme + public int getEncodingScheme() { + return mEncodingScheme; + } + + public byte[] getSalt() { + return mSalt; + } + + /** Combines the encoding scheme and salt to a byte array + * that represents an {@link EncryptionInfo}. + */ + @Nullable + public static byte[] toByte(@EncodingScheme int encodingScheme, byte[] salt) { + if (ArrayUtils.isEmpty(salt)) { + return null; + } + if (salt.length != ENCRYPTION_INFO_LENGTH - 1) { + return null; + } + byte schemeByte = + (byte) ((encodingScheme << ENCODING_SCHEME_OFFSET) & ENCODING_SCHEME_MASK); + return ArrayUtils.append(schemeByte, salt); + } +} diff --git a/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisement.java b/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisement.java index 34a7514bd526723819f0384b07c3c7f34fb0343a..c2304ccbb5facf92ac856ee68179f4e8261dc1ac 100644 --- a/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisement.java +++ b/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisement.java @@ -16,22 +16,27 @@ package com.android.server.nearby.presence; +import static android.nearby.BroadcastRequest.PRESENCE_VERSION_V1; + import static com.android.server.nearby.NearbyService.TAG; +import static com.android.server.nearby.presence.EncryptionInfo.ENCRYPTION_INFO_LENGTH; +import static com.android.server.nearby.presence.PresenceConstants.PRESENCE_UUID_BYTES; import android.annotation.Nullable; -import android.nearby.BroadcastRequest; +import android.nearby.BroadcastRequest.BroadcastVersion; import android.nearby.DataElement; +import android.nearby.DataElement.DataType; import android.nearby.PresenceBroadcastRequest; import android.nearby.PresenceCredential; import android.nearby.PublicCredential; import android.util.Log; +import com.android.server.nearby.util.ArrayUtils; import com.android.server.nearby.util.encryption.Cryptor; -import com.android.server.nearby.util.encryption.CryptorImpFake; -import com.android.server.nearby.util.encryption.CryptorImpIdentityV1; -import com.android.server.nearby.util.encryption.CryptorImpV1; +import com.android.server.nearby.util.encryption.CryptorMicImp; import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -51,37 +56,51 @@ import java.util.Objects; * The header contains: * version (3 bits) | 5 bit reserved for future use (RFU) */ -public class ExtendedAdvertisement extends Advertisement{ +public class ExtendedAdvertisement extends Advertisement { public static final int SALT_DATA_LENGTH = 2; - static final int HEADER_LENGTH = 1; static final int IDENTITY_DATA_LENGTH = 16; - + // Identity Index is always 2 . + // 0 is reserved, 1 is Salt or Credential Element. + private static final int CIPHER_START_INDEX = 2; private final List mDataElements; + private final byte[] mKeySeed; - private final byte[] mAuthenticityKey; + private final byte[] mData; - // All Data Elements including salt and identity. - // Each list item (byte array) is a Data Element (with its header). - private final List mCompleteDataElementsBytes; - // Signature generated from data elements. - private final byte[] mHmacTag; + private ExtendedAdvertisement( + @PresenceCredential.IdentityType int identityType, + byte[] identity, + byte[] salt, + byte[] keySeed, + List actions, + List dataElements) { + this.mVersion = PRESENCE_VERSION_V1; + this.mIdentityType = identityType; + this.mIdentity = identity; + this.mSalt = salt; + this.mKeySeed = keySeed; + this.mDataElements = dataElements; + this.mActions = actions; + mData = toBytesInternal(); + } /** * Creates an {@link ExtendedAdvertisement} from a Presence Broadcast Request. + * * @return {@link ExtendedAdvertisement} object. {@code null} when the request is illegal. */ @Nullable public static ExtendedAdvertisement createFromRequest(PresenceBroadcastRequest request) { - if (request.getVersion() != BroadcastRequest.PRESENCE_VERSION_V1) { + if (request.getVersion() != PRESENCE_VERSION_V1) { Log.v(TAG, "ExtendedAdvertisement only supports V1 now."); return null; } byte[] salt = request.getSalt(); - if (salt.length != SALT_DATA_LENGTH) { + if (salt.length != SALT_DATA_LENGTH && salt.length != ENCRYPTION_INFO_LENGTH - 1) { Log.v(TAG, "Salt does not match correct length"); return null; } @@ -94,12 +113,12 @@ public class ExtendedAdvertisement extends Advertisement{ } List actions = request.getActions(); - if (actions.isEmpty()) { - Log.v(TAG, "ExtendedAdvertisement must contain at least one action"); - return null; - } - List dataElements = request.getExtendedProperties(); + // DataElements should include actions. + for (int action : actions) { + dataElements.add( + new DataElement(DataType.ACTION, new byte[]{(byte) action})); + } return new ExtendedAdvertisement( request.getCredential().getIdentityType(), identity, @@ -109,149 +128,252 @@ public class ExtendedAdvertisement extends Advertisement{ dataElements); } - /** Serialize an {@link ExtendedAdvertisement} object into bytes with {@link DataElement}s */ - @Nullable - public byte[] toBytes() { - ByteBuffer buffer = ByteBuffer.allocate(getLength()); - - // Header - buffer.put(ExtendedAdvertisementUtils.constructHeader(getVersion())); - - // Salt - buffer.put(mCompleteDataElementsBytes.get(0)); - - // Identity - buffer.put(mCompleteDataElementsBytes.get(1)); - - List rawDataBytes = new ArrayList<>(); - // Data Elements (Already includes salt and identity) - for (int i = 2; i < mCompleteDataElementsBytes.size(); i++) { - byte[] dataElementBytes = mCompleteDataElementsBytes.get(i); - for (Byte b : dataElementBytes) { - rawDataBytes.add(b); - } - } - - byte[] dataElements = new byte[rawDataBytes.size()]; - for (int i = 0; i < rawDataBytes.size(); i++) { - dataElements[i] = rawDataBytes.get(i); - } - - buffer.put( - getCryptor(/* encrypt= */ true).encrypt(dataElements, getSalt(), mAuthenticityKey)); - - buffer.put(mHmacTag); - - return buffer.array(); - } - - /** Deserialize from bytes into an {@link ExtendedAdvertisement} object. - * {@code null} when there is something when parsing. + /** + * Deserialize from bytes into an {@link ExtendedAdvertisement} object. + * Return {@code null} when there is an error in parsing. */ @Nullable - public static ExtendedAdvertisement fromBytes(byte[] bytes, PublicCredential publicCredential) { - @BroadcastRequest.BroadcastVersion + public static ExtendedAdvertisement fromBytes(byte[] bytes, PublicCredential sharedCredential) { + @BroadcastVersion int version = ExtendedAdvertisementUtils.getVersion(bytes); - if (version != PresenceBroadcastRequest.PRESENCE_VERSION_V1) { + if (version != PRESENCE_VERSION_V1) { Log.v(TAG, "ExtendedAdvertisement is used in V1 only and version is " + version); return null; } - byte[] authenticityKey = publicCredential.getAuthenticityKey(); + byte[] keySeed = sharedCredential.getAuthenticityKey(); + byte[] metadataEncryptionKeyUnsignedAdvTag = sharedCredential.getEncryptedMetadataKeyTag(); + if (keySeed == null || metadataEncryptionKeyUnsignedAdvTag == null) { + return null; + } - int index = HEADER_LENGTH; - // Salt - byte[] saltHeaderArray = ExtendedAdvertisementUtils.getDataElementHeader(bytes, index); - DataElementHeader saltHeader = DataElementHeader.fromBytes(version, saltHeaderArray); - if (saltHeader == null || saltHeader.getDataType() != DataElement.DataType.SALT) { - Log.v(TAG, "First data element has to be salt."); + int index = 0; + // Header + byte[] header = new byte[]{bytes[index]}; + index += HEADER_LENGTH; + // Section header + byte[] sectionHeader = new byte[]{bytes[index]}; + index += HEADER_LENGTH; + // Salt or Encryption Info + byte[] firstHeaderArray = ExtendedAdvertisementUtils.getDataElementHeader(bytes, index); + DataElementHeader firstHeader = DataElementHeader.fromBytes(version, firstHeaderArray); + if (firstHeader == null) { + Log.v(TAG, "Cannot find salt."); return null; } - index += saltHeaderArray.length; - byte[] salt = new byte[saltHeader.getDataLength()]; - for (int i = 0; i < saltHeader.getDataLength(); i++) { - salt[i] = bytes[index++]; + @DataType int firstType = firstHeader.getDataType(); + if (firstType != DataType.SALT && firstType != DataType.ENCRYPTION_INFO) { + Log.v(TAG, "First data element has to be Salt or Encryption Info."); + return null; + } + index += firstHeaderArray.length; + byte[] firstDeBytes = new byte[firstHeader.getDataLength()]; + for (int i = 0; i < firstHeader.getDataLength(); i++) { + firstDeBytes[i] = bytes[index++]; + } + byte[] nonce = getNonce(firstType, firstDeBytes); + if (nonce == null) { + return null; } + byte[] saltBytes = firstType == DataType.SALT + ? firstDeBytes : (new EncryptionInfo(firstDeBytes)).getSalt(); - // Identity + // Identity header byte[] identityHeaderArray = ExtendedAdvertisementUtils.getDataElementHeader(bytes, index); DataElementHeader identityHeader = DataElementHeader.fromBytes(version, identityHeaderArray); - if (identityHeader == null) { - Log.v(TAG, "The second element has to be identity."); + if (identityHeader == null || identityHeader.getDataLength() != IDENTITY_DATA_LENGTH) { + Log.v(TAG, "The second element has to be a 16-bytes identity."); return null; } index += identityHeaderArray.length; @PresenceCredential.IdentityType int identityType = toPresenceCredentialIdentityType(identityHeader.getDataType()); - if (identityType == PresenceCredential.IDENTITY_TYPE_UNKNOWN) { - Log.v(TAG, "The identity type is unknown."); + if (identityType != PresenceCredential.IDENTITY_TYPE_PRIVATE + && identityType != PresenceCredential.IDENTITY_TYPE_TRUSTED) { + Log.v(TAG, "Only supports encrypted advertisement."); return null; } - byte[] encryptedIdentity = new byte[identityHeader.getDataLength()]; - for (int i = 0; i < identityHeader.getDataLength(); i++) { - encryptedIdentity[i] = bytes[index++]; - } - byte[] identity = - CryptorImpIdentityV1 - .getInstance().decrypt(encryptedIdentity, salt, authenticityKey); - - Cryptor cryptor = getCryptor(/* encrypt= */ true); - byte[] encryptedDataElements = - new byte[bytes.length - index - cryptor.getSignatureLength()]; - // Decrypt other data elements - System.arraycopy(bytes, index, encryptedDataElements, 0, encryptedDataElements.length); - byte[] decryptedDataElements = - cryptor.decrypt(encryptedDataElements, salt, authenticityKey); - if (decryptedDataElements == null) { + // Ciphertext + Cryptor cryptor = CryptorMicImp.getInstance(); + byte[] ciphertext = new byte[bytes.length - index - cryptor.getSignatureLength()]; + System.arraycopy(bytes, index, ciphertext, 0, ciphertext.length); + byte[] plaintext = cryptor.decrypt(ciphertext, nonce, keySeed); + if (plaintext == null) { return null; } + // Verification + // Verify the computed metadata encryption key tag + // First 16 bytes is metadata encryption key data + byte[] metadataEncryptionKey = new byte[IDENTITY_DATA_LENGTH]; + System.arraycopy(plaintext, 0, metadataEncryptionKey, 0, IDENTITY_DATA_LENGTH); + // Verify metadata encryption key tag + byte[] computedMetadataEncryptionKeyTag = + CryptorMicImp.generateMetadataEncryptionKeyTag(metadataEncryptionKey, + keySeed); + if (!Arrays.equals(computedMetadataEncryptionKeyTag, metadataEncryptionKeyUnsignedAdvTag)) { + Log.w(TAG, + "The calculated metadata encryption key tag is different from the metadata " + + "encryption key unsigned adv tag in the SharedCredential."); + return null; + } // Verify the computed HMAC tag is equal to HMAC tag in advertisement - if (cryptor.getSignatureLength() > 0) { - byte[] expectedHmacTag = new byte[cryptor.getSignatureLength()]; - System.arraycopy( - bytes, bytes.length - cryptor.getSignatureLength(), - expectedHmacTag, 0, cryptor.getSignatureLength()); - if (!cryptor.verify(decryptedDataElements, authenticityKey, expectedHmacTag)) { - Log.e(TAG, "HMAC tags not match."); - return null; - } + byte[] expectedHmacTag = new byte[cryptor.getSignatureLength()]; + System.arraycopy( + bytes, bytes.length - cryptor.getSignatureLength(), + expectedHmacTag, 0, cryptor.getSignatureLength()); + byte[] micInput = ArrayUtils.concatByteArrays( + PRESENCE_UUID_BYTES, header, sectionHeader, + firstHeaderArray, firstDeBytes, + nonce, identityHeaderArray, ciphertext); + if (!cryptor.verify(micInput, keySeed, expectedHmacTag)) { + Log.e(TAG, "HMAC tag not match."); + return null; } - int dataElementArrayIndex = 0; - // Other Data Elements - List actions = new ArrayList<>(); - List dataElements = new ArrayList<>(); - while (dataElementArrayIndex < decryptedDataElements.length) { - byte[] deHeaderArray = ExtendedAdvertisementUtils - .getDataElementHeader(decryptedDataElements, dataElementArrayIndex); - DataElementHeader deHeader = DataElementHeader.fromBytes(version, deHeaderArray); - dataElementArrayIndex += deHeaderArray.length; + byte[] otherDataElements = new byte[plaintext.length - IDENTITY_DATA_LENGTH]; + System.arraycopy(plaintext, IDENTITY_DATA_LENGTH, + otherDataElements, 0, otherDataElements.length); + List dataElements = getDataElementsFromBytes(version, otherDataElements); + if (dataElements.isEmpty()) { + return null; + } + List actions = getActionsFromDataElements(dataElements); + if (actions == null) { + return null; + } + return new ExtendedAdvertisement(identityType, metadataEncryptionKey, saltBytes, keySeed, + actions, dataElements); + } - @DataElement.DataType int type = Objects.requireNonNull(deHeader).getDataType(); - if (type == DataElement.DataType.ACTION) { - if (deHeader.getDataLength() != 1) { - Log.v(TAG, "Action id should only 1 byte."); - return null; - } - actions.add((int) decryptedDataElements[dataElementArrayIndex++]); - } else { - if (isSaltOrIdentity(type)) { - Log.v(TAG, "Type " + type + " is duplicated. There should be only one salt" - + " and one identity in the advertisement."); + @PresenceCredential.IdentityType + private static int toPresenceCredentialIdentityType(@DataType int type) { + switch (type) { + case DataType.PRIVATE_IDENTITY: + return PresenceCredential.IDENTITY_TYPE_PRIVATE; + case DataType.PROVISIONED_IDENTITY: + return PresenceCredential.IDENTITY_TYPE_PROVISIONED; + case DataType.TRUSTED_IDENTITY: + return PresenceCredential.IDENTITY_TYPE_TRUSTED; + case DataType.PUBLIC_IDENTITY: + default: + return PresenceCredential.IDENTITY_TYPE_UNKNOWN; + } + } + + @DataType + private static int toDataType(@PresenceCredential.IdentityType int identityType) { + switch (identityType) { + case PresenceCredential.IDENTITY_TYPE_PRIVATE: + return DataType.PRIVATE_IDENTITY; + case PresenceCredential.IDENTITY_TYPE_PROVISIONED: + return DataType.PROVISIONED_IDENTITY; + case PresenceCredential.IDENTITY_TYPE_TRUSTED: + return DataType.TRUSTED_IDENTITY; + case PresenceCredential.IDENTITY_TYPE_UNKNOWN: + default: + return DataType.PUBLIC_IDENTITY; + } + } + + /** + * Returns {@code true} if the given {@link DataType} is salt, or one of the + * identities. Identities should be able to convert to {@link PresenceCredential.IdentityType}s. + */ + private static boolean isSaltOrIdentity(@DataType int type) { + return type == DataType.SALT || type == DataType.ENCRYPTION_INFO + || type == DataType.PRIVATE_IDENTITY + || type == DataType.TRUSTED_IDENTITY + || type == DataType.PROVISIONED_IDENTITY + || type == DataType.PUBLIC_IDENTITY; + } + + /** Serialize an {@link ExtendedAdvertisement} object into bytes with {@link DataElement}s */ + @Nullable + public byte[] toBytes() { + return mData.clone(); + } + + /** Serialize an {@link ExtendedAdvertisement} object into bytes with {@link DataElement}s */ + @Nullable + public byte[] toBytesInternal() { + int sectionLength = 0; + // Salt + DataElement saltDe; + byte[] nonce; + try { + switch (mSalt.length) { + case SALT_DATA_LENGTH: + saltDe = new DataElement(DataType.SALT, mSalt); + nonce = CryptorMicImp.generateAdvNonce(mSalt); + break; + case ENCRYPTION_INFO_LENGTH - 1: + saltDe = new DataElement(DataType.ENCRYPTION_INFO, + EncryptionInfo.toByte(EncryptionInfo.EncodingScheme.MIC, mSalt)); + nonce = CryptorMicImp.generateAdvNonce(mSalt, CIPHER_START_INDEX); + break; + default: + Log.w(TAG, "Invalid salt size."); return null; - } - byte[] deData = new byte[deHeader.getDataLength()]; - for (int i = 0; i < deHeader.getDataLength(); i++) { - deData[i] = decryptedDataElements[dataElementArrayIndex++]; - } - dataElements.add(new DataElement(type, deData)); } + } catch (GeneralSecurityException e) { + Log.w(TAG, "Failed to generate the IV for encryption.", e); + return null; } - return new ExtendedAdvertisement(identityType, identity, salt, authenticityKey, actions, - dataElements); + byte[] saltOrEncryptionInfoBytes = + ExtendedAdvertisementUtils.convertDataElementToBytes(saltDe); + sectionLength += saltOrEncryptionInfoBytes.length; + // 16 bytes encrypted identity + @DataType int identityDataType = toDataType(getIdentityType()); + byte[] identityHeaderBytes = new DataElementHeader(PRESENCE_VERSION_V1, + identityDataType, mIdentity.length).toBytes(); + sectionLength += identityHeaderBytes.length; + final List dataElementList = getDataElements(); + byte[] ciphertext = getCiphertext(nonce, dataElementList); + if (ciphertext == null) { + return null; + } + sectionLength += ciphertext.length; + // mic + sectionLength += CryptorMicImp.MIC_LENGTH; + mLength = sectionLength; + // header + byte header = ExtendedAdvertisementUtils.constructHeader(getVersion()); + mLength += HEADER_LENGTH; + // section header + if (sectionLength > 255) { + Log.e(TAG, "A section should be shorter than 255 bytes."); + return null; + } + byte sectionHeader = (byte) sectionLength; + mLength += HEADER_LENGTH; + + // generates mic + ByteBuffer micInputBuffer = ByteBuffer.allocate( + mLength + PRESENCE_UUID_BYTES.length + nonce.length - CryptorMicImp.MIC_LENGTH); + micInputBuffer.put(PRESENCE_UUID_BYTES); + micInputBuffer.put(header); + micInputBuffer.put(sectionHeader); + micInputBuffer.put(saltOrEncryptionInfoBytes); + micInputBuffer.put(nonce); + micInputBuffer.put(identityHeaderBytes); + micInputBuffer.put(ciphertext); + byte[] micInput = micInputBuffer.array(); + byte[] mic = CryptorMicImp.getInstance().sign(micInput, mKeySeed); + if (mic == null) { + return null; + } + + ByteBuffer buffer = ByteBuffer.allocate(mLength); + buffer.put(header); + buffer.put(sectionHeader); + buffer.put(saltOrEncryptionInfoBytes); + buffer.put(identityHeaderBytes); + buffer.put(ciphertext); + buffer.put(mic); + return buffer.array(); } /** Returns the {@link DataElement}s in the advertisement. */ @@ -260,7 +382,7 @@ public class ExtendedAdvertisement extends Advertisement{ } /** Returns the {@link DataElement}s in the advertisement according to the key. */ - public List getDataElements(@DataElement.DataType int key) { + public List getDataElements(@DataType int key) { List res = new ArrayList<>(); for (DataElement dataElement : mDataElements) { if (key == dataElement.getKey()) { @@ -285,125 +407,86 @@ public class ExtendedAdvertisement extends Advertisement{ getActions()); } - ExtendedAdvertisement( - @PresenceCredential.IdentityType int identityType, - byte[] identity, - byte[] salt, - byte[] authenticityKey, - List actions, - List dataElements) { - this.mVersion = BroadcastRequest.PRESENCE_VERSION_V1; - this.mIdentityType = identityType; - this.mIdentity = identity; - this.mSalt = salt; - this.mAuthenticityKey = authenticityKey; - this.mActions = actions; - this.mDataElements = dataElements; - this.mCompleteDataElementsBytes = new ArrayList<>(); - - int length = HEADER_LENGTH; // header - - // Salt - DataElement saltElement = new DataElement(DataElement.DataType.SALT, salt); - byte[] saltByteArray = ExtendedAdvertisementUtils.convertDataElementToBytes(saltElement); - mCompleteDataElementsBytes.add(saltByteArray); - length += saltByteArray.length; - - // Identity - byte[] encryptedIdentity = - CryptorImpIdentityV1.getInstance().encrypt(identity, salt, authenticityKey); - DataElement identityElement = new DataElement(toDataType(identityType), encryptedIdentity); - byte[] identityByteArray = - ExtendedAdvertisementUtils.convertDataElementToBytes(identityElement); - mCompleteDataElementsBytes.add(identityByteArray); - length += identityByteArray.length; - - List dataElementBytes = new ArrayList<>(); - // Intents - for (int action : mActions) { - DataElement actionElement = new DataElement(DataElement.DataType.ACTION, - new byte[] {(byte) action}); - byte[] intentByteArray = - ExtendedAdvertisementUtils.convertDataElementToBytes(actionElement); - mCompleteDataElementsBytes.add(intentByteArray); - for (Byte b : intentByteArray) { - dataElementBytes.add(b); - } - } - - // Data Elements (Extended properties) - for (DataElement dataElement : mDataElements) { - byte[] deByteArray = ExtendedAdvertisementUtils.convertDataElementToBytes(dataElement); - mCompleteDataElementsBytes.add(deByteArray); - for (Byte b : deByteArray) { - dataElementBytes.add(b); - } - } - - byte[] data = new byte[dataElementBytes.size()]; - for (int i = 0; i < dataElementBytes.size(); i++) { - data[i] = dataElementBytes.get(i); + @Nullable + private byte[] getCiphertext(byte[] nonce, List dataElements) { + Cryptor cryptor = CryptorMicImp.getInstance(); + byte[] rawDeBytes = mIdentity; + for (DataElement dataElement : dataElements) { + rawDeBytes = ArrayUtils.concatByteArrays(rawDeBytes, + ExtendedAdvertisementUtils.convertDataElementToBytes(dataElement)); } - Cryptor cryptor = getCryptor(/* encrypt= */ true); - byte[] encryptedDeBytes = cryptor.encrypt(data, salt, authenticityKey); - - length += encryptedDeBytes.length; - - // Signature - byte[] hmacTag = Objects.requireNonNull(cryptor.sign(data, authenticityKey)); - mHmacTag = hmacTag; - length += hmacTag.length; - - this.mLength = length; + return cryptor.encrypt(rawDeBytes, nonce, mKeySeed); } - @PresenceCredential.IdentityType - private static int toPresenceCredentialIdentityType(@DataElement.DataType int type) { - switch (type) { - case DataElement.DataType.PRIVATE_IDENTITY: - return PresenceCredential.IDENTITY_TYPE_PRIVATE; - case DataElement.DataType.PROVISIONED_IDENTITY: - return PresenceCredential.IDENTITY_TYPE_PROVISIONED; - case DataElement.DataType.TRUSTED_IDENTITY: - return PresenceCredential.IDENTITY_TYPE_TRUSTED; - case DataElement.DataType.PUBLIC_IDENTITY: - default: - return PresenceCredential.IDENTITY_TYPE_UNKNOWN; + private static List getDataElementsFromBytes( + @BroadcastVersion int version, byte[] bytes) { + List res = new ArrayList<>(); + if (ArrayUtils.isEmpty(bytes)) { + return res; } - } - - @DataElement.DataType - private static int toDataType(@PresenceCredential.IdentityType int identityType) { - switch (identityType) { - case PresenceCredential.IDENTITY_TYPE_PRIVATE: - return DataElement.DataType.PRIVATE_IDENTITY; - case PresenceCredential.IDENTITY_TYPE_PROVISIONED: - return DataElement.DataType.PROVISIONED_IDENTITY; - case PresenceCredential.IDENTITY_TYPE_TRUSTED: - return DataElement.DataType.TRUSTED_IDENTITY; - case PresenceCredential.IDENTITY_TYPE_UNKNOWN: - default: - return DataElement.DataType.PUBLIC_IDENTITY; + int index = 0; + while (index < bytes.length) { + byte[] deHeaderArray = ExtendedAdvertisementUtils + .getDataElementHeader(bytes, index); + DataElementHeader deHeader = DataElementHeader.fromBytes(version, deHeaderArray); + index += deHeaderArray.length; + @DataType int type = Objects.requireNonNull(deHeader).getDataType(); + if (isSaltOrIdentity(type)) { + Log.v(TAG, "Type " + type + " is duplicated. There should be only one salt" + + " and one identity in the advertisement."); + return new ArrayList<>(); + } + byte[] deData = new byte[deHeader.getDataLength()]; + for (int i = 0; i < deHeader.getDataLength(); i++) { + deData[i] = bytes[index++]; + } + res.add(new DataElement(type, deData)); } + return res; } - /** - * Returns {@code true} if the given {@link DataElement.DataType} is salt, or one of the - * identities. Identities should be able to convert to {@link PresenceCredential.IdentityType}s. - */ - private static boolean isSaltOrIdentity(@DataElement.DataType int type) { - return type == DataElement.DataType.SALT || type == DataElement.DataType.PRIVATE_IDENTITY - || type == DataElement.DataType.TRUSTED_IDENTITY - || type == DataElement.DataType.PROVISIONED_IDENTITY - || type == DataElement.DataType.PUBLIC_IDENTITY; + @Nullable + private static byte[] getNonce(@DataType int type, byte[] data) { + try { + if (type == DataType.SALT) { + if (data.length != SALT_DATA_LENGTH) { + Log.v(TAG, "Salt DataElement needs to be 2 bytes."); + return null; + } + return CryptorMicImp.generateAdvNonce(data); + } else if (type == DataType.ENCRYPTION_INFO) { + try { + EncryptionInfo info = new EncryptionInfo(data); + if (info.getEncodingScheme() != EncryptionInfo.EncodingScheme.MIC) { + Log.v(TAG, "Not support Signature yet."); + return null; + } + return CryptorMicImp.generateAdvNonce(info.getSalt(), CIPHER_START_INDEX); + } catch (IllegalArgumentException e) { + Log.w(TAG, "Salt DataElement needs to be 17 bytes.", e); + return null; + } + } + } catch (GeneralSecurityException e) { + Log.w(TAG, "Failed to decrypt metadata encryption key.", e); + return null; + } + return null; } - private static Cryptor getCryptor(boolean encrypt) { - if (encrypt) { - Log.d(TAG, "get V1 Cryptor"); - return CryptorImpV1.getInstance(); + @Nullable + private static List getActionsFromDataElements(List dataElements) { + List actions = new ArrayList<>(); + for (DataElement dataElement : dataElements) { + if (dataElement.getKey() == DataElement.DataType.ACTION) { + byte[] value = dataElement.getValue(); + if (value.length != 1) { + Log.w(TAG, "Action should be only 1 byte."); + return null; + } + actions.add(Byte.toUnsignedInt(value[0])); + } } - Log.d(TAG, "get fake Cryptor"); - return CryptorImpFake.getInstance(); + return actions; } } diff --git a/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java b/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java index c355df2a063fb9e17c9ad755375cc07816878f14..50dada295c28f912f441565560ab4b1690ed3b53 100644 --- a/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java +++ b/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java @@ -16,15 +16,18 @@ package com.android.server.nearby.presence; -import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid; +import android.os.ParcelUuid; -import java.util.UUID; +import com.android.server.nearby.util.ArrayUtils; /** * Constants for Nearby Presence operations. */ public class PresenceConstants { + /** The Presence UUID value in byte array format. */ + public static final byte[] PRESENCE_UUID_BYTES = ArrayUtils.intToByteArray(0xFCF1); /** Presence advertisement service data uuid. */ - public static final UUID PRESENCE_UUID = to128BitUuid((short) 0xFCF1); + public static final ParcelUuid PRESENCE_UUID = + ParcelUuid.fromString("0000fcf1-0000-1000-8000-00805f9b34fb"); } diff --git a/nearby/service/java/com/android/server/nearby/presence/PresenceManager.java b/nearby/service/java/com/android/server/nearby/presence/PresenceManager.java index deb51673f388a4ef4243e41181896353c8573750..0a510683cd8d82f9c3a277548364323cd99bd1d9 100644 --- a/nearby/service/java/com/android/server/nearby/presence/PresenceManager.java +++ b/nearby/service/java/com/android/server/nearby/presence/PresenceManager.java @@ -36,9 +36,6 @@ import android.util.Log; import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.nearby.common.locator.Locator; -import com.android.server.nearby.common.locator.LocatorContextWrapper; -import com.android.server.nearby.fastpair.Constant; import java.util.Arrays; import java.util.List; @@ -48,8 +45,7 @@ import java.util.concurrent.Executors; /** PresenceManager is the class initiated in nearby service to handle presence related work. */ public class PresenceManager { - final LocatorContextWrapper mLocatorContextWrapper; - final Locator mLocator; + final Context mContext; private final IntentFilter mIntentFilter; @VisibleForTesting @@ -76,7 +72,7 @@ public class PresenceManager { @Override public void onError(int errorCode) { - Log.w(Constant.TAG, "[PresenceManager] Scan error is " + errorCode); + Log.w(TAG, "[PresenceManager] Scan error is " + errorCode); } }; @@ -123,9 +119,8 @@ public class PresenceManager { } }; - public PresenceManager(LocatorContextWrapper contextWrapper) { - mLocatorContextWrapper = contextWrapper; - mLocator = mLocatorContextWrapper.getLocator(); + public PresenceManager(Context context) { + mContext = context; mIntentFilter = new IntentFilter(); } @@ -133,8 +128,7 @@ public class PresenceManager { @Nullable private NearbyManager getNearbyManager() { return (NearbyManager) - mLocatorContextWrapper - .getApplicationContext() + mContext.getApplicationContext() .getSystemService(Context.NEARBY_SERVICE); } @@ -142,8 +136,6 @@ public class PresenceManager { public void initiate() { mIntentFilter.addAction(Intent.ACTION_SCREEN_ON); mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF); - mLocatorContextWrapper - .getContext() - .registerReceiver(mScreenBroadcastReceiver, mIntentFilter); + mContext.registerReceiver(mScreenBroadcastReceiver, mIntentFilter); } } diff --git a/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java b/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java index 5632ab530af00994fe2d5885e67d9e4d9ea146df..66ae79c567bfa49f9c15aa4d91727a6fd9ecd788 100644 --- a/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java +++ b/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java @@ -29,7 +29,6 @@ import android.bluetooth.le.AdvertisingSetParameters; import android.bluetooth.le.BluetoothLeAdvertiser; import android.nearby.BroadcastCallback; import android.nearby.BroadcastRequest; -import android.os.ParcelUuid; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -79,14 +78,14 @@ public class BleBroadcastProvider extends AdvertiseCallback { advertiseStarted = true; AdvertiseData advertiseData = new AdvertiseData.Builder() - .addServiceData(new ParcelUuid(PRESENCE_UUID), - advertisementPackets).build(); + .addServiceData(PRESENCE_UUID, advertisementPackets).build(); try { mBroadcastListener = listener; switch (version) { case BroadcastRequest.PRESENCE_VERSION_V0: bluetoothLeAdvertiser.startAdvertising(getAdvertiseSettings(), advertiseData, this); + Log.v(TAG, "Start to broadcast V0 advertisement."); break; case BroadcastRequest.PRESENCE_VERSION_V1: if (adapter.isLeExtendedAdvertisingSupported()) { @@ -94,6 +93,7 @@ public class BleBroadcastProvider extends AdvertiseCallback { getAdvertisingSetParameters(), advertiseData, null, null, null, mAdvertisingSetCallback); + Log.v(TAG, "Start to broadcast V1 advertisement."); } else { Log.w(TAG, "Failed to start advertising set because the chipset" + " does not supports LE Extended Advertising feature."); @@ -105,7 +105,8 @@ public class BleBroadcastProvider extends AdvertiseCallback { + " is wrong."); advertiseStarted = false; } - } catch (NullPointerException | IllegalStateException | SecurityException e) { + } catch (NullPointerException | IllegalStateException | SecurityException + | IllegalArgumentException e) { Log.w(TAG, "Failed to start advertising.", e); advertiseStarted = false; } diff --git a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java index c9098a6e66744a9a4210727480933d143f0fd6cf..e4651b791bdd8b358db44d33fd6f881c5158088c 100644 --- a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java +++ b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java @@ -19,6 +19,7 @@ package com.android.server.nearby.provider; import static android.nearby.ScanCallback.ERROR_UNKNOWN; import static com.android.server.nearby.NearbyService.TAG; +import static com.android.server.nearby.presence.PresenceConstants.PRESENCE_UUID; import android.annotation.Nullable; import android.bluetooth.BluetoothAdapter; @@ -40,13 +41,10 @@ import android.os.ParcelUuid; import android.util.Log; import com.android.internal.annotations.GuardedBy; -import com.android.server.nearby.common.bluetooth.fastpair.Constants; import com.android.server.nearby.injector.Injector; import com.android.server.nearby.presence.ExtendedAdvertisement; -import com.android.server.nearby.presence.PresenceConstants; import com.android.server.nearby.util.ArrayUtils; import com.android.server.nearby.util.ForegroundThread; -import com.android.server.nearby.util.encryption.CryptorImpIdentityV1; import com.google.common.annotations.VisibleForTesting; @@ -61,10 +59,6 @@ import java.util.concurrent.Executor; */ public class BleDiscoveryProvider extends AbstractDiscoveryProvider { - @VisibleForTesting - static final ParcelUuid FAST_PAIR_UUID = new ParcelUuid(Constants.FastPairService.ID); - private static final ParcelUuid PRESENCE_UUID = new ParcelUuid(PresenceConstants.PRESENCE_UUID); - // Don't block the thread as it may be used by other services. private static final Executor NEARBY_EXECUTOR = ForegroundThread.getExecutor(); private final Injector mInjector; @@ -74,15 +68,6 @@ public class BleDiscoveryProvider extends AbstractDiscoveryProvider { @GuardedBy("mLock") @Nullable private List mScanFilters; - private android.bluetooth.le.ScanCallback mScanCallbackLegacy = - new android.bluetooth.le.ScanCallback() { - @Override - public void onScanResult(int callbackType, ScanResult scanResult) { - } - @Override - public void onScanFailed(int errorCode) { - } - }; private android.bluetooth.le.ScanCallback mScanCallback = new android.bluetooth.le.ScanCallback() { @Override @@ -103,15 +88,10 @@ public class BleDiscoveryProvider extends AbstractDiscoveryProvider { } Map serviceDataMap = record.getServiceData(); if (serviceDataMap != null) { - byte[] fastPairData = serviceDataMap.get(FAST_PAIR_UUID); - if (fastPairData != null) { - builder.setData(serviceDataMap.get(FAST_PAIR_UUID)); - } else { - byte[] presenceData = serviceDataMap.get(PRESENCE_UUID); - if (presenceData != null) { - setPresenceDevice(presenceData, builder, deviceName, - scanResult.getRssi()); - } + byte[] presenceData = serviceDataMap.get(PRESENCE_UUID); + if (presenceData != null) { + setPresenceDevice(presenceData, builder, deviceName, + scanResult.getRssi()); } } } @@ -143,10 +123,6 @@ public class BleDiscoveryProvider extends AbstractDiscoveryProvider { .addMedium(NearbyDevice.Medium.BLE) .setName(deviceName) .setRssi(rssi); - for (int i : advertisement.getActions()) { - builder.addExtendedProperty(new DataElement(DataElement.DataType.ACTION, - new byte[]{(byte) i})); - } for (DataElement dataElement : advertisement.getDataElements()) { builder.addExtendedProperty(dataElement); } @@ -155,10 +131,6 @@ public class BleDiscoveryProvider extends AbstractDiscoveryProvider { private static List getScanFilters() { List scanFilterList = new ArrayList<>(); - scanFilterList.add( - new ScanFilter.Builder() - .setServiceData(FAST_PAIR_UUID, new byte[]{0}, new byte[]{0}) - .build()); scanFilterList.add( new ScanFilter.Builder() .setServiceData(PRESENCE_UUID, new byte[]{0}, new byte[]{0}) @@ -189,7 +161,6 @@ public class BleDiscoveryProvider extends AbstractDiscoveryProvider { if (isBleAvailable()) { Log.d(TAG, "BleDiscoveryProvider started"); startScan(getScanFilters(), getScanSettings(/* legacy= */ false), mScanCallback); - startScan(getScanFilters(), getScanSettings(/* legacy= */ true), mScanCallbackLegacy); return; } Log.w(TAG, "Cannot start BleDiscoveryProvider because Ble is not available."); @@ -206,7 +177,6 @@ public class BleDiscoveryProvider extends AbstractDiscoveryProvider { } Log.v(TAG, "Ble scan stopped."); bluetoothLeScanner.stopScan(mScanCallback); - bluetoothLeScanner.stopScan(mScanCallbackLegacy); synchronized (mLock) { if (mScanFilters != null) { mScanFilters = null; @@ -298,17 +268,13 @@ public class BleDiscoveryProvider extends AbstractDiscoveryProvider { if (advertisement == null) { continue; } - if (CryptorImpIdentityV1.getInstance().verify( - advertisement.getIdentity(), - credential.getEncryptedMetadataKeyTag())) { - builder.setPresenceDevice(getPresenceDevice(advertisement, deviceName, - rssi)); - builder.setEncryptionKeyTag(credential.getEncryptedMetadataKeyTag()); - if (!ArrayUtils.isEmpty(credential.getSecretId())) { - builder.setDeviceId(Arrays.hashCode(credential.getSecretId())); - } - return; + builder.setPresenceDevice(getPresenceDevice(advertisement, deviceName, + rssi)); + builder.setEncryptionKeyTag(credential.getEncryptedMetadataKeyTag()); + if (!ArrayUtils.isEmpty(credential.getSecretId())) { + builder.setDeviceId(Arrays.hashCode(credential.getSecretId())); } + return; } } } diff --git a/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java index d69d42d44db90af6da1413c9e2eeb0bb754825e0..21ec2520d3a3b5512a18881e89e1d2c854ee2768 100644 --- a/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java +++ b/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java @@ -20,6 +20,9 @@ import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE; import static com.android.server.nearby.NearbyService.TAG; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.Context; @@ -155,7 +158,7 @@ public class ChreDiscoveryProvider extends AbstractDiscoveryProvider { builder.setFastPairSupported(version != ChreCommunication.INVALID_NANO_APP_VERSION); try { callback.onQueryComplete(builder.build()); - } catch (RemoteException e) { + } catch (RemoteException | NullPointerException e) { e.printStackTrace(); } }); @@ -350,7 +353,11 @@ public class ChreDiscoveryProvider extends AbstractDiscoveryProvider { DataElement.DataType.ACTION, new byte[]{(byte) filterResult.getIntent()})); } - + if (filterResult.hasTimestampNs()) { + presenceDeviceBuilder + .setDiscoveryTimestampMillis(MILLISECONDS.convert( + filterResult.getTimestampNs(), NANOSECONDS)); + } PublicCredential publicCredential = new PublicCredential.Builder( secretId, diff --git a/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java b/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java deleted file mode 100644 index d925f07f6ace9c1034e5bcb2735982a520cc44aa..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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.server.nearby.provider; - -import android.accounts.Account; -import android.annotation.Nullable; -import android.content.Context; -import android.nearby.FastPairDataProviderService; -import android.nearby.aidl.ByteArrayParcel; -import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel; -import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel; -import android.nearby.aidl.FastPairEligibleAccountsRequestParcel; -import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel; -import android.nearby.aidl.FastPairManageAccountRequestParcel; -import android.util.Log; - -import androidx.annotation.WorkerThread; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.nearby.fastpair.footprint.FastPairUploadInfo; - -import java.util.ArrayList; -import java.util.List; - -import service.proto.Data; -import service.proto.Rpcs; - -/** - * FastPairDataProvider is a singleton that implements APIs to get FastPair data. - */ -public class FastPairDataProvider { - - private static final String TAG = "FastPairDataProvider"; - - private static FastPairDataProvider sInstance; - - private ProxyFastPairDataProvider mProxyFastPairDataProvider; - - /** - * Initializes FastPairDataProvider singleton. - */ - public static synchronized FastPairDataProvider init(Context context) { - if (sInstance == null) { - sInstance = new FastPairDataProvider(context); - } - if (sInstance.mProxyFastPairDataProvider == null) { - Log.w(TAG, "no proxy fast pair data provider found"); - } else { - sInstance.mProxyFastPairDataProvider.register(); - } - return sInstance; - } - - @Nullable - public static synchronized FastPairDataProvider getInstance() { - return sInstance; - } - - private FastPairDataProvider(Context context) { - mProxyFastPairDataProvider = ProxyFastPairDataProvider.create( - context, FastPairDataProviderService.ACTION_FAST_PAIR_DATA_PROVIDER); - if (mProxyFastPairDataProvider == null) { - Log.d("FastPairService", "fail to initiate the fast pair proxy provider"); - } else { - Log.d("FastPairService", "the fast pair proxy provider initiated"); - } - } - - @VisibleForTesting - void setProxyDataProvider(ProxyFastPairDataProvider proxyFastPairDataProvider) { - this.mProxyFastPairDataProvider = proxyFastPairDataProvider; - } - - /** - * Loads FastPairAntispoofKeyDeviceMetadata. - * - * @throws IllegalStateException If ProxyFastPairDataProvider is not available. - */ - @WorkerThread - @Nullable - public Rpcs.GetObservedDeviceResponse loadFastPairAntispoofKeyDeviceMetadata(byte[] modelId) { - if (mProxyFastPairDataProvider != null) { - FastPairAntispoofKeyDeviceMetadataRequestParcel requestParcel = - new FastPairAntispoofKeyDeviceMetadataRequestParcel(); - requestParcel.modelId = modelId; - return Utils.convertToGetObservedDeviceResponse( - mProxyFastPairDataProvider - .loadFastPairAntispoofKeyDeviceMetadata(requestParcel)); - } - throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed"); - } - - /** - * Enrolls an account to Fast Pair. - * - * @throws IllegalStateException If ProxyFastPairDataProvider is not available. - */ - public void optIn(Account account) { - if (mProxyFastPairDataProvider != null) { - FastPairManageAccountRequestParcel requestParcel = - new FastPairManageAccountRequestParcel(); - requestParcel.account = account; - requestParcel.requestType = FastPairDataProviderService.MANAGE_REQUEST_ADD; - mProxyFastPairDataProvider.manageFastPairAccount(requestParcel); - return; - } - throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed"); - } - - /** - * Uploads the device info to Fast Pair account. - * - * @throws IllegalStateException If ProxyFastPairDataProvider is not available. - */ - public void upload(Account account, FastPairUploadInfo uploadInfo) { - if (mProxyFastPairDataProvider != null) { - FastPairManageAccountDeviceRequestParcel requestParcel = - new FastPairManageAccountDeviceRequestParcel(); - requestParcel.account = account; - requestParcel.requestType = FastPairDataProviderService.MANAGE_REQUEST_ADD; - requestParcel.accountKeyDeviceMetadata = - Utils.convertToFastPairAccountKeyDeviceMetadata(uploadInfo); - mProxyFastPairDataProvider.manageFastPairAccountDevice(requestParcel); - return; - } - throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed"); - } - - /** - * Loads FastPair device accountKeys for a given account, but not other detailed fields. - * - * @throws IllegalStateException If ProxyFastPairDataProvider is not available. - */ - public List loadFastPairDeviceWithAccountKey( - Account account) { - return loadFastPairDeviceWithAccountKey(account, new ArrayList(0)); - } - - /** - * Loads FastPair devices for a list of accountKeys of a given account. - * - * @param account The account of the FastPair devices. - * @param deviceAccountKeys The allow list of FastPair devices if it is not empty. Otherwise, - * the function returns accountKeys of all FastPair devices under the - * account, without detailed fields. - * - * @throws IllegalStateException If ProxyFastPairDataProvider is not available. - */ - public List loadFastPairDeviceWithAccountKey( - Account account, List deviceAccountKeys) { - if (mProxyFastPairDataProvider != null) { - FastPairAccountDevicesMetadataRequestParcel requestParcel = - new FastPairAccountDevicesMetadataRequestParcel(); - requestParcel.account = account; - requestParcel.deviceAccountKeys = new ByteArrayParcel[deviceAccountKeys.size()]; - int i = 0; - for (byte[] deviceAccountKey : deviceAccountKeys) { - requestParcel.deviceAccountKeys[i] = new ByteArrayParcel(); - requestParcel.deviceAccountKeys[i].byteArray = deviceAccountKey; - i = i + 1; - } - return Utils.convertToFastPairDevicesWithAccountKey( - mProxyFastPairDataProvider.loadFastPairAccountDevicesMetadata(requestParcel)); - } - throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed"); - } - - /** - * Loads FastPair Eligible Accounts. - * - * @throws IllegalStateException If ProxyFastPairDataProvider is not available. - */ - public List loadFastPairEligibleAccounts() { - if (mProxyFastPairDataProvider != null) { - FastPairEligibleAccountsRequestParcel requestParcel = - new FastPairEligibleAccountsRequestParcel(); - return Utils.convertToAccountList( - mProxyFastPairDataProvider.loadFastPairEligibleAccounts(requestParcel)); - } - throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed"); - } -} diff --git a/nearby/service/java/com/android/server/nearby/provider/ProxyFastPairDataProvider.java b/nearby/service/java/com/android/server/nearby/provider/ProxyFastPairDataProvider.java deleted file mode 100644 index f0ade6cb49ae53fa2239fe27e090aad23e8b1a3d..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/provider/ProxyFastPairDataProvider.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * 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.server.nearby.provider; - -import android.annotation.Nullable; -import android.content.Context; -import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel; -import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel; -import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel; -import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel; -import android.nearby.aidl.FastPairEligibleAccountParcel; -import android.nearby.aidl.FastPairEligibleAccountsRequestParcel; -import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel; -import android.nearby.aidl.FastPairManageAccountRequestParcel; -import android.nearby.aidl.IFastPairAccountDevicesMetadataCallback; -import android.nearby.aidl.IFastPairAntispoofKeyDeviceMetadataCallback; -import android.nearby.aidl.IFastPairDataProvider; -import android.nearby.aidl.IFastPairEligibleAccountsCallback; -import android.nearby.aidl.IFastPairManageAccountCallback; -import android.nearby.aidl.IFastPairManageAccountDeviceCallback; -import android.os.IBinder; -import android.os.RemoteException; - -import androidx.annotation.WorkerThread; - -import com.android.server.nearby.common.servicemonitor.CurrentUserServiceProvider; -import com.android.server.nearby.common.servicemonitor.CurrentUserServiceProvider.BoundServiceInfo; -import com.android.server.nearby.common.servicemonitor.ServiceMonitor; -import com.android.server.nearby.common.servicemonitor.ServiceMonitor.ServiceListener; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Proxy for IFastPairDataProvider implementations. - */ -public class ProxyFastPairDataProvider implements ServiceListener { - - private static final int TIME_OUT_MILLIS = 10000; - - /** - * Creates and registers this proxy. If no suitable service is available for the proxy, returns - * null. - */ - @Nullable - public static ProxyFastPairDataProvider create(Context context, String action) { - ProxyFastPairDataProvider proxy = new ProxyFastPairDataProvider(context, action); - if (proxy.checkServiceResolves()) { - return proxy; - } else { - return null; - } - } - - private final ServiceMonitor mServiceMonitor; - - private ProxyFastPairDataProvider(Context context, String action) { - // safe to use direct executor since our locks are not acquired in a code path invoked by - // our owning provider - - mServiceMonitor = ServiceMonitor.create(context, "FAST_PAIR_DATA_PROVIDER", - CurrentUserServiceProvider.create(context, action), this); - } - - private boolean checkServiceResolves() { - return mServiceMonitor.checkServiceResolves(); - } - - /** - * User service watch to connect to actually services implemented by OEMs. - */ - public void register() { - mServiceMonitor.register(); - } - - // Fast Pair Data Provider doesn't maintain a long running state. - // Therefore, it doesn't need setup at bind time. - @Override - public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException { - } - - // Fast Pair Data Provider doesn't maintain a long running state. - // Therefore, it doesn't need tear down at unbind time. - @Override - public void onUnbind() { - } - - /** - * Invokes system api loadFastPairEligibleAccounts. - * - * @return an array of acccounts and their opt in status. - */ - @WorkerThread - @Nullable - public FastPairEligibleAccountParcel[] loadFastPairEligibleAccounts( - FastPairEligibleAccountsRequestParcel requestParcel) { - final CountDownLatch waitForCompletionLatch = new CountDownLatch(1); - final AtomicReference response = new AtomicReference<>(); - mServiceMonitor.runOnBinder(new ServiceMonitor.BinderOperation() { - @Override - public void run(IBinder binder) throws RemoteException { - IFastPairDataProvider provider = IFastPairDataProvider.Stub.asInterface(binder); - IFastPairEligibleAccountsCallback callback = - new IFastPairEligibleAccountsCallback.Stub() { - public void onFastPairEligibleAccountsReceived( - FastPairEligibleAccountParcel[] accountParcels) { - response.set(accountParcels); - waitForCompletionLatch.countDown(); - } - - public void onError(int code, String message) { - waitForCompletionLatch.countDown(); - } - }; - provider.loadFastPairEligibleAccounts(requestParcel, callback); - } - - @Override - public void onError() { - waitForCompletionLatch.countDown(); - } - }); - try { - waitForCompletionLatch.await(TIME_OUT_MILLIS, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - // skip. - } - return response.get(); - } - - /** - * Invokes system api manageFastPairAccount to opt in account, or opt out account. - */ - @WorkerThread - public void manageFastPairAccount(FastPairManageAccountRequestParcel requestParcel) { - final CountDownLatch waitForCompletionLatch = new CountDownLatch(1); - mServiceMonitor.runOnBinder(new ServiceMonitor.BinderOperation() { - @Override - public void run(IBinder binder) throws RemoteException { - IFastPairDataProvider provider = IFastPairDataProvider.Stub.asInterface(binder); - IFastPairManageAccountCallback callback = - new IFastPairManageAccountCallback.Stub() { - public void onSuccess() { - waitForCompletionLatch.countDown(); - } - - public void onError(int code, String message) { - waitForCompletionLatch.countDown(); - } - }; - provider.manageFastPairAccount(requestParcel, callback); - } - - @Override - public void onError() { - waitForCompletionLatch.countDown(); - } - }); - try { - waitForCompletionLatch.await(TIME_OUT_MILLIS, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - // skip. - } - return; - } - - /** - * Invokes system api manageFastPairAccountDevice to add or remove a device from a Fast Pair - * account. - */ - @WorkerThread - public void manageFastPairAccountDevice( - FastPairManageAccountDeviceRequestParcel requestParcel) { - final CountDownLatch waitForCompletionLatch = new CountDownLatch(1); - mServiceMonitor.runOnBinder(new ServiceMonitor.BinderOperation() { - @Override - public void run(IBinder binder) throws RemoteException { - IFastPairDataProvider provider = IFastPairDataProvider.Stub.asInterface(binder); - IFastPairManageAccountDeviceCallback callback = - new IFastPairManageAccountDeviceCallback.Stub() { - public void onSuccess() { - waitForCompletionLatch.countDown(); - } - - public void onError(int code, String message) { - waitForCompletionLatch.countDown(); - } - }; - provider.manageFastPairAccountDevice(requestParcel, callback); - } - - @Override - public void onError() { - waitForCompletionLatch.countDown(); - } - }); - try { - waitForCompletionLatch.await(TIME_OUT_MILLIS, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - // skip. - } - return; - } - - /** - * Invokes system api loadFastPairAntispoofKeyDeviceMetadata. - * - * @return the Fast Pair AntispoofKeyDeviceMetadata of a given device. - */ - @WorkerThread - @Nullable - FastPairAntispoofKeyDeviceMetadataParcel loadFastPairAntispoofKeyDeviceMetadata( - FastPairAntispoofKeyDeviceMetadataRequestParcel requestParcel) { - final CountDownLatch waitForCompletionLatch = new CountDownLatch(1); - final AtomicReference response = - new AtomicReference<>(); - mServiceMonitor.runOnBinder(new ServiceMonitor.BinderOperation() { - @Override - public void run(IBinder binder) throws RemoteException { - IFastPairDataProvider provider = IFastPairDataProvider.Stub.asInterface(binder); - IFastPairAntispoofKeyDeviceMetadataCallback callback = - new IFastPairAntispoofKeyDeviceMetadataCallback.Stub() { - public void onFastPairAntispoofKeyDeviceMetadataReceived( - FastPairAntispoofKeyDeviceMetadataParcel metadata) { - response.set(metadata); - waitForCompletionLatch.countDown(); - } - - public void onError(int code, String message) { - waitForCompletionLatch.countDown(); - } - }; - provider.loadFastPairAntispoofKeyDeviceMetadata(requestParcel, callback); - } - - @Override - public void onError() { - waitForCompletionLatch.countDown(); - } - }); - try { - waitForCompletionLatch.await(TIME_OUT_MILLIS, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - // skip. - } - return response.get(); - } - - /** - * Invokes loadFastPairAccountDevicesMetadata. - * - * @return the metadata of Fast Pair devices that are associated with a given account. - */ - @WorkerThread - @Nullable - FastPairAccountKeyDeviceMetadataParcel[] loadFastPairAccountDevicesMetadata( - FastPairAccountDevicesMetadataRequestParcel requestParcel) { - final CountDownLatch waitForCompletionLatch = new CountDownLatch(1); - final AtomicReference response = - new AtomicReference<>(); - mServiceMonitor.runOnBinder(new ServiceMonitor.BinderOperation() { - @Override - public void run(IBinder binder) throws RemoteException { - IFastPairDataProvider provider = IFastPairDataProvider.Stub.asInterface(binder); - IFastPairAccountDevicesMetadataCallback callback = - new IFastPairAccountDevicesMetadataCallback.Stub() { - public void onFastPairAccountDevicesMetadataReceived( - FastPairAccountKeyDeviceMetadataParcel[] metadatas) { - response.set(metadatas); - waitForCompletionLatch.countDown(); - } - - public void onError(int code, String message) { - waitForCompletionLatch.countDown(); - } - }; - provider.loadFastPairAccountDevicesMetadata(requestParcel, callback); - } - - @Override - public void onError() { - waitForCompletionLatch.countDown(); - } - }); - try { - waitForCompletionLatch.await(TIME_OUT_MILLIS, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - // skip. - } - return response.get(); - } -} diff --git a/nearby/service/java/com/android/server/nearby/provider/Utils.java b/nearby/service/java/com/android/server/nearby/provider/Utils.java deleted file mode 100644 index 0f1c5673747b979f032413b29ce946cfd8b5d117..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/provider/Utils.java +++ /dev/null @@ -1,465 +0,0 @@ -/* - * 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.server.nearby.provider; - -import android.accounts.Account; -import android.annotation.Nullable; -import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel; -import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel; -import android.nearby.aidl.FastPairDeviceMetadataParcel; -import android.nearby.aidl.FastPairDiscoveryItemParcel; -import android.nearby.aidl.FastPairEligibleAccountParcel; - -import com.android.server.nearby.fastpair.footprint.FastPairUploadInfo; - -import com.google.protobuf.ByteString; - -import java.util.ArrayList; -import java.util.List; - -import service.proto.Cache; -import service.proto.Data; -import service.proto.FastPairString.FastPairStrings; -import service.proto.Rpcs; - -/** - * Utility functions to convert between different data classes. - */ -class Utils { - - static List convertToFastPairDevicesWithAccountKey( - @Nullable FastPairAccountKeyDeviceMetadataParcel[] metadataParcels) { - if (metadataParcels == null) { - return new ArrayList(0); - } - - List fpDeviceList = - new ArrayList<>(metadataParcels.length); - for (FastPairAccountKeyDeviceMetadataParcel metadataParcel : metadataParcels) { - if (metadataParcel == null) { - continue; - } - Data.FastPairDeviceWithAccountKey.Builder fpDeviceBuilder = - Data.FastPairDeviceWithAccountKey.newBuilder(); - if (metadataParcel.deviceAccountKey != null) { - fpDeviceBuilder.setAccountKey( - ByteString.copyFrom(metadataParcel.deviceAccountKey)); - } - if (metadataParcel.sha256DeviceAccountKeyPublicAddress != null) { - fpDeviceBuilder.setSha256AccountKeyPublicAddress( - ByteString.copyFrom(metadataParcel.sha256DeviceAccountKeyPublicAddress)); - } - - Cache.StoredDiscoveryItem.Builder storedDiscoveryItemBuilder = - Cache.StoredDiscoveryItem.newBuilder(); - - if (metadataParcel.discoveryItem != null) { - if (metadataParcel.discoveryItem.actionUrl != null) { - storedDiscoveryItemBuilder.setActionUrl(metadataParcel.discoveryItem.actionUrl); - } - Cache.ResolvedUrlType urlType = Cache.ResolvedUrlType.forNumber( - metadataParcel.discoveryItem.actionUrlType); - if (urlType != null) { - storedDiscoveryItemBuilder.setActionUrlType(urlType); - } - if (metadataParcel.discoveryItem.appName != null) { - storedDiscoveryItemBuilder.setAppName(metadataParcel.discoveryItem.appName); - } - if (metadataParcel.discoveryItem.authenticationPublicKeySecp256r1 != null) { - storedDiscoveryItemBuilder.setAuthenticationPublicKeySecp256R1( - ByteString.copyFrom( - metadataParcel.discoveryItem.authenticationPublicKeySecp256r1)); - } - if (metadataParcel.discoveryItem.description != null) { - storedDiscoveryItemBuilder.setDescription( - metadataParcel.discoveryItem.description); - } - if (metadataParcel.discoveryItem.deviceName != null) { - storedDiscoveryItemBuilder.setDeviceName( - metadataParcel.discoveryItem.deviceName); - } - if (metadataParcel.discoveryItem.displayUrl != null) { - storedDiscoveryItemBuilder.setDisplayUrl( - metadataParcel.discoveryItem.displayUrl); - } - storedDiscoveryItemBuilder.setFirstObservationTimestampMillis( - metadataParcel.discoveryItem.firstObservationTimestampMillis); - if (metadataParcel.discoveryItem.iconFifeUrl != null) { - storedDiscoveryItemBuilder.setIconFifeUrl( - metadataParcel.discoveryItem.iconFifeUrl); - } - if (metadataParcel.discoveryItem.iconPng != null) { - storedDiscoveryItemBuilder.setIconPng( - ByteString.copyFrom(metadataParcel.discoveryItem.iconPng)); - } - if (metadataParcel.discoveryItem.id != null) { - storedDiscoveryItemBuilder.setId(metadataParcel.discoveryItem.id); - } - storedDiscoveryItemBuilder.setLastObservationTimestampMillis( - metadataParcel.discoveryItem.lastObservationTimestampMillis); - if (metadataParcel.discoveryItem.macAddress != null) { - storedDiscoveryItemBuilder.setMacAddress( - metadataParcel.discoveryItem.macAddress); - } - if (metadataParcel.discoveryItem.packageName != null) { - storedDiscoveryItemBuilder.setPackageName( - metadataParcel.discoveryItem.packageName); - } - storedDiscoveryItemBuilder.setPendingAppInstallTimestampMillis( - metadataParcel.discoveryItem.pendingAppInstallTimestampMillis); - storedDiscoveryItemBuilder.setRssi(metadataParcel.discoveryItem.rssi); - Cache.StoredDiscoveryItem.State state = - Cache.StoredDiscoveryItem.State.forNumber( - metadataParcel.discoveryItem.state); - if (state != null) { - storedDiscoveryItemBuilder.setState(state); - } - if (metadataParcel.discoveryItem.title != null) { - storedDiscoveryItemBuilder.setTitle(metadataParcel.discoveryItem.title); - } - if (metadataParcel.discoveryItem.triggerId != null) { - storedDiscoveryItemBuilder.setTriggerId(metadataParcel.discoveryItem.triggerId); - } - storedDiscoveryItemBuilder.setTxPower(metadataParcel.discoveryItem.txPower); - } - if (metadataParcel.metadata != null) { - FastPairStrings.Builder stringsBuilder = FastPairStrings.newBuilder(); - if (metadataParcel.metadata.connectSuccessCompanionAppInstalled != null) { - stringsBuilder.setPairingFinishedCompanionAppInstalled( - metadataParcel.metadata.connectSuccessCompanionAppInstalled); - } - if (metadataParcel.metadata.connectSuccessCompanionAppNotInstalled != null) { - stringsBuilder.setPairingFinishedCompanionAppNotInstalled( - metadataParcel.metadata.connectSuccessCompanionAppNotInstalled); - } - if (metadataParcel.metadata.failConnectGoToSettingsDescription != null) { - stringsBuilder.setPairingFailDescription( - metadataParcel.metadata.failConnectGoToSettingsDescription); - } - if (metadataParcel.metadata.initialNotificationDescription != null) { - stringsBuilder.setTapToPairWithAccount( - metadataParcel.metadata.initialNotificationDescription); - } - if (metadataParcel.metadata.initialNotificationDescriptionNoAccount != null) { - stringsBuilder.setTapToPairWithoutAccount( - metadataParcel.metadata.initialNotificationDescriptionNoAccount); - } - if (metadataParcel.metadata.initialPairingDescription != null) { - stringsBuilder.setInitialPairingDescription( - metadataParcel.metadata.initialPairingDescription); - } - if (metadataParcel.metadata.retroactivePairingDescription != null) { - stringsBuilder.setRetroactivePairingDescription( - metadataParcel.metadata.retroactivePairingDescription); - } - if (metadataParcel.metadata.subsequentPairingDescription != null) { - stringsBuilder.setSubsequentPairingDescription( - metadataParcel.metadata.subsequentPairingDescription); - } - if (metadataParcel.metadata.waitLaunchCompanionAppDescription != null) { - stringsBuilder.setWaitAppLaunchDescription( - metadataParcel.metadata.waitLaunchCompanionAppDescription); - } - storedDiscoveryItemBuilder.setFastPairStrings(stringsBuilder.build()); - - Cache.FastPairInformation.Builder fpInformationBuilder = - Cache.FastPairInformation.newBuilder(); - Rpcs.TrueWirelessHeadsetImages.Builder imagesBuilder = - Rpcs.TrueWirelessHeadsetImages.newBuilder(); - if (metadataParcel.metadata.trueWirelessImageUrlCase != null) { - imagesBuilder.setCaseUrl(metadataParcel.metadata.trueWirelessImageUrlCase); - } - if (metadataParcel.metadata.trueWirelessImageUrlLeftBud != null) { - imagesBuilder.setLeftBudUrl( - metadataParcel.metadata.trueWirelessImageUrlLeftBud); - } - if (metadataParcel.metadata.trueWirelessImageUrlRightBud != null) { - imagesBuilder.setRightBudUrl( - metadataParcel.metadata.trueWirelessImageUrlRightBud); - } - fpInformationBuilder.setTrueWirelessImages(imagesBuilder.build()); - Rpcs.DeviceType deviceType = - Rpcs.DeviceType.forNumber(metadataParcel.metadata.deviceType); - if (deviceType != null) { - fpInformationBuilder.setDeviceType(deviceType); - } - - storedDiscoveryItemBuilder.setFastPairInformation(fpInformationBuilder.build()); - } - fpDeviceBuilder.setDiscoveryItem(storedDiscoveryItemBuilder.build()); - fpDeviceList.add(fpDeviceBuilder.build()); - } - return fpDeviceList; - } - - static List convertToAccountList( - @Nullable FastPairEligibleAccountParcel[] accountParcels) { - if (accountParcels == null) { - return new ArrayList(0); - } - List accounts = new ArrayList(accountParcels.length); - for (FastPairEligibleAccountParcel parcel : accountParcels) { - if (parcel != null && parcel.account != null) { - accounts.add(parcel.account); - } - } - return accounts; - } - - private static @Nullable Rpcs.Device convertToDevice( - FastPairAntispoofKeyDeviceMetadataParcel metadata) { - - Rpcs.Device.Builder deviceBuilder = Rpcs.Device.newBuilder(); - if (metadata.antispoofPublicKey != null) { - deviceBuilder.setAntiSpoofingKeyPair(Rpcs.AntiSpoofingKeyPair.newBuilder() - .setPublicKey(ByteString.copyFrom(metadata.antispoofPublicKey)) - .build()); - } - if (metadata.deviceMetadata != null) { - Rpcs.TrueWirelessHeadsetImages.Builder imagesBuilder = - Rpcs.TrueWirelessHeadsetImages.newBuilder(); - if (metadata.deviceMetadata.trueWirelessImageUrlLeftBud != null) { - imagesBuilder.setLeftBudUrl(metadata.deviceMetadata.trueWirelessImageUrlLeftBud); - } - if (metadata.deviceMetadata.trueWirelessImageUrlRightBud != null) { - imagesBuilder.setRightBudUrl(metadata.deviceMetadata.trueWirelessImageUrlRightBud); - } - if (metadata.deviceMetadata.trueWirelessImageUrlCase != null) { - imagesBuilder.setCaseUrl(metadata.deviceMetadata.trueWirelessImageUrlCase); - } - deviceBuilder.setTrueWirelessImages(imagesBuilder.build()); - if (metadata.deviceMetadata.imageUrl != null) { - deviceBuilder.setImageUrl(metadata.deviceMetadata.imageUrl); - } - if (metadata.deviceMetadata.intentUri != null) { - deviceBuilder.setIntentUri(metadata.deviceMetadata.intentUri); - } - if (metadata.deviceMetadata.name != null) { - deviceBuilder.setName(metadata.deviceMetadata.name); - } - Rpcs.DeviceType deviceType = - Rpcs.DeviceType.forNumber(metadata.deviceMetadata.deviceType); - if (deviceType != null) { - deviceBuilder.setDeviceType(deviceType); - } - deviceBuilder.setBleTxPower(metadata.deviceMetadata.bleTxPower) - .setTriggerDistance(metadata.deviceMetadata.triggerDistance); - } - - return deviceBuilder.build(); - } - - private static @Nullable ByteString convertToImage( - FastPairAntispoofKeyDeviceMetadataParcel metadata) { - if (metadata.deviceMetadata == null || metadata.deviceMetadata.image == null) { - return null; - } - - return ByteString.copyFrom(metadata.deviceMetadata.image); - } - - private static @Nullable Rpcs.ObservedDeviceStrings - convertToObservedDeviceStrings(FastPairAntispoofKeyDeviceMetadataParcel metadata) { - if (metadata.deviceMetadata == null) { - return null; - } - - Rpcs.ObservedDeviceStrings.Builder stringsBuilder = Rpcs.ObservedDeviceStrings.newBuilder(); - if (metadata.deviceMetadata.connectSuccessCompanionAppInstalled != null) { - stringsBuilder.setConnectSuccessCompanionAppInstalled( - metadata.deviceMetadata.connectSuccessCompanionAppInstalled); - } - if (metadata.deviceMetadata.connectSuccessCompanionAppNotInstalled != null) { - stringsBuilder.setConnectSuccessCompanionAppNotInstalled( - metadata.deviceMetadata.connectSuccessCompanionAppNotInstalled); - } - if (metadata.deviceMetadata.downloadCompanionAppDescription != null) { - stringsBuilder.setDownloadCompanionAppDescription( - metadata.deviceMetadata.downloadCompanionAppDescription); - } - if (metadata.deviceMetadata.failConnectGoToSettingsDescription != null) { - stringsBuilder.setFailConnectGoToSettingsDescription( - metadata.deviceMetadata.failConnectGoToSettingsDescription); - } - if (metadata.deviceMetadata.initialNotificationDescription != null) { - stringsBuilder.setInitialNotificationDescription( - metadata.deviceMetadata.initialNotificationDescription); - } - if (metadata.deviceMetadata.initialNotificationDescriptionNoAccount != null) { - stringsBuilder.setInitialNotificationDescriptionNoAccount( - metadata.deviceMetadata.initialNotificationDescriptionNoAccount); - } - if (metadata.deviceMetadata.initialPairingDescription != null) { - stringsBuilder.setInitialPairingDescription( - metadata.deviceMetadata.initialPairingDescription); - } - if (metadata.deviceMetadata.openCompanionAppDescription != null) { - stringsBuilder.setOpenCompanionAppDescription( - metadata.deviceMetadata.openCompanionAppDescription); - } - if (metadata.deviceMetadata.retroactivePairingDescription != null) { - stringsBuilder.setRetroactivePairingDescription( - metadata.deviceMetadata.retroactivePairingDescription); - } - if (metadata.deviceMetadata.subsequentPairingDescription != null) { - stringsBuilder.setSubsequentPairingDescription( - metadata.deviceMetadata.subsequentPairingDescription); - } - if (metadata.deviceMetadata.unableToConnectDescription != null) { - stringsBuilder.setUnableToConnectDescription( - metadata.deviceMetadata.unableToConnectDescription); - } - if (metadata.deviceMetadata.unableToConnectTitle != null) { - stringsBuilder.setUnableToConnectTitle( - metadata.deviceMetadata.unableToConnectTitle); - } - if (metadata.deviceMetadata.updateCompanionAppDescription != null) { - stringsBuilder.setUpdateCompanionAppDescription( - metadata.deviceMetadata.updateCompanionAppDescription); - } - if (metadata.deviceMetadata.waitLaunchCompanionAppDescription != null) { - stringsBuilder.setWaitLaunchCompanionAppDescription( - metadata.deviceMetadata.waitLaunchCompanionAppDescription); - } - - return stringsBuilder.build(); - } - - static @Nullable Rpcs.GetObservedDeviceResponse - convertToGetObservedDeviceResponse( - @Nullable FastPairAntispoofKeyDeviceMetadataParcel metadata) { - if (metadata == null) { - return null; - } - - Rpcs.GetObservedDeviceResponse.Builder responseBuilder = - Rpcs.GetObservedDeviceResponse.newBuilder(); - - Rpcs.Device device = convertToDevice(metadata); - if (device != null) { - responseBuilder.setDevice(device); - } - ByteString image = convertToImage(metadata); - if (image != null) { - responseBuilder.setImage(image); - } - Rpcs.ObservedDeviceStrings strings = convertToObservedDeviceStrings(metadata); - if (strings != null) { - responseBuilder.setStrings(strings); - } - - return responseBuilder.build(); - } - - static @Nullable FastPairAccountKeyDeviceMetadataParcel - convertToFastPairAccountKeyDeviceMetadata( - @Nullable FastPairUploadInfo uploadInfo) { - if (uploadInfo == null) { - return null; - } - - FastPairAccountKeyDeviceMetadataParcel accountKeyDeviceMetadataParcel = - new FastPairAccountKeyDeviceMetadataParcel(); - if (uploadInfo.getAccountKey() != null) { - accountKeyDeviceMetadataParcel.deviceAccountKey = - uploadInfo.getAccountKey().toByteArray(); - } - if (uploadInfo.getSha256AccountKeyPublicAddress() != null) { - accountKeyDeviceMetadataParcel.sha256DeviceAccountKeyPublicAddress = - uploadInfo.getSha256AccountKeyPublicAddress().toByteArray(); - } - if (uploadInfo.getStoredDiscoveryItem() != null) { - accountKeyDeviceMetadataParcel.metadata = - convertToFastPairDeviceMetadata(uploadInfo.getStoredDiscoveryItem()); - accountKeyDeviceMetadataParcel.discoveryItem = - convertToFastPairDiscoveryItem(uploadInfo.getStoredDiscoveryItem()); - } - - return accountKeyDeviceMetadataParcel; - } - - private static @Nullable FastPairDiscoveryItemParcel - convertToFastPairDiscoveryItem(Cache.StoredDiscoveryItem storedDiscoveryItem) { - FastPairDiscoveryItemParcel discoveryItemParcel = new FastPairDiscoveryItemParcel(); - discoveryItemParcel.actionUrl = storedDiscoveryItem.getActionUrl(); - discoveryItemParcel.actionUrlType = storedDiscoveryItem.getActionUrlType().getNumber(); - discoveryItemParcel.appName = storedDiscoveryItem.getAppName(); - discoveryItemParcel.authenticationPublicKeySecp256r1 = - storedDiscoveryItem.getAuthenticationPublicKeySecp256R1().toByteArray(); - discoveryItemParcel.description = storedDiscoveryItem.getDescription(); - discoveryItemParcel.deviceName = storedDiscoveryItem.getDeviceName(); - discoveryItemParcel.displayUrl = storedDiscoveryItem.getDisplayUrl(); - discoveryItemParcel.firstObservationTimestampMillis = - storedDiscoveryItem.getFirstObservationTimestampMillis(); - discoveryItemParcel.iconFifeUrl = storedDiscoveryItem.getIconFifeUrl(); - discoveryItemParcel.iconPng = storedDiscoveryItem.getIconPng().toByteArray(); - discoveryItemParcel.id = storedDiscoveryItem.getId(); - discoveryItemParcel.lastObservationTimestampMillis = - storedDiscoveryItem.getLastObservationTimestampMillis(); - discoveryItemParcel.macAddress = storedDiscoveryItem.getMacAddress(); - discoveryItemParcel.packageName = storedDiscoveryItem.getPackageName(); - discoveryItemParcel.pendingAppInstallTimestampMillis = - storedDiscoveryItem.getPendingAppInstallTimestampMillis(); - discoveryItemParcel.rssi = storedDiscoveryItem.getRssi(); - discoveryItemParcel.state = storedDiscoveryItem.getState().getNumber(); - discoveryItemParcel.title = storedDiscoveryItem.getTitle(); - discoveryItemParcel.triggerId = storedDiscoveryItem.getTriggerId(); - discoveryItemParcel.txPower = storedDiscoveryItem.getTxPower(); - - return discoveryItemParcel; - } - - /* Do we upload these? - String downloadCompanionAppDescription = - bundle.getString("downloadCompanionAppDescription"); - String locale = bundle.getString("locale"); - String openCompanionAppDescription = bundle.getString("openCompanionAppDescription"); - float triggerDistance = bundle.getFloat("triggerDistance"); - String unableToConnectDescription = bundle.getString("unableToConnectDescription"); - String unableToConnectTitle = bundle.getString("unableToConnectTitle"); - String updateCompanionAppDescription = bundle.getString("updateCompanionAppDescription"); - */ - private static @Nullable FastPairDeviceMetadataParcel - convertToFastPairDeviceMetadata(Cache.StoredDiscoveryItem storedDiscoveryItem) { - FastPairStrings fpStrings = storedDiscoveryItem.getFastPairStrings(); - - FastPairDeviceMetadataParcel metadataParcel = new FastPairDeviceMetadataParcel(); - metadataParcel.connectSuccessCompanionAppInstalled = - fpStrings.getPairingFinishedCompanionAppInstalled(); - metadataParcel.connectSuccessCompanionAppNotInstalled = - fpStrings.getPairingFinishedCompanionAppNotInstalled(); - metadataParcel.failConnectGoToSettingsDescription = fpStrings.getPairingFailDescription(); - metadataParcel.initialNotificationDescription = fpStrings.getTapToPairWithAccount(); - metadataParcel.initialNotificationDescriptionNoAccount = - fpStrings.getTapToPairWithoutAccount(); - metadataParcel.initialPairingDescription = fpStrings.getInitialPairingDescription(); - metadataParcel.retroactivePairingDescription = fpStrings.getRetroactivePairingDescription(); - metadataParcel.subsequentPairingDescription = fpStrings.getSubsequentPairingDescription(); - metadataParcel.waitLaunchCompanionAppDescription = fpStrings.getWaitAppLaunchDescription(); - - Cache.FastPairInformation fpInformation = storedDiscoveryItem.getFastPairInformation(); - metadataParcel.trueWirelessImageUrlCase = - fpInformation.getTrueWirelessImages().getCaseUrl(); - metadataParcel.trueWirelessImageUrlLeftBud = - fpInformation.getTrueWirelessImages().getLeftBudUrl(); - metadataParcel.trueWirelessImageUrlRightBud = - fpInformation.getTrueWirelessImages().getRightBudUrl(); - metadataParcel.deviceType = fpInformation.getDeviceType().getNumber(); - - return metadataParcel; - } -} diff --git a/nearby/service/java/com/android/server/nearby/util/ArrayUtils.java b/nearby/service/java/com/android/server/nearby/util/ArrayUtils.java index 35251d8199df89ca8d06d09721071c96a8f68632..e69d00478e6486413cca462836e7884318beb70a 100644 --- a/nearby/service/java/com/android/server/nearby/util/ArrayUtils.java +++ b/nearby/service/java/com/android/server/nearby/util/ArrayUtils.java @@ -22,6 +22,10 @@ import java.util.Arrays; * ArrayUtils class that help manipulate array. */ public class ArrayUtils { + private static final char[] HEX_UPPERCASE = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + /** Concatenate N arrays of bytes into a single array. */ public static byte[] concatByteArrays(byte[]... arrays) { // Degenerate case - no input provided. @@ -52,4 +56,67 @@ public class ArrayUtils { public static boolean isEmpty(byte[] bytes) { return bytes == null || bytes.length == 0; } + + /** Appends a byte array to a byte. */ + public static byte[] append(byte a, byte[] b) { + if (b == null) { + return new byte[]{a}; + } + + int length = b.length; + byte[] result = new byte[length + 1]; + result[0] = a; + System.arraycopy(b, 0, result, 1, length); + return result; + } + + /** + * Converts an Integer to a 2-byte array. + */ + public static byte[] intToByteArray(int value) { + return new byte[] {(byte) (value >> 8), (byte) value}; + } + + /** Appends a byte to a byte array. */ + public static byte[] append(byte[] a, byte b) { + if (a == null) { + return new byte[]{b}; + } + + int length = a.length; + byte[] result = new byte[length + 1]; + System.arraycopy(a, 0, result, 0, length); + result[length] = b; + return result; + } + + /** Convert an hex string to a byte array. */ + + public static byte[] stringToBytes(String hex) throws IllegalArgumentException { + int length = hex.length(); + if (length % 2 != 0) { + throw new IllegalArgumentException("Hex string has odd number of characters"); + } + byte[] out = new byte[length / 2]; + for (int i = 0; i < length; i += 2) { + // Byte.parseByte() doesn't work here because it expects a hex value in -128, 127, and + // our hex values are in 0, 255. + out[i / 2] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16); + } + return out; + } + + /** Encodes a byte array as a hexadecimal representation of bytes. */ + public static String bytesToStringUppercase(byte[] bytes) { + int length = bytes.length; + StringBuilder out = new StringBuilder(length * 2); + for (int i = 0; i < length; i++) { + if (i == length - 1 && (bytes[i] & 0xff) == 0) { + break; + } + out.append(HEX_UPPERCASE[(bytes[i] & 0xf0) >>> 4]); + out.append(HEX_UPPERCASE[bytes[i] & 0x0f]); + } + return out.toString(); + } } diff --git a/nearby/service/java/com/android/server/nearby/util/Clock.java b/nearby/service/java/com/android/server/nearby/util/Clock.java deleted file mode 100644 index 037b6f9163d440c4b65be07b89da4b0c125de481..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/util/Clock.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.util; - -import android.os.SystemClock; - -/** Wrapper interface for time operations. Allows replacement of clock operations for testing. */ -public interface Clock { - - /** - * Get the current time of the clock in milliseconds. - * - * @return Current time in milliseconds. - */ - long currentTimeMillis(); - - /** - * Returns milliseconds since boot, including time spent in sleep. - * - * @return Current time since boot in milliseconds. - */ - long elapsedRealtime(); - - /** - * Returns the current timestamp of the most precise timer available on the local system, in - * nanoseconds. - * - * @return Current time in nanoseconds. - */ - long nanoTime(); - - /** - * Returns the time spent in the current thread, in milliseconds - * - * @return Thread time in milliseconds. - */ - @SuppressWarnings("StaticOrDefaultInterfaceMethod") - default long currentThreadTimeMillis() { - return SystemClock.currentThreadTimeMillis(); - } -} diff --git a/nearby/service/java/com/android/server/nearby/util/DataUtils.java b/nearby/service/java/com/android/server/nearby/util/DataUtils.java deleted file mode 100644 index 12bf3840860911afdf66e6d32e22cd01d923c370..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/util/DataUtils.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.util; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import service.proto.Cache.ScanFastPairStoreItem; -import service.proto.Cache.StoredDiscoveryItem; -import service.proto.FastPairString.FastPairStrings; -import service.proto.Rpcs.Device; -import service.proto.Rpcs.GetObservedDeviceResponse; -import service.proto.Rpcs.ObservedDeviceStrings; - -/** - * Utils class converts different data types {@link ScanFastPairStoreItem}, - * {@link StoredDiscoveryItem} and {@link GetObservedDeviceResponse}, - * - */ -public final class DataUtils { - - /** - * Converts a {@link GetObservedDeviceResponse} to a {@link ScanFastPairStoreItem}. - */ - public static ScanFastPairStoreItem toScanFastPairStoreItem( - GetObservedDeviceResponse observedDeviceResponse, - @NonNull String bleAddress, @NonNull String modelId, @Nullable String account) { - Device device = observedDeviceResponse.getDevice(); - String deviceName = device.getName(); - return ScanFastPairStoreItem.newBuilder() - .setAddress(bleAddress) - .setModelId(modelId) - .setActionUrl(device.getIntentUri()) - .setDeviceName(deviceName) - .setIconPng(observedDeviceResponse.getImage()) - .setIconFifeUrl(device.getImageUrl()) - .setAntiSpoofingPublicKey(device.getAntiSpoofingKeyPair().getPublicKey()) - .setFastPairStrings(getFastPairStrings(observedDeviceResponse, deviceName, account)) - .build(); - } - - /** - * Prints readable string for a {@link ScanFastPairStoreItem}. - */ - public static String toString(ScanFastPairStoreItem item) { - return "ScanFastPairStoreItem=[address:" + item.getAddress() - + ", actionUrl:" + item.getActionUrl() - + ", deviceName:" + item.getDeviceName() - + ", iconFifeUrl:" + item.getIconFifeUrl() - + ", fastPairStrings:" + toString(item.getFastPairStrings()) - + "]"; - } - - /** - * Prints readable string for a {@link FastPairStrings} - */ - public static String toString(FastPairStrings fastPairStrings) { - return "FastPairStrings[" - + "tapToPairWithAccount=" + fastPairStrings.getTapToPairWithAccount() - + ", tapToPairWithoutAccount=" + fastPairStrings.getTapToPairWithoutAccount() - + ", initialPairingDescription=" + fastPairStrings.getInitialPairingDescription() - + ", pairingFinishedCompanionAppInstalled=" - + fastPairStrings.getPairingFinishedCompanionAppInstalled() - + ", pairingFinishedCompanionAppNotInstalled=" - + fastPairStrings.getPairingFinishedCompanionAppNotInstalled() - + ", subsequentPairingDescription=" - + fastPairStrings.getSubsequentPairingDescription() - + ", retroactivePairingDescription=" - + fastPairStrings.getRetroactivePairingDescription() - + ", waitAppLaunchDescription=" + fastPairStrings.getWaitAppLaunchDescription() - + ", pairingFailDescription=" + fastPairStrings.getPairingFailDescription() - + "]"; - } - - private static FastPairStrings getFastPairStrings(GetObservedDeviceResponse response, - String deviceName, @Nullable String account) { - ObservedDeviceStrings strings = response.getStrings(); - return FastPairStrings.newBuilder() - .setTapToPairWithAccount(strings.getInitialNotificationDescription()) - .setTapToPairWithoutAccount( - strings.getInitialNotificationDescriptionNoAccount()) - .setInitialPairingDescription(account == null - ? strings.getInitialNotificationDescriptionNoAccount() - : String.format(strings.getInitialPairingDescription(), - deviceName, account)) - .setPairingFinishedCompanionAppInstalled( - strings.getConnectSuccessCompanionAppInstalled()) - .setPairingFinishedCompanionAppNotInstalled( - strings.getConnectSuccessCompanionAppNotInstalled()) - .setSubsequentPairingDescription(strings.getSubsequentPairingDescription()) - .setRetroactivePairingDescription(strings.getRetroactivePairingDescription()) - .setWaitAppLaunchDescription(strings.getWaitLaunchCompanionAppDescription()) - .setPairingFailDescription(strings.getFailConnectGoToSettingsDescription()) - .build(); - } -} diff --git a/nearby/service/java/com/android/server/nearby/util/DefaultClock.java b/nearby/service/java/com/android/server/nearby/util/DefaultClock.java deleted file mode 100644 index 61998e944545199fa9fa09b159ea7446cb0d2432..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/util/DefaultClock.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.util; - -import android.os.SystemClock; - -/** Default implementation of Clock. Instances of this class handle time operations. */ -public class DefaultClock implements Clock { - - private static final DefaultClock sInstance = new DefaultClock(); - - /** Returns an instance of DefaultClock. */ - public static Clock getsInstance() { - return sInstance; - } - - @Override - public long currentTimeMillis() { - return System.currentTimeMillis(); - } - - @Override - public long elapsedRealtime() { - return SystemClock.elapsedRealtime(); - } - - @Override - public long nanoTime() { - return System.nanoTime(); - } - - @Override - public long currentThreadTimeMillis() { - return SystemClock.currentThreadTimeMillis(); - } - - public DefaultClock() {} -} diff --git a/nearby/service/java/com/android/server/nearby/util/Environment.java b/nearby/service/java/com/android/server/nearby/util/Environment.java deleted file mode 100644 index d397862b77e8235189887b6f53e5332294a9622f..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/util/Environment.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.server.nearby.util; - -import android.content.ApexEnvironment; -import android.content.pm.ApplicationInfo; -import android.os.UserHandle; - -import java.io.File; - -/** - * Provides function to make sure the function caller is from the same apex. - */ -public class Environment { - /** - * NEARBY apex name. - */ - private static final String NEARBY_APEX_NAME = "com.android.tethering"; - - /** - * The path where the Nearby apex is mounted. - * Current value = "/apex/com.android.tethering" - */ - private static final String NEARBY_APEX_PATH = - new File("/apex", NEARBY_APEX_NAME).getAbsolutePath(); - - /** - * Nearby shared folder. - */ - public static File getNearbyDirectory() { - return ApexEnvironment.getApexEnvironment(NEARBY_APEX_NAME).getDeviceProtectedDataDir(); - } - - /** - * Nearby user specific folder. - */ - public static File getNearbyDirectory(int userId) { - return ApexEnvironment.getApexEnvironment(NEARBY_APEX_NAME) - .getCredentialProtectedDataDirForUser(UserHandle.of(userId)); - } - - /** - * Returns true if the app is in the nearby apex, false otherwise. - * Checks if the app's path starts with "/apex/com.android.tethering". - */ - public static boolean isAppInNearbyApex(ApplicationInfo appInfo) { - return appInfo.sourceDir.startsWith(NEARBY_APEX_PATH); - } -} diff --git a/nearby/service/java/com/android/server/nearby/util/encryption/Cryptor.java b/nearby/service/java/com/android/server/nearby/util/encryption/Cryptor.java index 3c5132d58a662e5a47a7a5685ca3f234c7e033db..ba9ca4130004e25613c34770cab3aaf062c5e44e 100644 --- a/nearby/service/java/com/android/server/nearby/util/encryption/Cryptor.java +++ b/nearby/service/java/com/android/server/nearby/util/encryption/Cryptor.java @@ -22,6 +22,10 @@ import android.util.Log; import androidx.annotation.Nullable; +import com.google.common.hash.Hashing; + +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -30,22 +34,28 @@ import javax.crypto.spec.SecretKeySpec; /** Class for encryption/decryption functionality. */ public abstract class Cryptor { + /** + * In the form of "algorithm/mode/padding". Must be the same across broadcast and scan devices. + */ + public static final String CIPHER_ALGORITHM = "AES/CTR/NoPadding"; + + public static final byte[] NP_HKDF_SALT = "Google Nearby".getBytes(StandardCharsets.US_ASCII); /** AES only supports key sizes of 16, 24 or 32 bytes. */ static final int AUTHENTICITY_KEY_BYTE_SIZE = 16; - private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256"; + public static final String HMAC_SHA256_ALGORITHM = "HmacSHA256"; /** * Encrypt the provided data blob. * * @param data data blob to be encrypted. - * @param salt used for IV + * @param iv advertisement nonce * @param secretKeyBytes secrete key accessed from credentials * @return encrypted data, {@code null} if failed to encrypt. */ @Nullable - public byte[] encrypt(byte[] data, byte[] salt, byte[] secretKeyBytes) { + public byte[] encrypt(byte[] data, byte[] iv, byte[] secretKeyBytes) { return data; } @@ -53,12 +63,12 @@ public abstract class Cryptor { * Decrypt the original data blob from the provided byte array. * * @param encryptedData data blob to be decrypted. - * @param salt used for IV + * @param iv advertisement nonce * @param secretKeyBytes secrete key accessed from credentials * @return decrypted data, {@code null} if failed to decrypt. */ @Nullable - public byte[] decrypt(byte[] encryptedData, byte[] salt, byte[] secretKeyBytes) { + public byte[] decrypt(byte[] encryptedData, byte[] iv, byte[] secretKeyBytes) { return encryptedData; } @@ -90,7 +100,7 @@ public abstract class Cryptor { * A HAMC sha256 based HKDF algorithm to pseudo randomly hash data and salt into a byte array of * given size. */ - // Based on google3/third_party/tink/java/src/main/java/com/google/crypto/tink/subtle/Hkdf.java + // Based on crypto/tink/subtle/Hkdf.java @Nullable static byte[] computeHkdf(byte[] ikm, byte[] salt, int size) { Mac mac; @@ -146,4 +156,66 @@ public abstract class Cryptor { return result; } + + /** + * Computes an HKDF. + * + * @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or + * "HMACSHA256". + * @param ikm the input keying material. + * @param salt optional salt. A possibly non-secret random value. + * (If no salt is provided i.e. if salt has length 0) + * then an array of 0s of the same size as the hash + * digest is used as salt. + * @param info optional context and application specific information. + * @param size The length of the generated pseudorandom string in bytes. + * @throws GeneralSecurityException if the {@code macAlgorithm} is not supported or if {@code + * size} is too large or if {@code salt} is not a valid key for + * macAlgorithm (which should not + * happen since HMAC allows key sizes up to 2^64). + */ + public static byte[] computeHkdf( + String macAlgorithm, final byte[] ikm, final byte[] salt, final byte[] info, int size) + throws GeneralSecurityException { + Mac mac = Mac.getInstance(macAlgorithm); + if (size > 255 * mac.getMacLength()) { + throw new GeneralSecurityException("size too large"); + } + if (salt == null || salt.length == 0) { + // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided + // then HKDF uses a salt that is an array of zeros of the same length as the hash + // digest. + mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm)); + } else { + mac.init(new SecretKeySpec(salt, macAlgorithm)); + } + byte[] prk = mac.doFinal(ikm); + byte[] result = new byte[size]; + int ctr = 1; + int pos = 0; + mac.init(new SecretKeySpec(prk, macAlgorithm)); + byte[] digest = new byte[0]; + while (true) { + mac.update(digest); + mac.update(info); + mac.update((byte) ctr); + digest = mac.doFinal(); + if (pos + digest.length < size) { + System.arraycopy(digest, 0, result, pos, digest.length); + pos += digest.length; + ctr++; + } else { + System.arraycopy(digest, 0, result, pos, size - pos); + break; + } + } + return result; + } + + /** Generates the HMAC bytes. */ + public static byte[] generateHmac(String algorithm, byte[] input, byte[] key) { + return Hashing.hmacSha256(new SecretKeySpec(key, algorithm)) + .hashBytes(input) + .asBytes(); + } } diff --git a/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpFake.java b/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpFake.java deleted file mode 100644 index 1c0ec9efc8526f3fce9d2e2ef65aef1569ff4b3f..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpFake.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.util.encryption; - -import androidx.annotation.Nullable; - -/** - * A Cryptor that returns the original data without actual encryption - */ -public class CryptorImpFake extends Cryptor { - // Lazily instantiated when {@link #getInstance()} is called. - @Nullable - private static CryptorImpFake sCryptor; - - /** Returns an instance of CryptorImpFake. */ - public static CryptorImpFake getInstance() { - if (sCryptor == null) { - sCryptor = new CryptorImpFake(); - } - return sCryptor; - } - - private CryptorImpFake() { - } -} diff --git a/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpIdentityV1.java b/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpIdentityV1.java deleted file mode 100644 index b0e19b4eac67883122c909837255b0bfe827025c..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpIdentityV1.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.util.encryption; - -import static com.android.server.nearby.NearbyService.TAG; - -import android.security.keystore.KeyProperties; -import android.util.Log; - -import androidx.annotation.Nullable; - -import com.android.internal.annotations.VisibleForTesting; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -/** - * {@link android.nearby.BroadcastRequest#PRESENCE_VERSION_V1} for identity - * encryption and decryption. - */ -public class CryptorImpIdentityV1 extends Cryptor { - - // 3 16 byte arrays known by both the encryptor and decryptor. - private static final byte[] EK_IV = - new byte[] {14, -123, -39, 42, 109, 127, 83, 27, 27, 11, 91, -38, 92, 17, -84, 66}; - private static final byte[] ESALT_IV = - new byte[] {46, 83, -19, 10, -127, -31, -31, 12, 31, 76, 63, -9, 33, -66, 15, -10}; - private static final byte[] KTAG_IV = - {-22, -83, -6, 67, 16, -99, -13, -9, 8, -3, -16, 37, -75, 47, 1, -56}; - - /** Length of encryption key required by AES/GCM encryption. */ - private static final int ENCRYPTION_KEY_SIZE = 32; - - /** Length of salt required by AES/GCM encryption. */ - private static final int AES_CTR_IV_SIZE = 16; - - /** Length HMAC tag */ - public static final int HMAC_TAG_SIZE = 8; - - /** - * In the form of "algorithm/mode/padding". Must be the same across broadcast and scan devices. - */ - private static final String CIPHER_ALGORITHM = "AES/CTR/NoPadding"; - - @VisibleForTesting - static final String ENCRYPT_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES; - - // Lazily instantiated when {@link #getInstance()} is called. - @Nullable private static CryptorImpIdentityV1 sCryptor; - - /** Returns an instance of CryptorImpIdentityV1. */ - public static CryptorImpIdentityV1 getInstance() { - if (sCryptor == null) { - sCryptor = new CryptorImpIdentityV1(); - } - return sCryptor; - } - - @Nullable - @Override - public byte[] encrypt(byte[] data, byte[] salt, byte[] authenticityKey) { - if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) { - Log.w(TAG, "Illegal authenticity key size"); - return null; - } - - // Generates a 32 bytes encryption key from authenticity_key - byte[] encryptionKey = Cryptor.computeHkdf(authenticityKey, EK_IV, ENCRYPTION_KEY_SIZE); - if (encryptionKey == null) { - Log.e(TAG, "Failed to generate encryption key."); - return null; - } - - // Encrypts the data using the encryption key - SecretKey secretKey = new SecretKeySpec(encryptionKey, ENCRYPT_ALGORITHM); - Cipher cipher; - try { - cipher = Cipher.getInstance(CIPHER_ALGORITHM); - } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { - Log.e(TAG, "Failed to encrypt with secret key.", e); - return null; - } - byte[] esalt = Cryptor.computeHkdf(salt, ESALT_IV, AES_CTR_IV_SIZE); - if (esalt == null) { - Log.e(TAG, "Failed to generate salt."); - return null; - } - try { - cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(esalt)); - } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { - Log.e(TAG, "Failed to initialize cipher.", e); - return null; - } - try { - return cipher.doFinal(data); - } catch (IllegalBlockSizeException | BadPaddingException e) { - Log.e(TAG, "Failed to encrypt with secret key.", e); - return null; - } - } - - @Nullable - @Override - public byte[] decrypt(byte[] encryptedData, byte[] salt, byte[] authenticityKey) { - if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) { - Log.w(TAG, "Illegal authenticity key size"); - return null; - } - - // Generates a 32 bytes encryption key from authenticity_key - byte[] encryptionKey = Cryptor.computeHkdf(authenticityKey, EK_IV, ENCRYPTION_KEY_SIZE); - if (encryptionKey == null) { - Log.e(TAG, "Failed to generate encryption key."); - return null; - } - - // Decrypts the data using the encryption key - SecretKey secretKey = new SecretKeySpec(encryptionKey, ENCRYPT_ALGORITHM); - Cipher cipher; - try { - cipher = Cipher.getInstance(CIPHER_ALGORITHM); - } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { - Log.e(TAG, "Failed to get cipher instance.", e); - return null; - } - byte[] esalt = Cryptor.computeHkdf(salt, ESALT_IV, AES_CTR_IV_SIZE); - if (esalt == null) { - return null; - } - try { - cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(esalt)); - } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { - Log.e(TAG, "Failed to initialize cipher.", e); - return null; - } - - try { - return cipher.doFinal(encryptedData); - } catch (IllegalBlockSizeException | BadPaddingException e) { - Log.e(TAG, "Failed to decrypt bytes with secret key.", e); - return null; - } - } - - /** - * Generates a digital signature for the data. - * - * @return signature {@code null} if failed to sign - */ - @Nullable - @Override - public byte[] sign(byte[] data, byte[] salt) { - if (data == null) { - Log.e(TAG, "Not generate HMAC tag because of invalid data input."); - return null; - } - - // Generates a 8 bytes HMAC tag - return Cryptor.computeHkdf(data, salt, HMAC_TAG_SIZE); - } - - /** - * Generates a digital signature for the data. - * Uses KTAG_IV as salt value. - */ - @Nullable - public byte[] sign(byte[] data) { - // Generates a 8 bytes HMAC tag - return sign(data, KTAG_IV); - } - - @Override - public boolean verify(byte[] data, byte[] key, byte[] signature) { - return Arrays.equals(sign(data, key), signature); - } - - /** - * Verifies the signature generated by data and key, with the original signed data. Uses - * KTAG_IV as salt value. - */ - public boolean verify(byte[] data, byte[] signature) { - return verify(data, KTAG_IV, signature); - } -} diff --git a/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpV1.java b/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpV1.java deleted file mode 100644 index 15073fb8407874ef9e25e1d4af570140a4157927..0000000000000000000000000000000000000000 --- a/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpV1.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) 2022 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.server.nearby.util.encryption; - -import static com.android.server.nearby.NearbyService.TAG; - -import android.security.keystore.KeyProperties; -import android.util.Log; - -import androidx.annotation.Nullable; - -import com.android.internal.annotations.VisibleForTesting; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -/** - * {@link android.nearby.BroadcastRequest#PRESENCE_VERSION_V1} for encryption and decryption. - */ -public class CryptorImpV1 extends Cryptor { - - /** - * In the form of "algorithm/mode/padding". Must be the same across broadcast and scan devices. - */ - private static final String CIPHER_ALGORITHM = "AES/CTR/NoPadding"; - - @VisibleForTesting - static final String ENCRYPT_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES; - - /** Length of encryption key required by AES/GCM encryption. */ - private static final int ENCRYPTION_KEY_SIZE = 32; - - /** Length of salt required by AES/GCM encryption. */ - private static final int AES_CTR_IV_SIZE = 16; - - /** Length HMAC tag */ - public static final int HMAC_TAG_SIZE = 16; - - // 3 16 byte arrays known by both the encryptor and decryptor. - private static final byte[] AK_IV = - new byte[] {12, -59, 19, 23, 96, 57, -59, 19, 117, -31, -116, -61, 86, -25, -33, -78}; - private static final byte[] ASALT_IV = - new byte[] {111, 48, -83, -79, -10, -102, -16, 73, 43, 55, 102, -127, 58, -19, -113, 4}; - private static final byte[] HK_IV = - new byte[] {12, -59, 19, 23, 96, 57, -59, 19, 117, -31, -116, -61, 86, -25, -33, -78}; - - // Lazily instantiated when {@link #getInstance()} is called. - @Nullable private static CryptorImpV1 sCryptor; - - /** Returns an instance of CryptorImpV1. */ - public static CryptorImpV1 getInstance() { - if (sCryptor == null) { - sCryptor = new CryptorImpV1(); - } - return sCryptor; - } - - private CryptorImpV1() { - } - - @Nullable - @Override - public byte[] encrypt(byte[] data, byte[] salt, byte[] authenticityKey) { - if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) { - Log.w(TAG, "Illegal authenticity key size"); - return null; - } - - // Generates a 32 bytes encryption key from authenticity_key - byte[] encryptionKey = Cryptor.computeHkdf(authenticityKey, AK_IV, ENCRYPTION_KEY_SIZE); - if (encryptionKey == null) { - Log.e(TAG, "Failed to generate encryption key."); - return null; - } - - // Encrypts the data using the encryption key - SecretKey secretKey = new SecretKeySpec(encryptionKey, ENCRYPT_ALGORITHM); - Cipher cipher; - try { - cipher = Cipher.getInstance(CIPHER_ALGORITHM); - } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { - Log.e(TAG, "Failed to encrypt with secret key.", e); - return null; - } - byte[] asalt = Cryptor.computeHkdf(salt, ASALT_IV, AES_CTR_IV_SIZE); - if (asalt == null) { - Log.e(TAG, "Failed to generate salt."); - return null; - } - try { - cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(asalt)); - } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { - Log.e(TAG, "Failed to initialize cipher.", e); - return null; - } - try { - return cipher.doFinal(data); - } catch (IllegalBlockSizeException | BadPaddingException e) { - Log.e(TAG, "Failed to encrypt with secret key.", e); - return null; - } - } - - @Nullable - @Override - public byte[] decrypt(byte[] encryptedData, byte[] salt, byte[] authenticityKey) { - if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) { - Log.w(TAG, "Illegal authenticity key size"); - return null; - } - - // Generates a 32 bytes encryption key from authenticity_key - byte[] encryptionKey = Cryptor.computeHkdf(authenticityKey, AK_IV, ENCRYPTION_KEY_SIZE); - if (encryptionKey == null) { - Log.e(TAG, "Failed to generate encryption key."); - return null; - } - - // Decrypts the data using the encryption key - SecretKey secretKey = new SecretKeySpec(encryptionKey, ENCRYPT_ALGORITHM); - Cipher cipher; - try { - cipher = Cipher.getInstance(CIPHER_ALGORITHM); - } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { - Log.e(TAG, "Failed to get cipher instance.", e); - return null; - } - byte[] asalt = Cryptor.computeHkdf(salt, ASALT_IV, AES_CTR_IV_SIZE); - if (asalt == null) { - return null; - } - try { - cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(asalt)); - } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { - Log.e(TAG, "Failed to initialize cipher.", e); - return null; - } - - try { - return cipher.doFinal(encryptedData); - } catch (IllegalBlockSizeException | BadPaddingException e) { - Log.e(TAG, "Failed to decrypt bytes with secret key.", e); - return null; - } - } - - @Override - @Nullable - public byte[] sign(byte[] data, byte[] key) { - return generateHmacTag(data, key); - } - - @Override - public int getSignatureLength() { - return HMAC_TAG_SIZE; - } - - @Override - public boolean verify(byte[] data, byte[] key, byte[] signature) { - return Arrays.equals(sign(data, key), signature); - } - - /** Generates a 16 bytes HMAC tag. This is used for decryptor to verify if the computed HMAC tag - * is equal to HMAC tag in advertisement to see data integrity. */ - @Nullable - @VisibleForTesting - byte[] generateHmacTag(byte[] data, byte[] authenticityKey) { - if (data == null || authenticityKey == null) { - Log.e(TAG, "Not generate HMAC tag because of invalid data input."); - return null; - } - - if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) { - Log.e(TAG, "Illegal authenticity key size"); - return null; - } - - // Generates a 32 bytes HMAC key from authenticity_key - byte[] hmacKey = Cryptor.computeHkdf(authenticityKey, HK_IV, AES_CTR_IV_SIZE); - if (hmacKey == null) { - Log.e(TAG, "Failed to generate HMAC key."); - return null; - } - - // Generates a 16 bytes HMAC tag from authenticity_key - return Cryptor.computeHkdf(data, hmacKey, HMAC_TAG_SIZE); - } -} diff --git a/nearby/service/java/com/android/server/nearby/util/encryption/CryptorMicImp.java b/nearby/service/java/com/android/server/nearby/util/encryption/CryptorMicImp.java new file mode 100644 index 0000000000000000000000000000000000000000..3dbf85cf8e5e2f2404f618dee913880a49265052 --- /dev/null +++ b/nearby/service/java/com/android/server/nearby/util/encryption/CryptorMicImp.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2022 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.server.nearby.util.encryption; + +import static com.android.server.nearby.NearbyService.TAG; + +import android.security.keystore.KeyProperties; +import android.util.Log; + +import androidx.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.nearby.util.ArrayUtils; + +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * MIC encryption and decryption for {@link android.nearby.BroadcastRequest#PRESENCE_VERSION_V1} + * advertisement + */ +public class CryptorMicImp extends Cryptor { + + public static final int MIC_LENGTH = 16; + + private static final String ENCRYPT_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES; + private static final byte[] AES_KEY_INFO_BYTES = "Unsigned Section AES key".getBytes( + StandardCharsets.US_ASCII); + private static final byte[] ADV_NONCE_INFO_BYTES_SALT_DE = "Unsigned Section IV".getBytes( + StandardCharsets.US_ASCII); + private static final byte[] ADV_NONCE_INFO_BYTES_ENCRYPTION_INFO_DE = + "V1 derived salt".getBytes(StandardCharsets.US_ASCII); + private static final byte[] METADATA_KEY_HMAC_KEY_INFO_BYTES = + "Unsigned Section metadata key HMAC key".getBytes(StandardCharsets.US_ASCII); + private static final byte[] MIC_HMAC_KEY_INFO_BYTES = "Unsigned Section HMAC key".getBytes( + StandardCharsets.US_ASCII); + private static final int AES_KEY_SIZE = 16; + private static final int ADV_NONCE_SIZE_SALT_DE = 16; + private static final int ADV_NONCE_SIZE_ENCRYPTION_INFO_DE = 12; + private static final int HMAC_KEY_SIZE = 32; + + // Lazily instantiated when {@link #getInstance()} is called. + @Nullable + private static CryptorMicImp sCryptor; + + private CryptorMicImp() { + } + + /** Returns an instance of CryptorImpV1. */ + public static CryptorMicImp getInstance() { + if (sCryptor == null) { + sCryptor = new CryptorMicImp(); + } + return sCryptor; + } + + /** + * Generate the meta data encryption key tag + * @param metadataEncryptionKey used as identity + * @param keySeed authenticity key saved in local and shared credential + * @return bytes generated by hmac or {@code null} when there is an error + */ + @Nullable + public static byte[] generateMetadataEncryptionKeyTag(byte[] metadataEncryptionKey, + byte[] keySeed) { + try { + byte[] metadataKeyHmacKey = generateMetadataKeyHmacKey(keySeed); + return Cryptor.generateHmac(/* algorithm= */ HMAC_SHA256_ALGORITHM, /* input= */ + metadataEncryptionKey, /* key= */ metadataKeyHmacKey); + } catch (GeneralSecurityException e) { + Log.e(TAG, "Failed to generate Metadata encryption key tag.", e); + return null; + } + } + + /** + * @param salt from the 2 bytes Salt Data Element + */ + @Nullable + public static byte[] generateAdvNonce(byte[] salt) throws GeneralSecurityException { + return Cryptor.computeHkdf( + /* macAlgorithm= */ HMAC_SHA256_ALGORITHM, + /* ikm = */ salt, + /* salt= */ NP_HKDF_SALT, + /* info= */ ADV_NONCE_INFO_BYTES_SALT_DE, + /* size= */ ADV_NONCE_SIZE_SALT_DE); + } + + /** Generates the 12 bytes nonce with salt from the 2 bytes Salt Data Element */ + @Nullable + public static byte[] generateAdvNonce(byte[] salt, int deIndex) + throws GeneralSecurityException { + // go/nearby-specs-working-doc + // Indices are encoded as big-endian unsigned 32-bit integers, starting at 1. + // Index 0 is reserved + byte[] indexBytes = new byte[4]; + indexBytes[3] = (byte) deIndex; + byte[] info = + ArrayUtils.concatByteArrays(ADV_NONCE_INFO_BYTES_ENCRYPTION_INFO_DE, indexBytes); + return Cryptor.computeHkdf( + /* macAlgorithm= */ HMAC_SHA256_ALGORITHM, + /* ikm = */ salt, + /* salt= */ NP_HKDF_SALT, + /* info= */ info, + /* size= */ ADV_NONCE_SIZE_ENCRYPTION_INFO_DE); + } + + @Nullable + @Override + public byte[] encrypt(byte[] input, byte[] iv, byte[] keySeed) { + if (input == null || iv == null || keySeed == null) { + return null; + } + Cipher cipher; + try { + cipher = Cipher.getInstance(CIPHER_ALGORITHM); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + Log.e(TAG, "Failed to encrypt with secret key.", e); + return null; + } + + byte[] aesKey; + try { + aesKey = generateAesKey(keySeed); + } catch (GeneralSecurityException e) { + Log.e(TAG, "Encryption failed because failed to generate the AES key.", e); + return null; + } + if (aesKey == null) { + Log.i(TAG, "Failed to generate the AES key."); + return null; + } + SecretKey secretKey = new SecretKeySpec(aesKey, ENCRYPT_ALGORITHM); + try { + cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv)); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + Log.e(TAG, "Failed to initialize cipher.", e); + return null; + + } + try { + return cipher.doFinal(input); + } catch (IllegalBlockSizeException | BadPaddingException e) { + Log.e(TAG, "Failed to encrypt with secret key.", e); + return null; + } + } + + @Nullable + @Override + public byte[] decrypt(byte[] encryptedData, byte[] iv, byte[] keySeed) { + if (encryptedData == null || iv == null || keySeed == null) { + return null; + } + + Cipher cipher; + try { + cipher = Cipher.getInstance(CIPHER_ALGORITHM); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + Log.e(TAG, "Failed to get cipher instance.", e); + return null; + } + byte[] aesKey; + try { + aesKey = generateAesKey(keySeed); + } catch (GeneralSecurityException e) { + Log.e(TAG, "Decryption failed because failed to generate the AES key.", e); + return null; + } + SecretKey secretKey = new SecretKeySpec(aesKey, ENCRYPT_ALGORITHM); + try { + cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + Log.e(TAG, "Failed to initialize cipher.", e); + return null; + } + + try { + return cipher.doFinal(encryptedData); + } catch (IllegalBlockSizeException | BadPaddingException e) { + Log.e(TAG, "Failed to decrypt bytes with secret key.", e); + return null; + } + } + + @Override + @Nullable + public byte[] sign(byte[] data, byte[] key) { + byte[] res = generateHmacTag(data, key); + return res; + } + + @Override + public int getSignatureLength() { + return MIC_LENGTH; + } + + @Override + public boolean verify(byte[] data, byte[] key, byte[] signature) { + return Arrays.equals(sign(data, key), signature); + } + + /** + * Generates a 16 bytes HMAC tag. This is used for decryptor to verify if the computed HMAC tag + * is equal to HMAC tag in advertisement to see data integrity. + * + * @param input concatenated advertisement UUID, header, section header, derived salt, and + * section content + * @param keySeed the MIC HMAC key is calculated using the derived key + * @return the first 16 bytes of HMAC-SHA256 result + */ + @Nullable + @VisibleForTesting + byte[] generateHmacTag(byte[] input, byte[] keySeed) { + try { + if (input == null || keySeed == null) { + return null; + } + byte[] micHmacKey = generateMicHmacKey(keySeed); + byte[] hmac = Cryptor.generateHmac(/* algorithm= */ HMAC_SHA256_ALGORITHM, /* input= */ + input, /* key= */ micHmacKey); + if (ArrayUtils.isEmpty(hmac)) { + return null; + } + return Arrays.copyOf(hmac, MIC_LENGTH); + } catch (GeneralSecurityException e) { + Log.e(TAG, "Failed to generate mic hmac key.", e); + return null; + } + } + + @Nullable + private static byte[] generateAesKey(byte[] keySeed) throws GeneralSecurityException { + return Cryptor.computeHkdf( + /* macAlgorithm= */ HMAC_SHA256_ALGORITHM, + /* ikm = */ keySeed, + /* salt= */ NP_HKDF_SALT, + /* info= */ AES_KEY_INFO_BYTES, + /* size= */ AES_KEY_SIZE); + } + + private static byte[] generateMetadataKeyHmacKey(byte[] keySeed) + throws GeneralSecurityException { + return generateHmacKey(keySeed, METADATA_KEY_HMAC_KEY_INFO_BYTES); + } + + private static byte[] generateMicHmacKey(byte[] keySeed) throws GeneralSecurityException { + return generateHmacKey(keySeed, MIC_HMAC_KEY_INFO_BYTES); + } + + private static byte[] generateHmacKey(byte[] keySeed, byte[] info) + throws GeneralSecurityException { + return Cryptor.computeHkdf( + /* macAlgorithm= */ HMAC_SHA256_ALGORITHM, + /* ikm = */ keySeed, + /* salt= */ NP_HKDF_SALT, + /* info= */ info, + /* size= */ HMAC_KEY_SIZE); + } +} diff --git a/nearby/service/proto/src/fastpair/cache.proto b/nearby/service/proto/src/fastpair/cache.proto deleted file mode 100644 index d4c7c3d02805154d2444a1ae311222c3b7e670fe..0000000000000000000000000000000000000000 --- a/nearby/service/proto/src/fastpair/cache.proto +++ /dev/null @@ -1,427 +0,0 @@ -syntax = "proto3"; -package service.proto; -import "src/fastpair/rpcs.proto"; -import "src/fastpair/fast_pair_string.proto"; - -// db information for Fast Pair that gets from server. -message ServerResponseDbItem { - // Device's model id. - string model_id = 1; - - // Response was received from the server. Contains data needed to display - // FastPair notification such as device name, txPower of device, image used - // in the notification, etc. - GetObservedDeviceResponse get_observed_device_response = 2; - - // The timestamp that make the server fetch. - int64 last_fetch_info_timestamp_millis = 3; - - // Whether the item in the cache is expirable or not (when offline mode this - // will be false). - bool expirable = 4; -} - - -// Client side scan result. -message StoredScanResult { - // REQUIRED - // Unique ID generated based on scan result - string id = 1; - - // REQUIRED - NearbyType type = 2; - - // REQUIRED - // The most recent all upper case mac associated with this item. - // (Mac-to-DiscoveryItem is a many-to-many relationship) - string mac_address = 4; - - // Beacon's RSSI value - int32 rssi = 10; - - // Beacon's tx power - int32 tx_power = 11; - - // The mac address encoded in beacon advertisement. Currently only used by - // chromecast. - string device_setup_mac = 12; - - // Uptime of the device in minutes. Stops incrementing at 255. - int32 uptime_minutes = 13; - - // REQUIRED - // Client timestamp when the beacon was first observed in BLE scan. - int64 first_observation_timestamp_millis = 14; - - // REQUIRED - // Client timestamp when the beacon was last observed in BLE scan. - int64 last_observation_timestamp_millis = 15; - - // Deprecated fields. - reserved 3, 5, 6, 7, 8, 9; -} - - -// Data for a DiscoveryItem created from server response and client scan result. -// Only caching original data from scan result, server response, timestamps -// and user actions. Do not save generated data in this object. -// Next ID: 50 -message StoredDiscoveryItem { - enum State { - // Default unknown state. - STATE_UNKNOWN = 0; - - // The item is normal. - STATE_ENABLED = 1; - - // The item has been muted by user. - STATE_MUTED = 2; - - // The item has been disabled by us (likely temporarily). - STATE_DISABLED_BY_SYSTEM = 3; - } - - // The status of the item. - // TODO(b/204409421) remove enum - enum DebugMessageCategory { - // Default unknown state. - STATUS_UNKNOWN = 0; - - // The item is valid and visible in notification. - STATUS_VALID_NOTIFICATION = 1; - - // The item made it to list but not to notification. - STATUS_VALID_LIST_VIEW = 2; - - // The item is filtered out on client. Never made it to list view. - STATUS_DISABLED_BY_CLIENT = 3; - - // The item is filtered out by server. Never made it to client. - STATUS_DISABLED_BY_SERVER = 4; - } - - enum ExperienceType { - EXPERIENCE_UNKNOWN = 0; - EXPERIENCE_GOOD = 1; - EXPERIENCE_BAD = 2; - } - - // REQUIRED - // Offline item: unique ID generated on client. - // Online item: unique ID generated on server. - string id = 1; - - // REQUIRED - // The most recent all upper case mac associated with this item. - // (Mac-to-DiscoveryItem is a many-to-many relationship) - string mac_address = 4; - - // REQUIRED - string action_url = 5; - - // The bluetooth device name from advertisment - string device_name = 6; - - // REQUIRED - // Item's title - string title = 7; - - // Item's description. - string description = 8; - - // The URL for display - string display_url = 9; - - // REQUIRED - // Client timestamp when the beacon was last observed in BLE scan. - int64 last_observation_timestamp_millis = 10; - - // REQUIRED - // Client timestamp when the beacon was first observed in BLE scan. - int64 first_observation_timestamp_millis = 11; - - // REQUIRED - // Item's current state. e.g. if the item is blocked. - State state = 17; - - // The resolved url type for the action_url. - ResolvedUrlType action_url_type = 19; - - // The timestamp when the user is redirected to Play Store after clicking on - // the item. - int64 pending_app_install_timestamp_millis = 20; - - // Beacon's RSSI value - int32 rssi = 22; - - // Beacon's tx power - int32 tx_power = 23; - - // Human readable name of the app designated to open the uri - // Used in the second line of the notification, "Open in {} app" - string app_name = 25; - - // The timestamp when the attachment was created on PBS server. In case there - // are duplicate - // items with the same scanId/groupID, only show the one with the latest - // timestamp. - int64 attachment_creation_sec = 28; - - // Package name of the App that owns this item. - string package_name = 30; - - // The average star rating of the app. - float star_rating = 31; - - // TriggerId identifies the trigger/beacon that is attached with a message. - // It's generated from server for online messages to synchronize formatting - // across client versions. - // Example: - // * BLE_UID: 3||deadbeef - // * BLE_URL: http://trigger.id - // See go/discovery-store-message-and-trigger-id for more details. - string trigger_id = 34; - - // Bytes of item icon in PNG format displayed in Discovery item list. - bytes icon_png = 36; - - // A FIFE URL of the item icon displayed in Discovery item list. - string icon_fife_url = 49; - - // See equivalent field in NearbyItem. - bytes authentication_public_key_secp256r1 = 45; - - // See equivalent field in NearbyItem. - FastPairInformation fast_pair_information = 46; - - // Companion app detail. - CompanionAppDetails companion_detail = 47; - - // Fast pair strings - FastPairStrings fast_pair_strings = 48; - - // Deprecated fields. - reserved 2, 3, 12, 13, 14, 15, 16, 18, 21, 24, 26, 27, 29, 32, 33, 35, 37, 38, 39, 40, 41, 42, 43, 44; -} -enum ResolvedUrlType { - RESOLVED_URL_TYPE_UNKNOWN = 0; - - // The url is resolved to a web page that is not a play store app. - // This can be considered as the default resolved type when it's - // not the other specific types. - WEBPAGE = 1; - - // The url is resolved to the Google Play store app - // ie. play.google.com/store - APP = 2; -} -enum DiscoveryAttachmentType { - DISCOVERY_ATTACHMENT_TYPE_UNKNOWN = 0; - - // The attachment is posted in the prod namespace (without "-debug") - DISCOVERY_ATTACHMENT_TYPE_NORMAL = 1; - - // The attachment is posted in the debug namespace (with "-debug") - DISCOVERY_ATTACHMENT_TYPE_DEBUG = 2; -} -// Additional information relevant only for Fast Pair devices. -message FastPairInformation { - // When true, Fast Pair will only create a bond with the device and not - // attempt to connect any profiles (for example, A2DP or HFP). - bool data_only_connection = 1; - - // Additional images that are attached specifically for true wireless Fast - // Pair devices. - TrueWirelessHeadsetImages true_wireless_images = 3; - - // When true, this device can support assistant function. - bool assistant_supported = 4; - - // Features supported by the Fast Pair device. - repeated FastPairFeature features = 5; - - // Optional, the name of the company producing this Fast Pair device. - string company_name = 6; - - // Optional, the type of device. - DeviceType device_type = 7; - - reserved 2; -} - - -enum NearbyType { - NEARBY_TYPE_UNKNOWN = 0; - // Proximity Beacon Service (PBS). This is the only type of nearbyItems which - // can be customized by 3p and therefore the intents passed should not be - // completely trusted. Deprecated already. - NEARBY_PROXIMITY_BEACON = 1; - // Physical Web URL beacon. Deprecated already. - NEARBY_PHYSICAL_WEB = 2; - // Chromecast beacon. Used on client-side only. - NEARBY_CHROMECAST = 3; - // Wear beacon. Used on client-side only. - NEARBY_WEAR = 4; - // A device (e.g. a Magic Pair device that needs to be set up). The special- - // case devices above (e.g. ChromeCast, Wear) might migrate to this type. - NEARBY_DEVICE = 6; - // Popular apps/urls based on user's current geo-location. - NEARBY_POPULAR_HERE = 7; - - reserved 5; -} - -// A locally cached Fast Pair device associating an account key with the -// bluetooth address of the device. -message StoredFastPairItem { - // The device's public mac address. - string mac_address = 1; - - // The account key written to the device. - bytes account_key = 2; - - // When user need to update provider name, enable this value to trigger - // writing new name to provider. - bool need_to_update_provider_name = 3; - - // The retry times to update name into provider. - int32 update_name_retries = 4; - - // Latest firmware version from the server. - string latest_firmware_version = 5; - - // The firmware version that is on the device. - string device_firmware_version = 6; - - // The timestamp from the last time we fetched the firmware version from the - // device. - int64 last_check_firmware_timestamp_millis = 7; - - // The timestamp from the last time we fetched the firmware version from - // server. - int64 last_server_query_timestamp_millis = 8; - - // Only allows one bloom filter check process to create gatt connection and - // try to read the firmware version value. - bool can_read_firmware = 9; - - // Device's model id. - string model_id = 10; - - // Features that this Fast Pair device supports. - repeated FastPairFeature features = 11; - - // Keeps the stored discovery item in local cache, we can have most - // information of fast pair device locally without through footprints, i.e. we - // can have most fast pair features locally. - StoredDiscoveryItem discovery_item = 12; - - // When true, the latest uploaded event to FMA is connected. We use - // it as the previous ACL state when getting the BluetoothAdapter STATE_OFF to - // determine if need to upload the disconnect event to FMA. - bool fma_state_is_connected = 13; - - // Device's buffer size range. - repeated BufferSizeRange buffer_size_range = 18; - - // The additional account key if this device could be associated with multiple - // accounts. Notes that for this device, the account_key field is the basic - // one which will not be associated with the accounts. - repeated bytes additional_account_key = 19; - - // Deprecated fields. - reserved 14, 15, 16, 17; -} - -// Contains information about Fast Pair devices stored through our scanner. -// Next ID: 29 -message ScanFastPairStoreItem { - // Device's model id. - string model_id = 1; - - // Device's RSSI value - int32 rssi = 2; - - // Device's tx power - int32 tx_power = 3; - - // Bytes of item icon in PNG format displayed in Discovery item list. - bytes icon_png = 4; - - // A FIFE URL of the item icon displayed in Discovery item list. - string icon_fife_url = 28; - - // Device name like "Bose QC 35". - string device_name = 5; - - // Client timestamp when user last saw Fast Pair device. - int64 last_observation_timestamp_millis = 6; - - // Action url after user click the notification. - string action_url = 7; - - // Device's bluetooth address. - string address = 8; - - // The computed threshold rssi value that would trigger FastPair notifications - int32 threshold_rssi = 9; - - // Populated with the contents of the bloom filter in the event that - // the scanned device is advertising a bloom filter instead of a model id - bytes bloom_filter = 10; - - // Device name from the BLE scan record - string ble_device_name = 11; - - // Strings used for the FastPair UI - FastPairStrings fast_pair_strings = 12; - - // A key used to authenticate advertising device. - // See NearbyItem.authentication_public_key_secp256r1 for more information. - bytes anti_spoofing_public_key = 13; - - // When true, Fast Pair will only create a bond with the device and not - // attempt to connect any profiles (for example, A2DP or HFP). - bool data_only_connection = 14; - - // The type of the manufacturer (first party, third party, etc). - int32 manufacturer_type_num = 15; - - // Additional images that are attached specifically for true wireless Fast - // Pair devices. - TrueWirelessHeadsetImages true_wireless_images = 16; - - // When true, this device can support assistant function. - bool assistant_supported = 17; - - // Optional, the name of the company producing this Fast Pair device. - string company_name = 18; - - // Features supported by the Fast Pair device. - FastPairFeature features = 19; - - // The interaction type that this scan should trigger - InteractionType interaction_type = 20; - - // The copy of the advertisement bytes, used to pass along to other - // apps that use Fast Pair as the discovery vehicle. - bytes full_ble_record = 21; - - // Companion app related information - CompanionAppDetails companion_detail = 22; - - // Client timestamp when user first saw Fast Pair device. - int64 first_observation_timestamp_millis = 23; - - // The type of the device (wearable, headphones, etc). - int32 device_type_num = 24; - - // The type of notification (app launch smart setup, etc). - NotificationType notification_type = 25; - - // The customized title. - string customized_title = 26; - - // The customized description. - string customized_description = 27; -} diff --git a/nearby/service/proto/src/fastpair/data.proto b/nearby/service/proto/src/fastpair/data.proto deleted file mode 100644 index 6f4faddd1f189eddcd1694b3a0052b29bddb9677..0000000000000000000000000000000000000000 --- a/nearby/service/proto/src/fastpair/data.proto +++ /dev/null @@ -1,26 +0,0 @@ -syntax = "proto3"; - -package service.proto; -import "src/fastpair/cache.proto"; - -// A device that has been Fast Paired with. -message FastPairDeviceWithAccountKey { - // The account key which was written to the device after pairing completed. - bytes account_key = 1; - - // The stored discovery item which represents the notification that should be - // associated with the device. Note, this is stored as a raw byte array - // instead of StoredDiscoveryItem because icing only supports proto lite and - // StoredDiscoveryItem is handed around as a nano proto in implementation, - // which are not compatible with each other. - StoredDiscoveryItem discovery_item = 3; - - // SHA256 of "account key + headset's public address", this is used to - // identify the paired headset. Because of adding account key to generate the - // hash value, it makes the information anonymous, even for the same headset, - // different accounts have different values. - bytes sha256_account_key_public_address = 4; - - // Deprecated fields. - reserved 2; -} diff --git a/nearby/service/proto/src/fastpair/fast_pair_string.proto b/nearby/service/proto/src/fastpair/fast_pair_string.proto deleted file mode 100644 index f318c1aec3e079dcbadb3e2d9d8565310e645525..0000000000000000000000000000000000000000 --- a/nearby/service/proto/src/fastpair/fast_pair_string.proto +++ /dev/null @@ -1,40 +0,0 @@ -syntax = "proto2"; - -package service.proto; - -message FastPairStrings { - // Required for initial pairing, used when there is a Google account on the - // device - optional string tap_to_pair_with_account = 1; - - // Required for initial pairing, used when there is no Google account on the - // device - optional string tap_to_pair_without_account = 2; - - // Description for initial pairing - optional string initial_pairing_description = 3; - - // Description after successfully paired the device with companion app - // installed - optional string pairing_finished_companion_app_installed = 4; - - // Description after successfully paired the device with companion app not - // installed - optional string pairing_finished_companion_app_not_installed = 5; - - // Description when phone found the device that associates with user's account - // before remind user to pair with new device. - optional string subsequent_pairing_description = 6; - - // Description when fast pair finds the user paired with device manually - // reminds user to opt the device into cloud. - optional string retroactive_pairing_description = 7; - - // Description when user click setup device while device is still pairing - optional string wait_app_launch_description = 8; - - // Description when user fail to pair with device - optional string pairing_fail_description = 9; - - reserved 10, 11, 12, 13, 14,15, 16, 17, 18; -} diff --git a/nearby/service/proto/src/fastpair/rpcs.proto b/nearby/service/proto/src/fastpair/rpcs.proto deleted file mode 100644 index bce4378f77455235c012b60486977b2da9d9ea7c..0000000000000000000000000000000000000000 --- a/nearby/service/proto/src/fastpair/rpcs.proto +++ /dev/null @@ -1,301 +0,0 @@ -// RPCs for the Nearby Console service. -syntax = "proto3"; - -package service.proto; -// Response containing an observed device. -message GetObservedDeviceResponse { - // The device from the request. - Device device = 1; - - // The image icon that shows in the notification - bytes image = 3; - - // Strings to be displayed on notifications during the pairing process. - ObservedDeviceStrings strings = 4; - - reserved 2; -} - -message Device { - // Output only. The server-generated ID of the device. - int64 id = 1; - - // The pantheon project number the device is created under. Only Nearby admins - // can change this. - int64 project_number = 2; - - // How the notification will be displayed to the user - NotificationType notification_type = 3; - - // The image to show on the notification. - string image_url = 4; - - // The name of the device. - string name = 5; - - // The intent that will be launched via the notification. - string intent_uri = 6; - - // The transmit power of the device's BLE chip. - int32 ble_tx_power = 7; - - // The distance that the device must be within to show a notification. - // If no distance is set, we default to 0.6 meters. Only Nearby admins can - // change this. - float trigger_distance = 8; - - // Output only. Fast Pair only - The anti-spoofing key pair for the device. - AntiSpoofingKeyPair anti_spoofing_key_pair = 9; - - // Output only. The current status of the device. - Status status = 10; - - - // DEPRECATED - check for published_version instead. - // Output only. - // Whether the device has a different, already published version. - bool has_published_version = 12; - - // Fast Pair only - The type of device being registered. - DeviceType device_type = 13; - - - // Fast Pair only - Additional images for true wireless headsets. - TrueWirelessHeadsetImages true_wireless_images = 15; - - // Fast Pair only - When true, this device can support assistant function. - bool assistant_supported = 16; - - // Output only. - // The published version of a device that has been approved to be displayed - // as a notification - only populated if the device has a different published - // version. (A device that only has a published version would not have this - // populated). - Device published_version = 17; - - // Fast Pair only - When true, Fast Pair will only create a bond with the - // device and not attempt to connect any profiles (for example, A2DP or HFP). - bool data_only_connection = 18; - - // Name of the company/brand that will be selling the product. - string company_name = 19; - - repeated FastPairFeature features = 20; - - // Name of the device that is displayed on the console. - string display_name = 21; - - // How the device will be interacted with by the user when the scan record - // is detected. - InteractionType interaction_type = 22; - - // Companion app information. - CompanionAppDetails companion_detail = 23; - - reserved 11, 14; -} - - -// Represents the format of the final device notification (which is directly -// correlated to the action taken by the notification). -enum NotificationType { - // Unspecified notification type. - NOTIFICATION_TYPE_UNSPECIFIED = 0; - // Notification launches the fast pair intent. - // Example Notification Title: "Bose SoundLink II" - // Notification Description: "Tap to pair with this device" - FAST_PAIR = 1; - // Notification launches an app. - // Notification Title: "[X]" where X is type/name of the device. - // Notification Description: "Tap to setup this device" - APP_LAUNCH = 2; - // Notification launches for Nearby Setup. The notification title and - // description is the same as APP_LAUNCH. - NEARBY_SETUP = 3; - // Notification launches the fast pair intent, but doesn't include an anti- - // spoofing key. The notification title and description is the same as - // FAST_PAIR. - FAST_PAIR_ONE = 4; - // Notification launches Smart Setup on devices. - // These notifications are identical to APP_LAUNCH except that they always - // launch Smart Setup intents within GMSCore. - SMART_SETUP = 5; -} - -// How the device will be interacted with when it is seen. -enum InteractionType { - INTERACTION_TYPE_UNKNOWN = 0; - AUTO_LAUNCH = 1; - NOTIFICATION = 2; -} - -// Features that can be enabled for a Fast Pair device. -enum FastPairFeature { - FAST_PAIR_FEATURE_UNKNOWN = 0; - SILENCE_MODE = 1; - WIRELESS_CHARGING = 2; - DYNAMIC_BUFFER_SIZE = 3; - NO_PERSONALIZED_NAME = 4; - EDDYSTONE_TRACKING = 5; -} - -message CompanionAppDetails { - // Companion app slice provider's authority. - string authority = 1; - - // Companion app certificate value. - string certificate_hash = 2; - - // Deprecated fields. - reserved 3; -} - -// Additional images for True Wireless Fast Pair devices. -message TrueWirelessHeadsetImages { - // Image URL for the left bud. - string left_bud_url = 1; - - // Image URL for the right bud. - string right_bud_url = 2; - - // Image URL for the case. - string case_url = 3; -} - -// Represents the type of device that is being registered. -enum DeviceType { - DEVICE_TYPE_UNSPECIFIED = 0; - HEADPHONES = 1; - SPEAKER = 2; - WEARABLE = 3; - INPUT_DEVICE = 4; - AUTOMOTIVE = 5; - OTHER = 6; - TRUE_WIRELESS_HEADPHONES = 7; - WEAR_OS = 8; - ANDROID_AUTO = 9; -} - -// An anti-spoofing key pair for a device that allows us to verify the device is -// broadcasting legitimately. -message AntiSpoofingKeyPair { - // The private key (restricted to only be viewable by trusted clients). - bytes private_key = 1; - - // The public key. - bytes public_key = 2; -} - -// Various states that a customer-configured device notification can be in. -// PUBLISHED is the only state that shows notifications to the public. -message Status { - // Status types available for each device. - enum StatusType { - // Unknown status. - TYPE_UNSPECIFIED = 0; - // Drafted device. - DRAFT = 1; - // Submitted and waiting for approval. - SUBMITTED = 2; - // Fully approved and available for end users. - PUBLISHED = 3; - // Rejected and not available for end users. - REJECTED = 4; - } - - // Details about a device that has been rejected. - message RejectionDetails { - // The reason for the rejection. - enum RejectionReason { - // Unspecified reason. - REASON_UNSPECIFIED = 0; - // Name is not valid. - NAME = 1; - // Image is not valid. - IMAGE = 2; - // Tests have failed. - TESTS = 3; - // Other reason. - OTHER = 4; - } - - // A list of reasons the device was rejected. - repeated RejectionReason reasons = 1; - // Comment about an OTHER rejection reason. - string additional_comment = 2; - } - - // The status of the device. - StatusType status_type = 1; - - // Accompanies Status.REJECTED. - RejectionDetails rejection_details = 2; -} - -// Strings to be displayed in notifications surfaced for a device. -message ObservedDeviceStrings { - // The notification description for when the device is initially discovered. - string initial_notification_description = 2; - - // The notification description for when the device is initially discovered - // and no account is logged in. - string initial_notification_description_no_account = 3; - - // The notification description for once we have finished pairing and the - // companion app has been opened. For google assistant devices, this string will point - // users to setting up the assistant. - string open_companion_app_description = 4; - - // The notification description for once we have finished pairing and the - // companion app needs to be updated before use. - string update_companion_app_description = 5; - - // The notification description for once we have finished pairing and the - // companion app needs to be installed. - string download_companion_app_description = 6; - - // The notification title when a pairing fails. - string unable_to_connect_title = 7; - - // The notification summary when a pairing fails. - string unable_to_connect_description = 8; - - // The description that helps user initially paired with device. - string initial_pairing_description = 9; - - // The description that let user open the companion app. - string connect_success_companion_app_installed = 10; - - // The description that let user download the companion app. - string connect_success_companion_app_not_installed = 11; - - // The description that reminds user there is a paired device nearby. - string subsequent_pairing_description = 12; - - // The description that reminds users opt in their device. - string retroactive_pairing_description = 13; - - // The description that indicates companion app is about to launch. - string wait_launch_companion_app_description = 14; - - // The description that indicates go to bluetooth settings when connection - // fail. - string fail_connect_go_to_settings_description = 15; - - reserved 1, 16, 17, 18, 19, 20, 21, 22, 23, 24; -} - -// The buffer size range of a Fast Pair devices support dynamic buffer size. -message BufferSizeRange { - // The max buffer size in ms. - int32 max_size = 1; - - // The min buffer size in ms. - int32 min_size = 2; - - // The default buffer size in ms. - int32 default_size = 3; - - // The codec of this buffer size range. - int32 codec = 4; -} diff --git a/nearby/service/proto/src/presence/blefilter.proto b/nearby/service/proto/src/presence/blefilter.proto index e1bf45595a6040c2e1d8f3953ced50c02f00c510..bf9357b28640e2b42069c1cafd12b1c7ab211812 100644 --- a/nearby/service/proto/src/presence/blefilter.proto +++ b/nearby/service/proto/src/presence/blefilter.proto @@ -115,6 +115,9 @@ message BleFilterResult { repeated DataElement data_element = 7; optional bytes ble_service_data = 8; optional ResultType result_type = 9; + // Timestamp when the device is discovered, in nanoseconds, + // relative to Android SystemClock.elapsedRealtimeNanos(). + optional uint64 timestamp_ns = 10; } message BleFilterResults { diff --git a/nearby/tests/cts/fastpair/Android.bp b/nearby/tests/cts/fastpair/Android.bp index a61d180cb5559d12491518b3cfe69ce98dab09ff..66a1ffe236062b98ff7e16dc51da0e134dbab822 100644 --- a/nearby/tests/cts/fastpair/Android.bp +++ b/nearby/tests/cts/fastpair/Android.bp @@ -26,7 +26,7 @@ android_test { "bluetooth-test-util-lib", "compatibility-device-util-axt", "ctstestrunner-axt", - "truth-prebuilt", + "truth", ], libs: [ "android.test.base", diff --git a/nearby/tests/integration/privileged/Android.bp b/nearby/tests/integration/privileged/Android.bp index e3250f62664849fa9e3207f6ec71ed58dcf492ea..9b6e488029af47744fbbbb5891dc8610c10598d8 100644 --- a/nearby/tests/integration/privileged/Android.bp +++ b/nearby/tests/integration/privileged/Android.bp @@ -27,7 +27,7 @@ android_test { "androidx.test.ext.junit", "androidx.test.rules", "junit", - "truth-prebuilt", + "truth", ], test_suites: ["device-tests"], } diff --git a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/FastPairSettingsProviderTest.kt b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/FastPairSettingsProviderTest.kt deleted file mode 100644 index af3f75f4f7caa7c631e98b3e165775b0de475863..0000000000000000000000000000000000000000 --- a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/FastPairSettingsProviderTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.integration.privileged - -import android.content.Context -import android.provider.Settings -import androidx.test.core.app.ApplicationProvider -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.junit.runners.Parameterized.Parameters - -data class FastPairSettingsFlag(val name: String, val value: Int) { - override fun toString() = name -} - -@RunWith(Parameterized::class) -class FastPairSettingsProviderTest(private val flag: FastPairSettingsFlag) { - - /** Verify privileged app can enable/disable Fast Pair scan. */ - @Test - fun testSettingsFastPairScan_fromPrivilegedApp() { - val appContext = ApplicationProvider.getApplicationContext() - val contentResolver = appContext.contentResolver - - Settings.Secure.putInt(contentResolver, "fast_pair_scan_enabled", flag.value) - - val actualValue = Settings.Secure.getInt( - contentResolver, "fast_pair_scan_enabled", /* default value */ -1) - assertThat(actualValue).isEqualTo(flag.value) - } - - companion object { - @JvmStatic - @Parameters(name = "{0}Succeed") - fun fastPairScanFlags() = listOf( - FastPairSettingsFlag(name = "disable", value = 0), - FastPairSettingsFlag(name = "enable", value = 1), - ) - } -} diff --git a/nearby/tests/integration/ui/AndroidManifest.xml b/nearby/tests/integration/ui/AndroidManifest.xml deleted file mode 100644 index 9aea0c1cba09d62436a7d66b6e6a4a6fe6dddc98..0000000000000000000000000000000000000000 --- a/nearby/tests/integration/ui/AndroidManifest.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/BaseUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/BaseUiTest.kt deleted file mode 100644 index 658775b7db9fe2bf662f5ed606036c15f4b4a8b7..0000000000000000000000000000000000000000 --- a/nearby/tests/integration/ui/src/android/nearby/integration/ui/BaseUiTest.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.integration.ui - -import android.platform.test.rule.ArtifactSaver -import android.platform.test.rule.ScreenRecordRule -import android.platform.test.rule.TestWatcher -import org.junit.Rule -import org.junit.rules.TestRule -import org.junit.rules.Timeout -import org.junit.runner.Description - -abstract class BaseUiTest { - @get:Rule - var mGlobalTimeout: Timeout = Timeout.seconds(100) // Test times out in 1.67 minutes - - @get:Rule - val mTestWatcherRule: TestRule = object : TestWatcher() { - override fun failed(throwable: Throwable?, description: Description?) { - super.failed(throwable, description) - ArtifactSaver.onError(description, throwable) - } - } - - @get:Rule - val mScreenRecordRule: TestRule = ScreenRecordRule() -} \ No newline at end of file diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/CheckNearbyHalfSheetUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/CheckNearbyHalfSheetUiTest.kt deleted file mode 100644 index 5a3538e15e8d1b1ba90e9c4f2df18c50ba8bd538..0000000000000000000000000000000000000000 --- a/nearby/tests/integration/ui/src/android/nearby/integration/ui/CheckNearbyHalfSheetUiTest.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.integration.ui - -import android.content.Context -import android.os.Bundle -import android.platform.test.rule.ScreenRecordRule.ScreenRecord -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import androidx.test.uiautomator.Until -import com.android.server.nearby.common.eventloop.EventLoop -import com.android.server.nearby.common.locator.Locator -import com.android.server.nearby.common.locator.LocatorContextWrapper -import com.android.server.nearby.fastpair.FastPairController -import com.android.server.nearby.fastpair.cache.FastPairCacheManager -import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager -import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager -import com.google.common.truth.Truth.assertThat -import com.google.common.truth.Truth.assertWithMessage -import org.junit.AfterClass -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import service.proto.Cache -import service.proto.FastPairString.FastPairStrings -import java.time.Clock - -/** An instrumented test to check Nearby half sheet UI showed correctly. - * - * To run this test directly: - * am instrument -w -r \ - * -e class android.nearby.integration.ui.CheckNearbyHalfSheetUiTest \ - * android.nearby.integration.ui/androidx.test.runner.AndroidJUnitRunner - */ -@RunWith(AndroidJUnit4::class) -class CheckNearbyHalfSheetUiTest : BaseUiTest() { - private var waitHalfSheetPopupTimeoutMs: Long - private var halfSheetTitleText: String - private var halfSheetSubtitleText: String - - init { - val arguments: Bundle = InstrumentationRegistry.getArguments() - waitHalfSheetPopupTimeoutMs = arguments.getLong( - WAIT_HALF_SHEET_POPUP_TIMEOUT_KEY, - DEFAULT_WAIT_HALF_SHEET_POPUP_TIMEOUT_MS - ) - halfSheetTitleText = - arguments.getString(HALF_SHEET_TITLE_KEY, DEFAULT_HALF_SHEET_TITLE_TEXT) - halfSheetSubtitleText = - arguments.getString(HALF_SHEET_SUBTITLE_KEY, DEFAULT_HALF_SHEET_SUBTITLE_TEXT) - - device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - } - - /** For multidevice test snippet only. Force overwrites the test arguments. */ - fun updateTestArguments( - waitHalfSheetPopupTimeoutSeconds: Int, - halfSheetTitleText: String, - halfSheetSubtitleText: String - ) { - this.waitHalfSheetPopupTimeoutMs = waitHalfSheetPopupTimeoutSeconds * 1000L - this.halfSheetTitleText = halfSheetTitleText - this.halfSheetSubtitleText = halfSheetSubtitleText - } - - @Before - fun setUp() { - val appContext = ApplicationProvider.getApplicationContext() - val locator = Locator(appContext).apply { - overrideBindingForTest(EventLoop::class.java, EventLoop.newInstance("test")) - overrideBindingForTest( - FastPairCacheManager::class.java, - FastPairCacheManager(appContext) - ) - overrideBindingForTest(FootprintsDeviceManager::class.java, FootprintsDeviceManager()) - overrideBindingForTest(Clock::class.java, Clock.systemDefaultZone()) - } - val locatorContextWrapper = LocatorContextWrapper(appContext, locator) - locator.overrideBindingForTest( - FastPairController::class.java, - FastPairController(locatorContextWrapper) - ) - val scanFastPairStoreItem = Cache.ScanFastPairStoreItem.newBuilder() - .setDeviceName(DEFAULT_HALF_SHEET_TITLE_TEXT) - .setFastPairStrings( - FastPairStrings.newBuilder() - .setInitialPairingDescription(DEFAULT_HALF_SHEET_SUBTITLE_TEXT).build() - ) - .build() - FastPairHalfSheetManager(locatorContextWrapper).showHalfSheet(scanFastPairStoreItem) - } - - @Test - @ScreenRecord - fun checkNearbyHalfSheetUi() { - // Check Nearby half sheet showed by checking button "Connect" on the DevicePairingFragment. - val isConnectButtonShowed = device.wait( - Until.hasObject(NearbyHalfSheetUiMap.DevicePairingFragment.connectButton), - waitHalfSheetPopupTimeoutMs - ) - assertWithMessage("Nearby half sheet didn't show within $waitHalfSheetPopupTimeoutMs ms.") - .that(isConnectButtonShowed).isTrue() - - val halfSheetTitle = - device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.halfSheetTitle) - assertThat(halfSheetTitle).isNotNull() - assertThat(halfSheetTitle.text).isEqualTo(halfSheetTitleText) - - val halfSheetSubtitle = - device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.halfSheetSubtitle) - assertThat(halfSheetSubtitle).isNotNull() - assertThat(halfSheetSubtitle.text).isEqualTo(halfSheetSubtitleText) - - val deviceImage = device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.deviceImage) - assertThat(deviceImage).isNotNull() - - val infoButton = device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.infoButton) - assertThat(infoButton).isNotNull() - } - - companion object { - private const val DEFAULT_WAIT_HALF_SHEET_POPUP_TIMEOUT_MS = 30 * 1000L - private const val DEFAULT_HALF_SHEET_TITLE_TEXT = "Fast Pair Provider Simulator" - private const val DEFAULT_HALF_SHEET_SUBTITLE_TEXT = "Fast Pair Provider Simulator will " + - "appear on devices linked with nearby-mainline-fpseeker@google.com" - private const val WAIT_HALF_SHEET_POPUP_TIMEOUT_KEY = "WAIT_HALF_SHEET_POPUP_TIMEOUT_MS" - private const val HALF_SHEET_TITLE_KEY = "HALF_SHEET_TITLE" - private const val HALF_SHEET_SUBTITLE_KEY = "HALF_SHEET_SUBTITLE" - private lateinit var device: UiDevice - - @AfterClass - @JvmStatic - fun teardownClass() { - // Cleans up after saving screenshot in TestWatcher, leaves nothing dirty behind. - DismissNearbyHalfSheetUiTest().dismissHalfSheet() - } - } -} \ No newline at end of file diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/DismissNearbyHalfSheetUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/DismissNearbyHalfSheetUiTest.kt deleted file mode 100644 index 52d202aa44c0c18753fc6729d65e2fbae3bed480..0000000000000000000000000000000000000000 --- a/nearby/tests/integration/ui/src/android/nearby/integration/ui/DismissNearbyHalfSheetUiTest.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.integration.ui - -import android.platform.test.rule.ScreenRecordRule.ScreenRecord -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import com.google.common.truth.Truth.assertWithMessage -import org.junit.Test -import org.junit.runner.RunWith - -/** An instrumented test to dismiss Nearby half sheet UI. - * - * To run this test directly: - * am instrument -w -r \ - * -e class android.nearby.integration.ui.DismissNearbyHalfSheetUiTest \ - * android.nearby.integration.ui/androidx.test.runner.AndroidJUnitRunner - */ -@RunWith(AndroidJUnit4::class) -class DismissNearbyHalfSheetUiTest : BaseUiTest() { - private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - - @Test - @ScreenRecord - fun dismissHalfSheet() { - device.pressHome() - device.waitForIdle() - - assertWithMessage("Fail to dismiss Nearby half sheet.").that( - device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.connectButton) - ).isNull() - } -} \ No newline at end of file diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt deleted file mode 100644 index 4098865ff9a827d140238e4537db6bca6ae73cb5..0000000000000000000000000000000000000000 --- a/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.integration.ui - -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager.MATCH_SYSTEM_ONLY -import android.content.pm.PackageManager.ResolveInfoFlags -import android.content.pm.ResolveInfo -import android.util.Log -import androidx.test.core.app.ApplicationProvider -import androidx.test.uiautomator.By -import androidx.test.uiautomator.BySelector -import com.android.server.nearby.util.Environment -import com.google.common.truth.Truth.assertThat - -/** UiMap for Nearby Mainline Half Sheet. */ -object NearbyHalfSheetUiMap { - private val PACKAGE_NAME: String = getHalfSheetApkPkgName() - private const val ACTION_RESOURCES_APK = "android.nearby.SHOW_HALFSHEET" - private const val ANDROID_WIDGET_BUTTON = "android.widget.Button" - private const val ANDROID_WIDGET_IMAGE_VIEW = "android.widget.ImageView" - private const val ANDROID_WIDGET_TEXT_VIEW = "android.widget.TextView" - - object DevicePairingFragment { - val halfSheetTitle: BySelector = - By.res(PACKAGE_NAME, "toolbar_title").clazz(ANDROID_WIDGET_TEXT_VIEW) - val halfSheetSubtitle: BySelector = - By.res(PACKAGE_NAME, "header_subtitle").clazz(ANDROID_WIDGET_TEXT_VIEW) - val deviceImage: BySelector = - By.res(PACKAGE_NAME, "pairing_pic").clazz(ANDROID_WIDGET_IMAGE_VIEW) - val connectButton: BySelector = - By.res(PACKAGE_NAME, "connect_btn").clazz(ANDROID_WIDGET_BUTTON).text("Connect") - val infoButton: BySelector = - By.res(PACKAGE_NAME, "info_icon").clazz(ANDROID_WIDGET_IMAGE_VIEW) - } - - // Vendors might override HalfSheetUX in their vendor partition, query the package name - // instead of hard coding. ex: Google overrides it in vendor/google/modules/TetheringGoogle. - fun getHalfSheetApkPkgName(): String { - val appContext = ApplicationProvider.getApplicationContext() - val resolveInfos: MutableList = - appContext.packageManager.queryIntentActivities(Intent(ACTION_RESOURCES_APK), - ResolveInfoFlags.of(MATCH_SYSTEM_ONLY.toLong()) - ) - - // remove apps that don't live in the nearby apex - resolveInfos.removeIf { !Environment.isAppInNearbyApex(it.activityInfo.applicationInfo) } - - assertThat(resolveInfos).hasSize(1) - - val halfSheetApkPkgName: String = resolveInfos[0].activityInfo.applicationInfo.packageName - Log.i("NearbyHalfSheetUiMap", "Found half-sheet APK at: $halfSheetApkPkgName") - return halfSheetApkPkgName - } -} diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/PairByNearbyHalfSheetUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/PairByNearbyHalfSheetUiTest.kt deleted file mode 100644 index 27264b519aaf8d09d866f1307dbb8d3dda06b30c..0000000000000000000000000000000000000000 --- a/nearby/tests/integration/ui/src/android/nearby/integration/ui/PairByNearbyHalfSheetUiTest.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.integration.ui - -import android.platform.test.rule.ScreenRecordRule.ScreenRecord -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.uiautomator.UiDevice -import androidx.test.uiautomator.Until -import org.junit.AfterClass -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -/** An instrumented test to start pairing by interacting with Nearby half sheet UI. - * - * To run this test directly: - * am instrument -w -r \ - * -e class android.nearby.integration.ui.PairByNearbyHalfSheetUiTest \ - * android.nearby.integration.ui/androidx.test.runner.AndroidJUnitRunner - */ -@RunWith(AndroidJUnit4::class) -class PairByNearbyHalfSheetUiTest : BaseUiTest() { - init { - device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) - } - - @Before - fun setUp() { - CheckNearbyHalfSheetUiTest().apply { - setUp() - checkNearbyHalfSheetUi() - } - } - - @Test - @ScreenRecord - fun clickConnectButton() { - val connectButton = NearbyHalfSheetUiMap.DevicePairingFragment.connectButton - device.findObject(connectButton).click() - device.wait(Until.gone(connectButton), CONNECT_BUTTON_TIMEOUT_MILLS) - } - - companion object { - private const val CONNECT_BUTTON_TIMEOUT_MILLS = 3000L - private lateinit var device: UiDevice - - @AfterClass - @JvmStatic - fun teardownClass() { - // Cleans up after saving screenshot in TestWatcher, leaves nothing dirty behind. - device.pressBack() - DismissNearbyHalfSheetUiTest().dismissHalfSheet() - } - } -} \ No newline at end of file diff --git a/nearby/tests/integration/untrusted/Android.bp b/nearby/tests/integration/untrusted/Android.bp index 57499e403939468daa25df53af17c794e88c628b..75f765b67fcef9462c6bc10b36e92be965ffab0b 100644 --- a/nearby/tests/integration/untrusted/Android.bp +++ b/nearby/tests/integration/untrusted/Android.bp @@ -31,7 +31,7 @@ android_test { "androidx.test.uiautomator_uiautomator", "junit", "kotlin-test", - "truth-prebuilt", + "truth", ], test_suites: ["device-tests"], } diff --git a/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/FastPairSettingsProviderTest.kt b/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/FastPairSettingsProviderTest.kt deleted file mode 100644 index c549073edba9f0c999b93dd52a601ba494273611..0000000000000000000000000000000000000000 --- a/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/FastPairSettingsProviderTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.integration.untrusted - -import android.content.Context -import android.content.ContentResolver -import android.provider.Settings -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import kotlin.test.assertFailsWith - - -@RunWith(AndroidJUnit4::class) -class FastPairSettingsProviderTest { - private lateinit var contentResolver: ContentResolver - - @Before - fun setUp() { - contentResolver = ApplicationProvider.getApplicationContext().contentResolver - } - - /** Verify untrusted app can read Fast Pair scan enabled setting. */ - @Test - fun testSettingsFastPairScan_fromUnTrustedApp_readsSucceed() { - Settings.Secure.getInt(contentResolver, - "fast_pair_scan_enabled", /* default value */ -1) - } - - /** Verify untrusted app can't write Fast Pair scan enabled setting. */ - @Test - fun testSettingsFastPairScan_fromUnTrustedApp_writesFailed() { - assertFailsWith { - Settings.Secure.putInt(contentResolver, "fast_pair_scan_enabled", 1) - } - } -} diff --git a/nearby/tests/multidevices/OWNERS b/nearby/tests/multidevices/OWNERS deleted file mode 100644 index f4dbde2621b1d9258a6f1da90b0a9ed6a0ab3e38..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -# Bug component: 1092133 - -ericth@google.com -ryancllin@google.com \ No newline at end of file diff --git a/nearby/tests/multidevices/README.md b/nearby/tests/multidevices/README.md deleted file mode 100644 index 9d086de74d70ca7dae584e97243d3543833dfea2..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/README.md +++ /dev/null @@ -1,155 +0,0 @@ -# Nearby Mainline Fast Pair end-to-end tests - -This document refers to the Mainline Fast Pair project source code in the -packages/modules/Connectivity/nearby. This is not an officially supported Google -product. - -## About the Fast Pair Project - -The Connectivity Nearby mainline module is created in the Android T to host -Better Together related functionality. Fast Pair is one of the main -functionalities to provide seamless onboarding and integrated experiences for -peripheral devices (for example, headsets like Google Pixel Buds) in the Nearby -component. - -## Fully automated test - -### Prerequisites - -The fully automated end-to-end (e2e) tests are host-driven tests (which means -test logics are in the host test scripts) using Mobly runner in Python. The two -phones are installed with the test snippet -`NearbyMultiDevicesClientsSnippets.apk` in the test time to let the host scripts -control both sides for testing. Here's the overview of the test environment. - -Workstation (runs Python test scripts and controls Android devices through USB -ADB) \ -├── Phone 1: As Fast Pair seeker role, to scan, pair Fast Pair devices nearby \ -└── Phone 2: As Fast Pair provider role, to simulate a Fast Pair device (for -example, a Bluetooth headset) - -Note: These two phones need to be physically within 0.3 m of each other. - -### Prepare Phone 1 (Fast Pair seeker role) - -This is the phone to scan/pair Fast Pair devices nearby using the Nearby -Mainline module. Test it by flashing with the Android T ROM. - -### Prepare Phone 2 (Fast Pair provider role) - -This is the phone to simulate a Fast Pair device (for example, a Bluetooth -headset). Flash it with a customized ROM with the following changes: - -* Adjust Bluetooth profile configurations. \ - The Fast Pair provider simulator is an opposite role to the seeker. It needs - to enable/disable the following Bluetooth profile: - * Disable A2DP source (bluetooth.profile.a2dp.source.enabled) - * Enable A2DP sink (bluetooth.profile.a2dp.sink.enabled) - * Disable the AVRCP controller (bluetooth.profile.avrcp.controller.enabled) - * Enable the AVRCP target (bluetooth.profile.avrcp.target.enabled) - * Enable the HFP service (bluetooth.profile.hfp.ag.enabled, bluetooth.profile.hfp.hf.enabled) - -```makefile -# The Bluetooth profiles that Fast Pair provider simulator expect to have enabled. -PRODUCT_PRODUCT_PROPERTIES += \ - bluetooth.device.default_name=FastPairProviderSimulator \ - bluetooth.profile.a2dp.source.enabled=false \ - bluetooth.profile.a2dp.sink.enabled=true \ - bluetooth.profile.avrcp.controller.enabled=false \ - bluetooth.profile.avrcp.target.enabled=true \ - bluetooth.profile.hfp.ag.enabled=true \ - bluetooth.profile.hfp.hf.enabled=true -``` - -* Adjust Bluetooth TX power limitation in Bluetooth module and disable the - Fast Pair in Google Play service (aka GMS) - -```shell -adb root -adb shell am broadcast \ - -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' \ - --es package "com.google.android.gms.nearby" \ - --es user "\*" \ - --esa flags "enabled" \ - --esa types "boolean" \ - --esa values "false" \ - com.google.android.gms -``` - -### Running tests - -To run the tests, enter: - -```shell -atest -v CtsNearbyMultiDevicesTestSuite -``` - -## Manual testing the seeker side with headsets - -Use this testing with headsets such as Google Pixel buds. - -The `FastPairTestDataProviderService.apk` is a run-time configurable Fast Pair -data provider service (`FastPairDataProviderService`): - -`packages/modules/Connectivity/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider` - -It has a test data manager(`FastPairTestDataManager`) to receive intent -broadcasts to add or clear the test data cache (`FastPairTestDataCache`). This -cache provides the data to return to the Fast Pair module for onXXX calls (for -example, `onLoadFastPairAntispoofKeyDeviceMetadata`) so you can feed the -metadata for your device. - -Here are some sample uses: - -* Send FastPairAntispoofKeyDeviceMetadata for PixelBuds-A to - FastPairTestDataCache \ - `./fast_pair_data_provider_shell.sh -m=718c17 - -a=../test_data/fastpair/pixelbuds-a_antispoofkey_devicemeta_json.txt` -* Send FastPairAccountDevicesMetadata for PixelBuds-A to FastPairTestDataCache - \ - `./fast_pair_data_provider_shell.sh - -d=../test_data/fastpair/pixelbuds-a_account_devicemeta_json.txt` -* Send FastPairAntispoofKeyDeviceMetadata for Provider Simulator to - FastPairTestDataCache \ - `./fast_pair_data_provider_shell.sh -m=00000c - -a=../test_data/fastpair/simulator_antispoofkey_devicemeta_json.txt` -* Send FastPairAccountDevicesMetadata for Provider Simulator to - FastPairTestDataCache \ - `./fast_pair_data_provider_shell.sh - -d=../test_data/fastpair/simulator_account_devicemeta_json.txt` -* Clear FastPairTestDataCache \ - `./fast_pair_data_provider_shell.sh -c` - -See -[host/tool/fast_pair_data_provider_shell.sh](host/tool/fast_pair_data_provider_shell.sh) -for more documentation. - -To install the data provider as system private app, consider remounting the -system partition: - -``` -adb root && adb remount -``` - -Push it in: - -``` -adb push ${ANDROID_PRODUCT_OUT}/system/app/NearbyFastPairSeekerDataProvider -/system/priv-app/ -``` - -Then reboot: - -``` -adb reboot -``` - -## Manual testing the seeker side with provider simulator app - -The `NearbyFastPairProviderSimulatorApp.apk` is a simple Android app to let you -control the state of the Fast Pair provider simulator. Install this app on phone -2 (Fast Pair provider role) to work correctly. - -See -[clients/test_support/fastpair_provider/simulator_app/Android.bp](clients/test_support/fastpair_provider/simulator_app/Android.bp) -for more documentation. diff --git a/nearby/tests/multidevices/clients/Android.bp b/nearby/tests/multidevices/clients/Android.bp deleted file mode 100644 index db6d191114dcf5148a5c694f6d15b01883bdce2a..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/Android.bp +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (C) 2022 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: ["Android-Apache-2.0"], -} - -android_library { - name: "NearbyMultiDevicesClientsLib", - srcs: ["src/**/*.kt"], - sdk_version: "test_current", - static_libs: [ - "MoblySnippetHelperLib", - "NearbyFastPairProviderLib", - "NearbyFastPairSeekerSharedLib", - "NearbyIntegrationUiTestsLib", - "androidx.test.core", - "androidx.test.ext.junit", - "kotlin-stdlib", - "mobly-snippet-lib", - "truth-prebuilt", - ], -} - -android_app { - name: "NearbyMultiDevicesClientsSnippets", - sdk_version: "test_current", - certificate: "platform", - static_libs: ["NearbyMultiDevicesClientsLib"], - optimize: { - enabled: true, - shrink: false, - // Required to avoid class collisions from static and shared linking - // of MessageNano. - proguard_compatibility: true, - proguard_flags_files: ["proguard.flags"], - }, -} diff --git a/nearby/tests/multidevices/clients/AndroidManifest.xml b/nearby/tests/multidevices/clients/AndroidManifest.xml deleted file mode 100644 index 86c10b2fb5d27113ac0e22a015ce9ea942581a55..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/AndroidManifest.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/nearby/tests/multidevices/clients/proguard.flags b/nearby/tests/multidevices/clients/proguard.flags deleted file mode 100644 index 11938cdf86e8f764d40e389a6e2d468ecc14c133..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/proguard.flags +++ /dev/null @@ -1,29 +0,0 @@ -# Keep all snippet classes. --keep class android.nearby.multidevices.** { - *; -} - -# Keep AdvertisingSetCallback#onOwnAddressRead callback. --keep class * extends android.bluetooth.le.AdvertisingSetCallback { - *; -} - -# Do not touch Mobly. --keep class com.google.android.mobly.** { - *; -} - -# Keep names for easy debugging. --dontobfuscate - -# Necessary to allow debugging. --keepattributes * - -# By default, proguard leaves all classes in their original package, which -# needlessly repeats com.google.android.apps.etc. --repackageclasses "" - -# Allows proguard to make private and protected methods and fields public as -# part of optimization. This lets proguard inline trivial getter/setter -# methods. --allowaccessmodification \ No newline at end of file diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorSnippet.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorSnippet.kt deleted file mode 100644 index 922e950cc52ecb6f1443201cc2b88f7c9a4d3956..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorSnippet.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.multidevices.fastpair.provider - -import android.annotation.TargetApi -import android.content.Context -import android.nearby.multidevices.fastpair.provider.controller.FastPairProviderSimulatorController -import android.nearby.multidevices.fastpair.provider.events.ProviderStatusEvents -import android.os.Build -import androidx.test.platform.app.InstrumentationRegistry -import com.google.android.mobly.snippet.Snippet -import com.google.android.mobly.snippet.rpc.AsyncRpc -import com.google.android.mobly.snippet.rpc.Rpc - -/** Expose Mobly RPC methods for Python side to simulate fast pair provider role. */ -@TargetApi(Build.VERSION_CODES.LOLLIPOP) -class FastPairProviderSimulatorSnippet : Snippet { - private val context: Context = InstrumentationRegistry.getInstrumentation().context - private val fastPairProviderSimulatorController = FastPairProviderSimulatorController(context) - - /** Sets up the Fast Pair provider simulator. */ - @AsyncRpc(description = "Sets up FP provider simulator.") - fun setupProviderSimulator(callbackId: String) { - fastPairProviderSimulatorController.setupProviderSimulator(ProviderStatusEvents(callbackId)) - } - - /** - * Starts model id advertising for scanning and initial pairing. - * - * @param callbackId the callback ID corresponding to the - * [FastPairProviderSimulatorSnippet#startProviderSimulator] call that started the scanning. - * @param modelId a 3-byte hex string for seeker side to recognize the device (ex: 0x00000C). - * @param antiSpoofingKeyString a public key for registered headsets. - */ - @AsyncRpc(description = "Starts model id advertising for scanning and initial pairing.") - fun startModelIdAdvertising( - callbackId: String, - modelId: String, - antiSpoofingKeyString: String - ) { - fastPairProviderSimulatorController.startModelIdAdvertising( - modelId, - antiSpoofingKeyString, - ProviderStatusEvents(callbackId) - ) - } - - /** Tears down the Fast Pair provider simulator. */ - @Rpc(description = "Tears down FP provider simulator.") - fun teardownProviderSimulator() { - fastPairProviderSimulatorController.teardownProviderSimulator() - } - - /** Gets BLE mac address of the Fast Pair provider simulator. */ - @Rpc(description = "Gets BLE mac address of the Fast Pair provider simulator.") - fun getBluetoothLeAddress(): String { - return fastPairProviderSimulatorController.getProviderSimulatorBleAddress() - } - - /** Gets the latest account key received on the Fast Pair provider simulator */ - @Rpc(description = "Gets the latest account key received on the Fast Pair provider simulator.") - fun getLatestReceivedAccountKey(): String? { - return fastPairProviderSimulatorController.getLatestReceivedAccountKey() - } -} diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/controller/FastPairProviderSimulatorController.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/controller/FastPairProviderSimulatorController.kt deleted file mode 100644 index a2d2659abc9819494930911492b79b9f193b65bb..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/controller/FastPairProviderSimulatorController.kt +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.multidevices.fastpair.provider.controller - -import android.bluetooth.le.AdvertiseSettings -import android.content.Context -import android.nearby.fastpair.provider.FastPairSimulator -import android.nearby.fastpair.provider.bluetooth.BluetoothController -import com.google.android.mobly.snippet.util.Log -import com.google.common.io.BaseEncoding.base64 - -class FastPairProviderSimulatorController(private val context: Context) : - FastPairSimulator.AdvertisingChangedCallback, BluetoothController.EventListener { - private lateinit var bluetoothController: BluetoothController - private lateinit var eventListener: EventListener - private var simulator: FastPairSimulator? = null - - fun setupProviderSimulator(listener: EventListener) { - eventListener = listener - - bluetoothController = BluetoothController(context, this) - bluetoothController.registerBluetoothStateReceiver() - bluetoothController.enableBluetooth() - bluetoothController.connectA2DPSinkProfile() - } - - fun teardownProviderSimulator() { - simulator?.destroy() - bluetoothController.unregisterBluetoothStateReceiver() - } - - fun startModelIdAdvertising( - modelId: String, - antiSpoofingKeyString: String, - listener: EventListener - ) { - eventListener = listener - - val antiSpoofingKey = base64().decode(antiSpoofingKeyString) - simulator = FastPairSimulator( - context, FastPairSimulator.Options.builder(modelId) - .setAdvertisingModelId(modelId) - .setBluetoothAddress(null) - .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) - .setAdvertisingChangedCallback(this) - .setAntiSpoofingPrivateKey(antiSpoofingKey) - .setUseRandomSaltForAccountKeyRotation(false) - .setDataOnlyConnection(false) - .setShowsPasskeyConfirmation(false) - .setRemoveAllDevicesDuringPairing(true) - .build() - ) - } - - fun getProviderSimulatorBleAddress() = simulator!!.bleAddress!! - - fun getLatestReceivedAccountKey() = - simulator!!.accountKey?.let { base64().encode(it.toByteArray()) } - - /** - * Called when we change our BLE advertisement. - * - * @param isAdvertising the advertising status. - */ - override fun onAdvertisingChanged(isAdvertising: Boolean) { - Log.i("FastPairSimulator onAdvertisingChanged(isAdvertising: $isAdvertising)") - eventListener.onAdvertisingChange(isAdvertising) - } - - /** The callback for the first onServiceConnected of A2DP sink profile. */ - override fun onA2DPSinkProfileConnected() { - eventListener.onA2DPSinkProfileConnected() - } - - /** - * Reports the current bond state of the remote device. - * - * @param bondState the bond state of the remote device. - */ - override fun onBondStateChanged(bondState: Int) { - } - - /** - * Reports the current connection state of the remote device. - * - * @param connectionState the bond state of the remote device. - */ - override fun onConnectionStateChanged(connectionState: Int) { - } - - /** - * Reports the current scan mode of the local Adapter. - * - * @param mode the current scan mode of the local Adapter. - */ - override fun onScanModeChange(mode: Int) { - eventListener.onScanModeChange(FastPairSimulator.scanModeToString(mode)) - } - - /** Interface for listening the events from Fast Pair Provider Simulator. */ - interface EventListener { - /** Reports the first onServiceConnected of A2DP sink profile. */ - fun onA2DPSinkProfileConnected() - - /** - * Reports the current scan mode of the local Adapter. - * - * @param mode the current scan mode in string. - */ - fun onScanModeChange(mode: String) - - /** - * Indicates the advertising state of the Fast Pair provider simulator has changed. - * - * @param isAdvertising the current advertising state, true if advertising otherwise false. - */ - fun onAdvertisingChange(isAdvertising: Boolean) - } -} \ No newline at end of file diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/events/ProviderStatusEvents.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/events/ProviderStatusEvents.kt deleted file mode 100644 index 2addd7765fa2fec6321dc0175c0c41560c78296b..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/events/ProviderStatusEvents.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.multidevices.fastpair.provider.events - -import android.nearby.multidevices.fastpair.provider.controller.FastPairProviderSimulatorController -import com.google.android.mobly.snippet.util.postSnippetEvent - -/** The Mobly snippet events to report to the Python side. */ -class ProviderStatusEvents(private val callbackId: String) : - FastPairProviderSimulatorController.EventListener { - - /** Reports the first onServiceConnected of A2DP sink profile. */ - override fun onA2DPSinkProfileConnected() { - postSnippetEvent(callbackId, "onA2DPSinkProfileConnected") {} - } - - /** - * Indicates the Bluetooth scan mode of the Fast Pair provider simulator has changed. - * - * @param mode the current scan mode in String mapping by [FastPairSimulator#scanModeToString]. - */ - override fun onScanModeChange(mode: String) { - postSnippetEvent(callbackId, "onScanModeChange") { putString("mode", mode) } - } - - /** - * Indicates the advertising state of the Fast Pair provider simulator has changed. - * - * @param isAdvertising the current advertising state, true if advertising otherwise false. - */ - override fun onAdvertisingChange(isAdvertising: Boolean) { - postSnippetEvent(callbackId, "onAdvertisingChange") { - putBoolean("isAdvertising", isAdvertising) - } - } -} \ No newline at end of file diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt deleted file mode 100644 index a2c39f727e8bf7a08f91af468435ec2e3980981a..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.multidevices.fastpair.seeker - -import android.content.Context -import android.nearby.FastPairDeviceMetadata -import android.nearby.NearbyManager -import android.nearby.ScanCallback -import android.nearby.ScanRequest -import android.nearby.fastpair.seeker.FAKE_TEST_ACCOUNT_NAME -import android.nearby.integration.ui.CheckNearbyHalfSheetUiTest -import android.nearby.integration.ui.DismissNearbyHalfSheetUiTest -import android.nearby.integration.ui.PairByNearbyHalfSheetUiTest -import android.nearby.multidevices.fastpair.seeker.data.FastPairTestDataManager -import android.nearby.multidevices.fastpair.seeker.events.PairingCallbackEvents -import android.nearby.multidevices.fastpair.seeker.events.ScanCallbackEvents -import android.provider.Settings -import androidx.test.core.app.ApplicationProvider -import com.google.android.mobly.snippet.Snippet -import com.google.android.mobly.snippet.rpc.AsyncRpc -import com.google.android.mobly.snippet.rpc.Rpc -import com.google.android.mobly.snippet.util.Log - -/** Expose Mobly RPC methods for Python side to test fast pair seeker role. */ -class FastPairSeekerSnippet : Snippet { - private val appContext = ApplicationProvider.getApplicationContext() - private val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager - private val fastPairTestDataManager = FastPairTestDataManager(appContext) - private lateinit var scanCallback: ScanCallback - - /** - * Starts scanning as a Fast Pair seeker to find provider devices. - * - * @param callbackId the callback ID corresponding to the {@link FastPairSeekerSnippet#startScan} - * call that started the scanning. - */ - @AsyncRpc(description = "Starts scanning as Fast Pair seeker to find provider devices.") - fun startScan(callbackId: String) { - val scanRequest = ScanRequest.Builder() - .setScanMode(ScanRequest.SCAN_MODE_LOW_LATENCY) - .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR) - .setBleEnabled(true) - .build() - scanCallback = ScanCallbackEvents(callbackId) - - Log.i("Start Fast Pair scanning via BLE...") - nearbyManager.startScan(scanRequest, /* executor */ { it.run() }, scanCallback) - } - - /** Stops the Fast Pair seeker scanning. */ - @Rpc(description = "Stops the Fast Pair seeker scanning.") - fun stopScan() { - Log.i("Stop Fast Pair scanning.") - nearbyManager.stopScan(scanCallback) - } - - /** Waits and asserts the HalfSheet showed for Fast Pair pairing. - * - * @param modelId the expected model id to be associated with the HalfSheet. - * @param timeout the number of seconds to wait before giving up. - */ - @Rpc(description = "Waits the HalfSheet showed for Fast Pair pairing.") - fun waitAndAssertHalfSheetShowed(modelId: String, timeout: Int) { - Log.i("Waits and asserts the HalfSheet showed for Fast Pair model $modelId.") - - val deviceMetadata: FastPairDeviceMetadata = - fastPairTestDataManager.testDataCache.getFastPairDeviceMetadata(modelId) - ?: throw IllegalArgumentException( - "Can't find $modelId-FastPairAntispoofKeyDeviceMetadata pair in " + - "FastPairTestDataCache." - ) - val deviceName = deviceMetadata.name!! - val initialPairingDescriptionTemplateText = deviceMetadata.initialPairingDescription!! - - CheckNearbyHalfSheetUiTest().apply { - updateTestArguments( - waitHalfSheetPopupTimeoutSeconds = timeout, - halfSheetTitleText = deviceName, - halfSheetSubtitleText = initialPairingDescriptionTemplateText.format( - deviceName, - FAKE_TEST_ACCOUNT_NAME - ) - ) - checkNearbyHalfSheetUi() - } - } - - /** Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into test data cache. - * - * @param modelId a string of model id to be associated with. - * @param json a string of FastPairAntispoofKeyDeviceMetadata JSON object. - */ - @Rpc( - description = - "Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into test data cache." - ) - fun putAntispoofKeyDeviceMetadata(modelId: String, json: String) { - Log.i("Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into test data cache.") - fastPairTestDataManager.sendAntispoofKeyDeviceMetadata(modelId, json) - } - - /** Puts an array of FastPairAccountKeyDeviceMetadata into test data cache. - * - * @param json a string of FastPairAccountKeyDeviceMetadata JSON array. - */ - @Rpc(description = "Puts an array of FastPairAccountKeyDeviceMetadata into test data cache.") - fun putAccountKeyDeviceMetadata(json: String) { - Log.i("Puts an array of FastPairAccountKeyDeviceMetadata into test data cache.") - fastPairTestDataManager.sendAccountKeyDeviceMetadataJsonArray(json) - } - - /** Dumps all FastPairAccountKeyDeviceMetadata from the test data cache. */ - @Rpc(description = "Dumps all FastPairAccountKeyDeviceMetadata from the test data cache.") - fun dumpAccountKeyDeviceMetadata(): String { - Log.i("Dumps all FastPairAccountKeyDeviceMetadata from the test data cache.") - return fastPairTestDataManager.testDataCache.dumpAccountKeyDeviceMetadataListAsJson() - } - - /** Writes into {@link Settings} whether Fast Pair scan is enabled. - * - * @param enable whether the Fast Pair scan should be enabled. - */ - @Rpc(description = "Writes into Settings whether Fast Pair scan is enabled.") - fun setFastPairScanEnabled(enable: Boolean) { - Log.i("Writes into Settings whether Fast Pair scan is enabled.") - // TODO(b/228406038): Change back to use NearbyManager.setFastPairScanEnabled once un-hide. - val resolver = appContext.contentResolver - Settings.Secure.putInt(resolver, "fast_pair_scan_enabled", if (enable) 1 else 0) - } - - /** Dismisses the half sheet UI if showed. */ - @Rpc(description = "Dismisses the half sheet UI if showed.") - fun dismissHalfSheet() { - Log.i("Dismisses the half sheet UI if showed.") - - DismissNearbyHalfSheetUiTest().dismissHalfSheet() - } - - /** Starts pairing by interacting with half sheet UI. - * - * @param callbackId the callback ID corresponding to the - * {@link FastPairSeekerSnippet#startPairing} call that started the pairing. - */ - @AsyncRpc(description = "Starts pairing by interacting with half sheet UI.") - fun startPairing(callbackId: String) { - Log.i("Starts pairing by interacting with half sheet UI.") - - PairByNearbyHalfSheetUiTest().clickConnectButton() - fastPairTestDataManager.registerDataReceiveListener(PairingCallbackEvents(callbackId)) - } - - /** Invokes when the snippet runner shutting down. */ - override fun shutdown() { - super.shutdown() - - Log.i("Resets the Fast Pair test data cache.") - fastPairTestDataManager.unregisterDataReceiveListener() - fastPairTestDataManager.sendResetCache() - } -} \ No newline at end of file diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/data/FastPairTestDataManager.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/data/FastPairTestDataManager.kt deleted file mode 100644 index 239ac613369cfad99659ec8d89bc17299c93730d..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/data/FastPairTestDataManager.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.multidevices.fastpair.seeker.data - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.nearby.fastpair.seeker.ACTION_RESET_TEST_DATA_CACHE -import android.nearby.fastpair.seeker.ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA -import android.nearby.fastpair.seeker.ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA -import android.nearby.fastpair.seeker.ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA -import android.nearby.fastpair.seeker.DATA_JSON_STRING_KEY -import android.nearby.fastpair.seeker.DATA_MODEL_ID_STRING_KEY -import android.nearby.fastpair.seeker.FastPairTestDataCache -import android.util.Log - -/** Manage local FastPairTestDataCache and send to/sync from the remote cache in data provider. */ -class FastPairTestDataManager(private val context: Context) : BroadcastReceiver() { - val testDataCache = FastPairTestDataCache() - var listener: EventListener? = null - - /** Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into local and remote cache. - * - * @param modelId a string of model id to be associated with. - * @param json a string of FastPairAntispoofKeyDeviceMetadata JSON object. - */ - fun sendAntispoofKeyDeviceMetadata(modelId: String, json: String) { - Intent().also { intent -> - intent.action = ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA - intent.putExtra(DATA_MODEL_ID_STRING_KEY, modelId) - intent.putExtra(DATA_JSON_STRING_KEY, json) - context.sendBroadcast(intent) - } - testDataCache.putAntispoofKeyDeviceMetadata(modelId, json) - } - - /** Puts account key device metadata array to local and remote cache. - * - * @param json a string of FastPairAccountKeyDeviceMetadata JSON array. - */ - fun sendAccountKeyDeviceMetadataJsonArray(json: String) { - Intent().also { intent -> - intent.action = ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA - intent.putExtra(DATA_JSON_STRING_KEY, json) - context.sendBroadcast(intent) - } - testDataCache.putAccountKeyDeviceMetadataJsonArray(json) - } - - /** Clears local and remote cache. */ - fun sendResetCache() { - context.sendBroadcast(Intent(ACTION_RESET_TEST_DATA_CACHE)) - testDataCache.reset() - } - - /** - * Callback method for receiving Intent broadcast from FastPairTestDataProvider. - * - * See [BroadcastReceiver#onReceive]. - * - * @param context the Context in which the receiver is running. - * @param intent the Intent being received. - */ - override fun onReceive(context: Context, intent: Intent) { - when (intent.action) { - ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA -> { - Log.d(TAG, "ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA received!") - val json = intent.getStringExtra(DATA_JSON_STRING_KEY)!! - testDataCache.putAccountKeyDeviceMetadataJsonObject(json) - listener?.onManageFastPairAccountDevice(json) - } - else -> Log.d(TAG, "Unknown action received!") - } - } - - fun registerDataReceiveListener(listener: EventListener) { - this.listener = listener - val bondStateFilter = IntentFilter(ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA) - context.registerReceiver(this, bondStateFilter) - } - - fun unregisterDataReceiveListener() { - this.listener = null - context.unregisterReceiver(this) - } - - /** Interface for listening the data receive from the remote cache in data provider. */ - interface EventListener { - /** Reports a FastPairAccountKeyDeviceMetadata write into the cache. - * - * @param json the FastPairAccountKeyDeviceMetadata as JSON object string. - */ - fun onManageFastPairAccountDevice(json: String) - } - - companion object { - private const val TAG = "FastPairTestDataManager" - } -} \ No newline at end of file diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/PairingCallbackEvents.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/PairingCallbackEvents.kt deleted file mode 100644 index 19de1d995275a910244d2376ca41390b2b276ebf..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/PairingCallbackEvents.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.multidevices.fastpair.seeker.events - -import android.nearby.multidevices.fastpair.seeker.data.FastPairTestDataManager -import com.google.android.mobly.snippet.util.postSnippetEvent - -/** The Mobly snippet events to report to the Python side. */ -class PairingCallbackEvents(private val callbackId: String) : - FastPairTestDataManager.EventListener { - - /** Reports a FastPairAccountKeyDeviceMetadata write into the cache. - * - * @param json the FastPairAccountKeyDeviceMetadata as JSON object string. - */ - override fun onManageFastPairAccountDevice(json: String) { - postSnippetEvent(callbackId, "onManageAccountDevice") { - putString("accountDeviceJsonString", json) - } - } -} \ No newline at end of file diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/ScanCallbackEvents.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/ScanCallbackEvents.kt deleted file mode 100644 index 02847b55d46053e2e27764c21733acdb277c786e..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/events/ScanCallbackEvents.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.multidevices.fastpair.seeker.events - -import android.nearby.NearbyDevice -import android.nearby.ScanCallback -import com.google.android.mobly.snippet.util.postSnippetEvent - -/** The Mobly snippet events to report to the Python side. */ -class ScanCallbackEvents(private val callbackId: String) : ScanCallback { - - override fun onDiscovered(device: NearbyDevice) { - postSnippetEvent(callbackId, "onDiscovered") { - putString("device", device.toString()) - } - } - - override fun onUpdated(device: NearbyDevice) { - postSnippetEvent(callbackId, "onUpdated") { - putString("device", device.toString()) - } - } - - override fun onLost(device: NearbyDevice) { - postSnippetEvent(callbackId, "onLost") { - putString("device", device.toString()) - } - } - - override fun onError(errorCode: Int) { - postSnippetEvent(callbackId, "onError") { - putString("error", errorCode.toString()) - } - } -} diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/Android.bp b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/Android.bp deleted file mode 100644 index b40677602dd0f3a5232342f2896717dfb304e91b..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/Android.bp +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2022 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: ["Android-Apache-2.0"], -} - -android_library { - name: "NearbyFastPairSeekerSharedLib", - srcs: ["shared/**/*.kt"], - sdk_version: "test_current", - static_libs: [ - // TODO(b/228406038): Remove "framework-nearby-static" once Fast Pair system APIs add back. - "framework-nearby-static", - "gson", - "guava", - ], -} - -android_library { - name: "NearbyFastPairSeekerDataProviderLib", - srcs: ["src/**/*.kt"], - sdk_version: "test_current", - static_libs: ["NearbyFastPairSeekerSharedLib"], -} - -android_app { - name: "NearbyFastPairSeekerDataProvider", - sdk_version: "test_current", - certificate: "platform", - static_libs: ["NearbyFastPairSeekerDataProviderLib"], - optimize: { - enabled: true, - shrink: true, - proguard_flags_files: ["proguard.flags"], - }, -} diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/AndroidManifest.xml b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/AndroidManifest.xml deleted file mode 100644 index 1d62f04c94b22d1f2b2e539332ba67c2f81f5efd..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/AndroidManifest.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/proguard.flags b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/proguard.flags deleted file mode 100644 index 15debab5e9c3bb95b26c6689cc76d9c618f63a4b..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/proguard.flags +++ /dev/null @@ -1,19 +0,0 @@ -# Keep all receivers/service classes. --keep class android.nearby.fastpair.seeker.** { - *; -} - -# Keep names for easy debugging. --dontobfuscate - -# Necessary to allow debugging. --keepattributes * - -# By default, proguard leaves all classes in their original package, which -# needlessly repeats com.google.android.apps.etc. --repackageclasses "" - -# Allows proguard to make private and protected methods and fields public as -# part of optimization. This lets proguard inline trivial getter/setter -# methods. --allowaccessmodification \ No newline at end of file diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/Constants.kt b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/Constants.kt deleted file mode 100644 index 60701403d8c568e333e1ca536a19b2cecc744de5..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/Constants.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.fastpair.seeker - -const val FAKE_TEST_ACCOUNT_NAME = "nearby-mainline-fpseeker@google.com" - -const val ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA = - "android.nearby.fastpair.seeker.action.ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA" -const val ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA = - "android.nearby.fastpair.seeker.action.ACCOUNT_KEY_DEVICE_METADATA" -const val ACTION_RESET_TEST_DATA_CACHE = "android.nearby.fastpair.seeker.action.RESET" -const val ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA = - "android.nearby.fastpair.seeker.action.WRITE_ACCOUNT_KEY_DEVICE_METADATA" - -const val DATA_JSON_STRING_KEY = "json" -const val DATA_MODEL_ID_STRING_KEY = "modelId" diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt deleted file mode 100644 index 4fb883242da8e1685c8488810f183dbcf6e37ed2..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/shared/android/nearby/fastpair/seeker/FastPairTestDataCache.kt +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.fastpair.seeker - -import android.nearby.FastPairAccountKeyDeviceMetadata -import android.nearby.FastPairAntispoofKeyDeviceMetadata -import android.nearby.FastPairDeviceMetadata -import android.nearby.FastPairDiscoveryItem -import com.google.common.io.BaseEncoding -import com.google.gson.GsonBuilder -import com.google.gson.annotations.SerializedName - -/** Manage a cache of Fast Pair test data for testing. */ -class FastPairTestDataCache { - private val gson = GsonBuilder().disableHtmlEscaping().create() - private val accountKeyDeviceMetadataList = mutableListOf() - private val antispoofKeyDeviceMetadataDataMap = - mutableMapOf() - - fun putAccountKeyDeviceMetadataJsonArray(json: String) { - accountKeyDeviceMetadataList += - gson.fromJson(json, Array::class.java) - .map { it.toFastPairAccountKeyDeviceMetadata() } - } - - fun putAccountKeyDeviceMetadataJsonObject(json: String) { - accountKeyDeviceMetadataList += - gson.fromJson(json, FastPairAccountKeyDeviceMetadataData::class.java) - .toFastPairAccountKeyDeviceMetadata() - } - - fun putAccountKeyDeviceMetadata(accountKeyDeviceMetadata: FastPairAccountKeyDeviceMetadata) { - accountKeyDeviceMetadataList += accountKeyDeviceMetadata - } - - fun getAccountKeyDeviceMetadataList(): List = - accountKeyDeviceMetadataList.toList() - - fun dumpAccountKeyDeviceMetadataAsJson(metadata: FastPairAccountKeyDeviceMetadata): String = - gson.toJson(FastPairAccountKeyDeviceMetadataData(metadata)) - - fun dumpAccountKeyDeviceMetadataListAsJson(): String = - gson.toJson(accountKeyDeviceMetadataList.map { FastPairAccountKeyDeviceMetadataData(it) }) - - fun putAntispoofKeyDeviceMetadata(modelId: String, json: String) { - antispoofKeyDeviceMetadataDataMap[modelId] = - gson.fromJson(json, FastPairAntispoofKeyDeviceMetadataData::class.java) - } - - fun getAntispoofKeyDeviceMetadata(modelId: String): FastPairAntispoofKeyDeviceMetadata? { - return antispoofKeyDeviceMetadataDataMap[modelId]?.toFastPairAntispoofKeyDeviceMetadata() - } - - fun getFastPairDeviceMetadata(modelId: String): FastPairDeviceMetadata? = - antispoofKeyDeviceMetadataDataMap[modelId]?.deviceMeta?.toFastPairDeviceMetadata() - - fun reset() { - accountKeyDeviceMetadataList.clear() - antispoofKeyDeviceMetadataDataMap.clear() - } - - data class FastPairAccountKeyDeviceMetadataData( - @SerializedName("account_key") val accountKey: String?, - @SerializedName("sha256_account_key_public_address") val accountKeyPublicAddress: String?, - @SerializedName("fast_pair_device_metadata") val deviceMeta: FastPairDeviceMetadataData?, - @SerializedName("fast_pair_discovery_item") val discoveryItem: FastPairDiscoveryItemData? - ) { - constructor(meta: FastPairAccountKeyDeviceMetadata) : this( - accountKey = meta.deviceAccountKey?.base64Encode(), - accountKeyPublicAddress = meta.sha256DeviceAccountKeyPublicAddress?.base64Encode(), - deviceMeta = meta.fastPairDeviceMetadata?.let { FastPairDeviceMetadataData(it) }, - discoveryItem = meta.fastPairDiscoveryItem?.let { FastPairDiscoveryItemData(it) } - ) - - fun toFastPairAccountKeyDeviceMetadata(): FastPairAccountKeyDeviceMetadata { - return FastPairAccountKeyDeviceMetadata.Builder() - .setDeviceAccountKey(accountKey?.base64Decode()) - .setSha256DeviceAccountKeyPublicAddress(accountKeyPublicAddress?.base64Decode()) - .setFastPairDeviceMetadata(deviceMeta?.toFastPairDeviceMetadata()) - .setFastPairDiscoveryItem(discoveryItem?.toFastPairDiscoveryItem()) - .build() - } - } - - data class FastPairAntispoofKeyDeviceMetadataData( - @SerializedName("anti_spoofing_public_key_str") val antispoofPublicKey: String?, - @SerializedName("fast_pair_device_metadata") val deviceMeta: FastPairDeviceMetadataData? - ) { - fun toFastPairAntispoofKeyDeviceMetadata(): FastPairAntispoofKeyDeviceMetadata { - return FastPairAntispoofKeyDeviceMetadata.Builder() - .setAntispoofPublicKey(antispoofPublicKey?.base64Decode()) - .setFastPairDeviceMetadata(deviceMeta?.toFastPairDeviceMetadata()) - .build() - } - } - - data class FastPairDeviceMetadataData( - @SerializedName("ble_tx_power") val bleTxPower: Int, - @SerializedName("connect_success_companion_app_installed") val compAppInstalled: String?, - @SerializedName("connect_success_companion_app_not_installed") val comAppNotIns: String?, - @SerializedName("device_type") val deviceType: Int, - @SerializedName("download_companion_app_description") val downloadComApp: String?, - @SerializedName("fail_connect_go_to_settings_description") val failConnectDes: String?, - @SerializedName("image_url") val imageUrl: String?, - @SerializedName("initial_notification_description") val initNotification: String?, - @SerializedName("initial_notification_description_no_account") val initNoAccount: String?, - @SerializedName("initial_pairing_description") val initialPairingDescription: String?, - @SerializedName("intent_uri") val intentUri: String?, - @SerializedName("name") val name: String?, - @SerializedName("open_companion_app_description") val openCompanionAppDescription: String?, - @SerializedName("retroactive_pairing_description") val retroactivePairingDes: String?, - @SerializedName("subsequent_pairing_description") val subsequentPairingDescription: String?, - @SerializedName("trigger_distance") val triggerDistance: Double, - @SerializedName("case_url") val trueWirelessImageUrlCase: String?, - @SerializedName("left_bud_url") val trueWirelessImageUrlLeftBud: String?, - @SerializedName("right_bud_url") val trueWirelessImageUrlRightBud: String?, - @SerializedName("unable_to_connect_description") val unableToConnectDescription: String?, - @SerializedName("unable_to_connect_title") val unableToConnectTitle: String?, - @SerializedName("update_companion_app_description") val updateCompAppDes: String?, - @SerializedName("wait_launch_companion_app_description") val waitLaunchCompApp: String? - ) { - constructor(meta: FastPairDeviceMetadata) : this( - bleTxPower = meta.bleTxPower, - compAppInstalled = meta.connectSuccessCompanionAppInstalled, - comAppNotIns = meta.connectSuccessCompanionAppNotInstalled, - deviceType = meta.deviceType, - downloadComApp = meta.downloadCompanionAppDescription, - failConnectDes = meta.failConnectGoToSettingsDescription, - imageUrl = meta.imageUrl, - initNotification = meta.initialNotificationDescription, - initNoAccount = meta.initialNotificationDescriptionNoAccount, - initialPairingDescription = meta.initialPairingDescription, - intentUri = meta.intentUri, - name = meta.name, - openCompanionAppDescription = meta.openCompanionAppDescription, - retroactivePairingDes = meta.retroactivePairingDescription, - subsequentPairingDescription = meta.subsequentPairingDescription, - triggerDistance = meta.triggerDistance.toDouble(), - trueWirelessImageUrlCase = meta.trueWirelessImageUrlCase, - trueWirelessImageUrlLeftBud = meta.trueWirelessImageUrlLeftBud, - trueWirelessImageUrlRightBud = meta.trueWirelessImageUrlRightBud, - unableToConnectDescription = meta.unableToConnectDescription, - unableToConnectTitle = meta.unableToConnectTitle, - updateCompAppDes = meta.updateCompanionAppDescription, - waitLaunchCompApp = meta.waitLaunchCompanionAppDescription - ) - - fun toFastPairDeviceMetadata(): FastPairDeviceMetadata { - return FastPairDeviceMetadata.Builder() - .setBleTxPower(bleTxPower) - .setConnectSuccessCompanionAppInstalled(compAppInstalled) - .setConnectSuccessCompanionAppNotInstalled(comAppNotIns) - .setDeviceType(deviceType) - .setDownloadCompanionAppDescription(downloadComApp) - .setFailConnectGoToSettingsDescription(failConnectDes) - .setImageUrl(imageUrl) - .setInitialNotificationDescription(initNotification) - .setInitialNotificationDescriptionNoAccount(initNoAccount) - .setInitialPairingDescription(initialPairingDescription) - .setIntentUri(intentUri) - .setName(name) - .setOpenCompanionAppDescription(openCompanionAppDescription) - .setRetroactivePairingDescription(retroactivePairingDes) - .setSubsequentPairingDescription(subsequentPairingDescription) - .setTriggerDistance(triggerDistance.toFloat()) - .setTrueWirelessImageUrlCase(trueWirelessImageUrlCase) - .setTrueWirelessImageUrlLeftBud(trueWirelessImageUrlLeftBud) - .setTrueWirelessImageUrlRightBud(trueWirelessImageUrlRightBud) - .setUnableToConnectDescription(unableToConnectDescription) - .setUnableToConnectTitle(unableToConnectTitle) - .setUpdateCompanionAppDescription(updateCompAppDes) - .setWaitLaunchCompanionAppDescription(waitLaunchCompApp) - .build() - } - } - - data class FastPairDiscoveryItemData( - @SerializedName("action_url") val actionUrl: String?, - @SerializedName("action_url_type") val actionUrlType: Int, - @SerializedName("app_name") val appName: String?, - @SerializedName("authentication_public_key_secp256r1") val authenticationPublicKey: String?, - @SerializedName("description") val description: String?, - @SerializedName("device_name") val deviceName: String?, - @SerializedName("display_url") val displayUrl: String?, - @SerializedName("first_observation_timestamp_millis") val firstObservationMs: Long, - @SerializedName("icon_fife_url") val iconFfeUrl: String?, - @SerializedName("icon_png") val iconPng: String?, - @SerializedName("id") val id: String?, - @SerializedName("last_observation_timestamp_millis") val lastObservationMs: Long, - @SerializedName("mac_address") val macAddress: String?, - @SerializedName("package_name") val packageName: String?, - @SerializedName("pending_app_install_timestamp_millis") val pendingAppInstallMs: Long, - @SerializedName("rssi") val rssi: Int, - @SerializedName("state") val state: Int, - @SerializedName("title") val title: String?, - @SerializedName("trigger_id") val triggerId: String?, - @SerializedName("tx_power") val txPower: Int - ) { - constructor(item: FastPairDiscoveryItem) : this( - actionUrl = item.actionUrl, - actionUrlType = item.actionUrlType, - appName = item.appName, - authenticationPublicKey = item.authenticationPublicKeySecp256r1?.base64Encode(), - description = item.description, - deviceName = item.deviceName, - displayUrl = item.displayUrl, - firstObservationMs = item.firstObservationTimestampMillis, - iconFfeUrl = item.iconFfeUrl, - iconPng = item.iconPng?.base64Encode(), - id = item.id, - lastObservationMs = item.lastObservationTimestampMillis, - macAddress = item.macAddress, - packageName = item.packageName, - pendingAppInstallMs = item.pendingAppInstallTimestampMillis, - rssi = item.rssi, - state = item.state, - title = item.title, - triggerId = item.triggerId, - txPower = item.txPower - ) - - fun toFastPairDiscoveryItem(): FastPairDiscoveryItem { - return FastPairDiscoveryItem.Builder() - .setActionUrl(actionUrl) - .setActionUrlType(actionUrlType) - .setAppName(appName) - .setAuthenticationPublicKeySecp256r1(authenticationPublicKey?.base64Decode()) - .setDescription(description) - .setDeviceName(deviceName) - .setDisplayUrl(displayUrl) - .setFirstObservationTimestampMillis(firstObservationMs) - .setIconFfeUrl(iconFfeUrl) - .setIconPng(iconPng?.base64Decode()) - .setId(id) - .setLastObservationTimestampMillis(lastObservationMs) - .setMacAddress(macAddress) - .setPackageName(packageName) - .setPendingAppInstallTimestampMillis(pendingAppInstallMs) - .setRssi(rssi) - .setState(state) - .setTitle(title) - .setTriggerId(triggerId) - .setTxPower(txPower) - .build() - } - } -} - -private fun String.base64Decode(): ByteArray = BaseEncoding.base64().decode(this) - -private fun ByteArray.base64Encode(): String = BaseEncoding.base64().encode(this) diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/data/FastPairTestDataManager.kt b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/data/FastPairTestDataManager.kt deleted file mode 100644 index e924da1d55f79078e35b255a6b00c5c11a3c5c7f..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/data/FastPairTestDataManager.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.fastpair.seeker.data - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.nearby.FastPairAccountKeyDeviceMetadata -import android.nearby.fastpair.seeker.ACTION_RESET_TEST_DATA_CACHE -import android.nearby.fastpair.seeker.ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA -import android.nearby.fastpair.seeker.ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA -import android.nearby.fastpair.seeker.ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA -import android.nearby.fastpair.seeker.DATA_JSON_STRING_KEY -import android.nearby.fastpair.seeker.DATA_MODEL_ID_STRING_KEY -import android.nearby.fastpair.seeker.FastPairTestDataCache -import android.util.Log - -/** Manage local FastPairTestDataCache and receive/update the remote cache in test snippet. */ -class FastPairTestDataManager(private val context: Context) : BroadcastReceiver() { - val testDataCache = FastPairTestDataCache() - - /** Writes a FastPairAccountKeyDeviceMetadata into local and remote cache. - * - * @param accountKeyDeviceMetadata the FastPairAccountKeyDeviceMetadata to write. - * @return a json object string of the accountKeyDeviceMetadata. - */ - fun writeAccountKeyDeviceMetadata( - accountKeyDeviceMetadata: FastPairAccountKeyDeviceMetadata - ): String { - testDataCache.putAccountKeyDeviceMetadata(accountKeyDeviceMetadata) - - val json = - testDataCache.dumpAccountKeyDeviceMetadataAsJson(accountKeyDeviceMetadata) - Intent().also { intent -> - intent.action = ACTION_WRITE_ACCOUNT_KEY_DEVICE_METADATA - intent.putExtra(DATA_JSON_STRING_KEY, json) - context.sendBroadcast(intent) - } - return json - } - - /** - * Callback method for receiving Intent broadcast from test snippet. - * - * See [BroadcastReceiver#onReceive]. - * - * @param context the Context in which the receiver is running. - * @param intent the Intent being received. - */ - override fun onReceive(context: Context, intent: Intent) { - when (intent.action) { - ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA -> { - Log.d(TAG, "ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA received!") - val modelId = intent.getStringExtra(DATA_MODEL_ID_STRING_KEY)!! - val json = intent.getStringExtra(DATA_JSON_STRING_KEY)!! - testDataCache.putAntispoofKeyDeviceMetadata(modelId, json) - } - ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA -> { - Log.d(TAG, "ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA received!") - val json = intent.getStringExtra(DATA_JSON_STRING_KEY)!! - testDataCache.putAccountKeyDeviceMetadataJsonArray(json) - } - ACTION_RESET_TEST_DATA_CACHE -> { - Log.d(TAG, "ACTION_RESET_TEST_DATA_CACHE received!") - testDataCache.reset() - } - else -> Log.d(TAG, "Unknown action received!") - } - } - - companion object { - private const val TAG = "FastPairTestDataManager" - } -} \ No newline at end of file diff --git a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/dataprovider/FastPairTestDataProviderService.kt b/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/dataprovider/FastPairTestDataProviderService.kt deleted file mode 100644 index aec1379d4fa6c639ac0625839d3eaf5ab2d9028e..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider/src/android/nearby/fastpair/seeker/dataprovider/FastPairTestDataProviderService.kt +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2022 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.nearby.fastpair.seeker.dataprovider - -import android.accounts.Account -import android.content.IntentFilter -import android.nearby.FastPairDataProviderService -import android.nearby.FastPairEligibleAccount -import android.nearby.fastpair.seeker.ACTION_RESET_TEST_DATA_CACHE -import android.nearby.fastpair.seeker.ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA -import android.nearby.fastpair.seeker.ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA -import android.nearby.fastpair.seeker.FAKE_TEST_ACCOUNT_NAME -import android.nearby.fastpair.seeker.data.FastPairTestDataManager -import android.util.Log - -/** - * Fast Pair Test Data Provider Service entry point for platform overlay. - */ -class FastPairTestDataProviderService : FastPairDataProviderService(TAG) { - private lateinit var testDataManager: FastPairTestDataManager - - override fun onCreate() { - Log.d(TAG, "onCreate()") - testDataManager = FastPairTestDataManager(this) - - val bondStateFilter = IntentFilter(ACTION_RESET_TEST_DATA_CACHE).apply { - addAction(ACTION_SEND_ACCOUNT_KEY_DEVICE_METADATA) - addAction(ACTION_SEND_ANTISPOOF_KEY_DEVICE_METADATA) - } - registerReceiver(testDataManager, bondStateFilter) - } - - override fun onDestroy() { - Log.d(TAG, "onDestroy()") - unregisterReceiver(testDataManager) - - super.onDestroy() - } - - override fun onLoadFastPairAntispoofKeyDeviceMetadata( - request: FastPairAntispoofKeyDeviceMetadataRequest, - callback: FastPairAntispoofKeyDeviceMetadataCallback - ) { - val requestedModelId = request.modelId.bytesToStringLowerCase() - Log.d(TAG, "onLoadFastPairAntispoofKeyDeviceMetadata(modelId: $requestedModelId)") - - val fastPairAntispoofKeyDeviceMetadata = - testDataManager.testDataCache.getAntispoofKeyDeviceMetadata(requestedModelId) - if (fastPairAntispoofKeyDeviceMetadata != null) { - callback.onFastPairAntispoofKeyDeviceMetadataReceived( - fastPairAntispoofKeyDeviceMetadata - ) - } else { - Log.d(TAG, "No metadata available for $requestedModelId!") - callback.onError(ERROR_CODE_BAD_REQUEST, "No metadata available for $requestedModelId") - } - } - - override fun onLoadFastPairAccountDevicesMetadata( - request: FastPairAccountDevicesMetadataRequest, - callback: FastPairAccountDevicesMetadataCallback - ) { - val requestedAccount = request.account - val requestedAccountKeys = request.deviceAccountKeys - Log.d( - TAG, "onLoadFastPairAccountDevicesMetadata(" + - "account: $requestedAccount, accountKeys:$requestedAccountKeys)" - ) - Log.d(TAG, testDataManager.testDataCache.dumpAccountKeyDeviceMetadataListAsJson()) - - callback.onFastPairAccountDevicesMetadataReceived( - testDataManager.testDataCache.getAccountKeyDeviceMetadataList() - ) - } - - override fun onLoadFastPairEligibleAccounts( - request: FastPairEligibleAccountsRequest, - callback: FastPairEligibleAccountsCallback - ) { - Log.d(TAG, "onLoadFastPairEligibleAccounts()") - callback.onFastPairEligibleAccountsReceived(ELIGIBLE_ACCOUNTS_TEST_CONSTANT) - } - - override fun onManageFastPairAccount( - request: FastPairManageAccountRequest, - callback: FastPairManageActionCallback - ) { - val requestedAccount = request.account - val requestType = request.requestType - Log.d(TAG, "onManageFastPairAccount(account: $requestedAccount, requestType: $requestType)") - - callback.onSuccess() - } - - override fun onManageFastPairAccountDevice( - request: FastPairManageAccountDeviceRequest, - callback: FastPairManageActionCallback - ) { - val requestedAccount = request.account - val requestType = request.requestType - val requestTypeString = if (requestType == MANAGE_REQUEST_ADD) "Add" else "Remove" - val requestedAccountKeyDeviceMetadata = request.accountKeyDeviceMetadata - Log.d( - TAG, - "onManageFastPairAccountDevice(requestedAccount: $requestedAccount, " + - "requestType: $requestTypeString," - ) - - val requestedAccountKeyDeviceMetadataInJson = - testDataManager.writeAccountKeyDeviceMetadata(requestedAccountKeyDeviceMetadata) - Log.d(TAG, "requestedAccountKeyDeviceMetadata: $requestedAccountKeyDeviceMetadataInJson)") - - callback.onSuccess() - } - - companion object { - private const val TAG = "FastPairTestDataProviderService" - private val ELIGIBLE_ACCOUNTS_TEST_CONSTANT = listOf( - FastPairEligibleAccount.Builder() - .setAccount(Account(FAKE_TEST_ACCOUNT_NAME, "FakeTestAccount")) - .setOptIn(true) - .build() - ) - - private fun ByteArray.bytesToStringLowerCase(): String = - joinToString(separator = "") { eachByte -> "%02x".format(eachByte) } - } -} diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/Android.bp b/nearby/tests/multidevices/clients/test_support/fastpair_provider/Android.bp deleted file mode 100644 index 298c9dcfb0925ccf00452b054ada7cf373298838..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/Android.bp +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (C) 2022 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: ["Android-Apache-2.0"], -} - -android_library { - name: "NearbyFastPairProviderLib", - srcs: [ - "src/**/*.java", - "src/**/*.kt", - ], - sdk_version: "core_platform", - libs: [ - // order matters: classes in framework-bluetooth are resolved before framework, meaning - // @hide APIs in framework-bluetooth are resolved before @SystemApi stubs in framework - "framework-bluetooth.impl", - "framework", - - // if sdk_version="" this gets automatically included, but here we need to add manually. - "framework-res", - ], - static_libs: [ - "NearbyFastPairProviderLiteProtos", - "androidx.core_core", - "androidx.test.core", - "error_prone_annotations", - "fast-pair-lite-protos", - "framework-annotations-lib", - "guava", - "kotlin-stdlib", - "nearby-common-lib", - ], -} diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/proto/event_stream_protocol.proto b/nearby/tests/multidevices/clients/test_support/fastpair_provider/proto/event_stream_protocol.proto deleted file mode 100644 index 54db34a0afc2f09b0e72ced4d74d156496bf3021..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/proto/event_stream_protocol.proto +++ /dev/null @@ -1,85 +0,0 @@ -syntax = "proto2"; - -package android.nearby.fastpair.provider; - -option java_package = "android.nearby.fastpair.provider"; -option java_outer_classname = "EventStreamProtocol"; - -enum EventGroup { - UNSPECIFIED = 0; - BLUETOOTH = 1; - LOGGING = 2; - DEVICE = 3; - DEVICE_ACTION = 4; - DEVICE_CONFIGURATION = 5; - DEVICE_CAPABILITY_SYNC = 6; - SMART_AUDIO_SOURCE_SWITCHING = 7; - ACKNOWLEDGEMENT = 255; -} - -enum BluetoothEventCode { - BLUETOOTH_UNSPECIFIED = 0; - BLUETOOTH_ENABLE_SILENCE_MODE = 1; - BLUETOOTH_DISABLE_SILENCE_MODE = 2; -} - -enum LoggingEventCode { - LOG_UNSPECIFIED = 0; - LOG_FULL = 1; - LOG_SAVE_TO_BUFFER = 2; -} - -enum DeviceEventCode { - DEVICE_UNSPECIFIED = 0; - DEVICE_MODEL_ID = 1; - DEVICE_BLE_ADDRESS = 2; - DEVICE_BATTERY_INFO = 3; - ACTIVE_COMPONENTS_REQUEST = 5; - ACTIVE_COMPONENTS_RESPONSE = 6; - DEVICE_CAPABILITY = 7; - PLATFORM_TYPE = 8; - FIRMWARE_VERSION = 9; - SECTION_NONCE = 10; -} - -enum DeviceActionEventCode { - DEVICE_ACTION_UNSPECIFIED = 0; - DEVICE_ACTION_RING = 1; -} - -enum DeviceConfigurationEventCode { - CONFIGURATION_UNSPECIFIED = 0; - CONFIGURATION_BUFFER_SIZE = 1; -} - -enum DeviceCapabilitySyncEventCode { - REQUEST_UNSPECIFIED = 0; - REQUEST_CAPABILITY_UPDATE = 1; - CONFIGURABLE_BUFFER_SIZE_RANGE = 2; -} - -enum AcknowledgementEventCode { - ACKNOWLEDGEMENT_UNSPECIFIED = 0; - ACKNOWLEDGEMENT_ACK = 1; - ACKNOWLEDGEMENT_NAK = 2; -} - -enum PlatformType { - PLATFORM_TYPE_UNKNOWN = 0; - ANDROID = 1; -} - -enum SassEventCode { - EVENT_UNSPECIFIED = 0; - EVENT_GET_CAPABILITY_OF_SASS = 0x10; - EVENT_NOTIFY_CAPABILITY_OF_SASS = 0x11; - EVENT_SET_MULTI_POINT_STATE = 0x12; - EVENT_SWITCH_AUDIO_SOURCE_BETWEEN_CONNECTED_DEVICES = 0x30; - EVENT_SWITCH_BACK = 0x31; - EVENT_NOTIFY_MULTIPOINT_SWITCH_EVENT = 0x32; - EVENT_GET_CONNECTION_STATUS = 0x33; - EVENT_NOTIFY_CONNECTION_STATUS = 0x34; - EVENT_SASS_INITIATED_CONNECTION = 0x40; - EVENT_INDICATE_IN_USE_ACCOUNT_KEY = 0x41; - EVENT_SET_CUSTOM_DATA = 0x42; -} diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/Android.bp b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/Android.bp deleted file mode 100644 index 125c34e637db44d4bcc989c35f83aff99d1de6f7..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/Android.bp +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (C) 2022 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: ["Android-Apache-2.0"], -} - -// Build and install NearbyFastPairProviderSimulatorApp to your phone: -// m NearbyFastPairProviderSimulatorApp -// adb root -// adb remount && adb reboot (make first time remount work) -// -// adb root -// adb remount -// adb push ${ANDROID_PRODUCT_OUT}/system/app/NearbyFastPairProviderSimulatorApp /system/app/ -// adb reboot -// Grant all permissions requested to NearbyFastPairProviderSimulatorApp before launching it. -android_app { - name: "NearbyFastPairProviderSimulatorApp", - sdk_version: "test_current", - // Sign with "platform" certificate for accessing Bluetooth @SystemAPI - certificate: "platform", - static_libs: ["NearbyFastPairProviderSimulatorLib"], - optimize: { - enabled: true, - shrink: true, - proguard_flags_files: ["proguard.flags"], - }, -} - -android_library { - name: "NearbyFastPairProviderSimulatorLib", - sdk_version: "test_current", - srcs: [ - "src/**/*.java", - "src/**/*.kt", - ], - static_libs: [ - "NearbyFastPairProviderLib", - "NearbyFastPairProviderLiteProtos", - "NearbyFastPairProviderSimulatorLiteProtos", - "androidx.annotation_annotation", - "error_prone_annotations", - "fast-pair-lite-protos", - ], -} \ No newline at end of file diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/AndroidManifest.xml b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/AndroidManifest.xml deleted file mode 100644 index 8880b110718fee2d3b10da3b42384c1a4785807e..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/AndroidManifest.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proguard.flags b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proguard.flags deleted file mode 100644 index 0827c608de9ecce1656140110bc9754654d112b7..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proguard.flags +++ /dev/null @@ -1,19 +0,0 @@ -# Keep AdvertisingSetCallback#onOwnAddressRead callback. --keep class * extends android.bluetooth.le.AdvertisingSetCallback { - *; -} - -# Keep names for easy debugging. --dontobfuscate - -# Necessary to allow debugging. --keepattributes * - -# By default, proguard leaves all classes in their original package, which -# needlessly repeats com.google.android.apps.etc. --repackageclasses "" - -# Allows proguard to make private and protected methods and fields public as -# part of optimization. This lets proguard inline trivial getter/setter -# methods. --allowaccessmodification \ No newline at end of file diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proto/simulator_stream_protocol.proto b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proto/simulator_stream_protocol.proto deleted file mode 100644 index 9b17fda9b0f70600945fc78838ae5e49a1677895..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proto/simulator_stream_protocol.proto +++ /dev/null @@ -1,110 +0,0 @@ -syntax = "proto2"; - -package android.nearby.fastpair.provider.simulator; - -option java_package = "android.nearby.fastpair.provider.simulator"; -option java_outer_classname = "SimulatorStreamProtocol"; - -// Used by remote devices to control simulator behaviors. -message Command { - // Type of this command. - required Code code = 1; - - // Required for SHOW_BATTERY. - optional BatteryInfo battery_info = 2; - - enum Code { - // Request for simulator's acknowledge message. - POLLING = 0; - - // Reset and clear bluetooth state. - RESET = 1; - - // Present battery information in the advertisement. - SHOW_BATTERY = 2; - - // Remove battery information in the advertisement. - HIDE_BATTERY = 3; - - // Request for BR/EDR address. - REQUEST_BLUETOOTH_ADDRESS_PUBLIC = 4; - - // Request for BLE address. - REQUEST_BLUETOOTH_ADDRESS_BLE = 5; - - // Request for account key. - REQUEST_ACCOUNT_KEY = 6; - } - - // Battery information for true wireless headsets. - // https://devsite.googleplex.com/nearby/fast-pair/early-access/spec#BatteryNotification - message BatteryInfo { - // Show or hide the battery UI notification. - optional bool suppress_notification = 1; - repeated BatteryValue battery_values = 2; - - // Advertised battery level data. - message BatteryValue { - // The charging flag. - required bool charging = 1; - - // Battery level from 0 to 100. - required uint32 level = 2; - } - } -} - -// Notify the remote devices when states are changed or response the command on -// the simulator. -message Event { - // Type of this event. - required Code code = 1; - - // Required for BLUETOOTH_STATE_BOND. - optional int32 bond_state = 2; - - // Required for BLUETOOTH_STATE_CONNECTION. - optional int32 connection_state = 3; - - // Required for BLUETOOTH_STATE_SCAN_MODE. - optional int32 scan_mode = 4; - - // Required for BLUETOOTH_ADDRESS_PUBLIC. - optional string public_address = 5; - - // Required for BLUETOOTH_ADDRESS_BLE. - optional string ble_address = 6; - - // Required for BLUETOOTH_ALIAS_NAME. - optional string alias_name = 7; - - // Required for REQUEST_ACCOUNT_KEY. - optional bytes account_key = 8; - - enum Code { - // Response the polling. - ACKNOWLEDGE = 0; - - // Notify the event android.bluetooth.device.action.BOND_STATE_CHANGED - BLUETOOTH_STATE_BOND = 1; - - // Notify the event - // android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED - BLUETOOTH_STATE_CONNECTION = 2; - - // Notify the event android.bluetooth.adapter.action.SCAN_MODE_CHANGED - BLUETOOTH_STATE_SCAN_MODE = 3; - - // Notify the current BR/EDR address - BLUETOOTH_ADDRESS_PUBLIC = 4; - - // Notify the current BLE address - BLUETOOTH_ADDRESS_BLE = 5; - - // Notify the event android.bluetooth.device.action.ALIAS_CHANGED - BLUETOOTH_ALIAS_NAME = 6; - - // Response the REQUEST_ACCOUNT_KEY. - ACCOUNT_KEY = 7; - } -} diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/layout/activity_main.xml b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/layout/activity_main.xml deleted file mode 100644 index b7e85eb13b7c92768b47eeea1ea7038a2e642819..0000000000000000000000000000000000000000 --- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/layout/activity_main.xml +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - -