Merge "Add subsequent related db save info after pairing success" into tm-dev
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
index a2b248b..cb4e6cb 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
@@ -91,6 +91,7 @@
/** A notification ID which should be dismissed */
public static final String EXTRA_NOTIFICATION_ID = ACTION_PREFIX + "EXTRA_NOTIFICATION_ID";
public static final String ACTION_RESOURCES_APK = "android.nearby.SHOW_HALFSHEET";
+ public static final boolean ENFORCED_SCAN_ENABLED_VALUE = false;
private static Executor sFastPairExecutor;
@@ -99,7 +100,7 @@
final LocatorContextWrapper mLocatorContextWrapper;
final IntentFilter mIntentFilter;
final Locator mLocator;
- private boolean mScanEnabled = false;
+ private boolean mScanEnabled = ENFORCED_SCAN_ENABLED_VALUE;
private final BroadcastReceiver mScreenBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -153,6 +154,7 @@
Locator.getFromContextWrapper(mLocatorContextWrapper, FastPairCacheManager.class);
try {
mScanEnabled = getScanEnabledFromSettings();
+ mScanEnabled = ENFORCED_SCAN_ENABLED_VALUE;
} catch (Settings.SettingNotFoundException e) {
Log.w(TAG,
"initiate: Failed to get initial scan enabled status from Settings.", e);
@@ -413,6 +415,7 @@
return;
}
mScanEnabled = scanEnabled;
+ mScanEnabled = ENFORCED_SCAN_ENABLED_VALUE;
invalidateScan();
}
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
index 4f279a5..b8a9796 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java
@@ -31,6 +31,7 @@
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;
@@ -67,6 +68,15 @@
@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);
diff --git a/nearby/tests/cts/fastpair/Android.bp b/nearby/tests/cts/fastpair/Android.bp
index 599fe5c..6dc1af8 100644
--- a/nearby/tests/cts/fastpair/Android.bp
+++ b/nearby/tests/cts/fastpair/Android.bp
@@ -37,6 +37,7 @@
"general-tests",
"mts-tethering",
],
+ certificate: "platform",
platform_apis: true,
sdk_version: "module_current",
min_sdk_version: "30",
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyFrameworkInitializerTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyFrameworkInitializerTest.java
index 370bfe1..cf43cb1 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyFrameworkInitializerTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyFrameworkInitializerTest.java
@@ -16,12 +16,16 @@
package android.nearby.cts;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
import android.nearby.NearbyFrameworkInitializer;
import android.os.Build;
import androidx.annotation.RequiresApi;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,15 +35,11 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public class NearbyFrameworkInitializerTest {
-// // TODO(b/215435710) This test cannot pass now because our test cannot access system API.
-// // run "adb root && adb shell setenforce permissive" and uncomment testServicesRegistered,
-// // test passes.
-// @Test
-// public void testServicesRegistered() {
-// Context ctx = InstrumentationRegistry.getInstrumentation().getContext();
-// assertNotNull( "NearbyManager not registered",
-// ctx.getSystemService(Context.NEARBY_SERVICE));
-// }
+ @Test
+ public void testServicesRegistered() {
+ Context ctx = InstrumentationRegistry.getInstrumentation().getContext();
+ assertThat(ctx.getSystemService(Context.NEARBY_SERVICE)).isNotNull();
+ }
// registerServiceWrappers can only be called during initialization and should throw otherwise
@Test(expected = IllegalStateException.class)
diff --git a/nearby/tests/multidevices/clients/Android.bp b/nearby/tests/multidevices/clients/Android.bp
index 5e0ca15..e3c8bb1 100644
--- a/nearby/tests/multidevices/clients/Android.bp
+++ b/nearby/tests/multidevices/clients/Android.bp
@@ -24,15 +24,12 @@
],
sdk_version: "test_current",
static_libs: [
- "NearbyMultiDevicesClientsFastPairLiteProtos",
+ "MoblySnippetHelperLib",
+ "NearbyFastPairProviderLib",
"androidx.test.core",
- "error_prone_annotations",
- "fast-pair-lite-protos",
- "framework-annotations-lib",
"gson-prebuilt-jar",
"kotlin-stdlib",
"mobly-snippet-lib",
- "service-nearby",
],
}
diff --git a/nearby/tests/multidevices/clients/proguard.flags b/nearby/tests/multidevices/clients/proguard.flags
index 2e34dce..fd494a8 100644
--- a/nearby/tests/multidevices/clients/proguard.flags
+++ b/nearby/tests/multidevices/clients/proguard.flags
@@ -4,7 +4,7 @@
}
# Keep simulator reflection callback.
--keep class com.android.server.nearby.common.bluetooth.fastpair.testing.** {
+-keep class android.nearby.fastpair.provider.** {
*;
}
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
index a2f50b4..a03085c 100644
--- 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
@@ -21,7 +21,7 @@
import android.content.Context
import android.os.Build
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.nearby.common.bluetooth.fastpair.testing.FastPairSimulator
+import android.nearby.fastpair.provider.FastPairSimulator
import com.google.android.mobly.snippet.Snippet
import com.google.android.mobly.snippet.rpc.AsyncRpc
import com.google.android.mobly.snippet.rpc.Rpc
@@ -70,7 +70,9 @@
fastPairSimulator =
FastPairSimulator(
context,
- FastPairSimulator.Options.builder(modelId)
+ FastPairSimulator.Options.builder(
+ modelId
+ )
.setAdvertisingModelId(modelId)
.setBluetoothAddress(null)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/ProviderStatusEvents.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/ProviderStatusEvents.kt
index 20c8e85..eef4b8b 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/ProviderStatusEvents.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/ProviderStatusEvents.kt
@@ -16,7 +16,7 @@
package android.nearby.multidevices.fastpair.provider
-import android.nearby.multidevices.common.postSnippetEvent
+import com.google.android.mobly.snippet.util.postSnippetEvent
/** The Mobly snippet events to report to the Python side. */
class ProviderStatusEvents(private val callbackId: String) :
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/CompanionAppUtils.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/CompanionAppUtils.kt
deleted file mode 100644
index 7ed4372..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/CompanionAppUtils.kt
+++ /dev/null
@@ -1,34 +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
-
-fun generateCompanionAppLaunchIntentUri(
- companionAppPackageName: String? = null,
- activityName: String? = null,
- action: String? = null
-): String {
- if (companionAppPackageName.isNullOrEmpty() || activityName.isNullOrEmpty()) {
- return ""
- }
- var intentUriString = "intent:#Intent;"
- if (!action.isNullOrEmpty()) {
- intentUriString += "action=$action;"
- }
- intentUriString += "package=$companionAppPackageName;"
- intentUriString += "component=$companionAppPackageName/$activityName;"
- return "${intentUriString}end"
-}
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ScanCallbackEvents.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ScanCallbackEvents.kt
index 55a6b8f..5385238 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ScanCallbackEvents.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ScanCallbackEvents.kt
@@ -18,7 +18,7 @@
import android.nearby.NearbyDevice
import android.nearby.ScanCallback
-import android.nearby.multidevices.common.postSnippetEvent
+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 {
diff --git a/nearby/tests/multidevices/clients/proto/Android.bp b/nearby/tests/multidevices/clients/test_support/fastpair_provider/Android.bp
similarity index 62%
copy from nearby/tests/multidevices/clients/proto/Android.bp
copy to nearby/tests/multidevices/clients/test_support/fastpair_provider/Android.bp
index 80e09b4..dc3a919 100644
--- a/nearby/tests/multidevices/clients/proto/Android.bp
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/Android.bp
@@ -16,15 +16,20 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-java_library {
- name: "NearbyMultiDevicesClientsFastPairLiteProtos",
- proto: {
- type: "lite",
- canonical_path_from_root: false,
- },
- sdk_version: "system_current",
- min_sdk_version: "30",
- srcs: ["src/*/*.proto"],
+android_library {
+ name: "NearbyFastPairProviderLib",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ sdk_version: "test_current",
+ static_libs: [
+ "NearbyFastPairProviderLiteProtos",
+ "androidx.test.core",
+ "error_prone_annotations",
+ "fast-pair-lite-protos",
+ "framework-annotations-lib",
+ "kotlin-stdlib",
+ "service-nearby",
+ ],
}
-
-
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/AndroidManifest.xml b/nearby/tests/multidevices/clients/test_support/fastpair_provider/AndroidManifest.xml
new file mode 100644
index 0000000..400a434
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.nearby.fastpair.provider">
+
+ <uses-feature android:name="android.hardware.bluetooth" />
+ <uses-feature android:name="android.hardware.bluetooth_le" />
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+ <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
+
+</manifest>
diff --git a/nearby/tests/multidevices/clients/proto/Android.bp b/nearby/tests/multidevices/clients/test_support/fastpair_provider/proto/Android.bp
similarity index 90%
rename from nearby/tests/multidevices/clients/proto/Android.bp
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/proto/Android.bp
index 80e09b4..7ae43e5 100644
--- a/nearby/tests/multidevices/clients/proto/Android.bp
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/proto/Android.bp
@@ -17,14 +17,14 @@
}
java_library {
- name: "NearbyMultiDevicesClientsFastPairLiteProtos",
+ name: "NearbyFastPairProviderLiteProtos",
proto: {
type: "lite",
canonical_path_from_root: false,
},
sdk_version: "system_current",
min_sdk_version: "30",
- srcs: ["src/*/*.proto"],
+ srcs: ["*.proto"],
}
diff --git a/nearby/tests/multidevices/clients/proto/src/fastpair/event_stream_protocol.proto b/nearby/tests/multidevices/clients/test_support/fastpair_provider/proto/event_stream_protocol.proto
similarity index 94%
rename from nearby/tests/multidevices/clients/proto/src/fastpair/event_stream_protocol.proto
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/proto/event_stream_protocol.proto
index 69ed1ea..54db34a 100644
--- a/nearby/tests/multidevices/clients/proto/src/fastpair/event_stream_protocol.proto
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/proto/event_stream_protocol.proto
@@ -1,8 +1,8 @@
syntax = "proto2";
-package android.nearby.multidevices.fastpair;
+package android.nearby.fastpair.provider;
-option java_package = "android.nearby.multidevices.fastpair";
+option java_package = "android.nearby.fastpair.provider";
option java_outer_classname = "EventStreamProtocol";
enum EventGroup {
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
new file mode 100644
index 0000000..f3eed51
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/Android.bp
@@ -0,0 +1,45 @@
+// 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_app {
+ name: "NearbyFastPairProviderSimulatorApp",
+ sdk_version: "test_current",
+ 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
new file mode 100644
index 0000000..8880b11
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.nearby.fastpair.provider.simulator.app" >
+
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
+
+ <application
+ android:allowBackup="true"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".MainActivity"
+ android:windowSoftInputMode="stateHidden"
+ android:screenOrientation="portrait"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
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
new file mode 100644
index 0000000..28680b3
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proguard.flags
@@ -0,0 +1,19 @@
+# Keep simulator reflection callback.
+-keep class android.nearby.fastpair.provider.** {
+ *;
+}
+
+# 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/proto/Android.bp b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proto/Android.bp
similarity index 90%
copy from nearby/tests/multidevices/clients/proto/Android.bp
copy to nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proto/Android.bp
index 80e09b4..e964800 100644
--- a/nearby/tests/multidevices/clients/proto/Android.bp
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proto/Android.bp
@@ -17,14 +17,14 @@
}
java_library {
- name: "NearbyMultiDevicesClientsFastPairLiteProtos",
+ name: "NearbyFastPairProviderSimulatorLiteProtos",
proto: {
type: "lite",
canonical_path_from_root: false,
},
sdk_version: "system_current",
min_sdk_version: "30",
- srcs: ["src/*/*.proto"],
+ srcs: ["*.proto"],
}
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
new file mode 100644
index 0000000..9b17fda
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/proto/simulator_stream_protocol.proto
@@ -0,0 +1,110 @@
+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
new file mode 100644
index 0000000..b7e85eb
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/layout/activity_main.xml
@@ -0,0 +1,190 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_margin="16dp"
+ android:keepScreenOn="true"
+ tools:context=".MainActivity">
+
+ <TextView
+ android:id="@+id/bluetooth_address_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="14dp"
+ android:textStyle="bold"
+ android:padding="8dp"/>
+
+ <TextView
+ android:id="@+id/device_name_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="14dp"
+ android:textStyle="bold"
+ android:padding="8dp"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="14dp"
+ android:textStyle="bold"
+ android:text="Model ID:"/>
+ <Spinner
+ android:id="@+id/model_id_spinner"
+ android:textSize="14dp"
+ android:textStyle="bold"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
+ <TextView
+ android:id="@+id/tx_power_text_view"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:textSize="14dp"
+ android:textStyle="bold" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/anti_spoofing_private_key_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="14dp"
+ android:textStyle="bold"
+ android:padding="8dp"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/is_advertising_text_view"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:textSize="14dp"
+ android:textStyle="bold"
+ android:padding="8dp"/>
+ <TextView
+ android:id="@+id/scan_mode_text_view"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:textSize="14dp"
+ android:textStyle="bold"
+ android:padding="8dp"/>
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/remote_device_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="14dp"
+ android:textStyle="bold"
+ android:padding="8dp"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/is_paired_text_view"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:textSize="14dp"
+ android:textStyle="bold"
+ android:padding="8dp"/>
+ <TextView
+ android:id="@+id/is_connected_text_view"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:textSize="14dp"
+ android:textStyle="bold"
+ android:padding="8dp"/>
+ </LinearLayout>
+
+ <Button
+ android:id="@+id/reset_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Reset"
+ android:onClick="onResetButtonClicked"/>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp"
+ android:orientation="horizontal"
+ android:layout_gravity="center_vertical">
+
+ <Spinner
+ android:id="@+id/event_stream_spinner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <Button
+ android:id="@+id/send_event_message_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Send Event Message"
+ android:onClick="onSendEventStreamMessageButtonClicked"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:orientation="horizontal"
+ android:layout_gravity="center_vertical">
+ <Switch
+ android:id="@+id/fail_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Force Fail" />
+ <Switch
+ android:id="@+id/app_launch_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Trigger app launch"
+ android:paddingLeft="8dp"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/adv_options"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp"
+ android:orientation="horizontal"
+ android:layout_gravity="center_vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="8dp"
+ android:textColor="@android:color/black"
+ android:text="adv options"/>
+
+ <Spinner
+ android:id="@+id/adv_option_spinner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="bottom"
+ android:scrollbars="vertical"/>
+</LinearLayout>
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/layout/user_input_dialog.xml b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/layout/user_input_dialog.xml
new file mode 100644
index 0000000..980b057
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/layout/user_input_dialog.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="16dp">
+
+ <EditText
+ android:id="@+id/userInputDialog"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/firmware_input_hint"
+ android:inputType="text" />
+
+</LinearLayout>
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/menu/menu.xml b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/menu/menu.xml
new file mode 100644
index 0000000..f225522
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/menu/menu.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context=".MainActivity">
+
+ <item
+ android:id="@+id/sign_out_menu_item"
+ android:title="Sign out"/>
+ <item
+ android:id="@+id/reset_account_keys_menu_item"
+ android:title="Reset Account Keys"/>
+ <item
+ android:id="@+id/reset_device_name_menu_item"
+ android:title="Reset Device Name"/>
+ <item
+ android:id="@+id/set_firmware_version"
+ android:title="Set Firmware Version"/>
+ <item
+ android:id="@+id/set_simulator_capability"
+ android:title="Set Simulator Capability"/>
+ <item
+ android:id="@+id/use_new_gatt_characteristics_id"
+ android:checkable="true"
+ android:checked="false"
+ android:title="Use new GATT characteristics id"/>
+</menu>
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/values/dimens.xml b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/values/dimens.xml
@@ -0,0 +1,5 @@
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/values/strings.xml b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/values/strings.xml
new file mode 100644
index 0000000..5123038
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/res/values/strings.xml
@@ -0,0 +1,31 @@
+<resources>
+ <string name="app_name">Fast Pair Provider Simulator</string>
+ <string-array name="adv_options">
+ <item>0: No battery info</item>
+ <item>1: Show L(⬆) + R(⬆) + C(⬆)</item>
+ <item>2: Show L + R + C(unknown)</item>
+ <item>3: Show L(low 10) + R(low 9) + C(low 25)</item>
+ <item>4: Suppress battery w/o level changes</item>
+ <item>5: Suppress L(low 10) + R(11) + C</item>
+ <item>6: Suppress L(low ⬆) + R(low ⬆) + C(low 10)</item>
+ <item>7: Suppress L(low ⬆) + R(low ⬆) + C(low ⬆)</item>
+ <item>8: Show subsequent pairing notification</item>
+ <item>9: Suppress subsequent pairing notification</item>
+ </string-array>
+ <string-array name="event_stream_options">
+ <item>OHD event</item>
+ <item>Log event</item>
+ <item>Battery event</item>
+ </string-array>
+ <string name="firmware_dialog_title">Firmware version number</string>
+ <string name="firmware_input_hint">Type in version number</string>
+ <string name="passkey_dialog_title">Passkey needed</string>
+ <string name="passkey_input_hint">Type in passkey</string>
+ <!-- Passkey confirmation dialog title. [CHAR_LIMIT=NONE]-->
+ <string name="confirm_passkey">Confirm passkey</string>
+ <string name="model_id_progress_title">Get models from server</string>
+
+ <!-- Fast Pair Simulator: pair one device only. -->
+ <string name="fast_pair_simulator" translatable="false">Fast Pair Simulator</string>
+
+</resources>
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/AppLogger.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/AppLogger.java
new file mode 100644
index 0000000..befc64b
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/AppLogger.java
@@ -0,0 +1,44 @@
+/*
+ * 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.provider.simulator.app;
+
+import android.util.Log;
+
+import com.google.errorprone.annotations.FormatMethod;
+
+/** Sends log to logcat with TAG. */
+public class AppLogger {
+ private static final String TAG = "FastPairSimulator";
+
+ @FormatMethod
+ public static void log(String message, Object... objects) {
+ Log.i(TAG, String.format(message, objects));
+ }
+
+ @FormatMethod
+ public static void warning(String message, Object... objects) {
+ Log.w(TAG, String.format(message, objects));
+ }
+
+ @FormatMethod
+ public static void error(String message, Object... objects) {
+ Log.e(TAG, String.format(message, objects));
+ }
+
+ private AppLogger() {
+ }
+}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/BluetoothController.kt b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/BluetoothController.kt
new file mode 100644
index 0000000..ed04eae
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/BluetoothController.kt
@@ -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 android.nearby.fastpair.provider.simulator.app
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothManager
+import android.bluetooth.BluetoothProfile
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.nearby.fastpair.provider.FastPairSimulator
+import android.nearby.fastpair.provider.simulator.app.AppLogger.*
+import android.nearby.fastpair.provider.simulator.testing.Reflect
+import android.nearby.fastpair.provider.simulator.testing.ReflectionException
+import android.os.SystemClock
+import android.provider.Settings
+
+/** Controls the local Bluetooth adapter for Fast Pair testing. */
+class BluetoothController(
+ private val context: Context,
+ private val listener: EventListener,
+) : BroadcastReceiver() {
+ private val bluetoothAdapter: BluetoothAdapter =
+ (context.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager)?.adapter!!
+ private var remoteDevice: BluetoothDevice? = null
+ private var remoteDeviceConnectionState: Int = BluetoothAdapter.STATE_DISCONNECTED
+ private var a2dpSinkProxy: BluetoothProfile? = null
+
+ /** Turns on the local Bluetooth adapter */
+ fun enableBluetooth() {
+ if (!bluetoothAdapter.isEnabled) {
+ bluetoothAdapter.enable()
+ waitForBluetoothState(BluetoothAdapter.STATE_ON)
+ }
+ }
+
+ /**
+ * Sets the Input/Output capability of the device for both classic Bluetooth and BLE operations.
+ * Note: In order to let changes take effect, this method will make sure the Bluetooth stack is
+ * restarted by blocking calling thread.
+ *
+ * @param ioCapabilityClassic One of {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_NONE},
+ * ```
+ * {@link #IO_CAPABILITY_KBDISP} or more in {@link BluetoothAdapter}.
+ * @param ioCapabilityBLE
+ * ```
+ * One of {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_NONE}, {@link
+ * ```
+ * #IO_CAPABILITY_KBDISP} or more in {@link BluetoothAdapter}.
+ * ```
+ */
+ fun setIoCapability(ioCapabilityClassic: Int, ioCapabilityBLE: Int) {
+ try {
+ Reflect.on(bluetoothAdapter)
+ .withMethod("setIoCapability", Int::class.javaPrimitiveType)[
+ ioCapabilityClassic]
+ } catch (e: ReflectionException) {
+ warning("Error setIoCapability to %s: %s", ioCapabilityClassic, e)
+ }
+ try {
+ Reflect.on(bluetoothAdapter)
+ .withMethod("setLeIoCapability", Int::class.javaPrimitiveType)[
+ ioCapabilityBLE]
+ } catch (e: ReflectionException) {
+ warning("Error setLeIoCapability to %s: %s", ioCapabilityBLE, e)
+ }
+
+ // Toggling airplane mode on/off to restart Bluetooth stack and reset the BLE.
+ // Since it also increases reliability, we will do so even if ReflectionException is caught.
+ try {
+ Settings.Global.putInt(
+ context.contentResolver,
+ Settings.Global.AIRPLANE_MODE_ON,
+ TURN_AIRPLANE_MODE_ON
+ )
+ } catch (expectedOnNonCustomAndroid: SecurityException) {
+ warning("Requires custom Android to toggle airplane mode")
+ // Fall back to turn off Bluetooth.
+ bluetoothAdapter.disable()
+ }
+ waitForBluetoothState(BluetoothAdapter.STATE_OFF)
+ try {
+ Settings.Global.putInt(
+ context.contentResolver,
+ Settings.Global.AIRPLANE_MODE_ON,
+ TURN_AIRPLANE_MODE_OFF
+ )
+ } catch (expectedOnNonCustomAndroid: SecurityException) {
+ error("SecurityException while toggled airplane mode.")
+ } finally {
+ // Double confirm that Bluetooth is turned on.
+ bluetoothAdapter.enable()
+ }
+ waitForBluetoothState(BluetoothAdapter.STATE_ON)
+ }
+
+ /** Registers this Bluetooth state change receiver. */
+ fun registerBluetoothStateReceiver() {
+ val bondStateFilter =
+ IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED).apply {
+ addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
+ addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED)
+ }
+ context.registerReceiver(
+ this,
+ bondStateFilter,
+ /* broadcastPermission= */ null,
+ /* scheduler= */ null
+ )
+ }
+
+ /** Unregisters this Bluetooth state change receiver. */
+ fun unregisterBluetoothStateReceiver() {
+ context.unregisterReceiver(this)
+ }
+
+ /** Clears current remote device. */
+ fun clearRemoteDevice() {
+ remoteDevice = null
+ }
+
+ /** Gets current remote device. */
+ fun getRemoteDevice(): BluetoothDevice? = remoteDevice
+
+ /** Gets current remote device as string. */
+ fun getRemoteDeviceAsString(): String = remoteDevice?.remoteDeviceToString() ?: "none"
+
+ /** Connects the Bluetooth A2DP sink profile service. */
+ fun connectA2DPSinkProfile() {
+ // Get the A2DP proxy before continuing with initialization.
+ bluetoothAdapter.getProfileProxy(
+ context,
+ object : BluetoothProfile.ServiceListener {
+ override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
+ // When Bluetooth turns off and then on again, this is called again. But we only care
+ // the first time. There doesn't seem to be a way to unregister our listener.
+ if (a2dpSinkProxy == null) {
+ a2dpSinkProxy = proxy
+ listener.onA2DPSinkProfileConnected()
+ }
+ }
+
+ override fun onServiceDisconnected(profile: Int) {}
+ },
+ BLUETOOTH_PROFILE_A2DP_SINK
+ )
+ }
+
+ /** Get the current Bluetooth scan mode of the local Bluetooth adapter. */
+ fun getScanMode(): Int = bluetoothAdapter.scanMode
+
+ /** Return true if the remote device is connected to the local adapter. */
+ fun isConnected(): Boolean = remoteDeviceConnectionState == BluetoothAdapter.STATE_CONNECTED
+
+ /** Return true if the remote device is bonded (paired) to the local adapter. */
+ fun isPaired(): Boolean = bluetoothAdapter.bondedDevices.contains(remoteDevice)
+
+ /** Gets the A2DP sink profile proxy. */
+ fun getA2DPSinkProfileProxy(): BluetoothProfile? = a2dpSinkProxy
+
+ /**
+ * Callback method for receiving Intent broadcast of Bluetooth state.
+ *
+ * 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) {
+ log("BluetoothController received intent, action=%s", intent.action)
+
+ when (intent.action) {
+ BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
+ // After a device starts bonding, we only pay attention to intents about that device.
+ val device =
+ intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
+ val bondState =
+ intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR)
+ remoteDevice =
+ when (bondState) {
+ BluetoothDevice.BOND_BONDING, BluetoothDevice.BOND_BONDED -> device
+ BluetoothDevice.BOND_NONE -> null
+ else -> remoteDevice
+ }
+ log(
+ "ACTION_BOND_STATE_CHANGED, the bound state of the remote device (%s) change to %s.",
+ remoteDevice?.remoteDeviceToString(),
+ bondState.bondStateToString()
+ )
+ listener.onBondStateChanged(bondState)
+ }
+ BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED -> {
+ remoteDeviceConnectionState =
+ intent.getIntExtra(
+ BluetoothAdapter.EXTRA_CONNECTION_STATE,
+ BluetoothAdapter.STATE_DISCONNECTED
+ )
+ log(
+ "ACTION_CONNECTION_STATE_CHANGED, the new connectionState: %s",
+ remoteDeviceConnectionState
+ )
+ listener.onConnectionStateChanged(remoteDeviceConnectionState)
+ }
+ BluetoothAdapter.ACTION_SCAN_MODE_CHANGED -> {
+ val scanMode =
+ intent.getIntExtra(
+ BluetoothAdapter.EXTRA_SCAN_MODE,
+ BluetoothAdapter.SCAN_MODE_NONE
+ )
+ log(
+ "ACTION_SCAN_MODE_CHANGED, the new scanMode: %s",
+ FastPairSimulator.scanModeToString(scanMode)
+ )
+ listener.onScanModeChange(scanMode)
+ }
+ else -> {}
+ }
+ }
+
+ private fun waitForBluetoothState(state: Int) {
+ while (bluetoothAdapter.state != state) {
+ SystemClock.sleep(1000)
+ }
+ }
+
+ private fun BluetoothDevice.remoteDeviceToString(): String = "${this.name}-${this.address}"
+
+ private fun Int.bondStateToString(): String =
+ when (this) {
+ BluetoothDevice.BOND_NONE -> "BOND_NONE"
+ BluetoothDevice.BOND_BONDING -> "BOND_BONDING"
+ BluetoothDevice.BOND_BONDED -> "BOND_BONDED"
+ else -> "BOND_ERROR"
+ }
+
+ /** Interface for listening the events from Bluetooth controller. */
+ interface EventListener {
+ /** The callback for the first onServiceConnected of A2DP sink profile. */
+ fun onA2DPSinkProfileConnected()
+
+ /**
+ * Reports the current bond state of the remote device.
+ *
+ * @param bondState the bond state of the remote device.
+ */
+ fun onBondStateChanged(bondState: Int)
+
+ /**
+ * Reports the current connection state of the remote device.
+ *
+ * @param connectionState the bond state of the remote device.
+ */
+ fun onConnectionStateChanged(connectionState: Int)
+
+ /**
+ * Reports the current scan mode of the local Adapter.
+ *
+ * @param mode the current scan mode of the local Adapter.
+ */
+ fun onScanModeChange(mode: Int)
+ }
+
+ companion object {
+ /** Hidden SystemApi field in [BluetoothProfile] interface. */
+ private const val BLUETOOTH_PROFILE_A2DP_SINK = 11
+
+ private const val TURN_AIRPLANE_MODE_OFF = 0
+ private const val TURN_AIRPLANE_MODE_ON = 1
+ }
+}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/FutureCallbackWrapper.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/FutureCallbackWrapper.java
new file mode 100644
index 0000000..4db8560
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/FutureCallbackWrapper.java
@@ -0,0 +1,69 @@
+/*
+ * 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.provider.simulator.app;
+
+import android.util.Log;
+
+import com.google.common.util.concurrent.FutureCallback;
+
+/** Wrapper for {@link FutureCallback} to prevent the memory linkage. */
+public abstract class FutureCallbackWrapper<T> implements FutureCallback<T> {
+ private static final String TAG = FutureCallback.class.getSimpleName();
+
+ public static FutureCallbackWrapper<Void> createRegisterCallback(MainActivity activity) {
+ String id = activity.mRemoteDeviceId;
+ return new FutureCallbackWrapper<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ Log.d(TAG, String.format("%s was registered", id));
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.w(TAG, String.format("Failed to register %s", id), t);
+ }
+ };
+ }
+
+ public static FutureCallbackWrapper<Void> createDefaultIOCallback(MainActivity activity) {
+ String id = activity.mRemoteDeviceId;
+ return new FutureCallbackWrapper<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.w(TAG, String.format("IO stream error on %s", id), t);
+ }
+ };
+ }
+
+ public static FutureCallbackWrapper<Void> createDestroyCallback() {
+ return new FutureCallbackWrapper<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ Log.d(TAG, "remote devices manager is destroyed");
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.w(TAG, "Failed to destroy remote devices manager", t);
+ }
+ };
+ }
+}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java
new file mode 100644
index 0000000..9252173
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java
@@ -0,0 +1,1043 @@
+/*
+ * 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.provider.simulator.app;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.BLUETOOTH_STATE_BOND;
+import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.BLUETOOTH_STATE_CONNECTION;
+import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.BLUETOOTH_STATE_SCAN_MODE;
+import static android.nearby.fastpair.provider.simulator.app.AppLogger.log;
+import static android.nearby.fastpair.provider.simulator.app.AppLogger.warning;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.io.BaseEncoding.base64;
+
+import android.Manifest.permission;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.AdvertiseSettings;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.graphics.Color;
+import android.nearby.fastpair.provider.EventStreamProtocol.EventGroup;
+import android.nearby.fastpair.provider.FastPairSimulator;
+import android.nearby.fastpair.provider.FastPairSimulator.BatteryValue;
+import android.nearby.fastpair.provider.FastPairSimulator.KeyInputCallback;
+import android.nearby.fastpair.provider.FastPairSimulator.PasskeyEventCallback;
+import android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event;
+import android.nearby.fastpair.provider.simulator.testing.RemoteDevice;
+import android.nearby.fastpair.provider.simulator.testing.RemoteDevicesManager;
+import android.nearby.fastpair.provider.simulator.testing.StreamIOHandlerFactory;
+import android.nearby.fastpair.provider.utils.Logger;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.text.method.ScrollingMovementMethod;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.Switch;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.core.util.Consumer;
+
+import com.google.common.base.Ascii;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import com.google.errorprone.annotations.FormatMethod;
+import com.google.protobuf.ByteString;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.Executors;
+
+import service.proto.Rpcs.AntiSpoofingKeyPair;
+import service.proto.Rpcs.Device;
+import service.proto.Rpcs.DeviceType;
+
+/**
+ * Simulates a Fast Pair device (e.g. a headset).
+ *
+ * <p>See README in this directory, and {http://go/fast-pair-spec}.
+ */
+@SuppressLint("SetTextI18n")
+public class MainActivity extends Activity {
+ /** Device has a display and the ability to input Yes/No. */
+ private static final int IO_CAPABILITY_IO = 1;
+
+ /** Device only has a keyboard for entry but no display. */
+ private static final int IO_CAPABILITY_IN = 2;
+
+ /** Device has no Input or Output capability. */
+ private static final int IO_CAPABILITY_NONE = 3;
+
+ /** Device has a display and a full keyboard. */
+ private static final int IO_CAPABILITY_KBDISP = 4;
+
+ private static final String SHARED_PREFS_NAME =
+ "android.nearby.fastpair.provider.simulator.app";
+ private static final String EXTRA_MODEL_ID = "MODEL_ID";
+ private static final String EXTRA_BLUETOOTH_ADDRESS = "BLUETOOTH_ADDRESS";
+ private static final String EXTRA_TX_POWER_LEVEL = "TX_POWER_LEVEL";
+ private static final String EXTRA_FIRMWARE_VERSION = "FIRMWARE_VERSION";
+ private static final String EXTRA_SUPPORT_DYNAMIC_SIZE = "SUPPORT_DYNAMIC_SIZE";
+ private static final String EXTRA_USE_RANDOM_SALT_FOR_ACCOUNT_KEY_ROTATION =
+ "USE_RANDOM_SALT_FOR_ACCOUNT_KEY_ROTATION";
+ private static final String EXTRA_REMOTE_DEVICE_ID = "REMOTE_DEVICE_ID";
+ private static final String EXTRA_USE_NEW_GATT_CHARACTERISTICS_ID =
+ "USE_NEW_GATT_CHARACTERISTICS_ID";
+ public static final String EXTRA_REMOVE_ALL_DEVICES_DURING_PAIRING =
+ "REMOVE_ALL_DEVICES_DURING_PAIRING";
+ private static final String KEY_ACCOUNT_NAME = "ACCOUNT_NAME";
+ private static final String[] PERMISSIONS =
+ new String[]{permission.BLUETOOTH, permission.BLUETOOTH_ADMIN, permission.GET_ACCOUNTS};
+ private static final int LIGHT_GREEN = 0xFFC8FFC8;
+ private static final String ANTI_SPOOFING_KEY_LABEL = "Anti-spoofing key";
+
+ private static final ImmutableMap<String, String> ANTI_SPOOFING_PRIVATE_KEY_MAP =
+ new ImmutableMap.Builder<String, String>()
+ .put("361A2E", "/1rMqyJRGeOK6vkTNgM70xrytxdKg14mNQkITeusK20=")
+ .put("00000D", "03/MAmUPTGNsN+2iA/1xASXoPplDh3Ha5/lk2JgEBx4=")
+ .put("00000C", "Cbj9eCJrTdDgSYxLkqtfADQi86vIaMvxJsQ298sZYWE=")
+ // BLE only devices
+ .put("49426D", "I5QFOJW0WWFgKKZiwGchuseXsq/p9RN/aYtNsGEVGT0=")
+ .put("01E5CE", "FbHt8STpHJDd4zFQFjimh4Zt7IU94U28MOEIXgUEeCw=")
+ .put("8D13B9", "mv++LcJB1n0mbLNGWlXCv/8Gb6aldctrJC4/Ma/Q3Rg=")
+ .put("9AB0F6", "9eKQNwJUr5vCg0c8rtOXkJcWTAsBmmvEKSgXIqAd50Q=")
+ // Android Auto
+ .put("8E083D", "hGQeREDKM/H1834zWMmTIe0Ap4Zl5igThgE62OtdcKA=")
+ .buildOrThrow();
+
+ private static final Uri REMOTE_DEVICE_INPUT_STREAM_URI =
+ Uri.fromFile(new File("/data/local/nearby/tmp/read.pipe"));
+
+ private static final Uri REMOTE_DEVICE_OUTPUT_STREAM_URI =
+ Uri.fromFile(new File("/data/local/nearby/tmp/write.pipe"));
+
+ private static final String MODEL_ID_DEFAULT = "00000C";
+
+ private static final String MODEL_ID_APP_LAUNCH = "60EB56";
+
+ private static final int MODEL_ID_LENGTH = 6;
+
+ private BluetoothController mBluetoothController;
+ private final BluetoothController.EventListener mEventListener =
+ new BluetoothController.EventListener() {
+
+ @Override
+ public void onBondStateChanged(int bondState) {
+ sendEventToRemoteDevice(
+ Event.newBuilder().setCode(BLUETOOTH_STATE_BOND).setBondState(
+ bondState));
+ updateStatusView();
+ }
+
+ @Override
+ public void onConnectionStateChanged(int connectionState) {
+ sendEventToRemoteDevice(
+ Event.newBuilder()
+ .setCode(BLUETOOTH_STATE_CONNECTION)
+ .setConnectionState(connectionState));
+ updateStatusView();
+ }
+
+ @Override
+ public void onScanModeChange(int mode) {
+ sendEventToRemoteDevice(
+ Event.newBuilder().setCode(BLUETOOTH_STATE_SCAN_MODE).setScanMode(
+ mode));
+ updateStatusView();
+ }
+
+ @Override
+ public void onA2DPSinkProfileConnected() {
+ reset();
+ }
+ };
+
+ @Nullable
+ private FastPairSimulator mFastPairSimulator;
+ @Nullable
+ private AlertDialog mInputPasskeyDialog;
+ private Switch mFailSwitch;
+ private Switch mAppLaunchSwitch;
+ private Spinner mAdvOptionSpinner;
+ private Spinner mEventStreamSpinner;
+ private EventGroup mEventGroup;
+ private SharedPreferences mSharedPreferences;
+ private Spinner mModelIdSpinner;
+ private final RemoteDevicesManager mRemoteDevicesManager = new RemoteDevicesManager();
+ @Nullable
+ private RemoteDeviceListener mInputStreamListener;
+ @Nullable
+ String mRemoteDeviceId;
+ private final Map<String, Device> mModelsMap = new LinkedHashMap<>();
+ private boolean mRemoveAllDevicesDuringPairing = true;
+
+ void sendEventToRemoteDevice(Event.Builder eventBuilder) {
+ if (mRemoteDeviceId == null) {
+ return;
+ }
+
+ log("Send data to output stream: %s", eventBuilder.getCode().getNumber());
+ mRemoteDevicesManager.writeDataToRemoteDevice(
+ mRemoteDeviceId,
+ eventBuilder.build().toByteString(),
+ FutureCallbackWrapper.createDefaultIOCallback(this));
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_main);
+
+ mSharedPreferences = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
+
+ mRemoveAllDevicesDuringPairing =
+ getIntent().getBooleanExtra(EXTRA_REMOVE_ALL_DEVICES_DURING_PAIRING, true);
+
+ mFailSwitch = findViewById(R.id.fail_switch);
+ mFailSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> {
+ if (mFastPairSimulator != null) {
+ mFastPairSimulator.setShouldFailPairing(isChecked);
+ }
+ });
+
+ mAppLaunchSwitch = findViewById(R.id.app_launch_switch);
+ mAppLaunchSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> reset());
+
+ mAdvOptionSpinner = findViewById(R.id.adv_option_spinner);
+ mEventStreamSpinner = findViewById(R.id.event_stream_spinner);
+ ArrayAdapter<CharSequence> advOptionAdapter =
+ ArrayAdapter.createFromResource(
+ this, R.array.adv_options, android.R.layout.simple_spinner_item);
+ ArrayAdapter<CharSequence> eventStreamAdapter =
+ ArrayAdapter.createFromResource(
+ this, R.array.event_stream_options, android.R.layout.simple_spinner_item);
+ advOptionAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mAdvOptionSpinner.setAdapter(advOptionAdapter);
+ mEventStreamSpinner.setAdapter(eventStreamAdapter);
+ mAdvOptionSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> adapterView, View view, int position,
+ long id) {
+ startAdvertisingBatteryInformationBasedOnOption(position);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> adapterView) {
+ }
+ });
+ mEventStreamSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position,
+ long id) {
+ switch (EventGroup.forNumber(position + 1)) {
+ case BLUETOOTH:
+ mEventGroup = EventGroup.BLUETOOTH;
+ break;
+ case LOGGING:
+ mEventGroup = EventGroup.LOGGING;
+ break;
+ case DEVICE:
+ mEventGroup = EventGroup.DEVICE;
+ break;
+ default:
+ // fall through
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ });
+ setupModelIdSpinner();
+ setupRemoteDevices();
+ if (checkPermissions(PERMISSIONS)) {
+ mBluetoothController = new BluetoothController(this, mEventListener);
+ mBluetoothController.registerBluetoothStateReceiver();
+ mBluetoothController.enableBluetooth();
+ mBluetoothController.connectA2DPSinkProfile();
+
+ if (mSharedPreferences.getString(KEY_ACCOUNT_NAME, "").isEmpty()) {
+ putFixedModelLocal();
+ resetModelIdSpinner();
+ reset();
+ }
+ } else {
+ requestPermissions(PERMISSIONS, 0 /* requestCode */);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu, menu);
+ menu.findItem(R.id.use_new_gatt_characteristics_id).setChecked(
+ getFromIntentOrPrefs(
+ EXTRA_USE_NEW_GATT_CHARACTERISTICS_ID, /* defaultValue= */ false));
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.sign_out_menu_item) {
+ recreate();
+ return true;
+ } else if (item.getItemId() == R.id.reset_account_keys_menu_item) {
+ resetAccountKeys();
+ return true;
+ } else if (item.getItemId() == R.id.reset_device_name_menu_item) {
+ resetDeviceName();
+ return true;
+ } else if (item.getItemId() == R.id.set_firmware_version) {
+ setFirmware();
+ return true;
+ } else if (item.getItemId() == R.id.set_simulator_capability) {
+ setSimulatorCapability();
+ return true;
+ } else if (item.getItemId() == R.id.use_new_gatt_characteristics_id) {
+ if (!item.isChecked()) {
+ item.setChecked(true);
+ mSharedPreferences.edit()
+ .putBoolean(EXTRA_USE_NEW_GATT_CHARACTERISTICS_ID, true).apply();
+ } else {
+ item.setChecked(false);
+ mSharedPreferences.edit()
+ .putBoolean(EXTRA_USE_NEW_GATT_CHARACTERISTICS_ID, false).apply();
+ }
+ reset();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void setFirmware() {
+ View firmwareInputView =
+ LayoutInflater.from(getApplicationContext()).inflate(R.layout.user_input_dialog,
+ null);
+ EditText userInputDialogEditText = firmwareInputView.findViewById(R.id.userInputDialog);
+ new AlertDialog.Builder(MainActivity.this)
+ .setView(firmwareInputView)
+ .setCancelable(false)
+ .setPositiveButton(android.R.string.ok, (dialogBox, id) -> {
+ String input = userInputDialogEditText.getText().toString();
+ mSharedPreferences.edit().putString(EXTRA_FIRMWARE_VERSION,
+ input).apply();
+ reset();
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .setTitle(R.string.firmware_dialog_title)
+ .show();
+ }
+
+ private void setSimulatorCapability() {
+ String[] capabilityKeys = new String[]{EXTRA_SUPPORT_DYNAMIC_SIZE};
+ String[] capabilityNames = new String[]{"Dynamic Buffer Size"};
+ // Default values.
+ boolean[] capabilitySelected = new boolean[]{false};
+ // Get from preferences if exist.
+ for (int i = 0; i < capabilityKeys.length; i++) {
+ capabilitySelected[i] =
+ mSharedPreferences.getBoolean(capabilityKeys[i], capabilitySelected[i]);
+ }
+
+ new AlertDialog.Builder(MainActivity.this)
+ .setMultiChoiceItems(
+ capabilityNames,
+ capabilitySelected,
+ (dialog, which, isChecked) -> capabilitySelected[which] = isChecked)
+ .setCancelable(false)
+ .setPositiveButton(
+ android.R.string.ok,
+ (dialogBox, id) -> {
+ for (int i = 0; i < capabilityKeys.length; i++) {
+ mSharedPreferences
+ .edit()
+ .putBoolean(capabilityKeys[i], capabilitySelected[i])
+ .apply();
+ }
+ setCapabilityToSimulator();
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .setTitle("Simulator Capability")
+ .show();
+ }
+
+ private void setCapabilityToSimulator() {
+ if (mFastPairSimulator != null) {
+ mFastPairSimulator.setDynamicBufferSize(
+ getFromIntentOrPrefs(EXTRA_SUPPORT_DYNAMIC_SIZE, false));
+ }
+ }
+
+ private static String getModelIdString(long id) {
+ String result = Ascii.toUpperCase(Long.toHexString(id));
+ while (result.length() < MODEL_ID_LENGTH) {
+ result = "0" + result;
+ }
+ return result;
+ }
+
+ private void putFixedModelLocal() {
+ mModelsMap.put(
+ "00000C",
+ Device.newBuilder()
+ .setId(12)
+ .setAntiSpoofingKeyPair(AntiSpoofingKeyPair.newBuilder().build())
+ .setDeviceType(DeviceType.HEADPHONES)
+ .build());
+ }
+
+ private void setupModelIdSpinner() {
+ mModelIdSpinner = findViewById(R.id.model_id_spinner);
+
+ ArrayAdapter<String> modelIdAdapter =
+ new ArrayAdapter<>(this, android.R.layout.simple_spinner_item);
+ modelIdAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ mModelIdSpinner.setAdapter(modelIdAdapter);
+ resetModelIdSpinner();
+ mModelIdSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position,
+ long id) {
+ setModelId(mModelsMap.keySet().toArray(new String[0])[position]);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> adapterView) {
+ }
+ });
+ }
+
+ private void setupRemoteDevices() {
+ if (Strings.isNullOrEmpty(getIntent().getStringExtra(EXTRA_REMOTE_DEVICE_ID))) {
+ log("Can't get remote device id");
+ return;
+ }
+ mRemoteDeviceId = getIntent().getStringExtra(EXTRA_REMOTE_DEVICE_ID);
+ mInputStreamListener = new RemoteDeviceListener(this);
+
+ try {
+ mRemoteDevicesManager.registerRemoteDevice(
+ mRemoteDeviceId,
+ new RemoteDevice(
+ mRemoteDeviceId,
+ StreamIOHandlerFactory.createStreamIOHandler(
+ StreamIOHandlerFactory.Type.LOCAL_FILE,
+ REMOTE_DEVICE_INPUT_STREAM_URI,
+ REMOTE_DEVICE_OUTPUT_STREAM_URI),
+ mInputStreamListener));
+ } catch (IOException e) {
+ warning("Failed to create stream IO handler: %s", e);
+ }
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ @UiThread
+ private void resetModelIdSpinner() {
+ ArrayAdapter adapter = (ArrayAdapter) mModelIdSpinner.getAdapter();
+ if (adapter == null) {
+ return;
+ }
+
+ adapter.clear();
+ if (!mModelsMap.isEmpty()) {
+ for (String modelId : mModelsMap.keySet()) {
+ adapter.add(modelId + "-" + mModelsMap.get(modelId).getName());
+ }
+ mModelIdSpinner.setEnabled(true);
+ int newPos = getPositionFromModelId(getModelId());
+ if (newPos < 0) {
+ String newModelId = mModelsMap.keySet().iterator().next();
+ Toast.makeText(this,
+ "Can't find Model ID " + getModelId() + " from console, reset it to "
+ + newModelId, Toast.LENGTH_SHORT).show();
+ setModelId(newModelId);
+ newPos = 0;
+ }
+ mModelIdSpinner.setSelection(newPos, /* animate= */ false);
+ } else {
+ mModelIdSpinner.setEnabled(false);
+ }
+ }
+
+ private String getModelId() {
+ return getFromIntentOrPrefs(EXTRA_MODEL_ID, MODEL_ID_DEFAULT).toUpperCase(Locale.US);
+ }
+
+ private boolean setModelId(String modelId) {
+ String validModelId = getValidModelId(modelId);
+ if (TextUtils.isEmpty(validModelId)) {
+ log("Can't do setModelId because inputted modelId is invalid!");
+ return false;
+ }
+
+ if (getModelId().equals(validModelId)) {
+ return false;
+ }
+ mSharedPreferences.edit().putString(EXTRA_MODEL_ID, validModelId).apply();
+ reset();
+ return true;
+ }
+
+ @Nullable
+ private static String getValidModelId(String modelId) {
+ if (TextUtils.isEmpty(modelId) || modelId.length() < MODEL_ID_LENGTH) {
+ return null;
+ }
+
+ return modelId.substring(0, MODEL_ID_LENGTH).toUpperCase(Locale.US);
+ }
+
+ private int getPositionFromModelId(String modelId) {
+ int i = 0;
+ for (String id : mModelsMap.keySet()) {
+ if (id.equals(modelId)) {
+ return i;
+ }
+ i++;
+ }
+ return -1;
+ }
+
+ private void resetAccountKeys() {
+ if (mFastPairSimulator != null) {
+ mFastPairSimulator.resetAccountKeys();
+ mFastPairSimulator.startAdvertising();
+ }
+ }
+
+ private void resetDeviceName() {
+ if (mFastPairSimulator != null) {
+ mFastPairSimulator.resetDeviceName();
+ }
+ }
+
+ /** Called via activity_main.xml */
+ public void onResetButtonClicked(View view) {
+ reset();
+ }
+
+ /** Called via activity_main.xml */
+ public void onSendEventStreamMessageButtonClicked(View view) {
+ if (mFastPairSimulator != null) {
+ mFastPairSimulator.sendEventStreamMessageToRfcommDevices(mEventGroup);
+ }
+ }
+
+ void reset() {
+ Button resetButton = findViewById(R.id.reset_button);
+ if (mModelsMap.isEmpty() || !resetButton.isEnabled()) {
+ return;
+ }
+ resetButton.setText("Resetting...");
+ resetButton.setEnabled(false);
+ mModelIdSpinner.setEnabled(false);
+ mAppLaunchSwitch.setEnabled(false);
+
+ if (mFastPairSimulator != null) {
+ mFastPairSimulator.stopAdvertising();
+
+ if (mBluetoothController.getRemoteDevice() != null) {
+ if (mRemoveAllDevicesDuringPairing) {
+ mFastPairSimulator.removeBond(mBluetoothController.getRemoteDevice());
+ }
+ mBluetoothController.clearRemoteDevice();
+ }
+ // To be safe, also unpair from all phones (this covers the case where you kill +
+ // relaunch the
+ // simulator while paired).
+ if (mRemoveAllDevicesDuringPairing) {
+ mFastPairSimulator.disconnectAllBondedDevices();
+ }
+ // Sometimes a device will still be connected even though it's not bonded. :( Clear
+ // that too.
+ BluetoothProfile profileProxy = mBluetoothController.getA2DPSinkProfileProxy();
+ for (BluetoothDevice device : profileProxy.getConnectedDevices()) {
+ mFastPairSimulator.disconnect(profileProxy, device);
+ }
+ }
+ updateStatusView();
+
+ if (mFastPairSimulator != null) {
+ mFastPairSimulator.destroy();
+ }
+ TextView textView = (TextView) findViewById(R.id.text_view);
+ textView.setText("");
+ textView.setMovementMethod(new ScrollingMovementMethod());
+
+ String modelId = getModelId();
+
+ String txPower = getFromIntentOrPrefs(EXTRA_TX_POWER_LEVEL, "HIGH");
+ updateStringStatusView(R.id.tx_power_text_view, "TxPower", txPower);
+
+ String bluetoothAddress = getFromIntentOrPrefs(EXTRA_BLUETOOTH_ADDRESS, "");
+
+ String firmwareVersion = getFromIntentOrPrefs(EXTRA_FIRMWARE_VERSION, "1.1");
+ try {
+ Preconditions.checkArgument(base16().decode(bluetoothAddress).length == 6);
+ } catch (IllegalArgumentException e) {
+ log("Invalid BLUETOOTH_ADDRESS extra (%s), using default.", bluetoothAddress);
+ bluetoothAddress = null;
+ }
+ final String finalBluetoothAddress = bluetoothAddress;
+
+ updateStringStatusView(
+ R.id.anti_spoofing_private_key_text_view, ANTI_SPOOFING_KEY_LABEL, "Loading...");
+
+ boolean useRandomSaltForAccountKeyRotation =
+ getFromIntentOrPrefs(EXTRA_USE_RANDOM_SALT_FOR_ACCOUNT_KEY_ROTATION, false);
+
+ Executors.newSingleThreadExecutor().execute(() -> {
+ // Fetch the anti-spoofing key corresponding to this model ID (if it
+ // exists).
+ // The account must have Project Viewer permission for the project
+ // that owns
+ // the model ID (normally discoverer-test or discoverer-devices).
+ byte[] antiSpoofingKey = getAntiSpoofingKey(modelId);
+ String antiSpoofingKeyString;
+ Device device = mModelsMap.get(modelId);
+ if (antiSpoofingKey != null) {
+ antiSpoofingKeyString = base64().encode(antiSpoofingKey);
+ } else {
+ if (mSharedPreferences.getString(KEY_ACCOUNT_NAME, "").isEmpty()) {
+ antiSpoofingKeyString = "Can't fetch, no account";
+ } else {
+ if (device == null) {
+ antiSpoofingKeyString = String.format(Locale.US,
+ "Can't find model %s from console", modelId);
+ } else if (!device.hasAntiSpoofingKeyPair()) {
+ antiSpoofingKeyString = String.format(Locale.US,
+ "Can't find AntiSpoofingKeyPair for model %s", modelId);
+ } else if (device.getAntiSpoofingKeyPair().getPrivateKey().isEmpty()) {
+ antiSpoofingKeyString = String.format(Locale.US,
+ "Can't find privateKey for model %s", modelId);
+ } else {
+ antiSpoofingKeyString = "Unknown error";
+ }
+ }
+ }
+
+ int desiredIoCapability = getIoCapabilityFromModelId(modelId);
+
+ mBluetoothController.setIoCapability(
+ /*ioCapabilityClassic=*/ desiredIoCapability,
+ /*ioCapabilityBLE=*/ desiredIoCapability);
+
+ runOnUiThread(() -> {
+ updateStringStatusView(
+ R.id.anti_spoofing_private_key_text_view,
+ ANTI_SPOOFING_KEY_LABEL,
+ antiSpoofingKeyString);
+ FastPairSimulator.Options option = FastPairSimulator.Options.builder(modelId)
+ .setAdvertisingModelId(
+ mAppLaunchSwitch.isChecked() ? MODEL_ID_APP_LAUNCH : modelId)
+ .setBluetoothAddress(finalBluetoothAddress)
+ .setTxPowerLevel(toTxPowerLevel(txPower))
+ .setCallback(this::updateStatusView)
+ .setAntiSpoofingPrivateKey(antiSpoofingKey)
+ .setUseRandomSaltForAccountKeyRotation(useRandomSaltForAccountKeyRotation)
+ .setDataOnlyConnection(device != null && device.getDataOnlyConnection())
+ .setIsMemoryTest(mInputStreamListener != null)
+ .setShowsPasskeyConfirmation(
+ device.getDeviceType().equals(DeviceType.ANDROID_AUTO))
+ .setRemoveAllDevicesDuringPairing(mRemoveAllDevicesDuringPairing)
+ .build();
+ Logger textViewLogger = new Logger(FastPairSimulator.TAG) {
+
+ @FormatMethod
+ public void log(@Nullable Throwable exception, String message,
+ Object... objects) {
+ super.log(exception, message, objects);
+
+ String exceptionMessage = (exception == null) ? ""
+ : " - " + exception.getMessage();
+ final String finalMessage =
+ String.format(message, objects) + exceptionMessage;
+
+ textView.post(() -> {
+ String newText =
+ textView.getText() + "\n\n" + finalMessage;
+ textView.setText(newText);
+ });
+ }
+ };
+ mFastPairSimulator =
+ new FastPairSimulator(this, option, textViewLogger);
+ mFastPairSimulator.setFirmwareVersion(firmwareVersion);
+ mFailSwitch.setChecked(
+ mFastPairSimulator.getShouldFailPairing());
+ mAdvOptionSpinner.setSelection(0);
+ setCapabilityToSimulator();
+
+ updateStringStatusView(R.id.bluetooth_address_text_view,
+ "Bluetooth address",
+ mFastPairSimulator.getBluetoothAddress());
+
+ updateStringStatusView(R.id.device_name_text_view,
+ "Device name",
+ mFastPairSimulator.getDeviceName());
+
+ resetButton.setText("Reset");
+ resetButton.setEnabled(true);
+ mModelIdSpinner.setEnabled(true);
+ mAppLaunchSwitch.setEnabled(true);
+ mFastPairSimulator.setDeviceNameCallback(deviceName ->
+ updateStringStatusView(
+ R.id.device_name_text_view,
+ "Device name", deviceName));
+
+ if (desiredIoCapability == IO_CAPABILITY_IN
+ || device.getDeviceType().equals(DeviceType.ANDROID_AUTO)) {
+ mFastPairSimulator.setPasskeyEventCallback(mPasskeyEventCallback);
+ }
+ if (mInputStreamListener != null) {
+ mInputStreamListener.setFastPairSimulator(mFastPairSimulator);
+ }
+ });
+ });
+ }
+
+ private int getIoCapabilityFromModelId(String modelId) {
+ Device device = mModelsMap.get(modelId);
+ if (device == null) {
+ return IO_CAPABILITY_NONE;
+ } else {
+ if (getAntiSpoofingKey(modelId) == null) {
+ return IO_CAPABILITY_NONE;
+ } else {
+ switch (device.getDeviceType()) {
+ case INPUT_DEVICE:
+ return IO_CAPABILITY_IN;
+
+ case DEVICE_TYPE_UNSPECIFIED:
+ return IO_CAPABILITY_NONE;
+
+ // Treats wearable to IO_CAPABILITY_KBDISP for simulator because there seems
+ // no suitable
+ // type.
+ case WEARABLE:
+ return IO_CAPABILITY_KBDISP;
+
+ default:
+ return IO_CAPABILITY_IO;
+ }
+ }
+ }
+ }
+
+ @Nullable
+ ByteString getAccontKey() {
+ if (mFastPairSimulator == null) {
+ return null;
+ }
+ return mFastPairSimulator.getAccountKey();
+ }
+
+ @Nullable
+ private byte[] getAntiSpoofingKey(String modelId) {
+ Device device = mModelsMap.get(modelId);
+ if (device != null
+ && device.hasAntiSpoofingKeyPair()
+ && !device.getAntiSpoofingKeyPair().getPrivateKey().isEmpty()) {
+ return base64().decode(device.getAntiSpoofingKeyPair().getPrivateKey().toStringUtf8());
+ } else if (ANTI_SPOOFING_PRIVATE_KEY_MAP.containsKey(modelId)) {
+ return base64().decode(ANTI_SPOOFING_PRIVATE_KEY_MAP.get(modelId));
+ } else {
+ return null;
+ }
+ }
+
+ private final PasskeyEventCallback mPasskeyEventCallback = new PasskeyEventCallback() {
+ @Override
+ public void onPasskeyRequested(KeyInputCallback keyInputCallback) {
+ showInputPasskeyDialog(keyInputCallback);
+ }
+
+ @Override
+ public void onPasskeyConfirmation(int passkey, Consumer<Boolean> isConfirmed) {
+ showConfirmPasskeyDialog(passkey, isConfirmed);
+ }
+
+ @Override
+ public void onRemotePasskeyReceived(int passkey) {
+ if (mInputPasskeyDialog == null) {
+ return;
+ }
+
+ EditText userInputDialogEditText = mInputPasskeyDialog.findViewById(
+ R.id.userInputDialog);
+ if (userInputDialogEditText == null) {
+ return;
+ }
+
+ userInputDialogEditText.setText(String.format("%d", passkey));
+ }
+ };
+
+ private void showInputPasskeyDialog(KeyInputCallback keyInputCallback) {
+ if (mInputPasskeyDialog == null) {
+ View userInputView =
+ LayoutInflater.from(getApplicationContext()).inflate(R.layout.user_input_dialog,
+ null);
+ EditText userInputDialogEditText = userInputView.findViewById(R.id.userInputDialog);
+ userInputDialogEditText.setHint(R.string.passkey_input_hint);
+ userInputDialogEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
+ mInputPasskeyDialog = new AlertDialog.Builder(MainActivity.this)
+ .setView(userInputView)
+ .setCancelable(false)
+ .setPositiveButton(
+ android.R.string.ok,
+ (DialogInterface dialogBox, int id) -> {
+ String input = userInputDialogEditText.getText().toString();
+ keyInputCallback.onKeyInput(Integer.parseInt(input));
+ })
+ .setNegativeButton(android.R.string.cancel, /* listener= */ null)
+ .setTitle(R.string.passkey_dialog_title)
+ .create();
+ }
+ if (!mInputPasskeyDialog.isShowing()) {
+ mInputPasskeyDialog.show();
+ }
+ }
+
+ private void showConfirmPasskeyDialog(int passkey, Consumer<Boolean> isConfirmed) {
+ runOnUiThread(() -> new AlertDialog.Builder(MainActivity.this)
+ .setCancelable(false)
+ .setTitle(R.string.confirm_passkey)
+ .setMessage(String.valueOf(passkey))
+ .setPositiveButton(android.R.string.ok,
+ (d, w) -> isConfirmed.accept(true))
+ .setNegativeButton(android.R.string.cancel,
+ (d, w) -> isConfirmed.accept(false))
+ .create()
+ .show());
+ }
+
+ @UiThread
+ private void updateStringStatusView(int id, String name, String value) {
+ ((TextView) findViewById(id)).setText(name + ": " + value);
+ }
+
+ @UiThread
+ private void updateStatusView() {
+ TextView remoteDeviceTextView = (TextView) findViewById(R.id.remote_device_text_view);
+ remoteDeviceTextView.setBackgroundColor(
+ mBluetoothController.getRemoteDevice() != null ? LIGHT_GREEN : Color.LTGRAY);
+ String remoteDeviceString = mBluetoothController.getRemoteDeviceAsString();
+ remoteDeviceTextView.setText("Remote device: " + remoteDeviceString);
+
+ updateBooleanStatusView(
+ R.id.is_advertising_text_view,
+ "BLE advertising",
+ mFastPairSimulator != null && mFastPairSimulator.isAdvertising());
+
+ updateStringStatusView(
+ R.id.scan_mode_text_view,
+ "Mode",
+ FastPairSimulator.scanModeToString(mBluetoothController.getScanMode()));
+
+ boolean isPaired = mBluetoothController.isPaired();
+ updateBooleanStatusView(R.id.is_paired_text_view, "Paired", isPaired);
+
+ updateBooleanStatusView(
+ R.id.is_connected_text_view, "Connected", mBluetoothController.isConnected());
+ }
+
+ @UiThread
+ private void updateBooleanStatusView(int id, String name, boolean value) {
+ TextView view = (TextView) findViewById(id);
+ view.setBackgroundColor(value ? LIGHT_GREEN : Color.LTGRAY);
+ view.setText(name + ": " + (value ? "Yes" : "No"));
+ }
+
+ private String getFromIntentOrPrefs(String key, String defaultValue) {
+ Bundle extras = getIntent().getExtras();
+ extras = extras != null ? extras : new Bundle();
+ SharedPreferences prefs = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
+ String value = extras.getString(key, prefs.getString(key, defaultValue));
+ if (value == null) {
+ prefs.edit().remove(key).apply();
+ } else {
+ prefs.edit().putString(key, value).apply();
+ }
+ return value;
+ }
+
+ private boolean getFromIntentOrPrefs(String key, boolean defaultValue) {
+ Bundle extras = getIntent().getExtras();
+ extras = extras != null ? extras : new Bundle();
+ SharedPreferences prefs = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
+ boolean value = extras.getBoolean(key, prefs.getBoolean(key, defaultValue));
+ prefs.edit().putBoolean(key, value).apply();
+ return value;
+ }
+
+ private static int toTxPowerLevel(String txPowerLevelString) {
+ switch (txPowerLevelString.toUpperCase()) {
+ case "3":
+ case "HIGH":
+ return AdvertiseSettings.ADVERTISE_TX_POWER_HIGH;
+ case "2":
+ case "MEDIUM":
+ return AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM;
+ case "1":
+ case "LOW":
+ return AdvertiseSettings.ADVERTISE_TX_POWER_LOW;
+ case "0":
+ case "ULTRA_LOW":
+ return AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW;
+ default:
+ throw new IllegalArgumentException(
+ "Unexpected TxPower="
+ + txPowerLevelString
+ + ", please provide HIGH, MEDIUM, LOW, or ULTRA_LOW.");
+ }
+ }
+
+ private boolean checkPermissions(String[] permissions) {
+ for (String permission : permissions) {
+ if (checkSelfPermission(permission) != PERMISSION_GRANTED) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected void onDestroy() {
+ mRemoteDevicesManager.destroy();
+
+ if (mFastPairSimulator != null) {
+ mFastPairSimulator.destroy();
+ mBluetoothController.unregisterBluetoothStateReceiver();
+ }
+
+ // Recover the IO capability.
+ mBluetoothController.setIoCapability(
+ /*ioCapabilityClassic=*/ IO_CAPABILITY_IO, /*ioCapabilityBLE=*/
+ IO_CAPABILITY_KBDISP);
+
+ super.onDestroy();
+ }
+
+ @Override
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ // Relaunch this activity.
+ recreate();
+ }
+
+ void startAdvertisingBatteryInformationBasedOnOption(int option) {
+ if (mFastPairSimulator == null) {
+ return;
+ }
+
+ // Option 0 is "No battery info", it means simulator will not pack battery information when
+ // advertising. For the others with battery info, since we are simulating the Presto's
+ // behavior,
+ // there will always be three battery values.
+ switch (option) {
+ case 0:
+ // Option "0: No battery info"
+ mFastPairSimulator.clearBatteryValues();
+ break;
+ case 1:
+ // Option "1: Show L(⬆) + R(⬆) + C(⬆)"
+ mFastPairSimulator.setSuppressBatteryNotification(false);
+ mFastPairSimulator.setBatteryValues(new BatteryValue(true, 60),
+ new BatteryValue(true, 61),
+ new BatteryValue(true, 62));
+ break;
+ case 2:
+ // Option "2: Show L + R + C(unknown)"
+ mFastPairSimulator.setSuppressBatteryNotification(false);
+ mFastPairSimulator.setBatteryValues(new BatteryValue(false, 70),
+ new BatteryValue(false, 71),
+ new BatteryValue(false, -1));
+ break;
+ case 3:
+ // Option "3: Show L(low 10) + R(low 9) + C(low 25)"
+ mFastPairSimulator.setSuppressBatteryNotification(false);
+ mFastPairSimulator.setBatteryValues(new BatteryValue(false, 10),
+ new BatteryValue(false, 9),
+ new BatteryValue(false, 25));
+ break;
+ case 4:
+ // Option "4: Suppress battery w/o level changes"
+ // Just change the suppress bit and keep the battery values the same as before.
+ mFastPairSimulator.setSuppressBatteryNotification(true);
+ break;
+ case 5:
+ // Option "5: Suppress L(low 10) + R(11) + C"
+ mFastPairSimulator.setSuppressBatteryNotification(true);
+ mFastPairSimulator.setBatteryValues(new BatteryValue(false, 10),
+ new BatteryValue(false, 11),
+ new BatteryValue(false, 82));
+ break;
+ case 6:
+ // Option "6: Suppress L(low ⬆) + R(low ⬆) + C(low 10)"
+ mFastPairSimulator.setSuppressBatteryNotification(true);
+ mFastPairSimulator.setBatteryValues(new BatteryValue(true, 10),
+ new BatteryValue(true, 9),
+ new BatteryValue(false, 10));
+ break;
+ case 7:
+ // Option "7: Suppress L(low ⬆) + R(low ⬆) + C(low ⬆)"
+ mFastPairSimulator.setSuppressBatteryNotification(true);
+ mFastPairSimulator.setBatteryValues(new BatteryValue(true, 10),
+ new BatteryValue(true, 9),
+ new BatteryValue(true, 25));
+ break;
+ case 8:
+ // Option "8: Show subsequent pairing notification"
+ mFastPairSimulator.setSuppressSubsequentPairingNotification(false);
+ break;
+ case 9:
+ // Option "9: Suppress subsequent pairing notification"
+ mFastPairSimulator.setSuppressSubsequentPairingNotification(true);
+ break;
+ default:
+ // Unknown option, do nothing.
+ return;
+ }
+
+ mFastPairSimulator.startAdvertising();
+ }
+}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/RemoteDeviceListener.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/RemoteDeviceListener.java
new file mode 100644
index 0000000..fac8cb5
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/RemoteDeviceListener.java
@@ -0,0 +1,164 @@
+/*
+ * 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.provider.simulator.app;
+
+import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.ACCOUNT_KEY;
+import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.ACKNOWLEDGE;
+import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.BLUETOOTH_ADDRESS_BLE;
+import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.BLUETOOTH_ADDRESS_PUBLIC;
+
+import android.nearby.fastpair.provider.FastPairSimulator;
+import android.nearby.fastpair.provider.FastPairSimulator.BatteryValue;
+import android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Command;
+import android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Command.BatteryInfo;
+import android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event;
+import android.nearby.fastpair.provider.simulator.testing.InputStreamListener;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+/** Listener for input stream of the remote device. */
+public class RemoteDeviceListener implements InputStreamListener {
+ private static final String TAG = RemoteDeviceListener.class.getSimpleName();
+
+ private final MainActivity mMainActivity;
+ @Nullable
+ private FastPairSimulator mFastPairSimulator;
+
+ public RemoteDeviceListener(MainActivity mainActivity) {
+ this.mMainActivity = mainActivity;
+ }
+
+ @Override
+ public void onInputData(ByteString byteString) {
+ Command command;
+ try {
+ command = Command.parseFrom(byteString);
+ } catch (InvalidProtocolBufferException e) {
+ Log.w(TAG, String.format("%s input data is not a Command",
+ mMainActivity.mRemoteDeviceId), e);
+ return;
+ }
+
+ mMainActivity.runOnUiThread(() -> {
+ Log.d(TAG, String.format("%s new command %s",
+ mMainActivity.mRemoteDeviceId, command.getCode()));
+ switch (command.getCode()) {
+ case POLLING:
+ mMainActivity.sendEventToRemoteDevice(
+ Event.newBuilder().setCode(ACKNOWLEDGE));
+ break;
+ case RESET:
+ mMainActivity.reset();
+ break;
+ case SHOW_BATTERY:
+ onShowBattery(command.getBatteryInfo());
+ break;
+ case HIDE_BATTERY:
+ onHideBattery();
+ break;
+ case REQUEST_BLUETOOTH_ADDRESS_BLE:
+ onRequestBleAddress();
+ break;
+ case REQUEST_BLUETOOTH_ADDRESS_PUBLIC:
+ onRequestPublicAddress();
+ break;
+ case REQUEST_ACCOUNT_KEY:
+ ByteString accountKey = mMainActivity.getAccontKey();
+ if (accountKey == null) {
+ break;
+ }
+ mMainActivity.sendEventToRemoteDevice(
+ Event.newBuilder().setCode(ACCOUNT_KEY)
+ .setAccountKey(accountKey));
+ break;
+ }
+ });
+ }
+
+ @Override
+ public void onClose() {
+ Log.d(TAG, String.format("%s input stream is closed", mMainActivity.mRemoteDeviceId));
+ }
+
+ void setFastPairSimulator(FastPairSimulator fastPairSimulator) {
+ this.mFastPairSimulator = fastPairSimulator;
+ }
+
+ private void onShowBattery(@Nullable BatteryInfo batteryInfo) {
+ if (mFastPairSimulator == null || batteryInfo == null) {
+ Log.w(TAG, "skip showing battery");
+ return;
+ }
+
+ if (batteryInfo.getBatteryValuesCount() != 3) {
+ Log.w(TAG, String.format("skip showing battery: count is not valid %d",
+ batteryInfo.getBatteryValuesCount()));
+ return;
+ }
+
+ Log.d(TAG, String.format("Show battery %s", batteryInfo));
+
+ if (batteryInfo.hasSuppressNotification()) {
+ mFastPairSimulator.setSuppressBatteryNotification(
+ batteryInfo.getSuppressNotification());
+ }
+ mFastPairSimulator.setBatteryValues(
+ convertFrom(batteryInfo.getBatteryValues(0)),
+ convertFrom(batteryInfo.getBatteryValues(1)),
+ convertFrom(batteryInfo.getBatteryValues(2)));
+ mFastPairSimulator.startAdvertising();
+ }
+
+ private void onHideBattery() {
+ if (mFastPairSimulator == null) {
+ return;
+ }
+
+ mFastPairSimulator.clearBatteryValues();
+ mFastPairSimulator.startAdvertising();
+ }
+
+ private void onRequestBleAddress() {
+ if (mFastPairSimulator == null) {
+ return;
+ }
+
+ mMainActivity.sendEventToRemoteDevice(
+ Event.newBuilder()
+ .setCode(BLUETOOTH_ADDRESS_BLE)
+ .setBleAddress(mFastPairSimulator.getBleAddress()));
+ }
+
+ private void onRequestPublicAddress() {
+ if (mFastPairSimulator == null) {
+ return;
+ }
+
+ mMainActivity.sendEventToRemoteDevice(
+ Event.newBuilder()
+ .setCode(BLUETOOTH_ADDRESS_PUBLIC)
+ .setPublicAddress(mFastPairSimulator.getBluetoothAddress()));
+ }
+
+ private static BatteryValue convertFrom(BatteryInfo.BatteryValue batteryValue) {
+ return new BatteryValue(batteryValue.getCharging(), batteryValue.getLevel());
+ }
+}
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairAdvertiser.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/InputStreamListener.java
similarity index 62%
copy from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairAdvertiser.java
copy to nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/InputStreamListener.java
index 33add27..b29225a 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairAdvertiser.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/InputStreamListener.java
@@ -14,14 +14,16 @@
* limitations under the License.
*/
-package com.android.server.nearby.common.bluetooth.fastpair.testing;
+package android.nearby.fastpair.provider.simulator.testing;
-import androidx.annotation.Nullable;
+import com.google.protobuf.ByteString;
-/** Helper for advertising Fast Pair data. */
-public interface FastPairAdvertiser {
+/** Listener for input stream. */
+public interface InputStreamListener {
- void startAdvertising(@Nullable byte[] serviceData);
+ /** Called when new data {@code byteString} is read from the input stream. */
+ void onInputData(ByteString byteString);
- void stopAdvertising();
+ /** Called when the input stream is closed. */
+ void onClose();
}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/LocalFileStreamIOHandler.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/LocalFileStreamIOHandler.java
new file mode 100644
index 0000000..cf8b022
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/LocalFileStreamIOHandler.java
@@ -0,0 +1,119 @@
+/*
+ * 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.provider.simulator.testing;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.io.BaseEncoding.base16;
+
+import android.net.Uri;
+
+import androidx.annotation.Nullable;
+
+import com.google.protobuf.ByteString;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+
+/**
+ * Opens the {@code inputUri} and {@code outputUri} as local files and provides reading/writing
+ * data operations.
+ *
+ * To support bluetooth testing on real devices, the named pipes are created as local files and the
+ * pipe data are transferred via usb cable, then (1) the peripheral device writes {@code Event} to
+ * the output stream and reads {@code Command} from the input stream (2) the central devices write
+ * {@code Command} to the output stream and read {@code Event} from the input stream.
+ *
+ * The {@code Event} and {@code Command} are special protocols which are defined at
+ * simulator_stream_protocol.proto.
+ */
+public class LocalFileStreamIOHandler implements StreamIOHandler {
+
+ private static final int MAX_IO_DATA_LENGTH_BYTE = 65535;
+
+ private final String mInputPath;
+ private final String mOutputPath;
+
+ LocalFileStreamIOHandler(Uri inputUri, Uri outputUri) throws IOException {
+ if (!isFileExists(inputUri.getPath())) {
+ throw new FileNotFoundException("Input path is not exists.");
+ }
+ if (!isFileExists(outputUri.getPath())) {
+ throw new FileNotFoundException("Output path is not exists.");
+ }
+
+ this.mInputPath = inputUri.getPath();
+ this.mOutputPath = outputUri.getPath();
+ }
+
+ /**
+ * Reads a {@code ByteString} from the input stream. The input stream must be opened before
+ * calling this method.
+ */
+ @Override
+ public ByteString read() throws IOException {
+ try (InputStreamReader inputStream = new InputStreamReader(
+ new FileInputStream(mInputPath))) {
+ int size = inputStream.read();
+ if (size == 0) {
+ throw new IOException(String.format("Missing data size %d", size));
+ }
+
+ if (size > MAX_IO_DATA_LENGTH_BYTE) {
+ throw new IOException("Exceed the maximum data length when reading.");
+ }
+
+ char[] data = new char[size];
+ int count = inputStream.read(data);
+ if (count != size) {
+ throw new IOException(
+ String.format("Expected size was %s but got %s", size, count));
+ }
+
+ return ByteString.copyFrom(base16().decode(new String(data)));
+ }
+ }
+
+ /**
+ * Writes a {@code output} into the output stream. The output stream must be opened before
+ * calling this method.
+ */
+ @Override
+ public void write(ByteString output) throws IOException {
+ checkArgument(output.size() > 0, "Output data is empty.");
+
+ if (output.size() > MAX_IO_DATA_LENGTH_BYTE) {
+ throw new IOException("Exceed the maximum data length when writing.");
+ }
+
+ try (OutputStreamWriter outputStream =
+ new OutputStreamWriter(new FileOutputStream(mOutputPath))) {
+ String base16Output = base16().encode(output.toByteArray());
+ outputStream.write(base16Output.length());
+ outputStream.write(base16Output);
+ }
+ }
+
+ private static boolean isFileExists(@Nullable String path) {
+ return path != null && new File(path).exists();
+ }
+}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/Reflect.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/Reflect.java
new file mode 100644
index 0000000..16fbc71
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/Reflect.java
@@ -0,0 +1,103 @@
+/*
+ * 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.provider.simulator.testing;
+
+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.
+ *
+ * <p>Example usage:
+ *
+ * <pre>{@code
+ * try {
+ * Reflect.on(btAdapter)
+ * .withMethod("setScanMode", int.class)
+ * .invoke(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)
+ * } catch (ReflectionException e) { }
+ * }</pre>
+ */
+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/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairAdvertiser.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/ReflectionException.java
similarity index 63%
copy from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairAdvertiser.java
copy to nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/ReflectionException.java
index 33add27..2a65f39 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairAdvertiser.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/ReflectionException.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.server.nearby.common.bluetooth.fastpair.testing;
+package android.nearby.fastpair.provider.simulator.testing;
-import androidx.annotation.Nullable;
-
-/** Helper for advertising Fast Pair data. */
-public interface FastPairAdvertiser {
-
- void startAdvertising(@Nullable byte[] serviceData);
-
- void stopAdvertising();
+/**
+ * An exception thrown during a reflection operation. Like ReflectiveOperationException, except
+ * compatible on older API versions.
+ */
+public final class ReflectionException extends Exception {
+ public ReflectionException(Throwable cause) {
+ super(cause.getMessage(), cause);
+ }
}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/RemoteDevice.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/RemoteDevice.java
new file mode 100644
index 0000000..11ec9cb
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/RemoteDevice.java
@@ -0,0 +1,46 @@
+/*
+ * 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.provider.simulator.testing;
+
+/** Represents a remote device and provides a {@link StreamIOHandler} to communicate with it. */
+public class RemoteDevice {
+ private final String mId;
+ private final StreamIOHandler mStreamIOHandler;
+ private final InputStreamListener mInputStreamListener;
+
+ public RemoteDevice(
+ String id, StreamIOHandler streamIOHandler, InputStreamListener inputStreamListener) {
+ this.mId = id;
+ this.mStreamIOHandler = streamIOHandler;
+ this.mInputStreamListener = inputStreamListener;
+ }
+
+ /** The id used by this device. */
+ public String getId() {
+ return mId;
+ }
+
+ /** The handler processes input and output data channels. */
+ public StreamIOHandler getStreamIOHandler() {
+ return mStreamIOHandler;
+ }
+
+ /** Listener for the input stream. */
+ public InputStreamListener getInputStreamListener() {
+ return mInputStreamListener;
+ }
+}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/RemoteDevicesManager.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/RemoteDevicesManager.java
new file mode 100644
index 0000000..02260c2
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/RemoteDevicesManager.java
@@ -0,0 +1,140 @@
+/*
+ * 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.provider.simulator.testing;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import android.util.Log;
+
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.protobuf.ByteString;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+
+/**
+ * Manages the IO streams with remote devices.
+ *
+ * <p>The caller must invoke {@link #registerRemoteDevice} before starting to communicate with the
+ * remote device, and invoke {@link #unregisterRemoteDevice} after finishing tasks. If this instance
+ * is not used anymore, the caller need to invoke {@link #destroy} to release all resources.
+ *
+ * <p>All of the methods are thread-safe.
+ */
+public class RemoteDevicesManager {
+ private static final String TAG = "RemoteDevicesManager";
+
+ private final Map<String, RemoteDevice> mRemoteDeviceMap = new HashMap<>();
+ private final ListeningExecutorService mBackgroundExecutor =
+ MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
+ private final ListeningExecutorService mListenInputStreamExecutors =
+ MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
+ private final Map<String, ListenableFuture<Void>> mListeningTaskMap = new HashMap<>();
+
+ /**
+ * Opens input and output data streams for {@code remoteDevice} in the background and notifies
+ * the
+ * open result via {@code callback}, and assigns a dedicated executor to listen the input data
+ * stream if data streams are opened successfully. The dedicated executor will invoke the
+ * {@code
+ * remoteDevice.inputStreamListener().onInputData()} directly if the new data exists in the
+ * input
+ * stream and invoke the {@code remoteDevice.inputStreamListener().onClose()} if the input
+ * stream
+ * is closed.
+ */
+ public synchronized void registerRemoteDevice(String id, RemoteDevice remoteDevice) {
+ checkState(mRemoteDeviceMap.put(id, remoteDevice) == null,
+ "The %s is already registered", id);
+ startListeningInputStreamTask(remoteDevice);
+ }
+
+ /**
+ * Closes the data streams for specific remote device {@code id} in the background and notifies
+ * the result via {@code callback}.
+ */
+ public synchronized void unregisterRemoteDevice(String id) {
+ RemoteDevice remoteDevice = mRemoteDeviceMap.remove(id);
+ checkState(remoteDevice != null, "The %s is not registered", id);
+ if (mListeningTaskMap.containsKey(id)) {
+ mListeningTaskMap.remove(id).cancel(/* mayInterruptIfRunning= */ true);
+ }
+ }
+
+ /** Closes all data streams of registered remote devices and stop all background tasks. */
+ public synchronized void destroy() {
+ mRemoteDeviceMap.clear();
+ mListeningTaskMap.clear();
+ mListenInputStreamExecutors.shutdownNow();
+ }
+
+ /**
+ * Writes {@code data} into the output data stream of specific remote device {@code id} in the
+ * background and notifies the result via {@code callback}.
+ */
+ public synchronized void writeDataToRemoteDevice(
+ String id, ByteString data, FutureCallback<Void> callback) {
+ RemoteDevice remoteDevice = mRemoteDeviceMap.get(id);
+ checkState(remoteDevice != null, "The %s is not registered", id);
+
+ runInBackground(() -> {
+ remoteDevice.getStreamIOHandler().write(data);
+ return null;
+ }, callback);
+ }
+
+ private void runInBackground(Callable<Void> callable, FutureCallback<Void> callback) {
+ Futures.addCallback(
+ mBackgroundExecutor.submit(callable), callback, MoreExecutors.directExecutor());
+ }
+
+ private void startListeningInputStreamTask(RemoteDevice remoteDevice) {
+ ListenableFuture<Void> listenFuture = mListenInputStreamExecutors.submit(() -> {
+ Log.i(TAG, "Start listening " + remoteDevice.getId());
+ while (true) {
+ ByteString data;
+ try {
+ data = remoteDevice.getStreamIOHandler().read();
+ } catch (IOException | IllegalStateException e) {
+ break;
+ }
+ remoteDevice.getInputStreamListener().onInputData(data);
+ }
+ }, /* result= */ null);
+ Futures.addCallback(listenFuture, new FutureCallback<Void>() {
+ @Override
+ public void onSuccess(Void result) {
+ Log.i(TAG, "Stop listening " + remoteDevice.getId());
+ remoteDevice.getInputStreamListener().onClose();
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.w(TAG, "Stop listening " + remoteDevice.getId() + ", cause: " + t);
+ remoteDevice.getInputStreamListener().onClose();
+ }
+ }, MoreExecutors.directExecutor());
+ mListeningTaskMap.put(remoteDevice.getId(), listenFuture);
+ }
+}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/StreamIOHandler.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/StreamIOHandler.java
new file mode 100644
index 0000000..d5fdb9e
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/StreamIOHandler.java
@@ -0,0 +1,43 @@
+/*
+ * 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.provider.simulator.testing;
+
+import com.google.protobuf.ByteString;
+
+import java.io.IOException;
+
+/**
+ * Opens input and output data channels, then provides read and write operations to the data
+ * channels.
+ */
+public interface StreamIOHandler {
+ /**
+ * Reads stream data from the input channel.
+ *
+ * @return a protocol buffer contains the input message
+ * @throws IOException errors occur when reading the input stream
+ */
+ ByteString read() throws IOException;
+
+ /**
+ * Writes stream data to the output channel.
+ *
+ * @param output a protocol buffer contains the output message
+ * @throws IOException errors occur when writing the output message to output stream
+ */
+ void write(ByteString output) throws IOException;
+}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/StreamIOHandlerFactory.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/StreamIOHandlerFactory.java
new file mode 100644
index 0000000..24cfe56
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/StreamIOHandlerFactory.java
@@ -0,0 +1,44 @@
+/*
+ * 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.provider.simulator.testing;
+
+import android.net.Uri;
+
+import java.io.IOException;
+
+/** A simple factory creating {@link StreamIOHandler} according to {@link Type}. */
+public class StreamIOHandlerFactory {
+
+ /** Types for creating {@link StreamIOHandler}. */
+ public enum Type {
+
+ /**
+ * A {@link StreamIOHandler} accepts local file uris and provides reading/writing file
+ * operations.
+ */
+ LOCAL_FILE
+ }
+
+ /** Creates an instance of {@link StreamIOHandler}. */
+ public static StreamIOHandler createStreamIOHandler(Type type, Uri input, Uri output)
+ throws IOException {
+ if (type.equals(Type.LOCAL_FILE)) {
+ return new LocalFileStreamIOHandler(input, output);
+ }
+ throw new IllegalArgumentException(String.format("Can't support %s", type));
+ }
+}
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairAdvertiser.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairAdvertiser.java
similarity index 92%
rename from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairAdvertiser.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairAdvertiser.java
index 33add27..95c077b 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairAdvertiser.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairAdvertiser.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.nearby.common.bluetooth.fastpair.testing;
+package android.nearby.fastpair.provider;
import androidx.annotation.Nullable;
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairSimulator.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulator.java
similarity index 76%
rename from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairSimulator.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulator.java
index cf8be76..931f2e0 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairSimulator.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulator.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.nearby.common.bluetooth.fastpair.testing;
+package android.nearby.fastpair.provider;
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
@@ -29,6 +29,8 @@
import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_NOTIFY;
import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_READ;
import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_WRITE;
+import static android.nearby.fastpair.provider.bluetooth.BluetoothManager.wrap;
+import static android.nearby.fastpair.provider.bluetooth.RfcommServer.State.CONNECTED;
import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH;
import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.encrypt;
@@ -37,8 +39,6 @@
import static com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService.BLUETOOTH_SIG_ORGANIZATION_ID;
import static com.android.server.nearby.common.bluetooth.fastpair.EllipticCurveDiffieHellmanExchange.PUBLIC_KEY_LENGTH;
import static com.android.server.nearby.common.bluetooth.fastpair.MessageStreamHmacEncoder.SECTION_NONCE_LENGTH;
-import static com.android.server.nearby.common.bluetooth.fastpair.testing.RfcommServer.State.CONNECTED;
-import static com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothManager.wrap;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.primitives.Bytes.concat;
@@ -56,12 +56,22 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.nearby.multidevices.fastpair.EventStreamProtocol.AcknowledgementEventCode;
-import android.nearby.multidevices.fastpair.EventStreamProtocol.DeviceActionEventCode;
-import android.nearby.multidevices.fastpair.EventStreamProtocol.DeviceCapabilitySyncEventCode;
-import android.nearby.multidevices.fastpair.EventStreamProtocol.DeviceConfigurationEventCode;
-import android.nearby.multidevices.fastpair.EventStreamProtocol.DeviceEventCode;
-import android.nearby.multidevices.fastpair.EventStreamProtocol.EventGroup;
+import android.nearby.fastpair.provider.EventStreamProtocol.AcknowledgementEventCode;
+import android.nearby.fastpair.provider.EventStreamProtocol.DeviceActionEventCode;
+import android.nearby.fastpair.provider.EventStreamProtocol.DeviceCapabilitySyncEventCode;
+import android.nearby.fastpair.provider.EventStreamProtocol.DeviceConfigurationEventCode;
+import android.nearby.fastpair.provider.EventStreamProtocol.DeviceEventCode;
+import android.nearby.fastpair.provider.EventStreamProtocol.EventGroup;
+import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerConfig;
+import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerConfig.ServiceConfig;
+import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerConnection;
+import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerConnection.Notifier;
+import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerHelper;
+import android.nearby.fastpair.provider.bluetooth.BluetoothGattServlet;
+import android.nearby.fastpair.provider.bluetooth.RfcommServer;
+import android.nearby.fastpair.provider.crypto.Crypto;
+import android.nearby.fastpair.provider.crypto.E2eeCalculator;
+import android.nearby.fastpair.provider.utils.Logger;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
@@ -99,12 +109,6 @@
import com.android.server.nearby.common.bluetooth.fastpair.NamingEncoder;
import com.android.server.nearby.common.bluetooth.fastpair.Reflect;
import com.android.server.nearby.common.bluetooth.fastpair.ReflectionException;
-import com.android.server.nearby.common.bluetooth.gatt.server.BluetoothGattServerConfig;
-import com.android.server.nearby.common.bluetooth.gatt.server.BluetoothGattServerConfig.ServiceConfig;
-import com.android.server.nearby.common.bluetooth.gatt.server.BluetoothGattServerConnection;
-import com.android.server.nearby.common.bluetooth.gatt.server.BluetoothGattServerConnection.Notifier;
-import com.android.server.nearby.common.bluetooth.gatt.server.BluetoothGattServerHelper;
-import com.android.server.nearby.common.bluetooth.gatt.server.BluetoothGattServlet;
import com.google.common.base.Ascii;
import com.google.common.primitives.Bytes;
@@ -156,8 +160,8 @@
* @see {http://go/fast-pair-2-spec}
*/
public class FastPairSimulator {
- private static final String TAG = "FastPairSimulator";
- private final Logger logger = new Logger(TAG);
+ public static final String TAG = "FastPairSimulator";
+ private final Logger mLogger;
/**
* Headphones. Generated by
@@ -207,11 +211,11 @@
* </ul>
* </ul>
*/
- private String deviceFirmwareVersion = "1.1.0";
+ private String mDeviceFirmwareVersion = "1.1.0";
- private byte[] sessionNonce;
+ private byte[] mSessionNonce;
- private boolean useLogFullEvent = true;
+ private boolean mUseLogFullEvent = true;
private enum ResultCode {
SUCCESS((byte) 0x00),
@@ -220,10 +224,10 @@
UNSUPPORTED_ORGANIZATION_ID((byte) 0x03),
OPERATION_FAILED((byte) 0x04);
- private final byte byteValue;
+ private final byte mByteValue;
ResultCode(byte byteValue) {
- this.byteValue = byteValue;
+ this.mByteValue = byteValue;
}
}
@@ -232,33 +236,33 @@
ON((byte) 0x01),
TEMPORARILY_UNAVAILABLE((byte) 0x10);
- private final byte byteValue;
+ private final byte mByteValue;
TransportState(byte byteValue) {
- this.byteValue = byteValue;
+ this.mByteValue = byteValue;
}
}
- private final Context context;
- private final Options options;
- private final Handler uiThreadHandler = new Handler(Looper.getMainLooper());
+ private final Context mContext;
+ private final Options mOptions;
+ private final Handler mUiThreadHandler = new Handler(Looper.getMainLooper());
// No thread pool: Only used in test app (outside gmscore) and in javatests/.../gmscore/.
- private final ScheduledExecutorService executor =
+ private final ScheduledExecutorService mExecutor =
Executors.newSingleThreadScheduledExecutor(); // exempt
- private final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- private final BroadcastReceiver broadcastReceiver =
+ private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ private final BroadcastReceiver mBroadcastReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (shouldFailPairing) {
- logger.log("Pairing disabled by test app switch");
+ if (mShouldFailPairing) {
+ mLogger.log("Pairing disabled by test app switch");
return;
}
- if (isDestroyed) {
+ if (mIsDestroyed) {
// Sometimes this receiver does not successfully unregister in destroy()
// which causes events to occur after the simulator is stopped, so ignore
// those events.
- logger.log("Intent received after simulator destroyed, ignoring");
+ mLogger.log("Intent received after simulator destroyed, ignoring");
return;
}
BluetoothDevice device = intent.getParcelableExtra(
@@ -266,52 +270,52 @@
switch (intent.getAction()) {
case BluetoothAdapter.ACTION_SCAN_MODE_CHANGED:
if (isDiscoverable()) {
- isDiscoverableLatch.countDown();
+ mIsDiscoverableLatch.countDown();
}
break;
case BluetoothDevice.ACTION_PAIRING_REQUEST:
int variant = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
ERROR);
int key = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, ERROR);
- logger.log(
+ mLogger.log(
"Pairing request, variant=%d, key=%s", variant,
key == ERROR ? "(none)" : key);
// Prevent Bluetooth Settings from getting the pairing request.
abortBroadcast();
- pairingDevice = device;
- if (secret == null) {
+ mPairingDevice = device;
+ if (mSecret == null) {
// We haven't done the handshake over GATT to agree on the shared
// secret. For now, just accept anyway (so we can still simulate
// old 1.0 model IDs).
- logger.log("No handshake, auto-accepting anyway.");
+ mLogger.log("No handshake, auto-accepting anyway.");
setPasskeyConfirmation(true);
} else if (variant
== BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) {
// Store the passkey. And check it, since there's a race (see
// method for why). Usually this check is a no-op and we'll get
// the passkey later over GATT.
- localPasskey = key;
+ mLocalPasskey = key;
checkPasskey();
} else if (variant == PAIRING_VARIANT_DISPLAY_PASSKEY) {
- if (passkeyEventCallback != null) {
- passkeyEventCallback.onPasskeyRequested(
+ if (mPasskeyEventCallback != null) {
+ mPasskeyEventCallback.onPasskeyRequested(
FastPairSimulator.this::enterPassKey);
} else {
- logger.log("passkeyEventCallback is not set!");
+ mLogger.log("passkeyEventCallback is not set!");
enterPassKey(key);
}
} else if (variant == PAIRING_VARIANT_CONSENT) {
setPasskeyConfirmation(true);
} else if (variant == BluetoothDevice.PAIRING_VARIANT_PIN) {
- if (passkeyEventCallback != null) {
- passkeyEventCallback.onPasskeyRequested(
+ if (mPasskeyEventCallback != null) {
+ mPasskeyEventCallback.onPasskeyRequested(
(int pin) -> {
byte[] newPin = convertPinToBytes(
String.format(Locale.ENGLISH, "%d", pin));
- pairingDevice.setPin(newPin);
+ mPairingDevice.setPin(newPin);
});
}
} else {
@@ -324,36 +328,36 @@
int bondState =
intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
BluetoothDevice.BOND_NONE);
- logger.log("Bond state to %s changed to %d", device, bondState);
+ mLogger.log("Bond state to %s changed to %d", device, bondState);
switch (bondState) {
case BluetoothDevice.BOND_BONDING:
// If we've started bonding, we shouldn't be advertising.
- advertiser.stopAdvertising();
+ mAdvertiser.stopAdvertising();
// Not discoverable anymore, but still connectable.
setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
break;
case BluetoothDevice.BOND_BONDED:
// Once bonded, advertise the account keys.
- advertiser.startAdvertising(accountKeysServiceData());
+ mAdvertiser.startAdvertising(accountKeysServiceData());
setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
// If it is subsequent pair, we need to add paired device here.
- if (isSubsequentPair
- && secret != null
- && secret.length == AES_BLOCK_LENGTH) {
- addAccountKey(secret, pairingDevice);
+ if (mIsSubsequentPair
+ && mSecret != null
+ && mSecret.length == AES_BLOCK_LENGTH) {
+ addAccountKey(mSecret, mPairingDevice);
}
break;
case BluetoothDevice.BOND_NONE:
// If the bonding process fails, we should be advertising again.
- advertiser.startAdvertising(getServiceData());
+ mAdvertiser.startAdvertising(getServiceData());
break;
default:
break;
}
break;
case BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED:
- logger.log(
+ mLogger.log(
"Connection state to %s changed to %d",
device,
intent.getIntExtra(
@@ -362,7 +366,7 @@
break;
case BluetoothAdapter.ACTION_STATE_CHANGED:
int state = intent.getIntExtra(EXTRA_STATE, -1);
- logger.log("Bluetooth adapter state=%s", state);
+ mLogger.log("Bluetooth adapter state=%s", state);
switch (state) {
case STATE_ON:
startRfcommServer();
@@ -374,7 +378,7 @@
}
break;
default:
- logger.log(new IllegalArgumentException(intent.toString()),
+ mLogger.log(new IllegalArgumentException(intent.toString()),
"Received unexpected intent");
break;
}
@@ -394,7 +398,7 @@
return pinBytes;
}
- private final NotifiableGattServlet passkeyServlet =
+ private final NotifiableGattServlet mPasskeyServlet =
new NotifiableGattServlet() {
@Override
// Simulating deprecated API {@code PasskeyCharacteristic.ID} for testing.
@@ -409,28 +413,28 @@
@Override
public void write(
BluetoothGattServerConnection connection, int offset, byte[] value) {
- logger.log("Got value from passkey servlet: %s", base16().encode(value));
- if (secret == null) {
- logger.log("Ignoring write to passkey characteristic, no pairing secret.");
+ mLogger.log("Got value from passkey servlet: %s", base16().encode(value));
+ if (mSecret == null) {
+ mLogger.log("Ignoring write to passkey characteristic, no pairing secret.");
return;
}
try {
- remotePasskey = PasskeyCharacteristic.decrypt(
- PasskeyCharacteristic.Type.SEEKER, secret, value);
- if (passkeyEventCallback != null) {
- passkeyEventCallback.onRemotePasskeyReceived(remotePasskey);
+ mRemotePasskey = PasskeyCharacteristic.decrypt(
+ PasskeyCharacteristic.Type.SEEKER, mSecret, value);
+ if (mPasskeyEventCallback != null) {
+ mPasskeyEventCallback.onRemotePasskeyReceived(mRemotePasskey);
}
checkPasskey();
} catch (GeneralSecurityException e) {
- logger.log(
+ mLogger.log(
"Decrypting passkey value %s failed using key %s",
- base16().encode(value), base16().encode(secret));
+ base16().encode(value), base16().encode(mSecret));
}
}
};
- private final NotifiableGattServlet deviceNameServlet =
+ private final NotifiableGattServlet mDeviceNameServlet =
new NotifiableGattServlet() {
@Override
// Simulating deprecated API {@code NameCharacteristic.ID} for testing.
@@ -445,112 +449,112 @@
@Override
public void write(
BluetoothGattServerConnection connection, int offset, byte[] value) {
- logger.log("Got value from device naming servlet: %s", base16().encode(value));
- if (secret == null) {
- logger.log("Ignoring write to name characteristic, no pairing secret.");
+ mLogger.log("Got value from device naming servlet: %s", base16().encode(value));
+ if (mSecret == null) {
+ mLogger.log("Ignoring write to name characteristic, no pairing secret.");
return;
}
// Parse the device name from seeker to write name into provider.
- logger.log("Got name byte array size = %d", value.length);
+ mLogger.log("Got name byte array size = %d", value.length);
try {
String decryptedDeviceName =
- NamingEncoder.decodeNamingPacket(secret, value);
+ NamingEncoder.decodeNamingPacket(mSecret, value);
if (decryptedDeviceName != null) {
setDeviceName(decryptedDeviceName.getBytes(StandardCharsets.UTF_8));
- logger.log("write device name = %s", decryptedDeviceName);
+ mLogger.log("write device name = %s", decryptedDeviceName);
}
} catch (GeneralSecurityException e) {
- logger.log(e, "Failed to decrypt device name.");
+ mLogger.log(e, "Failed to decrypt device name.");
}
// For testing to make sure we get the new provider name from simulator.
- if (writeNameCountDown != null) {
- logger.log("finish count down latch to write device name.");
- writeNameCountDown.countDown();
+ if (mWriteNameCountDown != null) {
+ mLogger.log("finish count down latch to write device name.");
+ mWriteNameCountDown.countDown();
}
}
};
- private Value bluetoothAddress;
- private final FastPairAdvertiser advertiser;
+ private Value mBluetoothAddress;
+ private final FastPairAdvertiser mAdvertiser;
private final Map<String, BluetoothGattServerHelper> mBluetoothGattServerHelpers =
new HashMap<>();
- private CountDownLatch isDiscoverableLatch = new CountDownLatch(1);
- private ScheduledFuture<?> revertDiscoverableFuture;
- private boolean shouldFailPairing = false;
- private boolean isDestroyed = false;
- private boolean isAdvertising;
+ private CountDownLatch mIsDiscoverableLatch = new CountDownLatch(1);
+ private ScheduledFuture<?> mRevertDiscoverableFuture;
+ private boolean mShouldFailPairing = false;
+ private boolean mIsDestroyed = false;
+ private boolean mIsAdvertising;
@Nullable
- private String bleAddress;
- private BluetoothDevice pairingDevice;
- private int localPasskey;
- private int remotePasskey;
+ private String mBleAddress;
+ private BluetoothDevice mPairingDevice;
+ private int mLocalPasskey;
+ private int mRemotePasskey;
@Nullable
- private byte[] secret;
+ private byte[] mSecret;
@Nullable
- private byte[] accountKey; // The latest account key added.
+ private byte[] mAccountKey; // The latest account key added.
// The first account key added. Eddystone treats that account as the owner of the device.
@Nullable
- private byte[] ownerAccountKey;
+ private byte[] mOwnerAccountKey;
@Nullable
- private PasskeyConfirmationCallback passkeyConfirmationCallback;
+ private PasskeyConfirmationCallback mPasskeyConfirmationCallback;
@Nullable
- private DeviceNameCallback deviceNameCallback;
+ private DeviceNameCallback mDeviceNameCallback;
@Nullable
- private PasskeyEventCallback passkeyEventCallback;
- private final List<BatteryValue> batteryValues;
- private boolean suppressBatteryNotification = false;
- private boolean suppressSubsequentPairingNotification = false;
- HandshakeRequest handshakeRequest;
+ private PasskeyEventCallback mPasskeyEventCallback;
+ private final List<BatteryValue> mBatteryValues;
+ private boolean mSuppressBatteryNotification = false;
+ private boolean mSuppressSubsequentPairingNotification = false;
+ HandshakeRequest mHandshakeRequest;
@Nullable
- private CountDownLatch writeNameCountDown;
- private final RfcommServer rfcommServer = new RfcommServer();
- private final boolean dataOnlyConnection;
- private boolean supportDynamicBufferSize = false;
- private NotifiableGattServlet beaconActionsServlet;
- private final FastPairSimulatorDatabase fastPairSimulatorDatabase;
- private boolean isSubsequentPair = false;
+ private CountDownLatch mWriteNameCountDown;
+ private final RfcommServer mRfcommServer = new RfcommServer();
+ private final boolean mDataOnlyConnection;
+ private boolean mSupportDynamicBufferSize = false;
+ private NotifiableGattServlet mBeaconActionsServlet;
+ private final FastPairSimulatorDatabase mFastPairSimulatorDatabase;
+ private boolean mIsSubsequentPair = false;
/** Sets the flag for failing paring for debug purpose. */
public void setShouldFailPairing(boolean shouldFailPairing) {
- this.shouldFailPairing = shouldFailPairing;
+ this.mShouldFailPairing = shouldFailPairing;
}
/** Gets the flag for failing paring for debug purpose. */
public boolean getShouldFailPairing() {
- return shouldFailPairing;
+ return mShouldFailPairing;
}
/** Clear the battery values, then no battery information is packed when advertising. */
public void clearBatteryValues() {
- batteryValues.clear();
+ mBatteryValues.clear();
}
/** Sets the battery items which will be included in the advertisement packet. */
public void setBatteryValues(BatteryValue... batteryValues) {
- this.batteryValues.clear();
- Collections.addAll(this.batteryValues, batteryValues);
+ this.mBatteryValues.clear();
+ Collections.addAll(this.mBatteryValues, batteryValues);
}
/** Sets whether the battery advertisement packet is within suppress type or not. */
public void setSuppressBatteryNotification(boolean suppressBatteryNotification) {
- this.suppressBatteryNotification = suppressBatteryNotification;
+ this.mSuppressBatteryNotification = suppressBatteryNotification;
}
/** Sets whether the account key data is within suppress type or not. */
public void setSuppressSubsequentPairingNotification(boolean isSuppress) {
- suppressSubsequentPairingNotification = isSuppress;
+ mSuppressSubsequentPairingNotification = isSuppress;
}
/** Calls this to start advertising after some values are changed. */
public void startAdvertising() {
- advertiser.startAdvertising(getServiceData());
+ mAdvertiser.startAdvertising(getServiceData());
}
/** Send Event Message on to rfcomm connected devices. */
public void sendEventStreamMessageToRfcommDevices(EventGroup eventGroup) {
// Send fake log when event code is logging and type is not using Log_Full event.
- if (eventGroup == EventGroup.LOGGING && !useLogFullEvent) {
- rfcommServer.sendFakeEventStreamLoggingMessage(
+ if (eventGroup == EventGroup.LOGGING && !mUseLogFullEvent) {
+ mRfcommServer.sendFakeEventStreamLoggingMessage(
getDeviceName()
+ " "
+ getBleAddress()
@@ -558,12 +562,12 @@
+ new SimpleDateFormat("HH:mm:ss:SSS", Locale.US)
.format(Calendar.getInstance().getTime()));
} else {
- rfcommServer.sendFakeEventStreamMessage(eventGroup);
+ mRfcommServer.sendFakeEventStreamMessage(eventGroup);
}
}
public void setUseLogFullEvent(boolean useLogFullEvent) {
- this.useLogFullEvent = useLogFullEvent;
+ this.mUseLogFullEvent = useLogFullEvent;
}
/** An optional way to get status updates. */
@@ -787,7 +791,7 @@
private String mBluetoothAddress;
@Nullable
- private String mbleAddress;
+ private String mBleAddress;
private boolean mDataOnlyConnection;
@@ -824,7 +828,7 @@
this.mModelId = option.mModelId;
this.mAdvertisingModelId = option.mAdvertisingModelId;
this.mBluetoothAddress = option.mBluetoothAddress;
- this.mbleAddress = option.mBleAddress;
+ this.mBleAddress = option.mBleAddress;
this.mDataOnlyConnection = option.mDataOnlyConnection;
this.mTxPowerLevel = option.mTxPowerLevel;
this.mEnableNameCharacteristic = option.mEnableNameCharacteristic;
@@ -860,7 +864,7 @@
}
public Builder setBleAddress(@Nullable String bleAddress) {
- this.mbleAddress = bleAddress;
+ this.mBleAddress = bleAddress;
return this;
}
@@ -955,7 +959,7 @@
Ascii.toUpperCase(mModelId),
Ascii.toUpperCase(mAdvertisingModelId),
mBluetoothAddress,
- mbleAddress,
+ mBleAddress,
mDataOnlyConnection,
mTxPowerLevel,
mEnableNameCharacteristic,
@@ -974,10 +978,15 @@
}
public FastPairSimulator(Context context, Options options) {
- this.context = context;
- this.options = options;
+ this(context, options, new Logger(TAG));
+ }
- this.batteryValues = new ArrayList<>();
+ public FastPairSimulator(Context context, Options options, Logger logger) {
+ this.mContext = context;
+ this.mOptions = options;
+ this.mLogger = logger;
+
+ this.mBatteryValues = new ArrayList<>();
String bluetoothAddress =
!TextUtils.isEmpty(options.getBluetoothAddress())
@@ -987,29 +996,29 @@
if (bluetoothAddress == null && VERSION.SDK_INT >= VERSION_CODES.O) {
// Requires a modified Android O build for access to bluetoothAdapter.getAddress().
// See http://google3/java/com/google/location/nearby/apps/fastpair/simulator/README.md.
- bluetoothAddress = bluetoothAdapter.getAddress();
+ bluetoothAddress = mBluetoothAdapter.getAddress();
}
- this.bluetoothAddress =
+ this.mBluetoothAddress =
new Value(BluetoothAddress.decode(bluetoothAddress), ByteOrder.BIG_ENDIAN);
- this.bleAddress = options.getBleAddress();
- this.dataOnlyConnection = options.getDataOnlyConnection();
- this.advertiser = new OreoFastPairAdvertiser(this);
+ this.mBleAddress = options.getBleAddress();
+ this.mDataOnlyConnection = options.getDataOnlyConnection();
+ this.mAdvertiser = new OreoFastPairAdvertiser(this);
- fastPairSimulatorDatabase = new FastPairSimulatorDatabase(context);
+ mFastPairSimulatorDatabase = new FastPairSimulatorDatabase(context);
byte[] deviceName = getDeviceNameInBytes();
- logger.log(
+ mLogger.log(
"Provider default device name is %s",
deviceName != null ? new String(deviceName, StandardCharsets.UTF_8) : null);
- if (dataOnlyConnection) {
+ if (mDataOnlyConnection) {
// To get BLE address, we need to start advertising first, and then
// {@code#setBleAddress} will be called with BLE address.
- advertiser.startAdvertising(modelIdServiceData(/* forAdvertising= */ true));
+ mAdvertiser.startAdvertising(modelIdServiceData(/* forAdvertising= */ true));
} else {
// Make this so that the simulator doesn't start automatically.
// This is tricky since the simulator is used in our integ tests as well.
- start(bleAddress != null ? bleAddress : bluetoothAddress);
+ start(mBleAddress != null ? mBleAddress : bluetoothAddress);
}
}
@@ -1019,23 +1028,22 @@
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- context.registerReceiver(broadcastReceiver, filter);
+ mContext.registerReceiver(mBroadcastReceiver, filter);
- BluetoothManager bluetoothManager =
- (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
+ BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
BluetoothGattServerHelper bluetoothGattServerHelper =
- new BluetoothGattServerHelper(context, wrap(bluetoothManager));
+ new BluetoothGattServerHelper(mContext, wrap(bluetoothManager));
mBluetoothGattServerHelpers.put(address, bluetoothGattServerHelper);
- if (options.getBecomeDiscoverable()) {
+ if (mOptions.getBecomeDiscoverable()) {
try {
becomeDiscoverable();
} catch (InterruptedException | TimeoutException e) {
- logger.log(e, "Error becoming discoverable");
+ mLogger.log(e, "Error becoming discoverable");
}
}
- advertiser.startAdvertising(modelIdServiceData(/* forAdvertising= */ true));
+ mAdvertiser.startAdvertising(modelIdServiceData(/* forAdvertising= */ true));
startGattServer(bluetoothGattServerHelper);
startRfcommServer();
scheduleAdvertisingRefresh();
@@ -1047,16 +1055,16 @@
*/
@SuppressWarnings("FutureReturnValueIgnored")
private void scheduleAdvertisingRefresh() {
- executor.scheduleAtFixedRate(
+ mExecutor.scheduleAtFixedRate(
() -> {
- if (isAdvertising) {
- advertiser.startAdvertising(getServiceData());
+ if (mIsAdvertising) {
+ mAdvertiser.startAdvertising(getServiceData());
}
},
- options.getIsMemoryTest()
+ mOptions.getIsMemoryTest()
? ADVERTISING_REFRESH_DELAY_5_MINS
: ADVERTISING_REFRESH_DELAY_1_MIN,
- options.getIsMemoryTest()
+ mOptions.getIsMemoryTest()
? ADVERTISING_REFRESH_DELAY_5_MINS
: ADVERTISING_REFRESH_DELAY_1_MIN,
TimeUnit.MILLISECONDS);
@@ -1064,49 +1072,50 @@
public void destroy() {
try {
- logger.log("Destroying simulator");
- isDestroyed = true;
- context.unregisterReceiver(broadcastReceiver);
- advertiser.stopAdvertising();
+ mLogger.log("Destroying simulator");
+ mIsDestroyed = true;
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ mAdvertiser.stopAdvertising();
for (BluetoothGattServerHelper helper : mBluetoothGattServerHelpers.values()) {
helper.close();
}
stopRfcommServer();
- deviceNameCallback = null;
- executor.shutdownNow();
+ mDeviceNameCallback = null;
+ mExecutor.shutdownNow();
} catch (IllegalArgumentException ignored) {
// Happens if you haven't given us permissions yet, so we didn't register the receiver.
}
}
public boolean isDestroyed() {
- return isDestroyed;
+ return mIsDestroyed;
}
@Nullable
public String getBluetoothAddress() {
- return BluetoothAddress.encode(bluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN));
+ return BluetoothAddress.encode(mBluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN));
}
public boolean isAdvertising() {
- return isAdvertising;
+ return mIsAdvertising;
}
public void setIsAdvertising(boolean isAdvertising) {
- if (this.isAdvertising != isAdvertising) {
- this.isAdvertising = isAdvertising;
- options.getCallback().onAdvertisingChanged();
+ if (this.mIsAdvertising != isAdvertising) {
+ this.mIsAdvertising = isAdvertising;
+ mOptions.getCallback().onAdvertisingChanged();
}
}
public void stopAdvertising() {
- advertiser.stopAdvertising();
+ mAdvertiser.stopAdvertising();
}
public void setBleAddress(String bleAddress) {
- this.bleAddress = bleAddress;
- if (dataOnlyConnection) {
- bluetoothAddress = new Value(BluetoothAddress.decode(bleAddress), ByteOrder.BIG_ENDIAN);
+ this.mBleAddress = bleAddress;
+ if (mDataOnlyConnection) {
+ mBluetoothAddress = new Value(BluetoothAddress.decode(bleAddress),
+ ByteOrder.BIG_ENDIAN);
start(bleAddress);
}
// When BLE address changes, needs to send BLE address to the client again.
@@ -1115,28 +1124,28 @@
// If we are advertising something other than the model id (e.g. the bloom filter), restart
// the advertisement so that it is updated with the new address.
if (isAdvertising() && !isDiscoverable()) {
- advertiser.startAdvertising(getServiceData());
+ mAdvertiser.startAdvertising(getServiceData());
}
}
@Nullable
public String getBleAddress() {
- return bleAddress;
+ return mBleAddress;
}
// This method is only for testing to make test block until write name success or time out.
@VisibleForTesting
public void setCountDownLatchToWriteName(CountDownLatch countDownLatch) {
- logger.log("Set up count down latch to write device name.");
- writeNameCountDown = countDownLatch;
+ mLogger.log("Set up count down latch to write device name.");
+ mWriteNameCountDown = countDownLatch;
}
public boolean areBeaconActionsNotificationsEnabled() {
- return beaconActionsServlet.areNotificationsEnabled();
+ return mBeaconActionsServlet.areNotificationsEnabled();
}
private abstract class NotifiableGattServlet extends BluetoothGattServlet {
- private final Map<BluetoothGattServerConnection, Notifier> connections = new HashMap<>();
+ private final Map<BluetoothGattServerConnection, Notifier> mConnections = new HashMap<>();
abstract BluetoothGattCharacteristic getBaseCharacteristic();
@@ -1155,39 +1164,39 @@
@Override
public void enableNotification(BluetoothGattServerConnection connection, Notifier notifier)
throws BluetoothGattException {
- logger.log("Registering notifier for %s", getCharacteristic());
- connections.put(connection, notifier);
+ mLogger.log("Registering notifier for %s", getCharacteristic());
+ mConnections.put(connection, notifier);
}
@Override
public void disableNotification(BluetoothGattServerConnection connection, Notifier notifier)
throws BluetoothGattException {
- logger.log("Removing notifier for %s", getCharacteristic());
- connections.remove(connection);
+ mLogger.log("Removing notifier for %s", getCharacteristic());
+ mConnections.remove(connection);
}
boolean areNotificationsEnabled() {
- return !connections.isEmpty();
+ return !mConnections.isEmpty();
}
void sendNotification(byte[] data) {
- if (connections.isEmpty()) {
- logger.log("Not sending notify as no notifier registered");
+ if (mConnections.isEmpty()) {
+ mLogger.log("Not sending notify as no notifier registered");
return;
}
// Needs to be on a separate thread to avoid deadlocking and timing out (waits for a
// callback from OS, which happens on the main thread).
- executor.execute(
+ mExecutor.execute(
() -> {
for (Map.Entry<BluetoothGattServerConnection, Notifier> entry :
- connections.entrySet()) {
+ mConnections.entrySet()) {
try {
- logger.log("Sending notify %s to %s",
+ mLogger.log("Sending notify %s to %s",
getCharacteristic(),
entry.getKey().getDevice().getAddress());
entry.getValue().notify(data);
} catch (BluetoothException e) {
- logger.log(
+ mLogger.log(
e,
"Failed to notify (indicate) result of %s to %s",
getCharacteristic(),
@@ -1199,17 +1208,17 @@
}
private void startRfcommServer() {
- rfcommServer.setRequestHandler(this::handleRfcommServerRequest);
- rfcommServer.setStateMonitor(state -> {
- logger.log("RfcommServer is in %s state", state);
+ mRfcommServer.setRequestHandler(this::handleRfcommServerRequest);
+ mRfcommServer.setStateMonitor(state -> {
+ mLogger.log("RfcommServer is in %s state", state);
if (CONNECTED.equals(state)) {
sendModelId();
- sendDeviceBleAddress(bleAddress);
+ sendDeviceBleAddress(mBleAddress);
sendFirmwareVersion();
sendSessionNonce();
}
});
- rfcommServer.start();
+ mRfcommServer.start();
}
private void handleRfcommServerRequest(int eventGroup, int eventCode, byte[] data) {
@@ -1221,25 +1230,25 @@
String deviceValue = base16().encode(data);
if (eventCode == DeviceEventCode.DEVICE_CAPABILITY_VALUE) {
- logger.log("Received phone capability: %s", deviceValue);
+ mLogger.log("Received phone capability: %s", deviceValue);
} else if (eventCode == DeviceEventCode.PLATFORM_TYPE_VALUE) {
- logger.log("Received platform type: %s", deviceValue);
+ mLogger.log("Received platform type: %s", deviceValue);
}
break;
case EventGroup.DEVICE_ACTION_VALUE:
if (eventCode == DeviceActionEventCode.DEVICE_ACTION_RING_VALUE) {
- logger.log("receive device action with ring value, data = %d",
+ mLogger.log("receive device action with ring value, data = %d",
data[0]);
sendDeviceRingActionResponse();
// Simulate notifying the seeker that the ringing has stopped due
// to user interaction (such as tapping the bud).
- uiThreadHandler.postDelayed(this::sendDeviceRingStoppedAction,
+ mUiThreadHandler.postDelayed(this::sendDeviceRingStoppedAction,
5000);
}
break;
case EventGroup.DEVICE_CONFIGURATION_VALUE:
if (eventCode == DeviceConfigurationEventCode.CONFIGURATION_BUFFER_SIZE_VALUE) {
- logger.log(
+ mLogger.log(
"receive device action with buffer size value, data = %s",
base16().encode(data));
sendSetBufferActionResponse(data);
@@ -1247,7 +1256,7 @@
break;
case EventGroup.DEVICE_CAPABILITY_SYNC_VALUE:
if (eventCode == DeviceCapabilitySyncEventCode.REQUEST_CAPABILITY_UPDATE_VALUE) {
- logger.log("receive device capability update request.");
+ mLogger.log("receive device capability update request.");
sendCapabilitySync();
}
break;
@@ -1257,23 +1266,23 @@
}
private void stopRfcommServer() {
- rfcommServer.stop();
- rfcommServer.setRequestHandler(null);
- rfcommServer.setStateMonitor(null);
+ mRfcommServer.stop();
+ mRfcommServer.setRequestHandler(null);
+ mRfcommServer.setStateMonitor(null);
}
private void sendModelId() {
- logger.log("Send model ID to the client");
- rfcommServer.send(
+ mLogger.log("Send model ID to the client");
+ mRfcommServer.send(
EventGroup.DEVICE_VALUE,
DeviceEventCode.DEVICE_MODEL_ID_VALUE,
modelIdServiceData(/* forAdvertising= */ false));
}
private void sendDeviceBleAddress(String bleAddress) {
- logger.log("Send BLE address (%s) to the client", bleAddress);
+ mLogger.log("Send BLE address (%s) to the client", bleAddress);
if (bleAddress != null) {
- rfcommServer.send(
+ mRfcommServer.send(
EventGroup.DEVICE_VALUE,
DeviceEventCode.DEVICE_BLE_ADDRESS_VALUE,
BluetoothAddress.decode(bleAddress));
@@ -1281,25 +1290,25 @@
}
private void sendFirmwareVersion() {
- logger.log("Send Firmware Version (%s) to the client", deviceFirmwareVersion);
- rfcommServer.send(
+ mLogger.log("Send Firmware Version (%s) to the client", mDeviceFirmwareVersion);
+ mRfcommServer.send(
EventGroup.DEVICE_VALUE,
DeviceEventCode.FIRMWARE_VERSION_VALUE,
- deviceFirmwareVersion.getBytes());
+ mDeviceFirmwareVersion.getBytes());
}
private void sendSessionNonce() {
- logger.log("Send SessionNonce (%s) to the client", deviceFirmwareVersion);
+ mLogger.log("Send SessionNonce (%s) to the client", mDeviceFirmwareVersion);
SecureRandom secureRandom = new SecureRandom();
- sessionNonce = new byte[SECTION_NONCE_LENGTH];
- secureRandom.nextBytes(sessionNonce);
- rfcommServer.send(
- EventGroup.DEVICE_VALUE, DeviceEventCode.SECTION_NONCE_VALUE, sessionNonce);
+ mSessionNonce = new byte[SECTION_NONCE_LENGTH];
+ secureRandom.nextBytes(mSessionNonce);
+ mRfcommServer.send(
+ EventGroup.DEVICE_VALUE, DeviceEventCode.SECTION_NONCE_VALUE, mSessionNonce);
}
private void sendDeviceRingActionResponse() {
- logger.log("Send device ring action response to the client");
- rfcommServer.send(
+ mLogger.log("Send device ring action response to the client");
+ mRfcommServer.send(
EventGroup.ACKNOWLEDGEMENT_VALUE,
AcknowledgementEventCode.ACKNOWLEDGEMENT_ACK_VALUE,
new byte[]{
@@ -1313,9 +1322,9 @@
for (ByteString accountKey : getAccountKeys()) {
try {
if (MessageStreamHmacEncoder.verifyHmac(
- accountKey.toByteArray(), sessionNonce, data)) {
+ accountKey.toByteArray(), mSessionNonce, data)) {
hmacPassed = true;
- logger.log("Buffer size data matches account key %s",
+ mLogger.log("Buffer size data matches account key %s",
base16().encode(accountKey.toByteArray()));
break;
}
@@ -1324,8 +1333,8 @@
}
}
if (hmacPassed) {
- logger.log("Send buffer size action response %s to the client", base16().encode(data));
- rfcommServer.send(
+ mLogger.log("Send buffer size action response %s to the client", base16().encode(data));
+ mRfcommServer.send(
EventGroup.ACKNOWLEDGEMENT_VALUE,
AcknowledgementEventCode.ACKNOWLEDGEMENT_ACK_VALUE,
new byte[]{
@@ -1336,15 +1345,15 @@
data[2]
});
} else {
- logger.log("No matched account key for sendSetBufferActionResponse");
+ mLogger.log("No matched account key for sendSetBufferActionResponse");
}
}
private void sendCapabilitySync() {
- logger.log("Send capability sync to the client");
- if (supportDynamicBufferSize) {
- logger.log("Send dynamic buffer size range to the client");
- rfcommServer.send(
+ mLogger.log("Send capability sync to the client");
+ if (mSupportDynamicBufferSize) {
+ mLogger.log("Send dynamic buffer size range to the client");
+ mRfcommServer.send(
EventGroup.DEVICE_CAPABILITY_SYNC_VALUE,
DeviceCapabilitySyncEventCode.CONFIGURABLE_BUFFER_SIZE_RANGE_VALUE,
new byte[]{
@@ -1358,8 +1367,8 @@
}
private void sendDeviceRingStoppedAction() {
- logger.log("Sending device ring stopped action to the client");
- rfcommServer.send(
+ mLogger.log("Sending device ring stopped action to the client");
+ mRfcommServer.send(
EventGroup.DEVICE_ACTION_VALUE,
DeviceActionEventCode.DEVICE_ACTION_RING_VALUE,
// Additional data for stopping ringing on all components.
@@ -1379,7 +1388,7 @@
public void write(
BluetoothGattServerConnection connection, int offset, byte[] value)
throws BluetoothGattException {
- logger.log("Requested TDS Control Point write, value=%s",
+ mLogger.log("Requested TDS Control Point write, value=%s",
base16().encode(value));
ResultCode resultCode = checkTdsControlPointRequest(value);
@@ -1387,19 +1396,19 @@
try {
becomeDiscoverable();
} catch (TimeoutException | InterruptedException e) {
- logger.log(e, "Failed to become discoverable");
+ mLogger.log(e, "Failed to become discoverable");
resultCode = ResultCode.OPERATION_FAILED;
}
}
- logger.log("Request complete, resultCode=%s", resultCode);
+ mLogger.log("Request complete, resultCode=%s", resultCode);
- logger.log("Sending TDS Control Point response indication");
+ mLogger.log("Sending TDS Control Point response indication");
sendNotification(
Bytes.concat(
new byte[]{
getTdsControlPointOpCode(value),
- resultCode.byteValue,
+ resultCode.mByteValue,
},
resultCode == ResultCode.SUCCESS
? TDS_CONTROL_POINT_RESPONSE_PARAMETER
@@ -1420,7 +1429,7 @@
public byte[] read(BluetoothGattServerConnection connection, int offset) {
return Bytes.concat(
new byte[]{BrHandoverDataCharacteristic.BR_EDR_FEATURES},
- bluetoothAddress.getBytes(ByteOrder.LITTLE_ENDIAN),
+ mBluetoothAddress.getBytes(ByteOrder.LITTLE_ENDIAN),
CLASS_OF_DEVICE.getBytes(ByteOrder.LITTLE_ENDIAN));
}
};
@@ -1436,7 +1445,7 @@
0 /* no properties */,
0 /* no permissions */);
- if (options.getIncludeTransportDataDescriptor()) {
+ if (mOptions.getIncludeTransportDataDescriptor()) {
characteristic.addDescriptor(
new BluetoothGattDescriptor(
TransportDiscoveryService.BluetoothSigDataCharacteristic
@@ -1471,16 +1480,16 @@
@Override
public void write(
BluetoothGattServerConnection connection, int offset, byte[] value) {
- logger.log("Got value from account key servlet: %s",
+ mLogger.log("Got value from account key servlet: %s",
base16().encode(value));
try {
- addAccountKey(AesEcbSingleBlockEncryption.decrypt(secret, value),
- pairingDevice);
+ addAccountKey(AesEcbSingleBlockEncryption.decrypt(mSecret, value),
+ mPairingDevice);
} catch (GeneralSecurityException e) {
- logger.log(e, "Failed to decrypt account key.");
+ mLogger.log(e, "Failed to decrypt account key.");
}
- uiThreadHandler.post(
- () -> advertiser.startAdvertising(accountKeysServiceData()));
+ mUiThreadHandler.post(
+ () -> mAdvertiser.startAdvertising(accountKeysServiceData()));
}
};
@@ -1494,7 +1503,7 @@
@Override
public byte[] read(BluetoothGattServerConnection connection, int offset) {
- return deviceFirmwareVersion.getBytes();
+ return mDeviceFirmwareVersion.getBytes();
}
};
@@ -1514,10 +1523,10 @@
@Override
public void write(
BluetoothGattServerConnection connection, int offset, byte[] value) {
- logger.log("Requesting key based pairing handshake, value=%s",
+ mLogger.log("Requesting key based pairing handshake, value=%s",
base16().encode(value));
- secret = null;
+ mSecret = null;
byte[] seekerPublicAddress = null;
if (value.length == AES_BLOCK_LENGTH) {
@@ -1525,67 +1534,67 @@
byte[] candidateSecret = key.toByteArray();
try {
seekerPublicAddress = handshake(candidateSecret, value);
- secret = candidateSecret;
- isSubsequentPair = true;
+ mSecret = candidateSecret;
+ mIsSubsequentPair = true;
break;
} catch (GeneralSecurityException e) {
- logger.log(e, "Failed to decrypt with %s",
+ mLogger.log(e, "Failed to decrypt with %s",
base16().encode(candidateSecret));
}
}
} else if (value.length == AES_BLOCK_LENGTH + PUBLIC_KEY_LENGTH
- && options.getAntiSpoofingPrivateKey() != null) {
+ && mOptions.getAntiSpoofingPrivateKey() != null) {
try {
byte[] encryptedRequest = Arrays.copyOf(value, AES_BLOCK_LENGTH);
byte[] receivedPublicKey =
Arrays.copyOfRange(value, AES_BLOCK_LENGTH, value.length);
byte[] candidateSecret =
EllipticCurveDiffieHellmanExchange.create(
- options.getAntiSpoofingPrivateKey())
+ mOptions.getAntiSpoofingPrivateKey())
.generateSecret(receivedPublicKey);
seekerPublicAddress = handshake(candidateSecret, encryptedRequest);
- secret = candidateSecret;
+ mSecret = candidateSecret;
} catch (Exception e) {
- logger.log(
+ mLogger.log(
e,
"Failed to decrypt with anti-spoofing private key %s",
- base16().encode(options.getAntiSpoofingPrivateKey()));
+ base16().encode(mOptions.getAntiSpoofingPrivateKey()));
}
} else {
- logger.log("Packet length invalid, %d", value.length);
+ mLogger.log("Packet length invalid, %d", value.length);
return;
}
- if (secret == null) {
- logger.log("Couldn't find a usable key to decrypt with.");
+ if (mSecret == null) {
+ mLogger.log("Couldn't find a usable key to decrypt with.");
return;
}
- logger.log("Found valid decryption key, %s", base16().encode(secret));
+ mLogger.log("Found valid decryption key, %s", base16().encode(mSecret));
byte[] salt = new byte[9];
new Random().nextBytes(salt);
try {
byte[] data = concat(
new byte[]{KeyBasedPairingCharacteristic.Response.TYPE},
- bluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN), salt);
- byte[] encryptedAddress = encrypt(secret, data);
- logger.log(
+ mBluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN), salt);
+ byte[] encryptedAddress = encrypt(mSecret, data);
+ mLogger.log(
"Sending handshake response %s with size %d",
base16().encode(encryptedAddress), encryptedAddress.length);
sendNotification(encryptedAddress);
// Notify seeker for NameCharacteristic to get provider device name
// when seeker request device name flag is true.
- if (options.getEnableNameCharacteristic()
- && handshakeRequest.requestDeviceName()) {
+ if (mOptions.getEnableNameCharacteristic()
+ && mHandshakeRequest.requestDeviceName()) {
byte[] encryptedResponse =
getDeviceNameInBytes() != null ? createEncryptedDeviceName()
: new byte[0];
- logger.log(
+ mLogger.log(
"Sending device name response %s with size %d",
base16().encode(encryptedResponse),
encryptedResponse.length);
- deviceNameServlet.sendNotification(encryptedResponse);
+ mDeviceNameServlet.sendNotification(encryptedResponse);
}
// Disconnects the current connection to allow the following pairing
@@ -1598,77 +1607,77 @@
// If headphones support multiple simultaneous connections, they
// should stay connected. But Android fails to pair with the new
// device if we don't first disconnect from any other device.
- logger.log("Skip remove bond, value=%s",
- options.getRemoveAllDevicesDuringPairing());
- if (options.getRemoveAllDevicesDuringPairing()
- && handshakeRequest.getType()
+ mLogger.log("Skip remove bond, value=%s",
+ mOptions.getRemoveAllDevicesDuringPairing());
+ if (mOptions.getRemoveAllDevicesDuringPairing()
+ && mHandshakeRequest.getType()
== HandshakeRequest.Type.KEY_BASED_PAIRING_REQUEST
- && !handshakeRequest.requestRetroactivePair()) {
- executor.execute(() -> disconnect());
+ && !mHandshakeRequest.requestRetroactivePair()) {
+ mExecutor.execute(() -> disconnectAllBondedDevices());
}
- if (handshakeRequest.getType()
+ if (mHandshakeRequest.getType()
== HandshakeRequest.Type.KEY_BASED_PAIRING_REQUEST
- && handshakeRequest.requestProviderInitialBonding()) {
+ && mHandshakeRequest.requestProviderInitialBonding()) {
// Run on executor to ensure it doesn't happen until after the
// notify (which tells the remote device what address to expect).
String seekerPublicAddressString =
BluetoothAddress.encode(seekerPublicAddress);
- executor.execute(() -> {
- logger.log("Sending pairing request to %s",
+ mExecutor.execute(() -> {
+ mLogger.log("Sending pairing request to %s",
seekerPublicAddressString);
- bluetoothAdapter.getRemoteDevice(
+ mBluetoothAdapter.getRemoteDevice(
seekerPublicAddressString).createBond();
});
}
} catch (GeneralSecurityException e) {
- logger.log(e, "Failed to notify of static mac address");
+ mLogger.log(e, "Failed to notify of static mac address");
}
}
@Nullable
private byte[] handshake(byte[] key, byte[] encryptedPairingRequest)
throws GeneralSecurityException {
- handshakeRequest = new HandshakeRequest(key, encryptedPairingRequest);
+ mHandshakeRequest = new HandshakeRequest(key, encryptedPairingRequest);
- byte[] decryptedAddress = handshakeRequest.getVerificationData();
- if (bleAddress != null
+ byte[] decryptedAddress = mHandshakeRequest.getVerificationData();
+ if (mBleAddress != null
&& Arrays.equals(decryptedAddress,
- BluetoothAddress.decode(bleAddress))
+ BluetoothAddress.decode(mBleAddress))
|| Arrays.equals(decryptedAddress,
- bluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN))) {
- logger.log("Address matches: %s", base16().encode(decryptedAddress));
+ mBluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN))) {
+ mLogger.log("Address matches: %s", base16().encode(decryptedAddress));
} else {
throw new GeneralSecurityException(
"Address (BLE or BR/EDR) is not correct: "
+ base16().encode(decryptedAddress)
+ ", "
- + bleAddress
+ + mBleAddress
+ ", "
+ getBluetoothAddress());
}
- switch (handshakeRequest.getType()) {
+ switch (mHandshakeRequest.getType()) {
case KEY_BASED_PAIRING_REQUEST:
- return handleKeyBasedPairingRequest(handshakeRequest);
+ return handleKeyBasedPairingRequest(mHandshakeRequest);
case ACTION_OVER_BLE:
- return handleActionOverBleRequest(handshakeRequest);
+ return handleActionOverBleRequest(mHandshakeRequest);
case UNKNOWN:
// continue to throw the exception;
}
throw new GeneralSecurityException(
- "Type is not correct: " + handshakeRequest.getType());
+ "Type is not correct: " + mHandshakeRequest.getType());
}
@Nullable
private byte[] handleKeyBasedPairingRequest(HandshakeRequest handshakeRequest)
throws GeneralSecurityException {
if (handshakeRequest.requestDiscoverable()) {
- logger.log("Requested discoverability");
+ mLogger.log("Requested discoverability");
setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
}
- logger.log(
+ mLogger.log(
"KeyBasedPairing: initialBonding=%s, requestDeviceName=%s, "
+ "retroactivePair=%s",
handshakeRequest.requestProviderInitialBonding(),
@@ -1679,13 +1688,14 @@
if (handshakeRequest.requestProviderInitialBonding()
|| handshakeRequest.requestRetroactivePair()) {
seekerPublicAddress = handshakeRequest.getSeekerPublicAddress();
- logger.log(
+ mLogger.log(
"Seeker sends BR/EDR address %s to provider",
BluetoothAddress.encode(seekerPublicAddress));
}
if (handshakeRequest.requestRetroactivePair()) {
- if (bluetoothAdapter.getRemoteDevice(seekerPublicAddress).getBondState()
+ if (mBluetoothAdapter.getRemoteDevice(
+ seekerPublicAddress).getBondState()
!= BluetoothDevice.BOND_BONDED) {
throw new GeneralSecurityException(
"Address (BR/EDR) is not bonded: "
@@ -1700,14 +1710,14 @@
private byte[] handleActionOverBleRequest(HandshakeRequest handshakeRequest) {
// TODO(wollohchou): implement action over ble request.
if (handshakeRequest.requestDeviceAction()) {
- logger.log("Requesting action over BLE, device action");
+ mLogger.log("Requesting action over BLE, device action");
} else if (handshakeRequest.requestFollowedByAdditionalData()) {
- logger.log(
+ mLogger.log(
"Requesting action over BLE, followed by additional data, "
+ "type:%s",
handshakeRequest.getAdditionalDataType());
} else {
- logger.log("Requesting action over BLE");
+ mLogger.log("Requesting action over BLE");
}
return null;
}
@@ -1718,14 +1728,14 @@
private byte[] createEncryptedDeviceName() throws GeneralSecurityException {
byte[] deviceName = getDeviceNameInBytes();
String providerName = new String(deviceName, StandardCharsets.UTF_8);
- logger.log(
+ mLogger.log(
"Sending handshake response for device name %s with size %d",
providerName, deviceName.length);
- return NamingEncoder.encodeNamingPacket(secret, providerName);
+ return NamingEncoder.encodeNamingPacket(mSecret, providerName);
}
};
- beaconActionsServlet =
+ mBeaconActionsServlet =
new NotifiableGattServlet() {
private static final int GATT_ERROR_UNAUTHENTICATED = 0x80;
private static final int GATT_ERROR_INVALID_VALUE = 0x81;
@@ -1735,17 +1745,17 @@
private static final int IDENTITY_KEY_LENGTH = 32;
private static final byte TRANSMISSION_POWER = 0;
- private final SecureRandom random = new SecureRandom();
- private final MessageDigest sha256;
+ private final SecureRandom mRandom = new SecureRandom();
+ private final MessageDigest mSha256;
@Nullable
- private byte[] lastNonce;
+ private byte[] mLastNonce;
@Nullable
- private ByteString identityKey = options.getEddystoneIdentityKey();
+ private ByteString mIdentityKey = mOptions.getEddystoneIdentityKey();
{
try {
- sha256 = MessageDigest.getInstance("SHA-256");
- sha256.reset();
+ mSha256 = MessageDigest.getInstance("SHA-256");
+ mSha256.reset();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(
"System missing SHA-256 implementation.", e);
@@ -1764,19 +1774,19 @@
@Override
public byte[] read(BluetoothGattServerConnection connection, int offset) {
- lastNonce = new byte[NONCE_LENGTH];
- random.nextBytes(lastNonce);
- return lastNonce;
+ mLastNonce = new byte[NONCE_LENGTH];
+ mRandom.nextBytes(mLastNonce);
+ return mLastNonce;
}
@Override
public void write(
BluetoothGattServerConnection connection, int offset, byte[] value)
throws BluetoothGattException {
- logger.log("Got value from beacon actions servlet: %s",
+ mLogger.log("Got value from beacon actions servlet: %s",
base16().encode(value));
if (value.length == 0) {
- logger.log("Packet length invalid, %d", value.length);
+ mLogger.log("Packet length invalid, %d", value.length);
throw new BluetoothGattException("Packet length invalid",
GATT_ERROR_INVALID_VALUE);
}
@@ -1807,7 +1817,7 @@
private boolean verifyAccountKeyToken(byte[] value, boolean ownerOnly)
throws BluetoothGattException {
if (value.length < ONE_TIME_AUTH_KEY_LENGTH + ONE_TIME_AUTH_KEY_OFFSET) {
- logger.log("Packet length invalid, %d", value.length);
+ mLogger.log("Packet length invalid, %d", value.length);
throw new BluetoothGattException(
"Packet length invalid", GATT_ERROR_INVALID_VALUE);
}
@@ -1816,27 +1826,28 @@
value,
ONE_TIME_AUTH_KEY_OFFSET,
ONE_TIME_AUTH_KEY_LENGTH + ONE_TIME_AUTH_KEY_OFFSET);
- if (lastNonce == null) {
+ if (mLastNonce == null) {
throw new BluetoothGattException(
"Nonce wasn't set", GATT_ERROR_UNAUTHENTICATED);
}
if (ownerOnly) {
ByteString accountKey = getOwnerAccountKey();
if (accountKey != null) {
- sha256.update(accountKey.toByteArray());
- sha256.update(lastNonce);
+ mSha256.update(accountKey.toByteArray());
+ mSha256.update(mLastNonce);
return Arrays.equals(
hashedAccountKey,
- Arrays.copyOf(sha256.digest(), ONE_TIME_AUTH_KEY_LENGTH));
+ Arrays.copyOf(mSha256.digest(), ONE_TIME_AUTH_KEY_LENGTH));
}
} else {
Set<ByteString> accountKeys = getAccountKeys();
for (ByteString accountKey : accountKeys) {
- sha256.update(accountKey.toByteArray());
- sha256.update(lastNonce);
+ mSha256.update(accountKey.toByteArray());
+ mSha256.update(mLastNonce);
if (Arrays.equals(
hashedAccountKey,
- Arrays.copyOf(sha256.digest(), ONE_TIME_AUTH_KEY_LENGTH))) {
+ Arrays.copyOf(mSha256.digest(),
+ ONE_TIME_AUTH_KEY_LENGTH))) {
return true;
}
}
@@ -1889,7 +1900,7 @@
if (verifyAccountKeyToken(value, /* ownerOnly= */ true)) {
flags |= (byte) (1 << 1);
}
- if (identityKey == null) {
+ if (mIdentityKey == null) {
sendNotification(
fromBytes(
(byte) BeaconActionType.READ_PROVISIONING_STATE,
@@ -1905,7 +1916,7 @@
flags)
.concat(
E2eeCalculator.computeE2eeEid(
- identityKey, /* exponent= */ 10,
+ mIdentityKey, /* exponent= */ 10,
getBeaconClock()))
.toByteArray());
}
@@ -1921,17 +1932,17 @@
if (value.length
!= ONE_TIME_AUTH_KEY_LENGTH + ONE_TIME_AUTH_KEY_OFFSET
+ IDENTITY_KEY_LENGTH) {
- logger.log("Packet length invalid, %d", value.length);
+ mLogger.log("Packet length invalid, %d", value.length);
throw new BluetoothGattException("Packet length invalid",
GATT_ERROR_INVALID_VALUE);
}
- if (identityKey != null) {
+ if (mIdentityKey != null) {
throw new BluetoothGattException(
"Device is already provisioned as Eddystone",
GATT_ERROR_UNAUTHENTICATED);
}
- identityKey = Crypto.aesEcbNoPaddingDecrypt(
- ByteString.copyFrom(ownerAccountKey),
+ mIdentityKey = Crypto.aesEcbNoPaddingDecrypt(
+ ByteString.copyFrom(mOwnerAccountKey),
ByteString.copyFrom(value)
.substring(ONE_TIME_AUTH_KEY_LENGTH
+ ONE_TIME_AUTH_KEY_OFFSET));
@@ -1942,10 +1953,10 @@
new ServiceConfig()
.addCharacteristic(accountKeyServlet)
.addCharacteristic(keyBasedPairingServlet)
- .addCharacteristic(passkeyServlet)
+ .addCharacteristic(mPasskeyServlet)
.addCharacteristic(firmwareVersionServlet);
- if (options.getEnableBeaconActionsCharacteristic()) {
- fastPairServiceConfig.addCharacteristic(beaconActionsServlet);
+ if (mOptions.getEnableBeaconActionsCharacteristic()) {
+ fastPairServiceConfig.addCharacteristic(mBeaconActionsServlet);
}
BluetoothGattServerConfig config =
@@ -1958,17 +1969,18 @@
.addCharacteristic(bluetoothSigServlet))
.addService(
FastPairService.ID,
- options.getEnableNameCharacteristic()
- ? fastPairServiceConfig.addCharacteristic(deviceNameServlet)
+ mOptions.getEnableNameCharacteristic()
+ ? fastPairServiceConfig.addCharacteristic(
+ mDeviceNameServlet)
: fastPairServiceConfig);
- logger.log(
+ mLogger.log(
"Starting GATT server, support name characteristic %b",
- options.getEnableNameCharacteristic());
+ mOptions.getEnableNameCharacteristic());
try {
helper.open(config);
} catch (BluetoothException e) {
- logger.log(e, "Error starting GATT server");
+ mLogger.log(e, "Error starting GATT server");
}
}
@@ -1978,36 +1990,37 @@
}
public void enterPassKey(int passkey) {
- logger.log("enterPassKey called with passkey %d.", passkey);
+ mLogger.log("enterPassKey called with passkey %d.", passkey);
try {
boolean result =
- (Boolean) Reflect.on(pairingDevice).withMethod("setPasskey", int.class).get(
+ (Boolean) Reflect.on(mPairingDevice).withMethod("setPasskey", int.class).get(
passkey);
- logger.log("enterPassKey called with result %b", result);
+ mLogger.log("enterPassKey called with result %b", result);
} catch (ReflectionException e) {
- logger.log("enterPassKey meet Exception %s.", e.getMessage());
+ mLogger.log("enterPassKey meet Exception %s.", e.getMessage());
}
}
private void checkPasskey() {
// There's a race between the PAIRING_REQUEST broadcast from the OS giving us the local
// passkey, and the remote passkey received over GATT. Skip the check until we have both.
- if (localPasskey == 0 || remotePasskey == 0) {
- logger.log(
+ if (mLocalPasskey == 0 || mRemotePasskey == 0) {
+ mLogger.log(
"Skipping passkey check, missing local (%s) or remote (%s).",
- localPasskey, remotePasskey);
+ mLocalPasskey, mRemotePasskey);
return;
}
// Regardless of whether it matches, send our (encrypted) passkey to the seeker.
- sendPasskeyToRemoteDevice(localPasskey);
+ sendPasskeyToRemoteDevice(mLocalPasskey);
- logger.log("Checking localPasskey %s == remotePasskey %s", localPasskey, remotePasskey);
- boolean passkeysMatched = localPasskey == remotePasskey;
- if (options.getShowsPasskeyConfirmation() && passkeysMatched
- && passkeyEventCallback != null) {
- logger.log("callbacks the UI for passkey confirmation.");
- passkeyEventCallback.onPasskeyConfirmation(localPasskey, this::setPasskeyConfirmation);
+ mLogger.log("Checking localPasskey %s == remotePasskey %s", mLocalPasskey, mRemotePasskey);
+ boolean passkeysMatched = mLocalPasskey == mRemotePasskey;
+ if (mOptions.getShowsPasskeyConfirmation() && passkeysMatched
+ && mPasskeyEventCallback != null) {
+ mLogger.log("callbacks the UI for passkey confirmation.");
+ mPasskeyEventCallback.onPasskeyConfirmation(mLocalPasskey,
+ this::setPasskeyConfirmation);
} else {
setPasskeyConfirmation(passkeysMatched);
}
@@ -2015,45 +2028,45 @@
private void sendPasskeyToRemoteDevice(int passkey) {
try {
- passkeyServlet.sendNotification(
+ mPasskeyServlet.sendNotification(
PasskeyCharacteristic.encrypt(
- PasskeyCharacteristic.Type.PROVIDER, secret, passkey));
+ PasskeyCharacteristic.Type.PROVIDER, mSecret, passkey));
} catch (GeneralSecurityException e) {
- logger.log(e, "Failed to encrypt passkey response.");
+ mLogger.log(e, "Failed to encrypt passkey response.");
}
}
public void setFirmwareVersion(String versionNumber) {
- deviceFirmwareVersion = versionNumber;
+ mDeviceFirmwareVersion = versionNumber;
}
public void setDynamicBufferSize(boolean support) {
- if (supportDynamicBufferSize != support) {
- supportDynamicBufferSize = support;
+ if (mSupportDynamicBufferSize != support) {
+ mSupportDynamicBufferSize = support;
sendCapabilitySync();
}
}
@VisibleForTesting
void setPasskeyConfirmationCallback(PasskeyConfirmationCallback callback) {
- this.passkeyConfirmationCallback = callback;
+ this.mPasskeyConfirmationCallback = callback;
}
public void setDeviceNameCallback(DeviceNameCallback callback) {
- this.deviceNameCallback = callback;
+ this.mDeviceNameCallback = callback;
}
public void setPasskeyEventCallback(PasskeyEventCallback passkeyEventCallback) {
- this.passkeyEventCallback = passkeyEventCallback;
+ this.mPasskeyEventCallback = passkeyEventCallback;
}
private void setPasskeyConfirmation(boolean confirm) {
- pairingDevice.setPairingConfirmation(confirm);
- if (passkeyConfirmationCallback != null) {
- passkeyConfirmationCallback.onPasskeyConfirmation(confirm);
+ mPairingDevice.setPairingConfirmation(confirm);
+ if (mPasskeyConfirmationCallback != null) {
+ mPasskeyConfirmationCallback.onPasskeyConfirmation(confirm);
}
- localPasskey = 0;
- remotePasskey = 0;
+ mLocalPasskey = 0;
+ mRemotePasskey = 0;
}
private void becomeDiscoverable() throws InterruptedException, TimeoutException {
@@ -2066,39 +2079,39 @@
private void setDiscoverable(boolean discoverable)
throws InterruptedException, TimeoutException {
- isDiscoverableLatch = new CountDownLatch(1);
+ mIsDiscoverableLatch = new CountDownLatch(1);
setScanMode(discoverable ? SCAN_MODE_CONNECTABLE_DISCOVERABLE : SCAN_MODE_CONNECTABLE);
// If we're already discoverable, count down the latch right away. Otherwise,
// we'll get a broadcast when we successfully become discoverable.
if (isDiscoverable()) {
- isDiscoverableLatch.countDown();
+ mIsDiscoverableLatch.countDown();
}
- if (isDiscoverableLatch.await(3, TimeUnit.SECONDS)) {
- logger.log("Successfully became switched discoverable mode %s", discoverable);
+ if (mIsDiscoverableLatch.await(3, TimeUnit.SECONDS)) {
+ mLogger.log("Successfully became switched discoverable mode %s", discoverable);
} else {
throw new TimeoutException();
}
}
private void setScanMode(int scanMode) {
- if (revertDiscoverableFuture != null) {
- revertDiscoverableFuture.cancel(false /* may interrupt if running */);
+ if (mRevertDiscoverableFuture != null) {
+ mRevertDiscoverableFuture.cancel(false /* may interrupt if running */);
}
- logger.log("Setting scan mode to %s", scanModeToString(scanMode));
+ mLogger.log("Setting scan mode to %s", scanModeToString(scanMode));
try {
- Method method = bluetoothAdapter.getClass().getMethod("setScanMode", Integer.TYPE);
- method.invoke(bluetoothAdapter, scanMode);
+ Method method = mBluetoothAdapter.getClass().getMethod("setScanMode", Integer.TYPE);
+ method.invoke(mBluetoothAdapter, scanMode);
if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
- revertDiscoverableFuture =
- executor.schedule(
+ mRevertDiscoverableFuture =
+ mExecutor.schedule(
() -> setScanMode(SCAN_MODE_CONNECTABLE),
- options.getIsMemoryTest() ? 300 : 30,
+ mOptions.getIsMemoryTest() ? 300 : 30,
TimeUnit.SECONDS);
}
} catch (Exception e) {
- logger.log(e, "Error setting scan mode to %d", scanMode);
+ mLogger.log(e, "Error setting scan mode to %d", scanMode);
}
}
@@ -2117,21 +2130,21 @@
private ResultCode checkTdsControlPointRequest(byte[] request) {
if (request.length < 2) {
- logger.log(
+ mLogger.log(
new IllegalArgumentException(), "Expected length >= 2 for %s",
base16().encode(request));
return ResultCode.INVALID_PARAMETER;
}
byte opCode = getTdsControlPointOpCode(request);
if (opCode != ControlPointCharacteristic.ACTIVATE_TRANSPORT_OP_CODE) {
- logger.log(
+ mLogger.log(
new IllegalArgumentException(),
"Expected Activate Transport op code (0x01), got %d",
opCode);
return ResultCode.OP_CODE_NOT_SUPPORTED;
}
if (request[1] != BLUETOOTH_SIG_ORGANIZATION_ID) {
- logger.log(
+ mLogger.log(
new IllegalArgumentException(),
"Expected Bluetooth SIG organization ID (0x01), got %d",
request[1]);
@@ -2145,15 +2158,15 @@
}
private boolean isDiscoverable() {
- return bluetoothAdapter.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE;
+ return mBluetoothAdapter.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE;
}
private byte[] modelIdServiceData(boolean forAdvertising) {
// Note: This used to be little-endian but is now big-endian. See b/78229467 for details.
byte[] modelIdPacket =
base16().decode(
- forAdvertising ? options.getAdvertisingModelId() : options.getModelId());
- if (!batteryValues.isEmpty()) {
+ forAdvertising ? mOptions.getAdvertisingModelId() : mOptions.getModelId());
+ if (!mBatteryValues.isEmpty()) {
// If we are going to advertise battery values with the packet, then switch to the
// non-3-byte model ID format.
modelIdPacket = concat(new byte[]{0b00000110}, modelIdPacket);
@@ -2187,22 +2200,22 @@
new BloomFilter(
new byte[(int) (1.2 * accountKeys.size()) + 3],
new FastPairBloomFilterHasher());
- String address = bleAddress == null ? SIMULATOR_FAKE_BLE_ADDRESS : bleAddress;
+ String address = mBleAddress == null ? SIMULATOR_FAKE_BLE_ADDRESS : mBleAddress;
// Simulator supports Central Address Resolution characteristic, so when paired, the BLE
// address in Seeker will be resolved to BR/EDR address. This caused Seeker fails on
// checking the bloom filter due to different address is used for salting. In order to
// let battery values notification be shown on paired device, we use random salt to
// workaround it.
- boolean advertisingBatteryValues = !batteryValues.isEmpty();
+ boolean advertisingBatteryValues = !mBatteryValues.isEmpty();
byte[] salt;
- if (options.getUseRandomSaltForAccountKeyRotation() || advertisingBatteryValues) {
+ if (mOptions.getUseRandomSaltForAccountKeyRotation() || advertisingBatteryValues) {
salt = new byte[1];
new SecureRandom().nextBytes(salt);
- logger.log("Using random salt %s for bloom filter", base16().encode(salt));
+ mLogger.log("Using random salt %s for bloom filter", base16().encode(salt));
} else {
salt = BluetoothAddress.decode(address);
- logger.log("Using address %s for bloom filter", address);
+ mLogger.log("Using address %s for bloom filter", address);
}
// To prevent tampering, account filter shall be slightly modified to include battery data
@@ -2219,7 +2232,7 @@
bloomFilter.add(concat(accountKey.toByteArray(), saltAndBatteryData));
}
byte[] packet = generateAccountKeyData(bloomFilter);
- return options.getUseRandomSaltForAccountKeyRotation() || advertisingBatteryValues
+ return mOptions.getUseRandomSaltForAccountKeyRotation() || advertisingBatteryValues
// Create a header with length 1 and type 1 for a random salt.
? concat(packet, createField((byte) 0x11, salt))
// Exclude the salt from the packet, BLE address will be assumed by the client.
@@ -2237,7 +2250,7 @@
}
public int getTxPower() {
- return options.getTxPowerLevel();
+ return mOptions.getTxPowerLevel();
}
@Nullable
@@ -2251,7 +2264,7 @@
@Nullable
private byte[] addBatteryValues(byte[] packet) {
- if (batteryValues.isEmpty() || packet == null) {
+ if (mBatteryValues.isEmpty() || packet == null) {
return packet;
}
@@ -2263,16 +2276,16 @@
// 4 are the type.
// Byte 1 - length: Battery values, the first bit is charging status, the remaining bits are
// the actual value between 0 and 100, or -1 for unknown.
- byte[] batteryData = new byte[batteryValues.size() + 1];
- batteryData[0] = (byte) (batteryValues.size() << 4
- | (suppressBatteryNotification ? 0b0100 : 0b0011));
+ byte[] batteryData = new byte[mBatteryValues.size() + 1];
+ batteryData[0] = (byte) (mBatteryValues.size() << 4
+ | (mSuppressBatteryNotification ? 0b0100 : 0b0011));
int batteryValueIndex = 1;
- for (BatteryValue batteryValue : batteryValues) {
+ for (BatteryValue batteryValue : mBatteryValues) {
batteryData[batteryValueIndex++] =
(byte)
- ((batteryValue.charging ? 0b10000000 : 0b00000000)
- | (0b01111111 & batteryValue.level));
+ ((batteryValue.mCharging ? 0b10000000 : 0b00000000)
+ | (0b01111111 & batteryValue.mLevel));
}
return batteryData;
@@ -2284,16 +2297,16 @@
// The following bytes are the data of bloom filter.
byte[] filterBytes = bloomFilter.asBytes();
byte lengthAndType = (byte) (filterBytes.length << 4
- | (suppressSubsequentPairingNotification ? 0b0010 : 0b0000));
- logger.log(
+ | (mSuppressSubsequentPairingNotification ? 0b0010 : 0b0000));
+ mLogger.log(
"Generate bloom filter with suppress subsequent pairing notification:%b",
- suppressSubsequentPairingNotification);
+ mSuppressSubsequentPairingNotification);
return createField(lengthAndType, filterBytes);
}
- /** Disconnects all connected devices. */
- private void disconnect() {
- for (BluetoothDevice device : bluetoothAdapter.getBondedDevices()) {
+ /** Disconnects all bonded devices. */
+ public void disconnectAllBondedDevices() {
+ for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) {
if (device.getBluetoothClass().getMajorDeviceClass() == Major.PHONE) {
removeBond(device);
}
@@ -2304,7 +2317,7 @@
try {
Reflect.on(profile).withMethod("disconnect", BluetoothDevice.class).invoke(device);
} catch (ReflectionException e) {
- logger.log(e, "Error disconnecting device=%s from profile=%s", device, profile);
+ mLogger.log(e, "Error disconnecting device=%s from profile=%s", device, profile);
}
}
@@ -2312,16 +2325,16 @@
try {
Reflect.on(device).withMethod("removeBond").invoke();
} catch (ReflectionException e) {
- logger.log(e, "Error removing bond for device=%s", device);
+ mLogger.log(e, "Error removing bond for device=%s", device);
}
}
public void resetAccountKeys() {
- fastPairSimulatorDatabase.setAccountKeys(new HashSet<>());
- fastPairSimulatorDatabase.setFastPairSeekerDevices(new HashSet<>());
- accountKey = null;
- ownerAccountKey = null;
- logger.log("Remove all account keys");
+ mFastPairSimulatorDatabase.setAccountKeys(new HashSet<>());
+ mFastPairSimulatorDatabase.setFastPairSeekerDevices(new HashSet<>());
+ mAccountKey = null;
+ mOwnerAccountKey = null;
+ mLogger.log("Remove all account keys");
}
public void addAccountKey(byte[] key) {
@@ -2329,43 +2342,43 @@
}
private void addAccountKey(byte[] key, @Nullable BluetoothDevice device) {
- accountKey = key;
- if (ownerAccountKey == null) {
- ownerAccountKey = key;
+ mAccountKey = key;
+ if (mOwnerAccountKey == null) {
+ mOwnerAccountKey = key;
}
- fastPairSimulatorDatabase.addAccountKey(key);
- fastPairSimulatorDatabase.addFastPairSeekerDevice(device, key);
- logger.log("Add account key: key=%s, device=%s", base16().encode(key), device);
+ mFastPairSimulatorDatabase.addAccountKey(key);
+ mFastPairSimulatorDatabase.addFastPairSeekerDevice(device, key);
+ mLogger.log("Add account key: key=%s, device=%s", base16().encode(key), device);
}
private Set<ByteString> getAccountKeys() {
- return fastPairSimulatorDatabase.getAccountKeys();
+ return mFastPairSimulatorDatabase.getAccountKeys();
}
/** Get the latest account key. */
@Nullable
public ByteString getAccountKey() {
- if (accountKey == null) {
+ if (mAccountKey == null) {
return null;
}
- return ByteString.copyFrom(accountKey);
+ return ByteString.copyFrom(mAccountKey);
}
/** Get the owner account key (the first account key registered). */
@Nullable
public ByteString getOwnerAccountKey() {
- if (ownerAccountKey == null) {
+ if (mOwnerAccountKey == null) {
return null;
}
- return ByteString.copyFrom(ownerAccountKey);
+ return ByteString.copyFrom(mOwnerAccountKey);
}
public void resetDeviceName() {
- fastPairSimulatorDatabase.setLocalDeviceName(null);
+ mFastPairSimulatorDatabase.setLocalDeviceName(null);
// Trigger simulator to update device name text view.
- if (deviceNameCallback != null) {
- deviceNameCallback.onNameChanged(getDeviceName());
+ if (mDeviceNameCallback != null) {
+ mDeviceNameCallback.onNameChanged(getDeviceName());
}
}
@@ -2375,18 +2388,18 @@
}
private void setDeviceName(@Nullable byte[] deviceName) {
- fastPairSimulatorDatabase.setLocalDeviceName(deviceName);
+ mFastPairSimulatorDatabase.setLocalDeviceName(deviceName);
- logger.log("Save device name : %s", getDeviceName());
+ mLogger.log("Save device name : %s", getDeviceName());
// Trigger simulator to update device name text view.
- if (deviceNameCallback != null) {
- deviceNameCallback.onNameChanged(getDeviceName());
+ if (mDeviceNameCallback != null) {
+ mDeviceNameCallback.onNameChanged(getDeviceName());
}
}
@Nullable
private byte[] getDeviceNameInBytes() {
- return fastPairSimulatorDatabase.getLocalDeviceName();
+ return mFastPairSimulatorDatabase.getLocalDeviceName();
}
@Nullable
@@ -2395,7 +2408,7 @@
getDeviceNameInBytes() != null
? new String(getDeviceNameInBytes(), StandardCharsets.UTF_8)
: null;
- logger.log("get device name = %s", providerDeviceName);
+ mLogger.log("get device name = %s", providerDeviceName);
return providerDeviceName;
}
@@ -2410,19 +2423,19 @@
* </ul>
*/
private static byte tdsFlags(TransportState transportState) {
- return (byte) (0b00000010 & (transportState.byteValue << 3));
+ return (byte) (0b00000010 & (transportState.mByteValue << 3));
}
/** Detailed information about battery value. */
public static class BatteryValue {
- boolean charging;
+ boolean mCharging;
// The range is 0 ~ 100, and -1 represents the battery level is unknown.
- int level;
+ int mLevel;
public BatteryValue(boolean charging, int level) {
- this.charging = charging;
- this.level = level;
+ this.mCharging = charging;
+ this.mLevel = level;
}
}
}
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairSimulatorDatabase.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulatorDatabase.java
similarity index 84%
rename from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairSimulatorDatabase.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulatorDatabase.java
index 4d2a9e8..254ec51 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairSimulatorDatabase.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulatorDatabase.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.nearby.common.bluetooth.fastpair.testing;
+package android.nearby.fastpair.provider;
import static com.google.common.io.BaseEncoding.base16;
@@ -45,15 +45,15 @@
// [for SASS]
private static final String KEY_FAST_PAIR_SEEKER_DEVICE = "FAST_PAIR_SEEKER_DEVICE";
- private final SharedPreferences sharedPreferences;
+ private final SharedPreferences mSharedPreferences;
public FastPairSimulatorDatabase(Context context) {
- sharedPreferences = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
+ mSharedPreferences = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
}
/** Adds single account key. */
public void addAccountKey(byte[] accountKey) {
- if (sharedPreferences == null) {
+ if (mSharedPreferences == null) {
return;
}
@@ -78,7 +78,7 @@
/** Sets account keys, overrides all. */
public void setAccountKeys(Set<ByteString> accountKeys) {
- if (sharedPreferences == null) {
+ if (mSharedPreferences == null) {
return;
}
@@ -87,16 +87,16 @@
keys.add(base16().encode(item.toByteArray()));
}
- sharedPreferences.edit().putStringSet(KEY_ACCOUNT_KEYS, keys).apply();
+ mSharedPreferences.edit().putStringSet(KEY_ACCOUNT_KEYS, keys).apply();
}
/** Gets all account keys. */
public Set<ByteString> getAccountKeys() {
- if (sharedPreferences == null) {
+ if (mSharedPreferences == null) {
return new HashSet<>();
}
- Set<String> keys = sharedPreferences.getStringSet(KEY_ACCOUNT_KEYS, new HashSet<>());
+ Set<String> keys = mSharedPreferences.getStringSet(KEY_ACCOUNT_KEYS, new HashSet<>());
Set<ByteString> accountKeys = new HashSet<>();
// Add new account keys one by one.
for (String key : keys) {
@@ -108,26 +108,26 @@
/** Sets local device name. */
public void setLocalDeviceName(byte[] deviceName) {
- if (sharedPreferences == null) {
+ if (mSharedPreferences == null) {
return;
}
String humanReadableName = deviceName != null ? new String(deviceName, UTF_8) : null;
if (humanReadableName == null) {
- sharedPreferences.edit().remove(KEY_DEVICE_NAME).apply();
+ mSharedPreferences.edit().remove(KEY_DEVICE_NAME).apply();
} else {
- sharedPreferences.edit().putString(KEY_DEVICE_NAME, humanReadableName).apply();
+ mSharedPreferences.edit().putString(KEY_DEVICE_NAME, humanReadableName).apply();
}
}
/** Gets local device name. */
@Nullable
public byte[] getLocalDeviceName() {
- if (sharedPreferences == null) {
+ if (mSharedPreferences == null) {
return null;
}
- String deviceName = sharedPreferences.getString(KEY_DEVICE_NAME, null);
+ String deviceName = mSharedPreferences.getString(KEY_DEVICE_NAME, null);
return deviceName != null ? deviceName.getBytes(UTF_8) : null;
}
@@ -136,7 +136,7 @@
* href="http://go/smart-audio-source-switching-design">Sass design doc</a>
*/
public void addFastPairSeekerDevice(@Nullable BluetoothDevice device, byte[] accountKey) {
- if (sharedPreferences == null) {
+ if (mSharedPreferences == null) {
return;
}
@@ -164,7 +164,7 @@
/** [for SASS] Sets all seeker device info, overrides all. */
public void setFastPairSeekerDevices(Set<FastPairSeekerDevice> fastPairSeekerDeviceSet) {
- if (sharedPreferences == null) {
+ if (mSharedPreferences == null) {
return;
}
@@ -173,18 +173,18 @@
rawStringSet.add(item.toRawString());
}
- sharedPreferences.edit().putStringSet(KEY_FAST_PAIR_SEEKER_DEVICE, rawStringSet).apply();
+ mSharedPreferences.edit().putStringSet(KEY_FAST_PAIR_SEEKER_DEVICE, rawStringSet).apply();
}
/** [for SASS] Gets all seeker device info. */
public Set<FastPairSeekerDevice> getFastPairSeekerDevices() {
- if (sharedPreferences == null) {
+ if (mSharedPreferences == null) {
return new HashSet<>();
}
Set<FastPairSeekerDevice> fastPairSeekerDevices = new HashSet<>();
Set<String> rawStringSet =
- sharedPreferences.getStringSet(KEY_FAST_PAIR_SEEKER_DEVICE, new HashSet<>());
+ mSharedPreferences.getStringSet(KEY_FAST_PAIR_SEEKER_DEVICE, new HashSet<>());
for (String rawString : rawStringSet) {
FastPairSeekerDevice fastPairDevice = FastPairSeekerDevice.fromRawString(rawString);
if (fastPairDevice == null) {
@@ -201,24 +201,24 @@
private static final int INDEX_DEVICE = 0;
private static final int INDEX_ACCOUNT_KEY = 1;
- private final BluetoothDevice device;
- private final byte[] accountKey;
+ private final BluetoothDevice mDevice;
+ private final byte[] mAccountKey;
private FastPairSeekerDevice(BluetoothDevice device, byte[] accountKey) {
- this.device = device;
- this.accountKey = accountKey;
+ this.mDevice = device;
+ this.mAccountKey = accountKey;
}
public BluetoothDevice getBluetoothDevice() {
- return device;
+ return mDevice;
}
public byte[] getAccountKey() {
- return accountKey;
+ return mAccountKey;
}
public String toRawString() {
- return String.format("%s,%s", device, base16().encode(accountKey));
+ return String.format("%s,%s", mDevice, base16().encode(mAccountKey));
}
/** Decodes the raw string if possible. */
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/HandshakeRequest.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/HandshakeRequest.java
similarity index 84%
rename from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/HandshakeRequest.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/HandshakeRequest.java
index 5453a87..9cfffd8 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/HandshakeRequest.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/HandshakeRequest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.nearby.common.bluetooth.fastpair.testing;
+package android.nearby.fastpair.provider;
import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.decrypt;
import static com.android.server.nearby.common.bluetooth.fastpair.Constants.BLUETOOTH_ADDRESS_LENGTH;
@@ -45,7 +45,7 @@
*
* @see {go/fast-pair-spec-handshake-message1}
*/
- private final byte[] decryptedMessage;
+ private final byte[] mDecryptedMessage;
/** Enumerates the handshake message types. */
public enum Type {
@@ -53,14 +53,14 @@
ACTION_OVER_BLE(Request.TYPE_ACTION_OVER_BLE),
UNKNOWN((byte) 0xFF);
- private final byte value;
+ private final byte mValue;
Type(byte type) {
- value = type;
+ mValue = type;
}
public byte getValue() {
- return value;
+ return mValue;
}
public static Type valueOf(byte value) {
@@ -75,21 +75,24 @@
public HandshakeRequest(byte[] key, byte[] encryptedPairingRequest)
throws GeneralSecurityException {
- decryptedMessage = decrypt(key, encryptedPairingRequest);
+ mDecryptedMessage = decrypt(key, encryptedPairingRequest);
}
/**
- * Gets the type of this handshake request. Currently, we have 2 types: 0x00 for Key-based Pairing
- * Request and 0x10 for Action Request.
+ * Gets the type of this handshake request. Currently, we have 2 types: 0x00 for Key-based
+ * Pairing Request and 0x10 for Action Request.
*/
public Type getType() {
- return Type.valueOf(decryptedMessage[Request.TYPE_INDEX]);
+ return Type.valueOf(mDecryptedMessage[Request.TYPE_INDEX]);
}
- /** Gets verification data of this handshake request, currently, we use Provider's BLE address. */
+ /**
+ * Gets verification data of this handshake request.
+ * Currently, we use Provider's BLE address.
+ */
public byte[] getVerificationData() {
return Arrays.copyOfRange(
- decryptedMessage,
+ mDecryptedMessage,
Request.VERIFICATION_DATA_INDEX,
Request.VERIFICATION_DATA_INDEX + Request.VERIFICATION_DATA_LENGTH);
}
@@ -97,7 +100,7 @@
/** Gets Seeker's public address of the handshake request. */
public byte[] getSeekerPublicAddress() {
return Arrays.copyOfRange(
- decryptedMessage,
+ mDecryptedMessage,
Request.SEEKER_PUBLIC_ADDRESS_INDEX,
Request.SEEKER_PUBLIC_ADDRESS_INDEX + BLUETOOTH_ADDRESS_LENGTH);
}
@@ -126,7 +129,7 @@
/** Gets the flags of this handshake request. */
private byte getFlags() {
- return decryptedMessage[Request.FLAGS_INDEX];
+ return mDecryptedMessage[Request.FLAGS_INDEX];
}
/** Checks whether the Seeker requests a device action. */
@@ -134,17 +137,21 @@
return (getFlags() & DEVICE_ACTION) != 0;
}
- /** Checks whether the Seeker requests an action which will be followed by an additional data. */
+ /**
+ * Checks whether the Seeker requests an action which will be followed by an additional data
+ * .
+ */
public boolean requestFollowedByAdditionalData() {
return (getFlags() & ADDITIONAL_DATA_CHARACTERISTIC) != 0;
}
/** Gets the {@link AdditionalDataType} of this handshake request. */
- public @AdditionalDataType int getAdditionalDataType() {
+ @AdditionalDataType
+ public int getAdditionalDataType() {
if (!requestFollowedByAdditionalData()
- || decryptedMessage.length <= ADDITIONAL_DATA_TYPE_INDEX) {
+ || mDecryptedMessage.length <= ADDITIONAL_DATA_TYPE_INDEX) {
return AdditionalDataType.UNKNOWN;
}
- return decryptedMessage[ADDITIONAL_DATA_TYPE_INDEX];
+ return mDecryptedMessage[ADDITIONAL_DATA_TYPE_INDEX];
}
}
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/OreoFastPairAdvertiser.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/OreoFastPairAdvertiser.java
similarity index 73%
rename from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/OreoFastPairAdvertiser.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/OreoFastPairAdvertiser.java
index 6913356..dd664ea 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/OreoFastPairAdvertiser.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/OreoFastPairAdvertiser.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.nearby.common.bluetooth.fastpair.testing;
+package android.nearby.fastpair.provider;
import static com.google.common.io.BaseEncoding.base16;
@@ -26,6 +26,7 @@
import android.bluetooth.le.AdvertisingSetCallback;
import android.bluetooth.le.AdvertisingSetParameters;
import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.nearby.fastpair.provider.utils.Logger;
import android.os.Build.VERSION_CODES;
import android.os.ParcelUuid;
@@ -43,41 +44,43 @@
@TargetApi(VERSION_CODES.O)
public final class OreoFastPairAdvertiser implements FastPairAdvertiser {
private static final String TAG = "OreoFastPairAdvertiser";
- private final Logger logger = new Logger(TAG);
+ private final Logger mLogger = new Logger(TAG);
- private final FastPairSimulator simulator;
- private final BluetoothLeAdvertiser advertiser;
- private final AdvertisingSetCallback advertisingSetCallback;
- private AdvertisingSet advertisingSet;
+ private final FastPairSimulator mSimulator;
+ private final BluetoothLeAdvertiser mAdvertiser;
+ private final AdvertisingSetCallback mAdvertisingSetCallback;
+ private AdvertisingSet mAdvertisingSet;
public OreoFastPairAdvertiser(FastPairSimulator simulator) {
- this.simulator = simulator;
- this.advertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
- this.advertisingSetCallback =
+ this.mSimulator = simulator;
+ this.mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
+ this.mAdvertisingSetCallback =
new AdvertisingSetCallback() {
@Override
- public void onAdvertisingSetStarted(AdvertisingSet set, int txPower, int status) {
+ public void onAdvertisingSetStarted(
+ AdvertisingSet set, int txPower, int status) {
if (status == AdvertisingSetCallback.ADVERTISE_SUCCESS) {
- logger.log("Advertising succeeded, advertising at %s dBm", txPower);
+ mLogger.log("Advertising succeeded, advertising at %s dBm", txPower);
simulator.setIsAdvertising(true);
- advertisingSet = set;
+ mAdvertisingSet = set;
try {
// Requires custom Android build, see callback below.
Reflect.on(set).withMethod("getOwnAddress").invoke();
} catch (ReflectionException e) {
- logger.log(e, "Error calling getOwnAddress for AdvertisingSet");
+ mLogger.log(e, "Error calling getOwnAddress for AdvertisingSet");
}
} else {
- logger.log(
- new IllegalStateException(), "Advertising failed, error code=%d", status);
+ mLogger.log(
+ new IllegalStateException(),
+ "Advertising failed, error code=%d", status);
}
}
@Override
public void onAdvertisingDataSet(AdvertisingSet set, int status) {
if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
- logger.log(
+ mLogger.log(
new IllegalStateException(),
"Updating advertisement failed, error code=%d",
status);
@@ -86,9 +89,10 @@
}
// Called via reflection with AdvertisingSet.getOwnAddress().
- public void onOwnAddressRead(AdvertisingSet set, int addressType, String address) {
+ public void onOwnAddressRead(
+ AdvertisingSet set, int addressType, String address) {
if (!address.equals(simulator.getBleAddress())) {
- logger.log(
+ mLogger.log(
"Read own BLE address=%s at %s",
address,
new SimpleDateFormat("HH:mm:ss:SSS", Locale.US)
@@ -102,21 +106,21 @@
@Override
public void startAdvertising(@Nullable byte[] serviceData) {
// To be informed that BLE address is rotated, we need to polling query it asynchronously.
- if (advertisingSet != null) {
+ if (mAdvertisingSet != null) {
try {
// Requires custom Android build, see callback: onOwnAddressRead.
- Reflect.on(advertisingSet).withMethod("getOwnAddress").invoke();
+ Reflect.on(mAdvertisingSet).withMethod("getOwnAddress").invoke();
} catch (ReflectionException ignored) {
// Ignore it due to user already knows it when setting advertisingSet.
}
}
- if (simulator.isDestroyed()) {
+ if (mSimulator.isDestroyed()) {
return;
}
if (serviceData == null) {
- logger.log("Service data is null, stop advertising");
+ mLogger.log("Service data is null, stop advertising");
stopAdvertising();
return;
}
@@ -127,10 +131,10 @@
.setIncludeTxPowerLevel(true)
.build();
- logger.log("Advertising FE2C service data=%s", base16().encode(serviceData));
+ mLogger.log("Advertising FE2C service data=%s", base16().encode(serviceData));
- if (advertisingSet != null) {
- advertisingSet.setAdvertisingData(data);
+ if (mAdvertisingSet != null) {
+ mAdvertisingSet.setAdvertisingData(data);
return;
}
@@ -141,9 +145,10 @@
.setConnectable(true)
.setScannable(true)
.setInterval(AdvertisingSetParameters.INTERVAL_LOW)
- .setTxPowerLevel(convertAdvertiseSettingsTxPower(simulator.getTxPower()))
+ .setTxPowerLevel(convertAdvertiseSettingsTxPower(mSimulator.getTxPower()))
.build();
- advertiser.startAdvertisingSet(parameters, data, null, null, null, advertisingSetCallback);
+ mAdvertiser.startAdvertisingSet(parameters, data, null, null, null,
+ mAdvertisingSetCallback);
}
private static int convertAdvertiseSettingsTxPower(int txPower) {
@@ -161,12 +166,12 @@
@Override
public void stopAdvertising() {
- if (simulator.isDestroyed()) {
+ if (mSimulator.isDestroyed()) {
return;
}
- advertiser.stopAdvertisingSet(advertisingSetCallback);
- advertisingSet = null;
- simulator.setIsAdvertising(false);
+ mAdvertiser.stopAdvertisingSet(mAdvertisingSetCallback);
+ mAdvertisingSet = null;
+ mSimulator.setIsAdvertising(false);
}
}
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/gatt/server/BluetoothGattServerConfig.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerConfig.java
similarity index 77%
rename from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/gatt/server/BluetoothGattServerConfig.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerConfig.java
index 9fa54fe..3cacd55 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/gatt/server/BluetoothGattServerConfig.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerConfig.java
@@ -1,5 +1,20 @@
+/*
+ * 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.bluetooth.gatt.server;
+package android.nearby.fastpair.provider.bluetooth;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothGattCharacteristic;
@@ -46,15 +61,16 @@
* TODO(lingjunl): remove them when b/21587710 is fixed.
*/
public BluetoothGattServerConfig addSelfDefinedDynamicService() {
- ServiceConfig serviceConfig = new ServiceConfig().addCharacteristic(new BluetoothGattServlet() {
- @Override
- public BluetoothGattCharacteristic getCharacteristic() {
- return new BluetoothGattCharacteristic(
- BluetoothConsts.SERVICE_DYNAMIC_CHARACTERISTIC,
- BluetoothGattCharacteristic.PROPERTY_READ,
- BluetoothGattCharacteristic.PERMISSION_READ);
- }
- });
+ ServiceConfig serviceConfig = new ServiceConfig().addCharacteristic(
+ new BluetoothGattServlet() {
+ @Override
+ public BluetoothGattCharacteristic getCharacteristic() {
+ return new BluetoothGattCharacteristic(
+ BluetoothConsts.SERVICE_DYNAMIC_CHARACTERISTIC,
+ BluetoothGattCharacteristic.PROPERTY_READ,
+ BluetoothGattCharacteristic.PERMISSION_READ);
+ }
+ });
return addService(BluetoothConsts.SERVICE_DYNAMIC_SERVICE, serviceConfig);
}
@@ -67,8 +83,8 @@
// This is not supposed to happen
throw new IllegalStateException();
}
- BluetoothGattService gattService =
- new BluetoothGattService(serviceUuid, BluetoothGattService.SERVICE_TYPE_PRIMARY);
+ BluetoothGattService gattService = new BluetoothGattService(serviceUuid,
+ BluetoothGattService.SERVICE_TYPE_PRIMARY);
for (Entry<BluetoothGattCharacteristic, BluetoothGattServlet> servletEntry :
serviceConfig.getServlets().entrySet()) {
BluetoothGattCharacteristic characteristic = servletEntry.getKey();
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/gatt/server/BluetoothGattServerConnection.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerConnection.java
similarity index 86%
rename from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/gatt/server/BluetoothGattServerConnection.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerConnection.java
index b67e00e..fae6951 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/gatt/server/BluetoothGattServerConnection.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerConnection.java
@@ -1,4 +1,20 @@
-package com.android.server.nearby.common.bluetooth.gatt.server;
+/*
+ * 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.provider.bluetooth;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothGatt;
@@ -12,7 +28,6 @@
import com.android.server.nearby.common.bluetooth.BluetoothGattException;
import com.android.server.nearby.common.bluetooth.ReservedUuids;
import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice;
-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;
@@ -53,7 +68,8 @@
/** Default MTU when value is unknown. */
public static final int DEFAULT_MTU = 23;
- @VisibleForTesting static final long OPERATION_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+ @VisibleForTesting
+ static final long OPERATION_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
/** Notification types as defined by the BLE spec vol 4, sec G, part 3.3.3.3 */
public enum NotificationType {
@@ -72,7 +88,8 @@
private final BluetoothGattServerHelper mBluetoothGattServerHelper;
private final BluetoothDevice mBluetoothDevice;
- @VisibleForTesting BluetoothOperationExecutor mBluetoothOperationScheduler =
+ @VisibleForTesting
+ BluetoothOperationExecutor mBluetoothOperationScheduler =
new BluetoothOperationExecutor(1);
/** Stores pending writes. For each UUID, we store an offset and a byte[] of data. */
@@ -129,12 +146,13 @@
}
}
- private final BluetoothGattServlet getServlet(BluetoothGattCharacteristic characteristic)
+ private BluetoothGattServlet getServlet(BluetoothGattCharacteristic characteristic)
throws BluetoothGattException {
BluetoothGattServlet servlet = mServlets.get(characteristic);
if (servlet == null) {
throw new BluetoothGattException(
- String.format("No handler registered for characteristic %s.", characteristic.getUuid()),
+ String.format("No handler registered for characteristic %s.",
+ characteristic.getUuid()),
BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
}
return servlet;
@@ -145,7 +163,8 @@
return getServlet(characteristic).read(this, offset);
}
- public void writeCharacteristic(BluetoothGattCharacteristic characteristic, boolean preparedWrite,
+ public void writeCharacteristic(BluetoothGattCharacteristic characteristic,
+ boolean preparedWrite,
int offset, byte[] value) throws BluetoothGattException {
Log.d(TAG, String.format(
"Received %d bytes at offset %d on %s from device %s, prepareWrite=%s.",
@@ -214,18 +233,21 @@
}
private void handleCharacteristicConfigurationChange(
- final BluetoothGattCharacteristic characteristic, BluetoothGattServlet servlet, int offset,
+ final BluetoothGattCharacteristic characteristic, BluetoothGattServlet servlet,
+ int offset,
byte[] value)
throws BluetoothGattException {
if (offset != 0) {
throw new BluetoothGattException(String.format(
- "Offset should be 0 when changing the client characteristic config: %d.", offset),
+ "Offset should be 0 when changing the client characteristic config: %d.",
+ offset),
BluetoothGatt.GATT_INVALID_OFFSET);
}
if (value.length != 2) {
throw new BluetoothGattException(String.format(
"Value 0x%s is undefined for the client characteristic config",
- BaseEncoding.base16().encode(value)), BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH);
+ BaseEncoding.base16().encode(value)),
+ BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH);
}
boolean notificationRegistered = mRegisteredNotifications.containsKey(characteristic);
@@ -268,7 +290,8 @@
default:
throw new BluetoothGattException(String.format(
"Value 0x%s is undefined for the client characteristic config",
- BaseEncoding.base16().encode(value)), BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
+ BaseEncoding.base16().encode(value)),
+ BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
}
}
@@ -305,27 +328,27 @@
* Assembles the specified queued writes and calls the provided write handler on the assembled
* chunks. Tries to assemble all the chunks into one write request. For example, if the content
* of byteChunks is:
- * <code>
- * offset data_size
- * 0 10
- * 10 1
- * 11 5
- * </code>
+ * <code>
+ * offset data_size
+ * 0 10
+ * 10 1
+ * 11 5
+ * </code>
*
- * then this method would call <code>writeHandler.onWrite(0, byte[16])</code>
+ * then this method would call <code>writeHandler.onWrite(0, byte[16])</code>
*
* However, if all the chunks cannot be assembled into a continuous byte[], then onWrite() will
* be called multiple times with the largest continuous chunks. For example, if the content of
* byteChunks is:
* <code>
- * offset data_size
- * 10 12
- * 30 5
- * 35 9
+ * offset data_size
+ * 10 12
+ * 30 5
+ * 35 9
* </code>
*
- * then this method would call <code>writeHandler.onWrite(10, byte[12)</code> and
- * <code>writeHandler.onWrite(30, byte[14]).
+ * then this method would call <code>writeHandler.onWrite(10, byte[12)</code> and
+ * <code>writeHandler.onWrite(30, byte[14]).
*/
private void assembleByteChunksAndHandle(BluetoothGattServlet servlet,
SortedMap<Integer, byte[]> byteChunks) throws BluetoothGattException {
@@ -336,8 +359,9 @@
Integer offset = byteChunks.firstKey();
if (offset.intValue() < startWritingAtOffset + assembledRequest.size()) {
- throw new BluetoothGattException("Expected offset of at least " + assembledRequest.size()
- + ", but got offset " + offset, BluetoothGatt.GATT_INVALID_OFFSET);
+ throw new BluetoothGattException(
+ "Expected offset of at least " + assembledRequest.size()
+ + ", but got offset " + offset, BluetoothGatt.GATT_INVALID_OFFSET);
}
// If we have a hole, then write what we've already assembled and start assembling a new
@@ -357,7 +381,8 @@
}
assembledRequest.write(dataChunk);
} catch (IOException e) {
- throw new BluetoothGattException("Error assembling request", BluetoothGatt.GATT_FAILURE);
+ throw new BluetoothGattException("Error assembling request",
+ BluetoothGatt.GATT_FAILURE);
}
}
@@ -375,8 +400,10 @@
new Operation<Void>(OperationType.SEND_NOTIFICATION) {
@Override
public void run() throws BluetoothException {
- mBluetoothGattServerHelper.sendNotification(mBluetoothDevice, characteristic,
- data, notificationType == NotificationType.INDICATION ? true : false);
+ mBluetoothGattServerHelper.sendNotification(mBluetoothDevice,
+ characteristic,
+ data,
+ notificationType == NotificationType.INDICATION ? true : false);
}
},
OPERATION_TIMEOUT);
@@ -409,7 +436,7 @@
private final Object mScope;
private final String mKey;
- public ScopedKey(Object scope, String key) {
+ ScopedKey(Object scope, String key) {
mScope = scope;
mKey = key;
}
@@ -430,12 +457,12 @@
}
/** Listener to be notified when the connection closes. */
- public static interface Listener {
+ public interface Listener {
void onClose();
}
/** Notifier to notify data over notification or indication. */
- public static interface Notifier {
+ public interface Notifier {
void notify(byte[] data) throws BluetoothException;
}
}
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/gatt/server/BluetoothGattServerHelper.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerHelper.java
similarity index 77%
rename from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/gatt/server/BluetoothGattServerHelper.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerHelper.java
index 0ba96c0..9339e14 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/gatt/server/BluetoothGattServerHelper.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServerHelper.java
@@ -1,4 +1,20 @@
-package com.android.server.nearby.common.bluetooth.gatt.server;
+/*
+ * 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.provider.bluetooth;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothGatt;
@@ -19,8 +35,6 @@
import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice;
import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattServer;
import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattServerCallback;
-import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothManager;
-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;
@@ -37,7 +51,8 @@
public class BluetoothGattServerHelper {
private static final String TAG = BluetoothGattServerHelper.class.getSimpleName();
- @VisibleForTesting static final long OPERATION_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(1);
+ @VisibleForTesting
+ static final long OPERATION_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(1);
private static final int MAX_PARALLEL_OPERATIONS = 5;
/** BT operation types that can be in flight. */
@@ -48,9 +63,11 @@
}
private final Object mOperationLock = new Object();
- @VisibleForTesting final BluetoothGattServerCallback mGattServerCallback =
+ @VisibleForTesting
+ final BluetoothGattServerCallback mGattServerCallback =
new GattServerCallback();
- @VisibleForTesting BluetoothOperationExecutor mBluetoothOperationScheduler =
+ @VisibleForTesting
+ BluetoothOperationExecutor mBluetoothOperationScheduler =
new BluetoothOperationExecutor(MAX_PARALLEL_OPERATIONS);
private final Context mContext;
@@ -58,12 +75,15 @@
private final VersionProvider mVersionProvider;
@Nullable
- @VisibleForTesting volatile BluetoothGattServerConfig mServerConfig = null;
+ @VisibleForTesting
+ volatile BluetoothGattServerConfig mServerConfig = null;
@Nullable
- @VisibleForTesting volatile BluetoothGattServer mBluetoothGattServer = null;
+ @VisibleForTesting
+ volatile BluetoothGattServer mBluetoothGattServer = null;
- @VisibleForTesting final ConcurrentMap<BluetoothDevice, BluetoothGattServerConnection>
+ @VisibleForTesting
+ final ConcurrentMap<BluetoothDevice, BluetoothGattServerConnection>
mConnections = new ConcurrentHashMap<BluetoothDevice, BluetoothGattServerConnection>();
public BluetoothGattServerHelper(Context context, BluetoothManager bluetoothManager) {
@@ -74,7 +94,8 @@
);
}
- @VisibleForTesting BluetoothGattServerHelper(
+ @VisibleForTesting
+ BluetoothGattServerHelper(
Context context, BluetoothManager bluetoothManager, VersionProvider versionProvider) {
mContext = context;
mBluetoothManager = bluetoothManager;
@@ -97,7 +118,8 @@
}
try {
- for (final BluetoothGattService service : gattServerConfig.getBluetoothGattServices()) {
+ for (final BluetoothGattService service :
+ gattServerConfig.getBluetoothGattServices()) {
if (service == null) {
continue;
}
@@ -165,7 +187,8 @@
if (bluetoothGattServer == null) {
throw new BluetoothException("Server is not open.");
}
- BluetoothGattCharacteristic clonedCharacteristic = BluetoothGattUtils.clone(characteristic);
+ BluetoothGattCharacteristic clonedCharacteristic =
+ BluetoothGattUtils.clone(characteristic);
clonedCharacteristic.setValue(data);
bluetoothGattServer.notifyCharacteristicChanged(device, clonedCharacteristic, confirm);
}
@@ -181,12 +204,13 @@
if (connectionSate != BluetoothGatt.STATE_CONNECTED) {
return;
}
- mBluetoothOperationScheduler.execute(new Operation<Void>(OperationType.CLOSE_CONNECTION) {
- @Override
- public void run() throws BluetoothException {
- bluetoothGattServer.cancelConnection(bluetoothDevice);
- }
- },
+ mBluetoothOperationScheduler.execute(
+ new Operation<Void>(OperationType.CLOSE_CONNECTION) {
+ @Override
+ public void run() throws BluetoothException {
+ bluetoothGattServer.cancelConnection(bluetoothDevice);
+ }
+ },
OPERATION_TIMEOUT_MILLIS);
}
@@ -214,8 +238,8 @@
}
Log.i(TAG, String.format("Connected to device %s.", device));
if (mConnections.containsKey(device)) {
- Log.w(TAG, String.format(
- "A connection is already open with device %s. Keeping existing one.", device));
+ Log.w(TAG, String.format("A connection is already open with device %s. "
+ + "Keeping existing one.", device));
return;
}
@@ -228,28 +252,35 @@
}
mConnections.put(device, connection);
- // By default, Android disconnects active GATT server connection if the advertisement is
- // stop (or sometime stopScanning also disconnect, see b/62667394). Asking the server to
+ // By default, Android disconnects active GATT server connection if the
+ // advertisement is
+ // stop (or sometime stopScanning also disconnect, see b/62667394). Asking
+ // the server to
// reverse connect will tell Android to keep the connection open.
// Code handling connect() on Android OS is: btif_gatt_server.c
// Note: for Android < P, unknown type devices don't connect. See b/62827460.
- // TODO(mfucci): this can be fixed if the GATT server is forced to be LE only.
- // for Android P+, unknown type devices always use LE to connect (see code)
- // Note: for Android < N, dual mode devices always connect using BT classic, so connect()
+ // for Android P+, unknown type devices always use LE to connect (see
+ // code)
+ // Note: for Android < N, dual mode devices always connect using BT classic,
+ // so connect()
// should *NOT* be called for those devices. See b/29819614.
if (mVersionProvider.getSdkInt() >= VERSION_CODES.N
|| device.getType() != BluetoothDevice.DEVICE_TYPE_DUAL) {
- boolean success = bluetoothGattServer.connect(device, /* autoConnect */false);
+ boolean success = bluetoothGattServer.connect(device, /* autoConnect */
+ false);
if (!success) {
Log.w(TAG, String.format(
- "Keeping connection open on stop advertising failed for device %s.", device));
+ "Keeping connection open on stop advertising failed for "
+ + "device %s.",
+ device));
}
}
break;
case BluetoothGattServer.STATE_DISCONNECTED:
if (status != BluetoothGatt.GATT_SUCCESS) {
- Log.w(TAG, String.format("Disconnection from %s error: %s. Proceeding anyway.", device,
- BluetoothGattUtils.getMessageForStatusCode(status)));
+ Log.w(TAG, String.format(
+ "Disconnection from %s error: %s. Proceeding anyway.",
+ device, BluetoothGattUtils.getMessageForStatusCode(status)));
}
bluetoothLeConnection = mConnections.remove(device);
if (bluetoothLeConnection != null) {
@@ -262,7 +293,6 @@
break;
default:
Log.e(TAG, String.format("Unexpected connection state: %d", newState));
- return;
}
}
@@ -274,8 +304,10 @@
return;
}
try {
- byte[] value = getConnectionByDevice(device).readCharacteristic(offset, characteristic);
- bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
+ byte[] value =
+ getConnectionByDevice(device).readCharacteristic(offset, characteristic);
+ bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
+ offset,
value);
} catch (BluetoothGattException e) {
Log.e(TAG,
@@ -285,7 +317,8 @@
device,
offset),
e);
- bluetoothGattServer.sendResponse(device, requestId, e.getGattErrorCode(), offset, null);
+ bluetoothGattServer.sendResponse(
+ device, requestId, e.getGattErrorCode(), offset, null);
}
}
@@ -307,8 +340,8 @@
offset,
value);
if (responseNeeded) {
- bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
- null);
+ bluetoothGattServer.sendResponse(
+ device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
}
} catch (BluetoothGattException e) {
Log.e(TAG,
@@ -318,7 +351,8 @@
device,
offset),
e);
- bluetoothGattServer.sendResponse(device, requestId, e.getGattErrorCode(), offset, null);
+ bluetoothGattServer.sendResponse(
+ device, requestId, e.getGattErrorCode(), offset, null);
}
}
@@ -331,8 +365,8 @@
}
try {
byte[] value = getConnectionByDevice(device).readDescriptor(offset, descriptor);
- bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
- value);
+ bluetoothGattServer.sendResponse(
+ device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
} catch (BluetoothGattException e) {
Log.e(TAG, String.format(
"Could not read %s on device %s at %d",
@@ -340,7 +374,8 @@
device,
offset),
e);
- bluetoothGattServer.sendResponse(device, requestId, e.getGattErrorCode(), offset, null);
+ bluetoothGattServer.sendResponse(
+ device, requestId, e.getGattErrorCode(), offset, null);
}
}
@@ -357,10 +392,11 @@
return;
}
try {
- getConnectionByDevice(device).writeDescriptor(descriptor, preparedWrite, offset, value);
+ getConnectionByDevice(device)
+ .writeDescriptor(descriptor, preparedWrite, offset, value);
if (responseNeeded) {
- bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
- null);
+ bluetoothGattServer.sendResponse(
+ device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
}
Log.d(TAG, "Operation onDescriptorWriteRequest successful.");
} catch (BluetoothGattException e) {
@@ -371,7 +407,8 @@
device,
offset),
e);
- bluetoothGattServer.sendResponse(device, requestId, e.getGattErrorCode(), offset, null);
+ bluetoothGattServer.sendResponse(
+ device, requestId, e.getGattErrorCode(), offset, null);
}
}
@@ -383,7 +420,8 @@
}
try {
getConnectionByDevice(device).executeWrite(execute);
- bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
+ bluetoothGattServer.sendResponse(
+ device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
} catch (BluetoothGattException e) {
Log.e(TAG, "Could not execute write.", e);
bluetoothGattServer.sendResponse(device, requestId, e.getGattErrorCode(), 0, null);
@@ -392,12 +430,13 @@
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
- Log.d(TAG, String.format("Received onNotificationSent for device %s with status %s", device,
- status));
+ Log.d(TAG,
+ String.format("Received onNotificationSent for device %s with status %s",
+ device, status));
try {
getConnectionByDevice(device).notifyNotificationSent(status);
} catch (BluetoothGattException e) {
- Log.e(TAG, String.format("An error occurred when receiving onNotificationSent"), e);
+ Log.e(TAG, "An error occurred when receiving onNotificationSent: " + e);
}
}
}
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/gatt/server/BluetoothGattServlet.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServlet.java
similarity index 75%
rename from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/gatt/server/BluetoothGattServlet.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServlet.java
index c4a2473..e25e223 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/gatt/server/BluetoothGattServlet.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattServlet.java
@@ -1,12 +1,28 @@
-package com.android.server.nearby.common.bluetooth.gatt.server;
+/*
+ * 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.provider.bluetooth;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
+import android.nearby.fastpair.provider.bluetooth.BluetoothGattServerConnection.Notifier;
import com.android.server.nearby.common.bluetooth.BluetoothGattException;
-import com.android.server.nearby.common.bluetooth.gatt.server.BluetoothGattServerConnection.Notifier;
/** Servlet to handle GATT operations on a characteristic. */
@TargetApi(18)
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtils.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattUtils.java
similarity index 98%
rename from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtils.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattUtils.java
index e90b732..7ac26ee 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtils.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothGattUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.nearby.common.bluetooth.util;
+package android.nearby.fastpair.provider.bluetooth;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothManager.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothManager.java
similarity index 83%
rename from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothManager.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothManager.java
index 4ccfb59..bf241f1 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothManager.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -14,12 +14,17 @@
* limitations under the License.
*/
-package com.android.server.nearby.common.bluetooth.testability.android.bluetooth;
+package android.nearby.fastpair.provider.bluetooth;
import android.content.Context;
import androidx.annotation.Nullable;
+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.BluetoothGattServer;
+import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattServerCallback;
+
import java.util.ArrayList;
import java.util.List;
@@ -72,4 +77,4 @@
public static BluetoothManager wrap(android.bluetooth.BluetoothManager bluetoothManager) {
return new BluetoothManager(bluetoothManager);
}
-}
\ No newline at end of file
+}
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/RfcommServer.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/RfcommServer.java
similarity index 76%
rename from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/RfcommServer.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/RfcommServer.java
index 7846226..9ed95ac 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/RfcommServer.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/RfcommServer.java
@@ -14,20 +14,21 @@
* limitations under the License.
*/
-package com.android.server.nearby.common.bluetooth.fastpair.testing;
+package android.nearby.fastpair.provider.bluetooth;
-import static com.android.server.nearby.common.bluetooth.fastpair.testing.RfcommServer.State.ACCEPTING;
-import static com.android.server.nearby.common.bluetooth.fastpair.testing.RfcommServer.State.CONNECTED;
-import static com.android.server.nearby.common.bluetooth.fastpair.testing.RfcommServer.State.RESTARTING;
-import static com.android.server.nearby.common.bluetooth.fastpair.testing.RfcommServer.State.STARTING;
-import static com.android.server.nearby.common.bluetooth.fastpair.testing.RfcommServer.State.STOPPED;
+import static android.nearby.fastpair.provider.bluetooth.RfcommServer.State.ACCEPTING;
+import static android.nearby.fastpair.provider.bluetooth.RfcommServer.State.CONNECTED;
+import static android.nearby.fastpair.provider.bluetooth.RfcommServer.State.RESTARTING;
+import static android.nearby.fastpair.provider.bluetooth.RfcommServer.State.STARTING;
+import static android.nearby.fastpair.provider.bluetooth.RfcommServer.State.STOPPED;
import static java.nio.charset.StandardCharsets.UTF_8;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
-import android.nearby.multidevices.fastpair.EventStreamProtocol;
+import android.nearby.fastpair.provider.EventStreamProtocol;
+import android.nearby.fastpair.provider.utils.Logger;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -46,33 +47,33 @@
*/
public class RfcommServer {
private static final String TAG = "RfcommServer";
- private final Logger logger = new Logger(TAG);
+ private final Logger mLogger = new Logger(TAG);
private static final String FAST_PAIR_RFCOMM_SERVICE_NAME = "FastPairServer";
public static final UUID FAST_PAIR_RFCOMM_UUID =
UUID.fromString("df21fe2c-2515-4fdb-8886-f12c4d67927c");
/** A single thread executor where all state checks are performed. */
- private final ExecutorService controllerExecutor = Executors.newSingleThreadExecutor();
+ private final ExecutorService mControllerExecutor = Executors.newSingleThreadExecutor();
- private final ExecutorService sendMessageExecutor = Executors.newSingleThreadExecutor();
- private final ExecutorService receiveMessageExecutor = Executors.newSingleThreadExecutor();
+ private final ExecutorService mSendMessageExecutor = Executors.newSingleThreadExecutor();
+ private final ExecutorService mReceiveMessageExecutor = Executors.newSingleThreadExecutor();
@Nullable
- private BluetoothServerSocket serverSocket;
+ private BluetoothServerSocket mServerSocket;
@Nullable
- private BluetoothSocket socket;
+ private BluetoothSocket mSocket;
- private State state = STOPPED;
- private boolean isStopRequested = false;
+ private State mState = STOPPED;
+ private boolean mIsStopRequested = false;
@Nullable
- private RequestHandler requestHandler;
+ private RequestHandler mRequestHandler;
@Nullable
- private CountDownLatch countDownLatch;
+ private CountDownLatch mCountDownLatch;
@Nullable
- private StateMonitor stateMonitor;
+ private StateMonitor mStateMonitor;
/**
* Manages RfcommServer status.
@@ -108,12 +109,12 @@
private void startServer() {
log("Start RfcommServer");
- if (!state.equals(STOPPED)) {
+ if (!mState.equals(STOPPED)) {
log("Server is not stopped, skip start request.");
return;
}
updateState(STARTING);
- isStopRequested = false;
+ mIsStopRequested = false;
startAccept();
}
@@ -127,7 +128,7 @@
private void startAccept() {
try {
// Gets server socket in controller thread for stop() API.
- serverSocket =
+ mServerSocket =
BluetoothAdapter.getDefaultAdapter()
.listenUsingRfcommWithServiceRecord(
FAST_PAIR_RFCOMM_SERVICE_NAME, FAST_PAIR_RFCOMM_UUID);
@@ -138,7 +139,7 @@
}
updateState(ACCEPTING);
- new Thread(() -> accept(serverSocket)).start();
+ new Thread(() -> accept(mServerSocket)).start();
}
private void accept(BluetoothServerSocket serverSocket) {
@@ -156,7 +157,7 @@
}
private void handleAcceptException(BluetoothServerSocket serverSocket) {
- if (isStopRequested) {
+ if (mIsStopRequested) {
stopServer();
} else {
closeServerSocket(serverSocket);
@@ -165,7 +166,7 @@
}
private void startListen(BluetoothSocket bluetoothSocket) {
- if (isStopRequested) {
+ if (mIsStopRequested) {
closeSocket(bluetoothSocket);
stopServer();
return;
@@ -173,7 +174,7 @@
updateState(CONNECTED);
// Sets method parameter to global socket for stop() API.
- this.socket = bluetoothSocket;
+ this.mSocket = bluetoothSocket;
new Thread(() -> listen(bluetoothSocket)).start();
}
@@ -195,11 +196,12 @@
} while (count < additionalLength);
}
- if (requestHandler != null) {
- // In order not to block listening thread, use different thread to dispatch message.
- receiveMessageExecutor.execute(
+ if (mRequestHandler != null) {
+ // In order not to block listening thread, use different thread to dispatch
+ // message.
+ mReceiveMessageExecutor.execute(
() -> {
- requestHandler.handleRequest(eventGroup, eventCode, data);
+ mRequestHandler.handleRequest(eventGroup, eventCode, data);
triggerCountdownLatch();
});
}
@@ -207,13 +209,14 @@
} catch (IOException e) {
log(
String.format(
- "IOException when listening to %s", bluetoothSocket.getRemoteDevice().getAddress()));
+ "IOException when listening to %s",
+ bluetoothSocket.getRemoteDevice().getAddress()));
runInControllerExecutor(() -> handleListenException(bluetoothSocket));
}
}
private void handleListenException(BluetoothSocket bluetoothSocket) {
- if (isStopRequested) {
+ if (mIsStopRequested) {
stopServer();
} else {
closeSocket(bluetoothSocket);
@@ -225,15 +228,18 @@
switch (eventGroup) {
case BLUETOOTH:
send(EventStreamProtocol.EventGroup.BLUETOOTH_VALUE,
- EventStreamProtocol.BluetoothEventCode.BLUETOOTH_ENABLE_SILENCE_MODE_VALUE, new byte[0]);
+ EventStreamProtocol.BluetoothEventCode.BLUETOOTH_ENABLE_SILENCE_MODE_VALUE,
+ new byte[0]);
break;
case LOGGING:
- send(EventStreamProtocol.EventGroup.LOGGING_VALUE, EventStreamProtocol.LoggingEventCode.LOG_FULL_VALUE,
+ send(EventStreamProtocol.EventGroup.LOGGING_VALUE,
+ EventStreamProtocol.LoggingEventCode.LOG_FULL_VALUE,
new byte[0]);
break;
case DEVICE:
send(EventStreamProtocol.EventGroup.DEVICE_VALUE,
- EventStreamProtocol.DeviceEventCode.DEVICE_BATTERY_INFO_VALUE, new byte[]{0x11, 0x12, 0x13});
+ EventStreamProtocol.DeviceEventCode.DEVICE_BATTERY_INFO_VALUE,
+ new byte[]{0x11, 0x12, 0x13});
break;
default: // fall out
}
@@ -248,12 +254,12 @@
public void send(int eventGroup, int eventCode, byte[] data) {
runInControllerExecutor(
() -> {
- if (!CONNECTED.equals(state)) {
+ if (!CONNECTED.equals(mState)) {
log("Server is not in CONNECTED state, skip send request");
return;
}
- BluetoothSocket bluetoothSocket = this.socket;
- sendMessageExecutor.execute(() -> {
+ BluetoothSocket bluetoothSocket = this.mSocket;
+ mSendMessageExecutor.execute(() -> {
String address = bluetoothSocket.getRemoteDevice().getAddress();
try {
DataOutputStream dataOutputStream =
@@ -285,23 +291,23 @@
runInControllerExecutor(() -> {
log("Stop RfcommServer");
- if (STOPPED.equals(state)) {
+ if (STOPPED.equals(mState)) {
log("Server is stopped, skip stop request.");
return;
}
- if (isStopRequested) {
+ if (mIsStopRequested) {
log("Stop is already requested, skip stop request.");
return;
}
- isStopRequested = true;
+ mIsStopRequested = true;
- if (ACCEPTING.equals(state)) {
- closeServerSocket(serverSocket);
+ if (ACCEPTING.equals(mState)) {
+ closeServerSocket(mServerSocket);
}
- if (CONNECTED.equals(state)) {
- closeSocket(socket);
+ if (CONNECTED.equals(mState)) {
+ closeSocket(mSocket);
}
});
}
@@ -312,11 +318,11 @@
}
private void updateState(State newState) {
- log(String.format("Change state from %s to %s", state, newState));
- if (stateMonitor != null) {
- stateMonitor.onStateChanged(newState);
+ log(String.format("Change state from %s to %s", mState, newState));
+ if (mStateMonitor != null) {
+ mStateMonitor.onStateChanged(newState);
}
- state = newState;
+ mState = newState;
}
private void closeServerSocket(BluetoothServerSocket serverSocket) {
@@ -342,25 +348,26 @@
socket.close();
}
} catch (IOException e) {
- log(String.format("IOException when close socket %s", socket.getRemoteDevice().getAddress()));
+ log(String.format("IOException when close socket %s",
+ socket.getRemoteDevice().getAddress()));
}
}
private void runInControllerExecutor(Runnable runnable) {
- controllerExecutor.execute(runnable);
+ mControllerExecutor.execute(runnable);
}
private void log(String message) {
- logger.log("Server=%s, %s", FAST_PAIR_RFCOMM_SERVICE_NAME, message);
+ mLogger.log("Server=%s, %s", FAST_PAIR_RFCOMM_SERVICE_NAME, message);
}
private void log(String message, Throwable e) {
- logger.log(e, "Server=%s, %s", FAST_PAIR_RFCOMM_SERVICE_NAME, message);
+ mLogger.log(e, "Server=%s, %s", FAST_PAIR_RFCOMM_SERVICE_NAME, message);
}
private void triggerCountdownLatch() {
- if (countDownLatch != null) {
- countDownLatch.countDown();
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
}
}
@@ -370,7 +377,7 @@
}
public void setRequestHandler(@Nullable RequestHandler requestHandler) {
- this.requestHandler = requestHandler;
+ this.mRequestHandler = requestHandler;
}
/** A state monitor to send signal when state is changed. */
@@ -379,24 +386,24 @@
}
public void setStateMonitor(@Nullable StateMonitor stateMonitor) {
- this.stateMonitor = stateMonitor;
+ this.mStateMonitor = stateMonitor;
}
@VisibleForTesting
void setCountDownLatch(@Nullable CountDownLatch countDownLatch) {
- this.countDownLatch = countDownLatch;
+ this.mCountDownLatch = countDownLatch;
}
@VisibleForTesting
void setIsStopRequested(boolean isStopRequested) {
- this.isStopRequested = isStopRequested;
+ this.mIsStopRequested = isStopRequested;
}
@VisibleForTesting
void simulateAcceptIOException() {
runInControllerExecutor(() -> {
- if (ACCEPTING.equals(state)) {
- closeServerSocket(serverSocket);
+ if (ACCEPTING.equals(mState)) {
+ closeServerSocket(mServerSocket);
}
});
}
@@ -404,8 +411,8 @@
@VisibleForTesting
void simulateListenIOException() {
runInControllerExecutor(() -> {
- if (CONNECTED.equals(state)) {
- closeSocket(socket);
+ if (CONNECTED.equals(mState)) {
+ closeSocket(mSocket);
}
});
}
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/Crypto.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/crypto/Crypto.java
similarity index 97%
rename from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/Crypto.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/crypto/Crypto.java
index 1543953..0aa4f6e 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/Crypto.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/crypto/Crypto.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.nearby.common.bluetooth.fastpair.testing;
+package android.nearby.fastpair.provider.crypto;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/E2eeCalculator.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/crypto/E2eeCalculator.java
similarity index 89%
rename from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/E2eeCalculator.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/crypto/E2eeCalculator.java
index 6f213e6..794c19d 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/E2eeCalculator.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/crypto/E2eeCalculator.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.nearby.common.bluetooth.fastpair.testing;
+package android.nearby.fastpair.provider.crypto;
import com.google.common.base.Preconditions;
import com.google.common.base.Verify;
@@ -43,18 +43,18 @@
private static final int E2EE_EID_SIZE = 20;
/**
- * Computes the E2EE EID value for the given device clock based time. Note that Eddystone beacons
- * start advertising the new EID at a random time within the window, therefore the currently
- * advertised EID for beacon time <em>t</em> may be either {@code computeE2eeEid(eik, k, t)} or
- * {@code computeE2eeEid(eik, k, t - (1 << k))}.
+ * Computes the E2EE EID value for the given device clock based time. Note that Eddystone
+ * beacons start advertising the new EID at a random time within the window, therefore the
+ * currently advertised EID for beacon time <em>t</em> may be either
+ * {@code computeE2eeEid(eik, k, t)} or {@code computeE2eeEid(eik, k, t - (1 << k))}.
*
* <p>The E2EE EID computation is based on https://goto.google.com/e2ee-eid-computation.
*
* @param identityKey the beacon's 32-byte Eddystone E2EE identity key
- * @param exponent rotation period exponent as configured on the beacon, must be in range the [0,
- * 15]
- * @param deviceClockSeconds the value of the beacon's 32-bit seconds time counter (treated as an
- * unsigned value)
+ * @param exponent rotation period exponent as configured on the beacon, must be in
+ * range the [0,15]
+ * @param deviceClockSeconds the value of the beacon's 32-bit seconds time counter (treated as
+ * an unsigned value)
* @return E2EE EID value.
*/
public static ByteString computeE2eeEid(
@@ -96,8 +96,8 @@
byte[] unalignedBytes = point.getAffineX().toByteArray();
// The unalignedBytes may have length < 32 if the leading E2EE EID bytes are zero, or
- // it may be E2EE_EID_SIZE + 1 if the leading bit is 1, in which case the first byte is always
- // zero.
+ // it may be E2EE_EID_SIZE + 1 if the leading bit is 1, in which case the first byte is
+ // always zero.
Verify.verify(
unalignedBytes.length <= E2EE_EID_SIZE
|| (unalignedBytes.length == E2EE_EID_SIZE + 1 && unalignedBytes[0] == 0));
@@ -106,7 +106,8 @@
if (unalignedBytes.length < E2EE_EID_SIZE) {
bytes = new byte[E2EE_EID_SIZE];
System.arraycopy(
- unalignedBytes, 0, bytes, bytes.length - unalignedBytes.length, unalignedBytes.length);
+ unalignedBytes, 0, bytes, bytes.length - unalignedBytes.length,
+ unalignedBytes.length);
} else if (unalignedBytes.length == E2EE_EID_SIZE + 1) {
bytes = new byte[E2EE_EID_SIZE];
System.arraycopy(unalignedBytes, 1, bytes, 0, E2EE_EID_SIZE);
@@ -161,7 +162,8 @@
.subtract(s.getAffineY())
.multiply(r.getAffineX().subtract(s.getAffineX()).modInverse(P))
.mod(P);
- BigInteger x = slope.modPow(TWO, P).subtract(r.getAffineX()).subtract(s.getAffineX()).mod(P);
+ BigInteger x =
+ slope.modPow(TWO, P).subtract(r.getAffineX()).subtract(s.getAffineX()).mod(P);
BigInteger y = s.getAffineY().negate().mod(P);
y = y.add(slope.multiply(s.getAffineX().subtract(x))).mod(P);
return new ECPoint(x, y);
@@ -176,7 +178,8 @@
slope = slope.add(CURVE_SPEC.getCurve().getA());
slope = slope.multiply(r.getAffineY().multiply(TWO).modInverse(P));
BigInteger x = slope.pow(2).subtract(r.getAffineX().multiply(TWO)).mod(P);
- BigInteger y = r.getAffineY().negate().add(slope.multiply(r.getAffineX().subtract(x))).mod(P);
+ BigInteger y =
+ r.getAffineY().negate().add(slope.multiply(r.getAffineX().subtract(x))).mod(P);
return new ECPoint(x, y);
}
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/Logger.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/utils/Logger.java
similarity index 79%
rename from nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/Logger.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/utils/Logger.java
index 37b065f..794f100 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/Logger.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/utils/Logger.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.nearby.common.bluetooth.fastpair.testing;
+package android.nearby.fastpair.provider.utils;
import android.util.Log;
@@ -26,10 +26,10 @@
* The base context for a logging statement.
*/
public class Logger {
- private final String tag;
+ private final String mString;
public Logger(String tag) {
- this.tag = tag;
+ this.mString = tag;
}
@FormatMethod
@@ -41,10 +41,10 @@
@FormatMethod
public void log(@Nullable Throwable exception, String message, Object... objects) {
if (exception == null) {
- Log.i(tag, String.format(message, objects));
+ Log.i(mString, String.format(message, objects));
} else {
- Log.w(tag, String.format(message, objects));
- Log.w(tag, String.format("Cause: %s", exception));
+ Log.w(mString, String.format(message, objects));
+ Log.w(mString, String.format("Cause: %s", exception));
}
}
}
diff --git a/nearby/tests/multidevices/clients/proto/Android.bp b/nearby/tests/multidevices/clients/test_support/snippet_helper/Android.bp
similarity index 73%
copy from nearby/tests/multidevices/clients/proto/Android.bp
copy to nearby/tests/multidevices/clients/test_support/snippet_helper/Android.bp
index 80e09b4..697c88d 100644
--- a/nearby/tests/multidevices/clients/proto/Android.bp
+++ b/nearby/tests/multidevices/clients/test_support/snippet_helper/Android.bp
@@ -16,15 +16,9 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-java_library {
- name: "NearbyMultiDevicesClientsFastPairLiteProtos",
- proto: {
- type: "lite",
- canonical_path_from_root: false,
- },
- sdk_version: "system_current",
- min_sdk_version: "30",
- srcs: ["src/*/*.proto"],
+android_library {
+ name: "MoblySnippetHelperLib",
+ srcs: ["src/**/*.kt"],
+ sdk_version: "test_current",
+ static_libs: ["mobly-snippet-lib",],
}
-
-
diff --git a/nearby/tests/multidevices/clients/test_support/snippet_helper/AndroidManifest.xml b/nearby/tests/multidevices/clients/test_support/snippet_helper/AndroidManifest.xml
new file mode 100644
index 0000000..4858f46
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/snippet_helper/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.mobly.snippet.util">
+
+</manifest>
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/common/SnippetEventHelper.kt b/nearby/tests/multidevices/clients/test_support/snippet_helper/src/com/google/android/mobly/snippet/util/SnippetEventHelper.kt
similarity index 96%
rename from nearby/tests/multidevices/clients/src/android/nearby/multidevices/common/SnippetEventHelper.kt
rename to nearby/tests/multidevices/clients/test_support/snippet_helper/src/com/google/android/mobly/snippet/util/SnippetEventHelper.kt
index c4816fb..0dbcb57 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/common/SnippetEventHelper.kt
+++ b/nearby/tests/multidevices/clients/test_support/snippet_helper/src/com/google/android/mobly/snippet/util/SnippetEventHelper.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.nearby.multidevices.common
+package com.google.android.mobly.snippet.util
import android.os.Bundle
import com.google.android.mobly.snippet.event.EventCache
diff --git a/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/Android.bp b/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/Android.bp
new file mode 100644
index 0000000..284d5c2
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/Android.bp
@@ -0,0 +1,38 @@
+// 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"],
+}
+
+// Run the tests: atest --host MoblySnippetHelperRoboTest
+android_robolectric_test {
+ name: "MoblySnippetHelperRoboTest",
+ srcs: ["src/**/*.kt"],
+ instrumentation_for: "NearbyMultiDevicesClientsSnippets",
+ java_resources: ["robolectric.properties"],
+
+ static_libs: [
+ "MoblySnippetHelperLib",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "junit",
+ "mobly-snippet-lib",
+ "truth-prebuilt",
+ ],
+ test_options: {
+ // timeout in seconds.
+ timeout: 36000,
+ },
+}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/AndroidManifest.xml b/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/AndroidManifest.xml
new file mode 100644
index 0000000..f1fef23
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.mobly.snippet.util"/>
diff --git a/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/robolectric.properties b/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/robolectric.properties
new file mode 100644
index 0000000..2ea03bb
--- /dev/null
+++ b/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/robolectric.properties
@@ -0,0 +1,16 @@
+#
+# Copyright (C) 2022 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
diff --git a/nearby/tests/multidevices/clients/tests/src/com/android/nearby/multidevices/common/SnippetEventHelperTest.kt b/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/src/com/google/android/mobly/snippet/util/SnippetEventHelperTest.kt
similarity index 94%
rename from nearby/tests/multidevices/clients/tests/src/com/android/nearby/multidevices/common/SnippetEventHelperTest.kt
rename to nearby/tests/multidevices/clients/test_support/snippet_helper/tests/src/com/google/android/mobly/snippet/util/SnippetEventHelperTest.kt
index 1fbd352..641ab82 100644
--- a/nearby/tests/multidevices/clients/tests/src/com/android/nearby/multidevices/common/SnippetEventHelperTest.kt
+++ b/nearby/tests/multidevices/clients/test_support/snippet_helper/tests/src/com/google/android/mobly/snippet/util/SnippetEventHelperTest.kt
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.nearby.multidevices.common
+package com.google.android.mobly.snippet.util
-import android.nearby.multidevices.common.postSnippetEvent
import androidx.test.platform.app.InstrumentationRegistry
import com.google.android.mobly.snippet.event.EventSnippet
import com.google.android.mobly.snippet.util.Log
diff --git a/nearby/tests/multidevices/clients/tests/src/com/android/nearby/multidevices/fastpair/seeker/CompanionAppUtilsTest.kt b/nearby/tests/multidevices/clients/tests/src/com/android/nearby/multidevices/fastpair/seeker/CompanionAppUtilsTest.kt
deleted file mode 100644
index 94c0952..0000000
--- a/nearby/tests/multidevices/clients/tests/src/com/android/nearby/multidevices/fastpair/seeker/CompanionAppUtilsTest.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 com.android.nearby.multidevices.fastpair.seeker
-
-import android.nearby.multidevices.fastpair.seeker.generateCompanionAppLaunchIntentUri
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.RobolectricTestRunner
-
-/** Robolectric tests for CompanionAppUtils.kt. */
-@RunWith(RobolectricTestRunner::class)
-class CompanionAppUtilsTest {
-
- @Test
- fun testGenerateCompanionAppLaunchIntentUri_defaultNullPackage_returnsEmptyString() {
- assertThat(generateCompanionAppLaunchIntentUri()).isEmpty()
- }
-
- @Test
- fun testGenerateCompanionAppLaunchIntentUri_emptyPackageName_returnsEmptyString() {
- assertThat(generateCompanionAppLaunchIntentUri(companionAppPackageName = "")).isEmpty()
- }
-
- @Test
- fun testGenerateCompanionAppLaunchIntentUri_emptyActivityName_returnsEmptyString() {
- val uriString = generateCompanionAppLaunchIntentUri(
- companionAppPackageName = COMPANION_APP_PACKAGE_TEST_CONSTANT, activityName = "")
-
- assertThat(uriString).isEmpty()
- }
-
- @Test
- fun testGenerateCompanionAppLaunchIntentUri_emptyAction_returnsNoActionUriString() {
- val uriString = generateCompanionAppLaunchIntentUri(
- companionAppPackageName = COMPANION_APP_PACKAGE_TEST_CONSTANT,
- activityName = COMPANION_APP_ACTIVITY_TEST_CONSTANT,
- action = "")
-
- assertThat(uriString).doesNotContain("action=")
- assertThat(uriString).contains("package=$COMPANION_APP_PACKAGE_TEST_CONSTANT")
- assertThat(uriString).contains(COMPANION_APP_ACTIVITY_TEST_CONSTANT)
- }
-
- @Test
- fun testGenerateCompanionAppLaunchIntentUri_nonNullArgs_returnsUriString() {
- val uriString = generateCompanionAppLaunchIntentUri(
- companionAppPackageName = COMPANION_APP_PACKAGE_TEST_CONSTANT,
- activityName = COMPANION_APP_ACTIVITY_TEST_CONSTANT,
- action = COMPANION_APP_ACTION_TEST_CONSTANT)
-
- assertThat(uriString).isEqualTo("intent:#Intent;" +
- "action=android.nearby.SHOW_WELCOME;" +
- "package=android.nearby.companion;" +
- "component=android.nearby.companion/android.nearby.companion.MainActivity;" +
- "end")
- }
-
- companion object {
- private const val COMPANION_APP_PACKAGE_TEST_CONSTANT = "android.nearby.companion"
- private const val COMPANION_APP_ACTIVITY_TEST_CONSTANT =
- "android.nearby.companion.MainActivity"
- private const val COMPANION_APP_ACTION_TEST_CONSTANT = "android.nearby.SHOW_WELCOME"
- }
-}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java
new file mode 100644
index 0000000..2ade5f2
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.pairinghandler;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import androidx.annotation.Nullable;
+
+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.notification.FastPairNotificationManager;
+import com.android.server.nearby.fastpair.testing.FakeDiscoveryItems;
+
+import com.google.protobuf.ByteString;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Clock;
+
+import service.proto.Cache;
+import service.proto.Rpcs;
+
+public class PairingProgressHandlerBaseTest {
+ @Mock
+ Locator mLocator;
+ @Mock
+ LocatorContextWrapper mContextWrapper;
+ @Mock
+ Clock mClock;
+ @Mock
+ FastPairCacheManager mFastPairCacheManager;
+ @Mock
+ FootprintsDeviceManager mFootprintsDeviceManager;
+ private static final byte[] ACCOUNT_KEY = new byte[]{0x01, 0x02};
+
+ @Before
+ public void setup() {
+
+ MockitoAnnotations.initMocks(this);
+ when(mContextWrapper.getLocator()).thenReturn(mLocator);
+ mLocator.overrideBindingForTest(FastPairCacheManager.class,
+ mFastPairCacheManager);
+ mLocator.overrideBindingForTest(Clock.class, mClock);
+ }
+
+ @Test
+ public void createHandler_halfSheetSubsequentPairing_notificationPairingHandlerCreated() {
+
+ DiscoveryItem discoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper);
+ discoveryItem.setStoredItemForTest(
+ discoveryItem.getStoredItemForTest().toBuilder()
+ .setAuthenticationPublicKeySecp256R1(ByteString.copyFrom(ACCOUNT_KEY))
+ .setFastPairInformation(
+ Cache.FastPairInformation.newBuilder()
+ .setDeviceType(Rpcs.DeviceType.HEADPHONES).build())
+ .build());
+
+ PairingProgressHandlerBase progressHandler =
+ createProgressHandler(ACCOUNT_KEY, discoveryItem, /* isRetroactivePair= */ false);
+
+ assertThat(progressHandler).isInstanceOf(NotificationPairingProgressHandler.class);
+ }
+
+ @Test
+ public void createHandler_halfSheetInitialPairing_halfSheetPairingHandlerCreated() {
+ // No account key
+ DiscoveryItem discoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper);
+ discoveryItem.setStoredItemForTest(
+ discoveryItem.getStoredItemForTest().toBuilder()
+ .setFastPairInformation(
+ Cache.FastPairInformation.newBuilder()
+ .setDeviceType(Rpcs.DeviceType.HEADPHONES).build())
+ .build());
+
+ PairingProgressHandlerBase progressHandler =
+ createProgressHandler(null, discoveryItem, /* isRetroactivePair= */ false);
+
+ assertThat(progressHandler).isInstanceOf(HalfSheetPairingProgressHandler.class);
+ }
+
+ private PairingProgressHandlerBase createProgressHandler(
+ @Nullable byte[] accountKey, DiscoveryItem fastPairItem, boolean isRetroactivePair) {
+ FastPairNotificationManager fastPairNotificationManager =
+ new FastPairNotificationManager(mContextWrapper, fastPairItem, true);
+ FastPairHalfSheetManager fastPairHalfSheetManager =
+ new FastPairHalfSheetManager(mContextWrapper);
+ mLocator.overrideBindingForTest(FastPairHalfSheetManager.class, fastPairHalfSheetManager);
+ PairingProgressHandlerBase pairingProgressHandlerBase =
+ PairingProgressHandlerBase.create(
+ mContextWrapper,
+ fastPairItem,
+ fastPairItem.getAppPackageName(),
+ accountKey,
+ mFootprintsDeviceManager,
+ fastPairNotificationManager,
+ fastPairHalfSheetManager,
+ isRetroactivePair);
+ return pairingProgressHandlerBase;
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java
new file mode 100644
index 0000000..aa7e6f6
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java
@@ -0,0 +1,55 @@
+/*
+ * 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.testing;
+
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+
+import service.proto.Cache;
+
+public class FakeDiscoveryItems {
+ public static final String DEFAULT_MAC_ADDRESS = "00:11:22:33:44:55";
+ public static final long DEFAULT_TIMESTAMP = 1000000000L;
+ public static final String DEFAULT_DESCRIPITON = "description";
+ public static final String TRIGGER_ID = "trigger.id";
+ private static final String FAST_PAIR_ID = "id";
+ private static final int RSSI = -80;
+ private static final int TX_POWER = -10;
+ public static DiscoveryItem newFastPairDiscoveryItem(LocatorContextWrapper contextWrapper) {
+ return new DiscoveryItem(contextWrapper, newFastPairDeviceStoredItem());
+ }
+
+ public static Cache.StoredDiscoveryItem newFastPairDeviceStoredItem() {
+ return newFastPairDeviceStoredItem(TRIGGER_ID);
+ }
+
+ public static Cache.StoredDiscoveryItem newFastPairDeviceStoredItem(String triggerId) {
+ Cache.StoredDiscoveryItem.Builder item = Cache.StoredDiscoveryItem.newBuilder();
+ item.setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED);
+ item.setId(FAST_PAIR_ID);
+ item.setDescription(DEFAULT_DESCRIPITON);
+ item.setType(Cache.NearbyType.NEARBY_DEVICE);
+ item.setTriggerId(triggerId);
+ item.setMacAddress(DEFAULT_MAC_ADDRESS);
+ item.setFirstObservationTimestampMillis(DEFAULT_TIMESTAMP);
+ item.setLastObservationTimestampMillis(DEFAULT_TIMESTAMP);
+ item.setRssi(RSSI);
+ item.setTxPower(TX_POWER);
+ return item.build();
+ }
+
+}