Merge "Revert "Fix the command to run CtsSeekerDiscoverProviderTest."" into tm-dev
diff --git a/nearby/framework/java/android/nearby/FastPairClient.java b/nearby/framework/java/android/nearby/FastPairClient.java
new file mode 100644
index 0000000..dc70d71
--- /dev/null
+++ b/nearby/framework/java/android/nearby/FastPairClient.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nearby;
+
+import android.annotation.BinderThread;
+import android.content.Context;
+import android.nearby.aidl.IFastPairClient;
+import android.nearby.aidl.IFastPairStatusCallback;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * 0p API for controlling Fast Pair. It communicates between main thread and service.
+ *
+ * @hide
+ */
+public class FastPairClient {
+
+ private static final String TAG = "FastPairClient";
+ private final IBinder mBinder;
+ private final WeakReference<Context> mWeakContext;
+ IFastPairClient mFastPairClient;
+ PairStatusCallbackIBinder mPairStatusCallbackIBinder;
+
+ /**
+ * The Ibinder instance should be from
+ * {@link com.android.server.nearby.fastpair.halfsheet.FastPairService} so that the client can
+ * talk with the service.
+ */
+ public FastPairClient(Context context, IBinder binder) {
+ mBinder = binder;
+ mFastPairClient = IFastPairClient.Stub.asInterface(mBinder);
+ mWeakContext = new WeakReference<>(context);
+ }
+
+ /**
+ * Registers a callback at service to get UI updates.
+ */
+ public void registerHalfSheet(FastPairStatusCallback fastPairStatusCallback) {
+ if (mPairStatusCallbackIBinder != null) {
+ return;
+ }
+ mPairStatusCallbackIBinder = new PairStatusCallbackIBinder(fastPairStatusCallback);
+ try {
+ mFastPairClient.registerHalfSheet(mPairStatusCallbackIBinder);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register fastPairStatusCallback", e);
+ }
+ }
+
+ /**
+ * Pairs the device at service.
+ */
+ public void connect(FastPairDevice fastPairDevice) {
+ try {
+ mFastPairClient.connect(fastPairDevice);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to connect Fast Pair device" + fastPairDevice, e);
+ }
+ }
+
+ private class PairStatusCallbackIBinder extends IFastPairStatusCallback.Stub {
+ private final FastPairStatusCallback mStatusCallback;
+
+ private PairStatusCallbackIBinder(FastPairStatusCallback fastPairStatusCallback) {
+ mStatusCallback = fastPairStatusCallback;
+ }
+
+ @BinderThread
+ @Override
+ public synchronized void onPairUpdate(FastPairDevice fastPairDevice,
+ PairStatusMetadata pairStatusMetadata) {
+ Context context = mWeakContext.get();
+ if (context != null) {
+ Handler handler = new Handler(context.getMainLooper());
+ handler.post(() ->
+ mStatusCallback.onPairUpdate(fastPairDevice, pairStatusMetadata));
+ }
+ }
+ }
+}
diff --git a/nearby/framework/java/android/nearby/INearbyManager.aidl b/nearby/framework/java/android/nearby/INearbyManager.aidl
index cd05106..4fff563 100644
--- a/nearby/framework/java/android/nearby/INearbyManager.aidl
+++ b/nearby/framework/java/android/nearby/INearbyManager.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007, 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.
diff --git a/nearby/framework/java/android/nearby/IScanListener.aidl b/nearby/framework/java/android/nearby/IScanListener.aidl
index 8747b07..54033aa 100644
--- a/nearby/framework/java/android/nearby/IScanListener.aidl
+++ b/nearby/framework/java/android/nearby/IScanListener.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007, 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.
diff --git a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
index 3b0a776..e9a29b7 100644
--- a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
+++ b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
@@ -76,6 +76,8 @@
return new NearbyDeviceParcelable[size];
}
};
+
+ @ScanRequest.ScanType int mScanType;
@Nullable
private final String mName;
@NearbyDevice.Medium
@@ -89,9 +91,10 @@
@Nullable
private final byte[] mData;
- private NearbyDeviceParcelable(@Nullable String name, int medium, int rssi,
- @Nullable String fastPairModelId, @Nullable String bluetoothAddress,
- @Nullable byte[] data) {
+ private NearbyDeviceParcelable(@ScanRequest.ScanType int scanType, @Nullable String name,
+ int medium, int rssi, @Nullable String fastPairModelId,
+ @Nullable String bluetoothAddress, @Nullable byte[] data) {
+ mScanType = scanType;
mName = name;
mMedium = medium;
mRssi = rssi;
@@ -176,6 +179,16 @@
}
/**
+ * Returns the type of the scan.
+ *
+ * @hide
+ */
+ @ScanRequest.ScanType
+ public int getScanType() {
+ return mScanType;
+ }
+
+ /**
* Gets the name of the NearbyDeviceParcelable. Returns {@code null} If there is no name.
*/
@Nullable
@@ -235,6 +248,7 @@
@NearbyDevice.Medium
private int mMedium;
private int mRssi;
+ @ScanRequest.ScanType int mScanType;
@Nullable
private String mFastPairModelId;
@Nullable
@@ -243,6 +257,16 @@
private byte[] mData;
/**
+ * Sets the scan type of the NearbyDeviceParcelable.
+ *
+ * @hide
+ */
+ public Builder setScanType(@ScanRequest.ScanType int scanType) {
+ mScanType = scanType;
+ return this;
+ }
+
+ /**
* Sets the name of the scanned device.
*
* @param name The local name of the scanned device.
@@ -314,7 +338,7 @@
*/
@NonNull
public NearbyDeviceParcelable build() {
- return new NearbyDeviceParcelable(mName, mMedium, mRssi, mFastPairModelId,
+ return new NearbyDeviceParcelable(mScanType, mName, mMedium, mRssi, mFastPairModelId,
mBluetoothAddress, mData);
}
}
diff --git a/nearby/framework/java/android/nearby/ScanRequest.aidl b/nearby/framework/java/android/nearby/ScanRequest.aidl
index 0aaf3af..438dfed 100644
--- a/nearby/framework/java/android/nearby/ScanRequest.aidl
+++ b/nearby/framework/java/android/nearby/ScanRequest.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012, 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.
diff --git a/nearby/halfsheet/Android.bp b/nearby/halfsheet/Android.bp
index 2b35254..486a3ff 100644
--- a/nearby/halfsheet/Android.bp
+++ b/nearby/halfsheet/Android.bp
@@ -28,7 +28,7 @@
certificate: ":com.android.nearby.halfsheetcertificate",
libs: [
"framework-bluetooth",
- "framework-connectivity-tiramisu",
+ "framework-connectivity-t",
"nearby-service-string",
],
static_libs: [
diff --git a/nearby/halfsheet/res/drawable/fast_pair_ic_info.xml b/nearby/halfsheet/res/drawable/fast_pair_ic_info.xml
index cc514e5..7d61d1c 100644
--- a/nearby/halfsheet/res/drawable/fast_pair_ic_info.xml
+++ b/nearby/halfsheet/res/drawable/fast_pair_ic_info.xml
@@ -17,7 +17,8 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="@color/fast_pair_half_sheet_subtitle_color">
<path
android:fillColor="@color/fast_pair_half_sheet_subtitle_color"
android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
diff --git a/nearby/halfsheet/res/layout/fast_pair_consent_fragment.xml b/nearby/halfsheet/res/layout/fast_pair_consent_fragment.xml
deleted file mode 100644
index aba9a32..0000000
--- a/nearby/halfsheet/res/layout/fast_pair_consent_fragment.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<android.support.v7.widget.LinearLayoutCompat
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:orientation="vertical"
- android:layout_width="match_parent" android:layout_height="match_parent">
-
- <android.support.constraint.ConstraintLayout
- android:id="@+id/image_view"
- android:layout_width="match_parent"
- android:layout_height="340dp"
- android:paddingStart="12dp"
- android:paddingEnd="12dp"
- android:paddingTop="12dp">
- <TextView
- android:id="@+id/header_subtitle"
- android:textColor="@color/fast_pair_half_sheet_subtitle_color"
- android:fontFamily="google-sans"
- android:textSize="14sp"
- android:gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent" />
-
- <ImageView
- android:id="@+id/pairing_pic"
- android:layout_width="@dimen/fast_pair_half_sheet_image_size"
- android:layout_height="@dimen/fast_pair_half_sheet_image_size"
- android:paddingTop="18dp"
- android:paddingBottom="18dp"
- android:importantForAccessibility="no"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/header_subtitle" />
-
- <ProgressBar
- android:id="@+id/connect_progressbar"
- android:layout_width="@dimen/fast_pair_half_sheet_image_size"
- android:layout_height="2dp"
- android:indeterminate="true"
- android:indeterminateTint="@color/fast_pair_progress_color"
- android:indeterminateTintMode="src_in"
- style="?android:attr/progressBarStyleHorizontal"
- app:layout_constraintTop_toBottomOf="@+id/pairing_pic"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"/>
-
- <Button
- android:id="@+id/connect_btn"
- android:text="@string/fast_pair_app_launch_button"
- android:layout_height="@dimen/fast_pair_connect_button_height"
- android:layout_width="@dimen/fast_pair_half_sheet_image_size"
- android:background="@color/fast_pair_half_sheet_button_color"
- android:paddingTop="6dp"
- android:paddingBottom="6dp"
- app:layout_constraintTop_toBottomOf="@+id/pairing_pic"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- style="@style/HalfSheetButton" />
-
- <Button
- android:id="@+id/result_action_btn"
- android:text="@string/common_done"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:paddingTop="6dp"
- android:paddingBottom="6dp"
- app:layout_constraintTop_toBottomOf="@+id/pairing_pic"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- style="@style/HalfSheetButtonBorderless" />
-
- </android.support.constraint.ConstraintLayout>
-
-</android.support.v7.widget.LinearLayoutCompat>
diff --git a/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml b/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml
index 24fcd83..7fbe229 100644
--- a/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml
+++ b/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml
@@ -73,50 +73,67 @@
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@+id/connect_progressbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
+
<ImageView
android:id="@+id/info_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ app:srcCompat="@drawable/fast_pair_ic_info"
android:layout_centerInParent="true"
android:contentDescription="@null"
android:layout_marginEnd="10dp"
android:layout_toStartOf="@id/connect_btn"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
- <Button
+ android:visibility="invisible" />
+
+ <com.google.android.material.button.MaterialButton
android:id="@+id/connect_btn"
- android:text="@string/common_connect"
- android:layout_height="wrap_content"
android:layout_width="@dimen/fast_pair_half_sheet_image_size"
+ android:layout_height="wrap_content"
+ android:text="@string/paring_action_connect"
android:layout_centerInParent="true"
- android:background="@color/fast_pair_half_sheet_button_color"
style="@style/HalfSheetButton" />
+
</RelativeLayout>
- <Button
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settings_btn"
+ android:text="@string/paring_action_settings"
+ android:layout_height="wrap_content"
+ android:layout_width="@dimen/fast_pair_half_sheet_image_size"
+ app:layout_constraintTop_toBottomOf="@+id/connect_progressbar"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ android:visibility="invisible"
+ style="@style/HalfSheetButton" />
+
+ <com.google.android.material.button.MaterialButton
android:id="@+id/cancel_btn"
- android:text="@string/common_done"
- android:visibility="gone"
+ android:text="@string/paring_action_done"
+ android:visibility="invisible"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:gravity="start|center_vertical"
android:layout_marginTop="6dp"
- android:layout_marginBottom="16dp"
style="@style/HalfSheetButtonBorderless"/>
- <Button
+ <com.google.android.material.button.MaterialButton
android:id="@+id/setup_btn"
+ android:text="@string/paring_action_launch"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="6dp"
android:layout_marginBottom="16dp"
android:background="@color/fast_pair_half_sheet_button_color"
- android:visibility="gone"
+ android:visibility="invisible"
android:layout_height="@dimen/fast_pair_half_sheet_bottom_button_height"
android:layout_width="wrap_content"
style="@style/HalfSheetButton" />
+
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
diff --git a/nearby/halfsheet/res/values/strings.xml b/nearby/halfsheet/res/values/strings.xml
index 12e3c23..01a82e4 100644
--- a/nearby/halfsheet/res/values/strings.xml
+++ b/nearby/halfsheet/res/values/strings.xml
@@ -16,9 +16,57 @@
<resources>
- <string name="common_done" description="After pairing process finish button text to dismiss halfsheet">Done</string>
- <string name="common_save">Save</string>
- <string name="common_connect" description="Button text to start connecting process">Connect</string>
- <string name="fast_pair_app_launch_button" description="String on app launch half sheet button.">Set up</string>
+ <!--
+ ============================================================
+ PAIRING FRAGMENT
+ ============================================================
+ -->
+ <!--
+ A button shown to remind user setup is in progress. [CHAR LIMIT=30]
+ -->
+ <string name="fast_pair_setup_in_progress">Starting Setup…</string>
+ <!--
+ Title text shown to remind user to setup a device through companion app. [CHAR LIMIT=40]
+ -->
+ <string name="fast_pair_title_setup">Set up device</string>
+ <!--
+ Title after we successfully pair with the audio device
+ [CHAR LIMIT=30]
+ -->
+ <string name="fast_pair_device_ready">Device connected</string>
+ <!-- Title text shown when peripheral device fail to connect to phone. [CHAR_LIMIT=30] -->
+ <string name="fast_pair_title_fail">Couldn\'t connect</string>
+
+ <!--
+ ============================================================
+ MISCELLANEOUS
+ ============================================================
+ -->
+
+ <!--
+ A button shown after paring process to dismiss the current activity.
+ [CHAR LIMIT=30]
+ -->
+ <string name="paring_action_done">Done</string>
+ <!--
+ A button shown for retroactive paring.
+ [CHAR LIMIT=30]
+ -->
+ <string name="paring_action_save">Save</string>
+ <!--
+ A button to start connecting process.
+ [CHAR LIMIT=30]
+ -->
+ <string name="paring_action_connect">Connect</string>
+ <!--
+ A button to launch a companion app.
+ [CHAR LIMIT=30]
+ -->
+ <string name="paring_action_launch">Set up</string>
+ <!--
+ A button to launch a bluetooth Settings page.
+ [CHAR LIMIT=20]
+ -->
+ <string name="paring_action_settings">Settings</string>
</resources>
\ No newline at end of file
diff --git a/nearby/halfsheet/res/values/styles.xml b/nearby/halfsheet/res/values/styles.xml
index b48da70..917bb63 100644
--- a/nearby/halfsheet/res/values/styles.xml
+++ b/nearby/halfsheet/res/values/styles.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="HalfSheetStyle" parent="Theme.MaterialComponents.DayNight.NoActionBar">
+ <style name="HalfSheetStyle" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:windowFrame">@null</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowEnterAnimation">@anim/fast_pair_half_sheet_slide_in</item>
@@ -14,7 +14,7 @@
<item name="android:windowTranslucentNavigation">true</item>
</style>
- <style name="HalfSheetButton" parent="@style/Widget.MaterialComponents.Button.TextButton">
+ <style name="HalfSheetButton" parent="@style/Widget.Material3.Button.TonalButton">
<item name="android:textColor">@color/fast_pair_half_sheet_button_accent_text</item>
<item name="android:backgroundTint">@color/fast_pair_half_sheet_button_color</item>
<item name="android:textSize">@dimen/fast_pair_notification_text_size</item>
@@ -23,8 +23,7 @@
<item name="android:textAllCaps">false</item>
</style>
- <style name="HalfSheetButtonBorderless"
- parent="@style/Widget.MaterialComponents.Button.OutlinedButton">
+ <style name="HalfSheetButtonBorderless" parent="@style/Widget.Material3.Button.OutlinedButton">
<item name="android:textColor">@color/fast_pair_half_sheet_button_text</item>
<item name="android:strokeColor">@color/fast_pair_half_sheet_button_color</item>
<item name="android:textAllCaps">false</item>
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
index c495c35..9507b9b 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/HalfSheetActivity.java
@@ -19,22 +19,18 @@
import static com.android.nearby.halfsheet.fragment.DevicePairingFragment.APP_LAUNCH_FRAGMENT_TYPE;
import static com.android.server.nearby.common.bluetooth.fastpair.FastPairConstants.EXTRA_MODEL_ID;
import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_MAC_ADDRESS;
-import static com.android.server.nearby.fastpair.Constant.ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET;
import static com.android.server.nearby.fastpair.Constant.ACTION_FAST_PAIR_HALF_SHEET_CANCEL;
import static com.android.server.nearby.fastpair.Constant.DEVICE_PAIRING_FRAGMENT_TYPE;
import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_TYPE;
-import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
import androidx.fragment.app.FragmentActivity;
import com.android.nearby.halfsheet.fragment.DevicePairingFragment;
@@ -48,14 +44,12 @@
import service.proto.Cache;
/**
- * Half sheet activity to show pairing ux.
+ * A class show Fast Pair related information in Half sheet format.
*/
public class HalfSheetActivity extends FragmentActivity {
- public static final String EXTRA_HALF_SHEET_PENDING_INTENT_CALL_BACK =
- "com.android.nearby.halfsheet.EXTRA_HALF_SHEET_PENDING_INTENT_CALL_BACK";
- public static final String EXTRA_HALF_SHEET_PACKAGE_NAME =
- "com.android.nearby.halfsheet.EXTRA_HALF_SHEET_PACKAGE_NAME";
+ public static final String TAG = "HalfSheetActivity";
+
public static final String EXTRA_HALF_SHEET_CONTENT =
"com.android.nearby.halfsheet.HALF_SHEET_CONTENT";
public static final String EXTRA_TITLE =
@@ -72,38 +66,11 @@
"com.android.nearby.halfsheet.EXTRA_HALF_SHEET_PAIRING_RESURFACE";
public static final String ACTION_HALF_SHEET_FOREGROUND_STATE =
"com.android.nearby.halfsheet.ACTION_HALF_SHEET_FOREGROUND_STATE";
- public static final String ACTION_HALF_SHEET_BAN_ALL_ITEM =
- "com.android.nearby.halfsheet.ACTION_HALF_SHEET_BAN_ALL_ITEM";
- public static final String ACTION_HALF_SHEET_APP_LAUNCH_CLICKED =
- "com.android.nearby.halfsheet.ACTION_HALF_SHEET_APP_LAUNCH_CLICKED";
- public static final String ACTION_HALF_SHEET_WEAR_OS_CLICKED =
- "com.android.nearby.halfsheet.ACTION_HALF_SHEET_WEAR_OS_CLICKED";
- public static final String ACTION_HALF_SHEET_USER_COMPLETE_CONFIRMATION =
- "com.android.nearby.halfsheet.ACTION_HALF_SHEET_USER_COMPLETE_CONFIRMATION";
- public static final String ACTION_FAST_PAIR_HANDLE_CHIP_DEVICE =
- "com.android.nearby.halfsheet.ACTION_FAST_PAIR_HANDLE_CHIP_DEVICE";
- // Intent extra contains another intent that will trigger DiscoveryChimeraService to upload
- // device
- // information to the cloud.
- public static final String EXTRA_HALF_SHEET_CLOUD_SYNC_INTENT =
- "com.android.nearby.halfsheet.HALF_SHEET_CLOUD_SYNC_INTENT";
// Intent extra contains the user gmail name eg. testaccount@gmail.com.
public static final String EXTRA_HALF_SHEET_ACCOUNT_NAME =
"com.android.nearby.halfsheet.HALF_SHEET_ACCOUNT_NAME";
public static final String EXTRA_HALF_SHEET_FOREGROUND =
"com.android.nearby.halfsheet.EXTRA_HALF_SHEET_FOREGROUND";
- public static final String EXTRA_USER_CONSENT_SYNC_CONTACTS =
- "com.android.nearby.halfsheet.EXTRA_USER_CONSENT_SYNC_CONTACTS";
- public static final String EXTRA_USER_CONSENT_SYNC_SMS =
- "com.android.nearby.halfsheet.EXTRA_USER_CONSENT_SYNC_SMS";
- public static final String EXTRA_USER_CONFIRM_PASSKEY =
- "com.android.nearby.halfsheet.EXTRA_USER_CONFIRM_PASSKEY";
- public static final String CLASS_NAME =
- "com.android.nearby.halfsheet.HalfSheetActivity";
- public static final String ACTION_HALF_SHEET_STATUS_CHANGE =
- "com.android.nearby.halfsheet.ACTION_HALF_SHEET_STATUS_CHANGE";
- public static final String FINISHED_STATE = "FINISHED_STATE";
- public static final String EXTRA_CLASSIC_MAC_ADDRESS = "EXTRA_CLASSIC_MAC_ADDRESS";
public static final String ARG_FRAGMENT_STATE = "ARG_FRAGMENT_STATE";
@Nullable
private HalfSheetModuleFragment mHalfSheetModuleFragment;
@@ -128,7 +95,7 @@
mHalfSheetModuleFragment = DevicePairingFragment.newInstance(getIntent(),
savedInstanceState);
if (mHalfSheetModuleFragment == null) {
- Log.d("HalfSheetActivity", "device pairing fragment has error.");
+ Log.d(TAG, "device pairing fragment has error.");
finish();
return;
}
@@ -136,13 +103,13 @@
case APP_LAUNCH_FRAGMENT_TYPE:
// currentFragment = AppLaunchFragment.newInstance(getIntent());
if (mHalfSheetModuleFragment == null) {
- Log.v("HalfSheetActivity", "app launch fragment has error.");
+ Log.v(TAG, "app launch fragment has error.");
finish();
return;
}
break;
default:
- Log.w("HalfSheetActivity", "there is no valid type for half sheet");
+ Log.w(TAG, "there is no valid type for half sheet");
finish();
return;
}
@@ -159,23 +126,19 @@
findViewById(R.id.background).setOnClickListener(v -> onCancelClicked());
findViewById(R.id.card)
.setOnClickListener(
- v -> Log.v("HalfSheetActivity", "card view is clicked noop"));
+ v -> Log.v(TAG, "card view is clicked noop"));
try {
mScanFastPairStoreItem =
Cache.ScanFastPairStoreItem.parseFrom(infoArray);
} catch (InvalidProtocolBufferException e) {
Log.w(
- "HalfSheetActivity", "error happens when pass info to half sheet");
+ TAG, "error happens when pass info to half sheet");
}
}
@Override
protected void onStart() {
super.onStart();
- BroadcastUtils.sendBroadcast(
- this,
- new Intent(ACTION_HALF_SHEET_FOREGROUND_STATE)
- .putExtra(EXTRA_HALF_SHEET_FOREGROUND, true));
}
@Override
@@ -217,17 +180,18 @@
mScanFastPairStoreItem.getAddress())
&& testScanFastPairStoreItem.getModelId().equals(
mScanFastPairStoreItem.getModelId())) {
- Log.d("HalfSheetActivity", "possible factory reset happens");
+ Log.d(TAG, "possible factory reset happens");
halfSheetStateChange();
}
} catch (InvalidProtocolBufferException | NullPointerException e) {
- Log.w("HalfSheetActivity", "error happens when pass info to half sheet");
+ Log.w(TAG, "error happens when pass info to half sheet");
}
}
}
/** This function should be called when user click empty area and cancel button. */
public void onCancelClicked() {
+ Log.d(TAG, "Cancels the half sheet and paring.");
sendHalfSheetCancelBroadcast();
finish();
}
@@ -241,20 +205,6 @@
finish();
}
- /**
- * Change the half sheet ban state to active sometimes users leave half sheet to go to fast pair
- * info page we do not want the behavior to be counted as dismiss.
- */
- public void sendBanStateResetBroadcast() {
- if (mScanFastPairStoreItem != null) {
- BroadcastUtils.sendBroadcast(
- this,
- new Intent(ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET)
- .putExtra(EXTRA_MODEL_ID,
- mScanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT)));
- }
- }
-
private void sendHalfSheetCancelBroadcast() {
BroadcastUtils.sendBroadcast(
this,
@@ -280,31 +230,10 @@
}
}
- @Nullable
- @VisibleForTesting
- public HalfSheetModuleFragment getFragmentModel() {
- return mHalfSheetModuleFragment;
- }
-
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
TextView toolbarTitle = findViewById(R.id.toolbar_title);
toolbarTitle.setText(title);
}
-
-
- /**
- * This method converts dp unit to equivalent pixels, depending on device density.
- *
- * @param dp A value in dp (density independent pixels) unit, which we need to convert into
- * pixels
- * @param context Context to get resources and device specific display metrics
- * @return A float value to represent px equivalent to dp depending on device density
- */
- private float convertDpToPixel(float dp, Context context) {
- return dp
- * ((float) context.getResources().getDisplayMetrics().densityDpi
- / DisplayMetrics.DENSITY_DEFAULT);
- }
}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
index c99c130..a62c8cc 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
@@ -17,44 +17,35 @@
import static android.text.TextUtils.isEmpty;
-import static com.android.nearby.halfsheet.HalfSheetActivity.ACTION_HALF_SHEET_STATUS_CHANGE;
import static com.android.nearby.halfsheet.HalfSheetActivity.ARG_FRAGMENT_STATE;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_CLASSIC_MAC_ADDRESS;
import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_DESCRIPTION;
import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_ACCOUNT_NAME;
import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_CONTENT;
import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_ID;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_IS_RETROACTIVE;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR;
-import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_PAIRING_RESURFACE;
import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_TITLE;
-import static com.android.nearby.halfsheet.HalfSheetActivity.FINISHED_STATE;
+import static com.android.nearby.halfsheet.HalfSheetActivity.TAG;
+import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.FAILED;
+import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.FOUND_DEVICE;
import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.NOT_STARTED;
-import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.RESULT_FAILURE;
-import static com.android.server.nearby.common.bluetooth.fastpair.FastPairConstants.EXTRA_MODEL_ID;
-import static com.android.server.nearby.fastpair.Constant.DISMISS;
+import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRED_LAUNCHABLE;
+import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRED_UNLAUNCHABLE;
+import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRING;
import static com.android.server.nearby.fastpair.Constant.EXTRA_BINDER;
import static com.android.server.nearby.fastpair.Constant.EXTRA_BUNDLE;
import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
-import static com.android.server.nearby.fastpair.Constant.FAIL_STATE;
-import static com.android.server.nearby.fastpair.Constant.SUCCESS_STATE;
-import static com.android.server.nearby.fastpair.UserActionHandler.ACTION_FAST_PAIR;
-import static com.android.server.nearby.fastpair.UserActionHandler.EXTRA_PRIVATE_BLE_ADDRESS;
-import android.animation.AnimatorSet;
-import android.app.Activity;
-import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.res.Configuration;
-import android.nearby.IFastPairHalfSheetCallback;
+import android.graphics.Bitmap;
+import android.nearby.FastPairClient;
+import android.nearby.FastPairDevice;
+import android.nearby.FastPairStatusCallback;
+import android.nearby.NearbyDevice;
+import android.nearby.PairStatusMetadata;
import android.os.Bundle;
-import android.os.RemoteException;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
@@ -69,12 +60,13 @@
import com.android.nearby.halfsheet.HalfSheetActivity;
import com.android.nearby.halfsheet.R;
-import com.android.nearby.halfsheet.utils.BroadcastUtils;
import com.android.nearby.halfsheet.utils.FastPairUtils;
-import com.android.server.nearby.fastpair.UserActionHandler;
+import com.android.nearby.halfsheet.utils.IconUtils;
import com.google.protobuf.InvalidProtocolBufferException;
+import java.util.Objects;
+
import service.proto.Cache.ScanFastPairStoreItem;
/**
@@ -83,36 +75,38 @@
* <p>This fragment will handle initial pairing subsequent pairing and retroactive pairing.
*/
@SuppressWarnings("nullness")
-public class DevicePairingFragment extends HalfSheetModuleFragment {
+public class DevicePairingFragment extends HalfSheetModuleFragment implements
+ FastPairStatusCallback {
+ private TextView mTitleView;
+ private TextView mSubTitleView;
+ private ImageView mImage;
+
private Button mConnectButton;
private Button mSetupButton;
private Button mCancelButton;
+ // Opens Bluetooth Settings.
+ private Button mSettingsButton;
private ImageView mInfoIconButton;
private ProgressBar mConnectProgressBar;
- private View mRootView;
- private TextView mSubTitle;
- private TextView mTitle;
- private ImageView mImage;
- private ScanFastPairStoreItem mScanFastPairStoreItem;
- // This open companion app intent will be triggered after user finish Fast Pair.
- private Intent mOpenCompanionAppIntent;
- // Indicates that the setup button is clicked before.
- private boolean mSetupButtonClicked = false;
- private boolean mIsSubsequentPair = false;
- private boolean mIsPairingResurface = false;
- private String mBluetoothMacAddress = "";
- private HalfSheetFragmentState mFragmentState = NOT_STARTED;
- private AnimatorSet mAnimatorSet = new AnimatorSet();
- // True means pairing was successful and false means failed.
- private Boolean mPairingResult = false;
+
private Bundle mBundle;
- public static final String APP_LAUNCH_FRAGMENT_TYPE = "APP_LAUNCH";
- public static final String FAST_PAIR_CONSENT_FRAGMENT_TYPE = "FAST_PAIR_CONSENT";
- private static final String ARG_SETUP_BUTTON_CLICKED = "SETUP_BUTTON_CLICKED";
- public static final String RESULT_FAIL = "RESULT_FAIL";
- private static final String ARG_PAIRING_RESULT = "PAIRING_RESULT";
+ private ScanFastPairStoreItem mScanFastPairStoreItem;
+ private FastPairClient mFastPairClient;
+ private @PairStatusMetadata.Status int mPairStatus = PairStatusMetadata.Status.UNKNOWN;
+ // True when there is a companion app to open.
+ private boolean mIsLaunchable;
+ private boolean mIsConnecting;
+ // Indicates that the setup button is clicked before.
+ private boolean mSetupButtonClicked = false;
+
+ // Holds the new text while we transition between the two.
+ private static final int TAG_PENDING_TEXT = R.id.toolbar_title;
+ public static final String APP_LAUNCH_FRAGMENT_TYPE = "APP_LAUNCH";
+
+ private static final String ARG_SETUP_BUTTON_CLICKED = "SETUP_BUTTON_CLICKED";
+ private static final String ARG_PAIRING_RESULT = "PAIRING_RESULT";
/**
* Create certain fragment according to the intent.
@@ -122,23 +116,15 @@
Intent intent, @Nullable Bundle saveInstanceStates) {
Bundle args = new Bundle();
byte[] infoArray = intent.getByteArrayExtra(EXTRA_HALF_SHEET_INFO);
- boolean isRetroactive = intent.getBooleanExtra(EXTRA_HALF_SHEET_IS_RETROACTIVE, false);
- boolean isSubsequentPair = intent.getBooleanExtra(EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR,
- false);
- boolean isPairingResurface = intent.getBooleanExtra(EXTRA_HALF_SHEET_PAIRING_RESURFACE,
- false);
+
Bundle bundle = intent.getBundleExtra(EXTRA_BUNDLE);
String title = intent.getStringExtra(EXTRA_TITLE);
String description = intent.getStringExtra(EXTRA_DESCRIPTION);
String accountName = intent.getStringExtra(EXTRA_HALF_SHEET_ACCOUNT_NAME);
String result = intent.getStringExtra(EXTRA_HALF_SHEET_CONTENT);
- String publicAddress = intent.getStringExtra(EXTRA_CLASSIC_MAC_ADDRESS);
int halfSheetId = intent.getIntExtra(EXTRA_HALF_SHEET_ID, 0);
args.putByteArray(EXTRA_HALF_SHEET_INFO, infoArray);
- args.putBoolean(EXTRA_HALF_SHEET_IS_RETROACTIVE, isRetroactive);
- args.putBoolean(EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR, isSubsequentPair);
- args.putBoolean(EXTRA_HALF_SHEET_PAIRING_RESURFACE, isPairingResurface);
args.putString(EXTRA_HALF_SHEET_ACCOUNT_NAME, accountName);
args.putString(EXTRA_TITLE, title);
args.putString(EXTRA_DESCRIPTION, description);
@@ -180,21 +166,22 @@
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
- mRootView =
- inflater.inflate(
- R.layout.fast_pair_device_pairing_fragment, container, /* attachToRoot= */
- false);
+ /* attachToRoot= */
+ View rootView = inflater.inflate(
+ R.layout.fast_pair_device_pairing_fragment, container, /* attachToRoot= */
+ false);
if (getContext() == null) {
- Log.d("DevicePairingFragment", "can't find the attached activity");
- return mRootView;
+ Log.d(TAG, "can't find the attached activity");
+ return rootView;
}
+
Bundle args = getArguments();
byte[] storeFastPairItemBytesArray = args.getByteArray(EXTRA_HALF_SHEET_INFO);
- boolean isRetroactive = args.getBoolean(EXTRA_HALF_SHEET_IS_RETROACTIVE);
- mIsSubsequentPair = args.getBoolean(EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR);
- mIsPairingResurface = args.getBoolean(EXTRA_HALF_SHEET_PAIRING_RESURFACE);
- String accountName = args.getString(EXTRA_HALF_SHEET_ACCOUNT_NAME);
mBundle = args.getBundle(EXTRA_BUNDLE);
+ if (mBundle != null) {
+ mFastPairClient = new FastPairClient(getContext(), mBundle.getBinder(EXTRA_BINDER));
+ mFastPairClient.registerHalfSheet(this);
+ }
if (args.containsKey(ARG_FRAGMENT_STATE)) {
mFragmentState = (HalfSheetFragmentState) args.getSerializable(ARG_FRAGMENT_STATE);
}
@@ -202,86 +189,53 @@
mSetupButtonClicked = args.getBoolean(ARG_SETUP_BUTTON_CLICKED);
}
if (args.containsKey(ARG_PAIRING_RESULT)) {
- mPairingResult = args.getBoolean(ARG_PAIRING_RESULT);
- } else {
- mPairingResult = false;
+ mPairStatus = args.getInt(ARG_PAIRING_RESULT);
}
- // title = ((FragmentActivity) getContext()).findViewById(R.id.toolbar_title);
- mConnectButton = mRootView.findViewById(R.id.connect_btn);
- mImage = mRootView.findViewById(R.id.pairing_pic);
+ // Initiate views.
+ mTitleView = Objects.requireNonNull(getActivity()).findViewById(R.id.toolbar_title);
+ mSubTitleView = rootView.findViewById(R.id.header_subtitle);
+ mImage = rootView.findViewById(R.id.pairing_pic);
+ mConnectProgressBar = rootView.findViewById(R.id.connect_progressbar);
+ mConnectButton = rootView.findViewById(R.id.connect_btn);
+ mCancelButton = rootView.findViewById(R.id.cancel_btn);
+ mSettingsButton = rootView.findViewById(R.id.settings_btn);
+ mSetupButton = rootView.findViewById(R.id.setup_btn);
+ mInfoIconButton = rootView.findViewById(R.id.info_icon);
+ mInfoIconButton.setImageResource(R.drawable.fast_pair_ic_info);
- mConnectProgressBar = mRootView.findViewById(R.id.connect_progressbar);
- mConnectProgressBar.setVisibility(View.INVISIBLE);
+ try {
+ setScanFastPairStoreItem(ScanFastPairStoreItem.parseFrom(storeFastPairItemBytesArray));
+ } catch (InvalidProtocolBufferException e) {
+ Log.w(TAG,
+ "DevicePairingFragment: error happens when pass info to half sheet");
+ return rootView;
+ }
+ // Config for landscape mode
DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
- mRootView.getLayoutParams().height = displayMetrics.heightPixels * 4 / 5;
- mRootView.getLayoutParams().width = displayMetrics.heightPixels * 4 / 5;
+ rootView.getLayoutParams().height = displayMetrics.heightPixels * 4 / 5;
+ rootView.getLayoutParams().width = displayMetrics.heightPixels * 4 / 5;
mImage.getLayoutParams().height = displayMetrics.heightPixels / 2;
mImage.getLayoutParams().width = displayMetrics.heightPixels / 2;
mConnectProgressBar.getLayoutParams().width = displayMetrics.heightPixels / 2;
mConnectButton.getLayoutParams().width = displayMetrics.heightPixels / 2;
+ //TODO(b/213373051): Add cancel button
}
- mCancelButton = mRootView.findViewById(R.id.cancel_btn);
- mSetupButton = mRootView.findViewById(R.id.setup_btn);
- mInfoIconButton = mRootView.findViewById(R.id.info_icon);
- mSubTitle = mRootView.findViewById(R.id.header_subtitle);
- mSetupButton.setVisibility(View.GONE);
- mInfoIconButton.setVisibility(View.GONE);
-
- try {
- if (storeFastPairItemBytesArray != null) {
- mScanFastPairStoreItem =
- ScanFastPairStoreItem.parseFrom(storeFastPairItemBytesArray);
- }
-
- // If the fragmentState is not NOT_STARTED, it is because the fragment was just
- // resumed from
- // configuration change (e.g. rotating the screen or half-sheet resurface). Let's
- // recover the
- // UI directly.
- if (mFragmentState != NOT_STARTED) {
- switch (mFragmentState) {
- case PAIRING:
- Log.d("DevicePairingFragment", "redraw for PAIRING state.");
- return mRootView;
- case RESULT_SUCCESS:
- Log.d("DevicePairingFragment", "redraw for RESULT_SUCCESS state.");
- return mRootView;
- case RESULT_FAILURE:
- Log.d("DevicePairingFragment", "redraw for RESULT_FAILURE state.");
- return mRootView;
- default:
- // fall-out
- Log.d("DevicePairingFragment",
- "DevicePairingFragment: not supported state");
- }
- }
- if (mIsPairingResurface) {
- // Since the Settings contextual card has sent the pairing intent, we don't send the
- // pairing intent here.
- onConnectClick(/* sendPairingIntent= */ false);
- } else {
- mSubTitle.setText(this.getArguments().getString(EXTRA_DESCRIPTION));
- mSubTitle.setText("");
- mConnectButton.setOnClickListener(
- v -> onConnectClick(/* sendPairingIntent= */ true));
- // Pairing fail half sheet resurface
- if (this.getArguments().getString(EXTRA_HALF_SHEET_CONTENT).equals(RESULT_FAIL)) {
- mFragmentState = RESULT_FAILURE;
- showFailInfo();
- } else {
- mConnectButton.setOnClickListener(
- v -> onConnectClick(/* sendPairingIntent= */ true));
- }
- }
- } catch (InvalidProtocolBufferException e) {
- Log.w("DevicePairingFragment",
- "DevicePairingFragment: error happens when pass info to half sheet");
+ Bitmap icon = IconUtils.getIcon(mScanFastPairStoreItem.getIconPng().toByteArray(),
+ mScanFastPairStoreItem.getIconPng().size());
+ if (icon != null) {
+ mImage.setImageBitmap(icon);
}
- return mRootView;
+ mConnectButton.setOnClickListener(v -> onConnectClick());
+ mCancelButton.setOnClickListener(v ->
+ ((HalfSheetActivity) getActivity()).onCancelClicked());
+ mSettingsButton.setOnClickListener(v -> onSettingsClicked());
+ mSetupButton.setOnClickListener(v -> onSetupClick());
+
+ return rootView;
}
@Override
@@ -294,19 +248,8 @@
@Override
public void onStart() {
super.onStart();
- if (getContext() != null) {
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(ACTION_HALF_SHEET_STATUS_CHANGE);
- BroadcastUtils.registerReceiver(getContext(), mHalfSheetChangeReceiver, intentFilter);
- }
- }
-
- @Override
- public void onStop() {
- super.onStop();
- if (getContext() != null) {
- BroadcastUtils.unregisterReceiver(getContext(), mHalfSheetChangeReceiver);
- }
+ Log.v(TAG, "onStart: invalidate states");
+ invalidateState();
}
@Override
@@ -315,118 +258,229 @@
savedInstanceState.putSerializable(ARG_FRAGMENT_STATE, mFragmentState);
savedInstanceState.putBoolean(ARG_SETUP_BUTTON_CLICKED, mSetupButtonClicked);
- savedInstanceState.putBoolean(ARG_PAIRING_RESULT, mPairingResult);
-
-
+ savedInstanceState.putInt(ARG_PAIRING_RESULT, mPairStatus);
}
- @Nullable
- private Intent createCompletionIntent(@Nullable String companionApp, @Nullable String address) {
- if (isEmpty(companionApp)) {
- return null;
- } else if (FastPairUtils.isAppInstalled(companionApp, getContext())
- && isLaunchable(companionApp)) {
- mOpenCompanionAppIntent = createCompanionAppIntent(companionApp, address);
- return mOpenCompanionAppIntent;
- } else {
- return null;
- }
- }
-
- @Nullable
- private Intent createCompanionAppIntent(String packageName, @Nullable String address) {
- return createCompanionAppIntent(getContext(), packageName, address);
- }
-
- @Nullable
- private static Intent createCompanionAppIntent(
- Context context, String packageName, @Nullable String address) {
- Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
- BluetoothManager manager = context.getSystemService(BluetoothManager.class);
- if (address != null && manager != null) {
- BluetoothAdapter adapter = manager.getAdapter();
- if (intent != null && adapter != null) {
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, adapter.getRemoteDevice(address));
- }
- }
- return intent;
- }
-
- private void onConnectClick(boolean sendPairingIntent) {
- if (mScanFastPairStoreItem == null) {
- Log.w("DevicePairingFragment", "No pairing related information in half sheet");
- return;
- }
-
- Log.d("FastPairHalfSheet", "on connect click");
- // Allow user to setup device before connection setup.
- // showPairingLastPhase();
- ((Activity) getContext())
- .findViewById(R.id.background)
- .setOnClickListener(
- v ->
- Log.d("DevicePairingFragment",
- "DevicePairingFragment: tap empty area do not dismiss "
- + "half sheet when pairing."));
- if (sendPairingIntent) {
- try {
- Log.d("FastPairHalfSheet", "on connect click");
- Intent intent =
- new Intent(ACTION_FAST_PAIR)
- // Using the DiscoveryChimeraService notification id for
- // backwards compat
- .putExtra(
- UserActionHandler.EXTRA_DISCOVERY_ITEM,
- FastPairUtils.convertFrom(
- mScanFastPairStoreItem).toByteArray())
- .putExtra(EXTRA_MODEL_ID, mScanFastPairStoreItem.getModelId())
- .putExtra(EXTRA_PRIVATE_BLE_ADDRESS,
- mScanFastPairStoreItem.getAddress());
- IFastPairHalfSheetCallback.Stub.asInterface(mBundle.getBinder(EXTRA_BINDER))
- .onHalfSheetConnectionConfirm(intent);
- } catch (RemoteException e) {
- Log.d("FastPairHalfSheet", "invoke callback fall");
- }
- }
- }
-
- private void onFailConnectClick() {
+ private void onSettingsClicked() {
startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
}
- private final BroadcastReceiver mHalfSheetChangeReceiver =
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!ACTION_HALF_SHEET_STATUS_CHANGE.equals(intent.getAction())) {
- return;
- }
- if (SUCCESS_STATE.equals(intent.getStringExtra(FINISHED_STATE))) {
- mBluetoothMacAddress = intent.getStringExtra(EXTRA_CLASSIC_MAC_ADDRESS);
- showSuccessInfo();
- if (mOpenCompanionAppIntent != null) {
- //((HalfSheetActivity) getContext()).halfSheetStateChange();
- String companionApp =
- FastPairUtils.getCompanionAppFromActionUrl(
- mScanFastPairStoreItem.getActionUrl());
- // Redirect user to companion app if user choose to setup the app.
- // Recreate the intent
- // since the correct mac address just populated.
- startActivity(
- createCompletionIntent(companionApp, mBluetoothMacAddress));
- }
- } else if (FAIL_STATE.equals(intent.getStringExtra(FINISHED_STATE))) {
- showFailInfo();
- } else if (DISMISS.equals(intent.getStringExtra(FINISHED_STATE))) {
- if (getContext() != null) {
- HalfSheetActivity activity = (HalfSheetActivity) getContext();
- activity.finish();
- }
- }
- }
- };
+ private void onSetupClick() {
+ String companionApp =
+ FastPairUtils.getCompanionAppFromActionUrl(mScanFastPairStoreItem.getActionUrl());
+ Intent intent =
+ FastPairUtils.createCompanionAppIntent(
+ Objects.requireNonNull(getContext()),
+ companionApp,
+ mScanFastPairStoreItem.getAddress());
+ mSetupButtonClicked = true;
+ if (mFragmentState == PAIRED_LAUNCHABLE) {
+ if (intent != null) {
+ startActivity(intent);
+ }
+ } else {
+ Log.d(TAG, "onSetupClick: State is " + mFragmentState);
+ }
+ }
- private boolean isLaunchable(String companionApp) {
- return createCompanionAppIntent(companionApp, null) != null;
+ private void onConnectClick() {
+ if (mScanFastPairStoreItem == null) {
+ Log.w(TAG, "No pairing related information in half sheet");
+ return;
+ }
+ if (getFragmentState() == PAIRING) {
+ return;
+ }
+ mIsConnecting = true;
+ invalidateState();
+ mFastPairClient.connect(
+ new FastPairDevice.Builder()
+ .addMedium(NearbyDevice.Medium.BLE)
+ .setBluetoothAddress(mScanFastPairStoreItem.getAddress())
+ .setData(FastPairUtils.convertFrom(mScanFastPairStoreItem)
+ .toByteArray())
+ .build());
+ }
+
+ // Receives callback from service.
+ @Override
+ public void onPairUpdate(FastPairDevice fastPairDevice, PairStatusMetadata pairStatusMetadata) {
+ @PairStatusMetadata.Status int status = pairStatusMetadata.getStatus();
+ if (status == PairStatusMetadata.Status.DISMISS && getActivity() != null) {
+ getActivity().finish();
+ }
+ mIsConnecting = false;
+ mPairStatus = status;
+ invalidateState();
+ }
+
+ @Override
+ public void invalidateState() {
+ HalfSheetFragmentState newState = NOT_STARTED;
+ if (mIsConnecting) {
+ newState = PAIRING;
+ } else {
+ switch (mPairStatus) {
+ case PairStatusMetadata.Status.SUCCESS:
+ newState = mIsLaunchable ? PAIRED_LAUNCHABLE : PAIRED_UNLAUNCHABLE;
+ break;
+ case PairStatusMetadata.Status.FAIL:
+ newState = FAILED;
+ break;
+ default:
+ if (mScanFastPairStoreItem != null) {
+ newState = FOUND_DEVICE;
+ }
+ }
+ }
+ if (newState == mFragmentState) {
+ return;
+ }
+ setState(newState);
+ }
+
+ @Override
+ public void setState(HalfSheetFragmentState state) {
+ super.setState(state);
+ invalidateTitles();
+ invalidateButtons();
+ }
+
+ private void setScanFastPairStoreItem(ScanFastPairStoreItem item) {
+ mScanFastPairStoreItem = item;
+ invalidateLaunchable();
+ }
+
+ private void invalidateLaunchable() {
+ String companionApp =
+ FastPairUtils.getCompanionAppFromActionUrl(mScanFastPairStoreItem.getActionUrl());
+ if (isEmpty(companionApp)) {
+ mIsLaunchable = false;
+ return;
+ }
+ mIsLaunchable =
+ FastPairUtils.isLaunchable(Objects.requireNonNull(getContext()), companionApp);
+ }
+
+ private void invalidateButtons() {
+ mConnectProgressBar.setVisibility(View.INVISIBLE);
+ mConnectButton.setVisibility(View.INVISIBLE);
+ mCancelButton.setVisibility(View.INVISIBLE);
+ mSetupButton.setVisibility(View.INVISIBLE);
+ mSettingsButton.setVisibility(View.INVISIBLE);
+ mInfoIconButton.setVisibility(View.INVISIBLE);
+
+ switch (mFragmentState) {
+ case FOUND_DEVICE:
+ mInfoIconButton.setVisibility(View.VISIBLE);
+ mConnectButton.setVisibility(View.VISIBLE);
+ break;
+ case PAIRING:
+ mConnectProgressBar.setVisibility(View.VISIBLE);
+ mCancelButton.setVisibility(View.VISIBLE);
+ setBackgroundClickable(false);
+ break;
+ case PAIRED_LAUNCHABLE:
+ mCancelButton.setVisibility(View.VISIBLE);
+ mSetupButton.setVisibility(View.VISIBLE);
+ setBackgroundClickable(true);
+ break;
+ case FAILED:
+ mSettingsButton.setVisibility(View.VISIBLE);
+ setBackgroundClickable(true);
+ break;
+ case NOT_STARTED:
+ case PAIRED_UNLAUNCHABLE:
+ default:
+ mCancelButton.setVisibility(View.VISIBLE);
+ setBackgroundClickable(true);
+ }
+ }
+
+ private void setBackgroundClickable(boolean isClickable) {
+ HalfSheetActivity activity = (HalfSheetActivity) getActivity();
+ if (activity == null) {
+ Log.w(TAG, "setBackgroundClickable: failed to set clickable to " + isClickable
+ + " because cannot get HalfSheetActivity.");
+ return;
+ }
+ View background = activity.findViewById(R.id.background);
+ if (background == null) {
+ Log.w(TAG, "setBackgroundClickable: failed to set clickable to " + isClickable
+ + " cannot find background at HalfSheetActivity.");
+ return;
+ }
+ Log.d(TAG, "setBackgroundClickable to " + isClickable);
+ background.setClickable(isClickable);
+ }
+
+ private void invalidateTitles() {
+ String newTitle = getTitle();
+ invalidateTextView(mTitleView, newTitle);
+ String newSubTitle = getSubTitle();
+ invalidateTextView(mSubTitleView, newSubTitle);
+ }
+
+ private void invalidateTextView(TextView textView, String newText) {
+ CharSequence oldText =
+ textView.getTag(TAG_PENDING_TEXT) != null
+ ? (CharSequence) textView.getTag(TAG_PENDING_TEXT)
+ : textView.getText();
+ if (TextUtils.equals(oldText, newText)) {
+ return;
+ }
+ if (TextUtils.isEmpty(oldText)) {
+ // First time run. Don't animate since there's nothing to animate from.
+ textView.setText(newText);
+ } else {
+ textView.setTag(TAG_PENDING_TEXT, newText);
+ textView
+ .animate()
+ .alpha(0f)
+ .setDuration(TEXT_ANIMATION_DURATION_MILLISECONDS)
+ .withEndAction(
+ () -> {
+ textView.setText(newText);
+ textView
+ .animate()
+ .alpha(1f)
+ .setDuration(TEXT_ANIMATION_DURATION_MILLISECONDS);
+ });
+ }
+ }
+
+ private String getTitle() {
+ switch (mFragmentState) {
+ case PAIRED_LAUNCHABLE:
+ return getString(R.string.fast_pair_title_setup);
+ case FAILED:
+ return getString(R.string.fast_pair_title_fail);
+ case FOUND_DEVICE:
+ case NOT_STARTED:
+ case PAIRED_UNLAUNCHABLE:
+ default:
+ return mScanFastPairStoreItem.getDeviceName();
+ }
+ }
+
+ private String getSubTitle() {
+ switch (mFragmentState) {
+ case PAIRED_LAUNCHABLE:
+ return String.format(
+ mScanFastPairStoreItem
+ .getFastPairStrings()
+ .getPairingFinishedCompanionAppInstalled(),
+ mScanFastPairStoreItem.getDeviceName());
+ case FAILED:
+ return mScanFastPairStoreItem.getFastPairStrings().getPairingFailDescription();
+ case PAIRED_UNLAUNCHABLE:
+ getString(R.string.fast_pair_device_ready);
+ // fall through
+ case FOUND_DEVICE:
+ case NOT_STARTED:
+ return mScanFastPairStoreItem.getFastPairStrings().getInitialPairingDescription();
+ default:
+ return "";
+ }
}
}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java
index 88caf95..f1db4d0 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/HalfSheetModuleFragment.java
@@ -15,17 +15,23 @@
*/
package com.android.nearby.halfsheet.fragment;
+import static com.android.nearby.halfsheet.HalfSheetActivity.TAG;
+import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.NOT_STARTED;
+
import android.os.Bundle;
+import android.util.Log;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
/** Base class for all of the half sheet fragment. */
-// TODO(b/177675274): Resolve nullness suppression.
-@SuppressWarnings("nullness")
public abstract class HalfSheetModuleFragment extends Fragment {
+ static final int TEXT_ANIMATION_DURATION_MILLISECONDS = 200;
+
+ HalfSheetFragmentState mFragmentState = NOT_STARTED;
+
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -38,26 +44,15 @@
/** UI states of the half-sheet fragment. */
public enum HalfSheetFragmentState {
- NOT_STARTED,
- SYNC_CONTACTS,
- SYNC_SMS,
- PROGRESSING,
- CONFIRM_PASSKEY,
- WRONG_PASSKEY,
- PAIRING,
- ADDITIONAL_SETUP_PROGRESS,
- ADDITIONAL_SETUP_FINAL,
- RESULT_SUCCESS,
- RESULT_FAILURE,
- FINISHED
- }
-
- /** Only used in {@link DevicePairingFragment} show pairing success info in half sheet. */
- public void showSuccessInfo() {
- }
-
- /** Only used in {@link DevicePairingFragment} show pairing fail info in half sheet. */
- public void showFailInfo() {
+ NOT_STARTED, // Initial status
+ FOUND_DEVICE, // When a device is found found from Nearby scan service
+ PAIRING, // When user taps 'Connect' and Fast Pair stars pairing process
+ PAIRED_LAUNCHABLE, // When pair successfully
+ // and we found a launchable companion app installed
+ PAIRED_UNLAUNCHABLE, // When pair successfully
+ // but we cannot find a companion app to launch it
+ FAILED, // When paring was failed
+ FINISHED // When the activity is about to end finished.
}
/**
@@ -67,6 +62,16 @@
* activity.
*/
public HalfSheetFragmentState getFragmentState() {
- return HalfSheetFragmentState.NOT_STARTED;
+ return mFragmentState;
}
+
+ void setState(HalfSheetFragmentState state) {
+ Log.v(TAG, "Settings state from " + mFragmentState + " to " + state);
+ mFragmentState = state;
+ }
+
+ /**
+ * Populate data to UI widgets according to the latest {@link HalfSheetFragmentState}.
+ */
+ abstract void invalidateState();
}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
index cae2d8e..467997c 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/BroadcastUtils.java
@@ -16,30 +16,13 @@
package com.android.nearby.halfsheet.utils;
-
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
/**
* Broadcast util class
*/
public class BroadcastUtils {
- /**
- * Registers a list of broadcast receiver.
- */
- public static void registerReceiver(
- Context context, BroadcastReceiver receiver, IntentFilter intentFilter) {
- context.registerReceiver(receiver, intentFilter);
- }
-
- /**
- * Unregisters the already registered receiver.
- */
- public static void unregisterReceiver(Context context, BroadcastReceiver receiver) {
- context.unregisterReceiver(receiver);
- }
/**
* Helps send broadcast.
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
index ee869ba..903ea90 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
@@ -18,11 +18,15 @@
import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_COMPANION_APP;
import static com.android.server.nearby.fastpair.UserActionHandler.ACTION_FAST_PAIR;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.net.URISyntaxException;
@@ -34,10 +38,8 @@
*/
public class FastPairUtils {
- public static final String TAG = "HalfSheetActivity";
-
/** FastPair util method check certain app is install on the device or not. */
- public static boolean isAppInstalled(String packageName, Context context) {
+ public static boolean isAppInstalled(Context context, String packageName) {
try {
context.getPackageManager().getPackageInfo(packageName, 0);
return true;
@@ -71,7 +73,8 @@
}
/**
- * Converts a {@link ScanFastPairStoreItem} to a {@link StoredDiscoveryItem}.
+ * Converts a {@link service.proto.Cache.ScanFastPairStoreItem}
+ * to a {@link service.proto.Cache.StoredDiscoveryItem}.
*
* <p>This is needed to make the new Fast Pair scanning stack compatible with the rest of the
* legacy Fast Pair code.
@@ -110,7 +113,39 @@
.build();
}
- private FastPairUtils() {
-
+ /**
+ * Returns true the application is installed and can be opened on device.
+ */
+ public static boolean isLaunchable(@NonNull Context context, String companionApp) {
+ return isAppInstalled(context, companionApp)
+ && createCompanionAppIntent(context, companionApp, null) != null;
}
+
+ /**
+ * Returns an intent to launch given the package name and bluetooth address (if provided).
+ * Returns null if no such an intent can be found.
+ */
+ @Nullable
+ public static Intent createCompanionAppIntent(@NonNull Context context, String packageName,
+ @Nullable String address) {
+ Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
+ if (intent == null) {
+ return null;
+ }
+ if (address != null) {
+ BluetoothAdapter adapter = getBluetoothAdapter(context);
+ if (adapter != null) {
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, adapter.getRemoteDevice(address));
+ }
+ }
+ return intent;
+ }
+
+ @Nullable
+ private static BluetoothAdapter getBluetoothAdapter(@NonNull Context context) {
+ BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class);
+ return bluetoothManager == null ? null : bluetoothManager.getAdapter();
+ }
+
+ private FastPairUtils() {}
}
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java
index 0521b7b..218c756 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/IconUtils.java
@@ -16,7 +16,7 @@
package com.android.nearby.halfsheet.utils;
-import static com.android.nearby.halfsheet.utils.FastPairUtils.TAG;
+import static com.android.nearby.halfsheet.HalfSheetActivity.TAG;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
diff --git a/nearby/service/Android.bp b/nearby/service/Android.bp
index 8f6a227..6743c21 100644
--- a/nearby/service/Android.bp
+++ b/nearby/service/Android.bp
@@ -53,7 +53,7 @@
libs: [
"framework-bluetooth.stubs.module_lib", // TODO(b/215722418): Change to framework-bluetooth once fixed
"error_prone_annotations",
- "framework-connectivity-tiramisu.impl",
+ "framework-connectivity-t.impl",
"framework-statsd.stubs.module_lib",
],
static_libs: [
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java
index a7c05d6..c963aa6 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java
@@ -18,7 +18,6 @@
import android.annotation.WorkerThread;
import android.bluetooth.BluetoothDevice;
-import android.content.Intent;
import androidx.annotation.Nullable;
import androidx.core.util.Consumer;
@@ -135,13 +134,6 @@
@Nullable
public abstract String getPublicAddress();
- /**
- * Creates cloud syncing intent which saves the Fast Pair device to the account.
- *
- * @param accountKey account key which is written into the Fast Pair device
- * @return cloud syncing intent
- */
- public abstract Intent createCloudSyncingIntent(byte[] accountKey);
/** Callback for getting notifications when pairing has completed. */
public interface OnPairedCallback {
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
index 8b466fa..789ef59 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
@@ -20,7 +20,6 @@
import static android.bluetooth.BluetoothDevice.BOND_BONDING;
import static android.bluetooth.BluetoothDevice.BOND_NONE;
-import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH;
import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress;
import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.get16BitUuid;
import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid;
@@ -28,7 +27,6 @@
import static com.android.server.nearby.common.bluetooth.fastpair.Bytes.toShorts;
import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.base.Verify.verifyNotNull;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.primitives.Bytes.concat;
@@ -221,6 +219,12 @@
},
REQUESTED_SERVICES_LTV);
+ private static boolean sTestMode = false;
+
+ static void enableTestMode() {
+ sTestMode = true;
+ }
+
/**
* Operation Result Code.
*/
@@ -493,6 +497,7 @@
// Lazily initialize a new connection manager for each pairing request.
initGattConnectionManager();
boolean isSecretHandshakeCompleted = true;
+
try {
if (key != null && key.length > 0) {
// GATT_CONNECTION_AND_SECRET_HANDSHAKE start.
@@ -731,11 +736,6 @@
}
}
- @VisibleForTesting
- void setBeforeDirectlyConnectProfileFromCacheForTest(Runnable runnable) {
- this.mBeforeDirectlyConnectProfileFromCacheForTest = runnable;
- }
-
/**
* Logs for user retry, check go/fastpairquality21q3 for more details.
*/
@@ -898,6 +898,7 @@
mBluetoothAdapter.getRemoteDevice(mBleAddress).unwrap(),
mPreferences.getNumSdpAttempts()));
}
+
BluetoothDevice device =
mBluetoothAdapter.getRemoteDevice(brEdrHandoverInformation.mBluetoothAddress)
.unwrap();
@@ -910,6 +911,7 @@
? null
: new KeyBasedPairingInfo(
mPairingSecret, mGattConnectionManager, mProviderInitiatesBonding);
+
BluetoothAudioPairer pairer =
new BluetoothAudioPairer(
mContext,
@@ -928,7 +930,9 @@
// normally do and we can finish early. It is also more reliable than tearing down the bond
// and recreating it.
try {
- attemptDirectConnectionIfBonded(device, pairer);
+ if (!sTestMode) {
+ attemptDirectConnectionIfBonded(device, pairer);
+ }
callbackOnPaired();
return maybeWriteAccountKey(device);
} catch (PairingException e) {
@@ -1299,26 +1303,6 @@
}
/**
- * Creates cloud syncing intent which saves the Fast Pair device to the account.
- *
- * @param accountKey account key which is written into the Fast Pair device
- * @return cloud syncing intent
- */
- public Intent createCloudSyncingIntent(byte[] accountKey) {
- Intent intent = new Intent(BroadcastConstants.ACTION_FAST_PAIR_DEVICE_ADDED);
- intent.setClassName(BroadcastConstants.PACKAGE_NAME, BroadcastConstants.SERVICE_NAME);
- intent.putExtra(BroadcastConstants.EXTRA_ADDRESS, mBleAddress);
- if (mPublicAddress != null) {
- intent.putExtra(BroadcastConstants.EXTRA_PUBLIC_ADDRESS, mPublicAddress);
- }
- intent.putExtra(BroadcastConstants.EXTRA_ACCOUNT_KEY, accountKey);
- intent.putExtra(
- BroadcastConstants.EXTRA_RETROACTIVE_PAIR, mPreferences.getIsRetroactivePairing());
-
- return intent;
- }
-
- /**
* Checks whether or not an account key should be written to the device and writes it if so.
* This is called after handle notifying the pairedCallback that we've finished pairing, because
* at this point the headset is ready to use.
@@ -1328,7 +1312,9 @@
throws InterruptedException, ExecutionException, TimeoutException,
NoSuchAlgorithmException,
BluetoothException {
- Locator.get(mContext, FastPairController.class).setShouldUpload(false);
+ if (!sTestMode) {
+ Locator.get(mContext, FastPairController.class).setShouldUpload(false);
+ }
if (!shouldWriteAccountKey()) {
// For FastPair 2.0, here should be a subsequent pairing case.
return null;
@@ -1611,95 +1597,6 @@
waitForBluetoothStateUsingPolling(state);
}
- /**
- * Update device name to provider.
- *
- * <pre>
- * A) Connect GATT
- * B) Handshake with provider to get the pairing secret and public address
- * C) Write new device name into provider through name characteristic in GATT
- * D) Disconnect GATT
- * </pre>
- *
- * Synchronous: Blocks until until the name has finished being written. Throws on any error.
- *
- * @param key is a 16-byte account key. See go/fast-pair-2-spec for how these keys are used.
- * @return true if the task is done, i.e. name is written successfully or it is skipped because
- * of unsupported Name characteristic, false if some error happens and may need to re-try.
- */
- @WorkerThread
- public boolean updateProviderName(@Nullable byte[] key, @Nullable String deviceName)
- throws BluetoothException, InterruptedException, TimeoutException, ExecutionException,
- PairingException, GeneralSecurityException {
- if (!mPreferences.getEnableNamingCharacteristic()) {
- Log.i(TAG, "Disable NamingCharacteristic feature, ignoring.");
- return false;
- }
- if (isNullOrEmpty(deviceName)) {
- Log.i(TAG, "Provider name is null or empty, ignoring.");
- return false;
- }
- if (key == null || key.length != AES_BLOCK_LENGTH) {
- Log.i(TAG, "key is null or key length is not account key size.");
- return false;
- }
-
- Log.i(TAG, "Start to update device name for provider.");
- boolean result = false;
- if (mPreferences.getExtraLoggingInformation() != null) {
- this.mEventLogger.bind(
- mContext, mBleAddress, mPreferences.getExtraLoggingInformation());
- }
-
- // Lazily initialize a new connection manager for each renaming request.
- mGattConnectionManager =
- new GattConnectionManager(
- mContext,
- mPreferences,
- mEventLogger,
- mBluetoothAdapter,
- this::toggleBluetooth,
- mBleAddress,
- mTimingLogger,
- mFastPairSignalChecker,
- /* setMtu= */ true);
-
- try (BluetoothGattConnection connection = mGattConnectionManager.getConnection()) {
- UUID characteristicUuid = NameCharacteristic.getId(connection);
- if (!validateBluetoothGattCharacteristic(connection, characteristicUuid)) {
- Log.i(TAG, "Can't find name characteristic, skip to write name with retry times.");
- mGattConnectionManager.closeConnection();
- // Returns true because the task is done with no name characteristic in device.
- return true;
- }
- mEventLogger.setCurrentEvent(EventCode.SECRET_HANDSHAKE);
- // Handshake to get pairing secret for name characteristic decryption and encryption.
- try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Handshake")) {
- handshakeForActionOverBle(key, AdditionalDataType.PERSONALIZED_NAME);
- }
- mEventLogger.logCurrentEventSucceeded();
- // After handshake to get secret, write the name back to provider.
- try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger,
- "WriteNameToProvider")) {
- result = writeNameToProvider(deviceName, mPublicAddress);
- }
- } catch (BluetoothException
- | InterruptedException
- | TimeoutException
- | ExecutionException
- | PairingException
- | GeneralSecurityException e) {
- if (mEventLogger.isCurrentEvent()) {
- mEventLogger.logCurrentEventFailed(e);
- }
- throw e;
- } finally {
- mTimingLogger.dump();
- }
- mGattConnectionManager.closeConnection();
- return result;
- }
-
private void waitForBluetoothStateUsingPolling(int state) throws TimeoutException {
// There's a bug where we (pretty often!) never get the broadcast for STATE_ON or STATE_OFF.
// So poll instead.
@@ -1882,9 +1779,13 @@
/* setMtu= */ true);
mGattConnectionManager.closeConnection();
}
+ if (sTestMode) {
+ return null;
+ }
BluetoothGattConnection connection = mGattConnectionManager.getConnection();
connection.setOperationTimeout(
TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds()));
+
try {
String firmwareVersion =
new String(
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManager.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManager.java
index ff0efc7..e7ce4bf 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManager.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManager.java
@@ -70,6 +70,11 @@
private final FastPairConnection.FastPairSignalChecker mFastPairSignalChecker;
@Nullable
private BluetoothGattConnection mGattConnection;
+ private static boolean sTestMode = false;
+
+ static void enableTestMode() {
+ sTestMode = true;
+ }
GattConnectionManager(
Context context,
@@ -147,6 +152,9 @@
try (ScopedTiming scopedTiming =
new ScopedTiming(mTimingLogger, "Connect GATT #" + i)) {
Log.i(TAG, "Connecting to GATT server at " + maskBluetoothAddress(address));
+ if (sTestMode) {
+ return null;
+ }
BluetoothGattConnection connection =
new BluetoothGattHelper(mContext, mBluetoothAdapter)
.connect(
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/Constant.java b/nearby/service/java/com/android/server/nearby/fastpair/Constant.java
index 5958007..0695b5f 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/Constant.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/Constant.java
@@ -33,22 +33,11 @@
public static final String EXTRA_BINDER = "com.android.server.nearby.fastpair.BINDER";
public static final String EXTRA_BUNDLE = "com.android.server.nearby.fastpair.BUNDLE_EXTRA";
- public static final String SUCCESS_STATE = "SUCCESS";
- public static final String FAIL_STATE = "FAIL";
- public static final String DISMISS = "DISMISS";
- public static final String NEED_CONFIRM_PASSKEY = "NEED CONFIRM PASSKEY";
- // device support assistant additional setup
- public static final String NEED_ADDITIONAL_SETUP = "NEED ADDITIONAL SETUP";
- public static final String SHOW_PAIRING_WITHOUT_INTERACTION =
- "SHOW_PAIRING_WITHOUT_INTERACTION";
public static final String ACTION_FAST_PAIR_HALF_SHEET_CANCEL =
"com.android.nearby.ACTION_FAST_PAIR_HALF_SHEET_CANCEL";
- public static final String ACTION_FAST_PAIR_HALF_SHEET_BAN_STATE_RESET =
- "com.android.nearby.ACTION_FAST_PAIR_BAN_STATE_RESET";
public static final String EXTRA_HALF_SHEET_INFO =
"com.android.nearby.halfsheet.HALF_SHEET";
public static final String EXTRA_HALF_SHEET_TYPE =
"com.android.nearby.halfsheet.HALF_SHEET_TYPE";
public static final String DEVICE_PAIRING_FRAGMENT_TYPE = "DEVICE_PAIRING";
-
}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
index f8d5a62..6a296c2 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
@@ -16,6 +16,8 @@
package com.android.server.nearby.fastpair;
+import static com.android.server.nearby.fastpair.Constant.TAG;
+
import static com.google.common.primitives.Bytes.concat;
import android.accounts.Account;
@@ -45,8 +47,6 @@
public class FastPairAdvHandler {
Context mContext;
String mBleAddress;
- private static final String TAG = "FastPairAdvHandler";
-
/** The types about how the bloomfilter is processed. */
public enum ProcessBloomFilterType {
IGNORE, // The bloomfilter is not handled. e.g. distance is too far away.
@@ -69,26 +69,26 @@
public void handleBroadcast(NearbyDevice device) {
FastPairDevice fastPairDevice = (FastPairDevice) device;
mBleAddress = fastPairDevice.getBluetoothAddress();
+ List<Account> accountList =
+ FastPairDataProvider.getInstance().loadFastPairEligibleAccounts();
if (FastPairDecoder.checkModelId(fastPairDevice.getData())) {
byte[] model = FastPairDecoder.getModelId(fastPairDevice.getData());
- Log.d("FastPairService",
- "On discovery model id " + Hex.bytesToStringLowercase(model));
+ Log.d(TAG, "On discovery model id " + Hex.bytesToStringLowercase(model));
// Use api to get anti spoofing key from model id.
try {
Rpcs.GetObservedDeviceResponse response =
FastPairDataProvider.getInstance()
.loadFastPairAntispoofkeyDeviceMetadata(model);
Locator.get(mContext, FastPairHalfSheetManager.class).showHalfSheet(
- DataUtils.toScanFastPairStoreItem(response, mBleAddress));
+ DataUtils.toScanFastPairStoreItem(
+ response, mBleAddress,
+ accountList.isEmpty() ? null : accountList.get(0).name));
} catch (IllegalStateException e) {
Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
}
-
} else {
// Start to process bloom filter
try {
- List<Account> accountList =
- FastPairDataProvider.getInstance().loadFastPairEligibleAccounts();
Log.d(TAG, "account list size" + accountList.size());
byte[] bloomFilterByteArray = FastPairDecoder
.getBloomFilter(fastPairDevice.getData());
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java
index 793e126..1264ade 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java
@@ -16,21 +16,15 @@
package com.android.server.nearby.fastpair;
-import static com.android.server.nearby.common.bluetooth.fastpair.BroadcastConstants.EXTRA_RETROACTIVE_PAIR;
-import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_COMPANION_APP;
-import static com.android.server.nearby.fastpair.FastPairManager.EXTRA_NOTIFICATION_ID;
-
-import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.primitives.Bytes.concat;
import android.accounts.Account;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.Intent;
+import android.nearby.FastPairDevice;
import android.text.TextUtils;
import android.util.Log;
-import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
import com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress;
@@ -110,73 +104,60 @@
/**
* Pairing function.
*/
- @UiThread
- public void pair(Intent intent) {
- String itemId = intent.getStringExtra(UserActionHandler.EXTRA_ITEM_ID);
- int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
- byte[] discoveryItem = intent.getByteArrayExtra(UserActionHandler.EXTRA_DISCOVERY_ITEM);
- String accountKeyString = intent.getStringExtra(UserActionHandler.EXTRA_FAST_PAIR_SECRET);
- String companionApp = trimCompanionApp(intent.getStringExtra(EXTRA_COMPANION_APP));
- byte[] accountKey = accountKeyString != null ? base16().decode(accountKeyString) : null;
- boolean isRetroactivePair = intent.getBooleanExtra(EXTRA_RETROACTIVE_PAIR, false);
+ public void pair(FastPairDevice fastPairDevice) {
+ byte[] discoveryItem = fastPairDevice.getData();
+ String modelId = fastPairDevice.getModelId();
+ Log.v(TAG, "pair: fastPairDevice " + fastPairDevice);
mEventLoop.postRunnable(
- new NamedRunnable("fastPairWith=" + itemId) {
+ new NamedRunnable("fastPairWith=" + modelId) {
@Override
public void run() {
- DiscoveryItem item = null;
- if (discoveryItem != null) {
- try {
- item = new DiscoveryItem(mContext,
- Cache.StoredDiscoveryItem.parseFrom(discoveryItem));
- } catch (InvalidProtocolBufferException e) {
- Log.w(TAG,
- "Error parsing serialized discovery item with size "
- + discoveryItem.length);
+ try {
+ DiscoveryItem item = new DiscoveryItem(mContext,
+ Cache.StoredDiscoveryItem.parseFrom(discoveryItem));
+ if (TextUtils.isEmpty(item.getMacAddress())) {
+ Log.w(TAG, "There is no mac address in the DiscoveryItem,"
+ + " ignore pairing");
return;
}
+ // Check enabled state to prevent multiple pair attempts if we get the
+ // intent more than once (this can happen due to an Android platform
+ // bug - b/31459521).
+ if (item.getState()
+ != Cache.StoredDiscoveryItem.State.STATE_ENABLED) {
+ Log.d(TAG, "Incorrect state, ignore pairing");
+ return;
+ }
+ boolean useLargeNotifications =
+ item.getAuthenticationPublicKeySecp256R1() != null;
+ FastPairNotificationManager fastPairNotificationManager =
+ new FastPairNotificationManager(mContext, item,
+ useLargeNotifications);
+ FastPairHalfSheetManager fastPairHalfSheetManager =
+ Locator.get(mContext, FastPairHalfSheetManager.class);
+ mFastPairCacheManager.saveDiscoveryItem(item);
+
+ PairingProgressHandlerBase pairingProgressHandlerBase =
+ PairingProgressHandlerBase.create(
+ mContext,
+ item,
+ /* companionApp= */ null,
+ /* accountKey= */ null,
+ mFootprintsDeviceManager,
+ fastPairNotificationManager,
+ fastPairHalfSheetManager,
+ /* isRetroactivePair= */ false);
+
+ pair(item,
+ /* accountKey= */ null,
+ /* companionApp= */ null,
+ pairingProgressHandlerBase);
+ } catch (InvalidProtocolBufferException e) {
+ Log.w(TAG,
+ "Error parsing serialized discovery item with size "
+ + discoveryItem.length);
}
-
-
- if (item == null || TextUtils.isEmpty(item.getMacAddress())) {
- Log.w(TAG, "Invalid DiscoveryItem, ignore pairing");
- return;
- }
-
- // Check enabled state to prevent multiple pair attempts if we get the
- // intent more than once (this can happen due to an Android platform
- // bug - b/31459521).
- if (item.getState() != Cache.StoredDiscoveryItem.State.STATE_ENABLED
- && !isRetroactivePair) {
- Log.d(TAG, "Incorrect state, ignore pairing");
- return;
- }
-
- boolean useLargeNotifications = accountKey != null
- || item.getAuthenticationPublicKeySecp256R1() != null;
- FastPairNotificationManager fastPairNotificationManager =
- notificationId == -1
- ? new FastPairNotificationManager(mContext, item,
- useLargeNotifications)
- : new FastPairNotificationManager(mContext, item,
- useLargeNotifications, notificationId);
- FastPairHalfSheetManager fastPairHalfSheetManager =
- Locator.get(mContext, FastPairHalfSheetManager.class);
-
- mFastPairCacheManager.saveDiscoveryItem(item);
-
- PairingProgressHandlerBase pairingProgressHandlerBase =
- PairingProgressHandlerBase.create(
- mContext,
- item,
- companionApp,
- accountKey,
- mFootprintsDeviceManager,
- fastPairNotificationManager,
- fastPairHalfSheetManager,
- isRetroactivePair);
-
- pair(item, accountKey, companionApp, pairingProgressHandlerBase);
}
});
}
@@ -315,4 +296,4 @@
interface Callback {
void fastPairUpdateDeviceItemsEnabled(boolean enabled);
}
-}
+}
\ No newline at end of file
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
index 9e1a718..3a3c962 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
@@ -56,6 +56,7 @@
import com.android.server.nearby.fastpair.cache.DiscoveryItem;
import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
+import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
import com.android.server.nearby.fastpair.pairinghandler.PairingProgressHandlerBase;
import com.android.server.nearby.util.FastPairDecoder;
import com.android.server.nearby.util.ForegroundThread;
@@ -194,7 +195,7 @@
@Nullable byte[] accountKey,
FootprintsDeviceManager footprints,
PairingProgressHandlerBase pairingProgressHandlerBase) {
-
+ FastPairHalfSheetManager manager = Locator.get(context, FastPairHalfSheetManager.class);
try {
pairingProgressHandlerBase.onPairingStarted();
if (pairingProgressHandlerBase.skipWaitingScreenUnlock()) {
@@ -279,6 +280,10 @@
// Fast Pair one
connection.pair();
}
+
+ // TODO(b/213373051): Merge logic with pairingProgressHandlerBase or delete the
+ // pairingProgressHandlerBase class.
+ manager.showPairingSuccessHalfSheet(connection.getPublicAddress());
pairingProgressHandlerBase.onPairingSuccess(connection.getPublicAddress());
} catch (BluetoothException
| InterruptedException
@@ -287,7 +292,11 @@
| ExecutionException
| PairingException
| GeneralSecurityException e) {
- Log.e(TAG, "FastPair: Error");
+ Log.e(TAG, "Failed to pair.", e);
+
+ // TODO(b/213373051): Merge logic with pairingProgressHandlerBase or delete the
+ // pairingProgressHandlerBase class.
+ manager.showPairingFailed();
pairingProgressHandlerBase.onPairingFailed(e);
}
}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java
index cfac904..6f79e6e 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java
@@ -29,11 +29,15 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.nearby.FastPairDevice;
+import android.nearby.FastPairStatusCallback;
+import android.nearby.PairStatusMetadata;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
-import com.android.server.nearby.common.locator.Locator;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
import com.android.server.nearby.fastpair.FastPairController;
import com.android.server.nearby.fastpair.cache.DiscoveryItem;
import com.android.server.nearby.util.Environment;
@@ -50,17 +54,22 @@
private static final String ACTIVITY_INTENT_ACTION = "android.nearby.SHOW_HALFSHEET";
private static final String HALF_SHEET_CLASS_NAME =
"com.android.nearby.halfsheet.HalfSheetActivity";
+ private static final String TAG = "FPHalfSheetManager";
private String mHalfSheetApkPkgName;
- private Context mContext;
+ private final LocatorContextWrapper mLocatorContextWrapper;
- /**
- * Construct function
- */
+ FastPairService mFastPairService;
+
public FastPairHalfSheetManager(Context context) {
- mContext = context;
+ this(new LocatorContextWrapper(context));
}
+ @VisibleForTesting
+ FastPairHalfSheetManager(LocatorContextWrapper locatorContextWrapper) {
+ mLocatorContextWrapper = locatorContextWrapper;
+ mFastPairService = new FastPairService();
+ }
/**
* Invokes half sheet in the other apk. This function can only be called in Nearby because other
@@ -68,13 +77,17 @@
*/
public void showHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem) {
try {
- if (mContext != null) {
+ if (mLocatorContextWrapper != null) {
String packageName = getHalfSheetApkPkgName();
- HalfSheetCallback callback = new HalfSheetCallback();
- callback.setmFastPairController(Locator.get(mContext, FastPairController.class));
+ if (packageName == null) {
+ Log.e(TAG, "package name is null");
+ return;
+ }
+ mFastPairService.setFastPairController(
+ mLocatorContextWrapper.getLocator().get(FastPairController.class));
Bundle bundle = new Bundle();
- bundle.putBinder(EXTRA_BINDER, callback);
- mContext
+ bundle.putBinder(EXTRA_BINDER, mFastPairService);
+ mLocatorContextWrapper
.startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION)
.putExtra(EXTRA_HALF_SHEET_INFO,
scanFastPairStoreItem.toByteArray())
@@ -84,11 +97,9 @@
.setComponent(new ComponentName(packageName,
HALF_SHEET_CLASS_NAME)),
UserHandle.CURRENT);
-
}
} catch (IllegalStateException e) {
- Log.e("FastPairHalfSheetManager",
- "Can't resolve package that contains half sheet");
+ Log.e(TAG, "Can't resolve package that contains half sheet");
}
}
@@ -96,7 +107,15 @@
* Shows pairing fail half sheet.
*/
public void showPairingFailed() {
- Log.d("FastPairHalfSheetManager", "show fail half sheet");
+ FastPairStatusCallback pairStatusCallback = mFastPairService.getPairStatusCallback();
+ if (pairStatusCallback != null) {
+ Log.v(TAG, "showPairingFailed: pairStatusCallback not NULL");
+ pairStatusCallback.onPairUpdate(new FastPairDevice.Builder().build(),
+ new PairStatusMetadata(PairStatusMetadata.Status.FAIL));
+ } else {
+ Log.w(TAG, "FastPairHalfSheetManager failed to show success half sheet because "
+ + "the pairStatusCallback is null");
+ }
}
/**
@@ -116,14 +135,22 @@
* This function will handle pairing steps for half sheet.
*/
public void showPairingHalfSheet(DiscoveryItem item) {
- Log.d("FastPairHalfSheetManager", "show pairing half sheet");
+ Log.d(TAG, "show pairing half sheet");
}
/**
* Shows pairing success info.
*/
public void showPairingSuccessHalfSheet(String address) {
- Log.d("FastPairHalfSheetManager", "show success half sheet");
+ FastPairStatusCallback pairStatusCallback = mFastPairService.getPairStatusCallback();
+ if (pairStatusCallback != null) {
+ pairStatusCallback.onPairUpdate(
+ new FastPairDevice.Builder().setBluetoothAddress(address).build(),
+ new PairStatusMetadata(PairStatusMetadata.Status.SUCCESS));
+ } else {
+ Log.w(TAG, "FastPairHalfSheetManager failed to show success half sheet because "
+ + "the pairStatusCallback is null");
+ }
}
/**
@@ -153,7 +180,7 @@
if (mHalfSheetApkPkgName != null) {
return mHalfSheetApkPkgName;
}
- List<ResolveInfo> resolveInfos = mContext
+ List<ResolveInfo> resolveInfos = mLocatorContextWrapper
.getPackageManager().queryIntentActivities(
new Intent(ACTION_RESOURCES_APK),
PackageManager.MATCH_SYSTEM_ONLY);
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairService.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairService.java
new file mode 100644
index 0000000..8c0d572
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairService.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair.halfsheet;
+
+import static com.android.server.nearby.fastpair.Constant.TAG;
+
+import android.nearby.FastPairDevice;
+import android.nearby.FastPairStatusCallback;
+import android.nearby.PairStatusMetadata;
+import android.nearby.aidl.IFastPairClient;
+import android.nearby.aidl.IFastPairStatusCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.nearby.fastpair.FastPairController;
+
+/**
+ * Service implementing Fast Pair functionality.
+ *
+ * @hide
+ */
+public class FastPairService extends IFastPairClient.Stub {
+
+ private IBinder mStatusCallbackProxy;
+ private FastPairController mFastPairController;
+ private FastPairStatusCallback mFastPairStatusCallback;
+
+ /**
+ * Registers the Binder call back in the server notifies the proxy when there is an update
+ * in the server.
+ */
+ @Override
+ public void registerHalfSheet(IFastPairStatusCallback iFastPairStatusCallback) {
+ mStatusCallbackProxy = iFastPairStatusCallback.asBinder();
+ mFastPairStatusCallback = new FastPairStatusCallback() {
+ @Override
+ public void onPairUpdate(FastPairDevice fastPairDevice,
+ PairStatusMetadata pairStatusMetadata) {
+ try {
+ iFastPairStatusCallback.onPairUpdate(fastPairDevice, pairStatusMetadata);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to update pair status.", e);
+ }
+ }
+ };
+ }
+
+ /**
+ * Unregisters the Binder call back in the server.
+ */
+ @Override
+ public void unregisterHalfSheet(IFastPairStatusCallback iFastPairStatusCallback) {
+ mStatusCallbackProxy = null;
+ mFastPairStatusCallback = null;
+ }
+
+ /**
+ * Asks the Fast Pair service to pair the device.
+ */
+ @Override
+ public void connect(FastPairDevice fastPairDevice) {
+ if (mFastPairController != null) {
+ mFastPairController.pair(fastPairDevice);
+ } else {
+ Log.w(TAG, "Failed to connect because there is no FastPairController.");
+ }
+ }
+
+ public FastPairStatusCallback getPairStatusCallback() {
+ return mFastPairStatusCallback;
+ }
+
+ /**
+ * Sets function for Fast Pair controller.
+ */
+ public void setFastPairController(FastPairController fastPairController) {
+ mFastPairController = fastPairController;
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/HalfSheetCallback.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/HalfSheetCallback.java
deleted file mode 100644
index 2c792ed..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/HalfSheetCallback.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.nearby.fastpair.halfsheet;
-
-import android.content.Intent;
-import android.nearby.IFastPairHalfSheetCallback;
-import android.util.Log;
-
-import com.android.server.nearby.fastpair.FastPairController;
-
-
-/**
- * Callback to send ux action back to nearby service.
- */
-public class HalfSheetCallback extends IFastPairHalfSheetCallback.Stub {
- private FastPairController mFastPairController;
-
- public HalfSheetCallback() {
- }
-
- /**
- * Set function for Fast Pair controller.
- */
- public void setmFastPairController(FastPairController fastPairController) {
- mFastPairController = fastPairController;
- }
-
- /**
- * Half Sheet connection button clicked.
- */
- @Override
- public void onHalfSheetConnectionConfirm(Intent intent) {
- Log.d("FastPairHalfSheet", "Call back receiver");
- if (mFastPairController != null) {
- mFastPairController.pair(intent);
- }
- }
-}
diff --git a/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java b/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java
new file mode 100644
index 0000000..c355df2
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java
@@ -0,0 +1,30 @@
+/*
+ * 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.presence;
+
+import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid;
+
+import java.util.UUID;
+
+/**
+ * Constants for Nearby Presence operations.
+ */
+public class PresenceConstants {
+
+ /** Presence advertisement service data uuid. */
+ public static final UUID PRESENCE_UUID = to128BitUuid((short) 0xFCF1);
+}
diff --git a/nearby/service/java/com/android/server/nearby/presence/PresenceDiscoveryResult.java b/nearby/service/java/com/android/server/nearby/presence/PresenceDiscoveryResult.java
new file mode 100644
index 0000000..7a77aa9
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/presence/PresenceDiscoveryResult.java
@@ -0,0 +1,152 @@
+/*
+ * 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.presence;
+
+import android.nearby.NearbyDevice;
+import android.nearby.PresenceDevice;
+import android.nearby.PresenceScanFilter;
+import android.nearby.PublicCredential;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a Presence discovery result.
+ */
+public class PresenceDiscoveryResult {
+
+ /**
+ * Creates a {@link PresenceDiscoveryResult} from the scan data.
+ */
+ public static PresenceDiscoveryResult fromScanData(byte[] scanData, int rssi) {
+ return new PresenceDiscoveryResult.Builder().setRssi(rssi).build();
+ }
+
+ private final int mTxPower;
+ private final int mRssi;
+ private final byte[] mSalt;
+ private final List<Integer> mPresenceActions;
+ private final PublicCredential mPublicCredential;
+
+ private PresenceDiscoveryResult(int txPower, int rssi, byte[] salt,
+ List<Integer> presenceActions, PublicCredential publicCredential) {
+ mTxPower = txPower;
+ mRssi = rssi;
+ mSalt = salt;
+ mPresenceActions = presenceActions;
+ mPublicCredential = publicCredential;
+ }
+
+ /**
+ * Returns whether the discovery result matches the scan filter.
+ */
+ public boolean matches(PresenceScanFilter scanFilter) {
+ return pathLossMatches(scanFilter.getMaxPathLoss())
+ && actionMatches(scanFilter.getPresenceActions())
+ && credentialMatches(scanFilter.getCredentials());
+ }
+
+ private boolean pathLossMatches(int maxPathLoss) {
+ return (mTxPower - mRssi) <= maxPathLoss;
+ }
+
+ private boolean actionMatches(List<Integer> filterActions) {
+ if (filterActions.isEmpty()) {
+ return true;
+ }
+ return filterActions.stream().anyMatch(mPresenceActions::contains);
+ }
+
+ private boolean credentialMatches(List<PublicCredential> credentials) {
+ return credentials.contains(mPublicCredential);
+ }
+
+ /**
+ * Converts a presence device from the discovery result.
+ */
+ public PresenceDevice toPresenceDevice() {
+ return new PresenceDevice.Builder()
+ .setRssi(mRssi)
+ .setSalt(mSalt)
+ .setSecretId(mPublicCredential.getSecretId())
+ .addMedium(NearbyDevice.Medium.BLE).build();
+ }
+
+ /**
+ * Builder for {@link PresenceDiscoveryResult}.
+ */
+ public static class Builder {
+ private int mTxPower;
+ private int mRssi;
+ private byte[] mSalt;
+
+ private PublicCredential mPublicCredential;
+ private final List<Integer> mPresenceActions;
+
+ public Builder() {
+ mPresenceActions = new ArrayList<>();
+ }
+
+ /**
+ * Sets the calibrated tx power for the discovery result.
+ */
+ public Builder setTxPower(int txPower) {
+ mTxPower = txPower;
+ return this;
+ }
+
+ /**
+ * Sets the rssi for the discovery result.
+ */
+ public Builder setRssi(int rssi) {
+ mRssi = rssi;
+ return this;
+ }
+
+ /**
+ * Sets the salt for the discovery result.
+ */
+ public Builder setSalt(byte[] salt) {
+ mSalt = salt;
+ return this;
+ }
+
+ /**
+ * Sets the public credential for the discovery result.
+ */
+ public Builder setPublicCredential(PublicCredential publicCredential) {
+ mPublicCredential = publicCredential;
+ return this;
+ }
+
+ /**
+ * Adds presence action of the discovery result.
+ */
+ public Builder addPresenceAction(int presenceAction) {
+ mPresenceActions.add(presenceAction);
+ return this;
+ }
+
+ /**
+ * Builds a {@link PresenceDiscoveryResult}.
+ */
+ public PresenceDiscoveryResult build() {
+ return new PresenceDiscoveryResult(mTxPower, mRssi, mSalt, mPresenceActions,
+ mPublicCredential);
+ }
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
index ae7f133..a989143 100644
--- a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
@@ -34,8 +35,11 @@
import com.android.server.nearby.common.bluetooth.fastpair.Constants;
import com.android.server.nearby.injector.Injector;
+import com.android.server.nearby.presence.PresenceConstants;
import com.android.server.nearby.util.ForegroundThread;
+import com.google.common.annotations.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -46,7 +50,10 @@
*/
public class BleDiscoveryProvider extends AbstractDiscoveryProvider {
- private static final ParcelUuid FAST_PAIR_UUID = new ParcelUuid(Constants.FastPairService.ID);
+ @VisibleForTesting
+ static final ParcelUuid FAST_PAIR_UUID = new ParcelUuid(Constants.FastPairService.ID);
+ private static final ParcelUuid PRESENCE_UUID = new ParcelUuid(PresenceConstants.PRESENCE_UUID);
+
// Don't block the thread as it may be used by other services.
private static final Executor NEARBY_EXECUTOR = ForegroundThread.getExecutor();
private final Injector mInjector;
@@ -69,6 +76,11 @@
byte[] fastPairData = serviceDataMap.get(FAST_PAIR_UUID);
if (fastPairData != null) {
builder.setData(serviceDataMap.get(FAST_PAIR_UUID));
+ } else {
+ byte [] presenceData = serviceDataMap.get(PRESENCE_UUID);
+ if (presenceData != null) {
+ builder.setData(serviceDataMap.get(PRESENCE_UUID));
+ }
}
}
mExecutor.execute(() -> mListener.onNearbyDeviceDiscovered(builder.build()));
@@ -148,6 +160,7 @@
if (bluetoothLeScanner == null) {
Log.w(TAG, "BleDiscoveryProvider failed to start BLE scanning "
+ "because BluetoothLeScanner is null.");
+ return;
}
bluetoothLeScanner.startScan(scanFilters, scanSettings, scanCallback);
} catch (NullPointerException | IllegalStateException | SecurityException e) {
@@ -180,4 +193,9 @@
}
return new ScanSettings.Builder().setScanMode(bleScanMode).build();
}
+
+ @VisibleForTesting
+ ScanCallback getScanCallback() {
+ return mScanCallback;
+ }
}
diff --git a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
index 27f3acb..825a4ab 100644
--- a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
+++ b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
@@ -16,11 +16,15 @@
package com.android.server.nearby.provider;
+import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE;
+
import static com.android.server.nearby.NearbyService.TAG;
import android.content.Context;
import android.nearby.IScanListener;
import android.nearby.NearbyDeviceParcelable;
+import android.nearby.PresenceScanFilter;
+import android.nearby.ScanFilter;
import android.nearby.ScanRequest;
import android.os.IBinder;
import android.os.RemoteException;
@@ -29,10 +33,13 @@
import com.android.internal.annotations.GuardedBy;
import com.android.server.nearby.injector.Injector;
import com.android.server.nearby.metrics.NearbyMetrics;
+import com.android.server.nearby.presence.PresenceDiscoveryResult;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.stream.Collectors;
/**
* Manages all aspects of discovery providers.
@@ -56,6 +63,16 @@
Log.w(TAG, "DiscoveryProviderManager cannot find the scan record.");
continue;
}
+ if (nearbyDevice.getScanType() == SCAN_TYPE_NEARBY_PRESENCE) {
+ List<ScanFilter> presenceFilters =
+ record.getScanRequest().getScanFilters().stream().filter(
+ scanFilter -> scanFilter.getType()
+ == SCAN_TYPE_NEARBY_PRESENCE).collect(
+ Collectors.toList());
+ if (!presenceFilterMatches(nearbyDevice, presenceFilters)) {
+ continue;
+ }
+ }
try {
record.getScanListener().onDiscovered(
PrivacyFilter.filter(record.getScanRequest().getScanType(),
@@ -175,6 +192,22 @@
mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode);
}
+ private static boolean presenceFilterMatches(NearbyDeviceParcelable device,
+ List<ScanFilter> scanFilters) {
+ if (scanFilters.isEmpty()) {
+ return true;
+ }
+ PresenceDiscoveryResult discoveryResult = PresenceDiscoveryResult.fromScanData(
+ device.getData(), device.getRssi());
+ for (ScanFilter scanFilter : scanFilters) {
+ PresenceScanFilter presenceScanFilter = (PresenceScanFilter) scanFilter;
+ if (discoveryResult.matches(presenceScanFilter)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static class ScanListenerRecord {
private final ScanRequest mScanRequest;
diff --git a/nearby/service/java/com/android/server/nearby/util/DataUtils.java b/nearby/service/java/com/android/server/nearby/util/DataUtils.java
index ce738c8..0b01bc0 100644
--- a/nearby/service/java/com/android/server/nearby/util/DataUtils.java
+++ b/nearby/service/java/com/android/server/nearby/util/DataUtils.java
@@ -17,6 +17,7 @@
package com.android.server.nearby.util;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import service.proto.Cache.ScanFastPairStoreItem;
import service.proto.Cache.StoredDiscoveryItem;
@@ -36,20 +37,36 @@
* Converts a {@link GetObservedDeviceResponse} to a {@link ScanFastPairStoreItem}.
*/
public static ScanFastPairStoreItem toScanFastPairStoreItem(
- GetObservedDeviceResponse observedDeviceResponse, @NonNull String bleAddress) {
+ GetObservedDeviceResponse observedDeviceResponse,
+ @NonNull String bleAddress, @Nullable String account) {
Device device = observedDeviceResponse.getDevice();
+ String deviceName = device.getName();
return ScanFastPairStoreItem.newBuilder()
.setAddress(bleAddress)
.setActionUrl(device.getIntentUri())
- .setDeviceName(device.getName())
+ .setDeviceName(deviceName)
.setIconPng(observedDeviceResponse.getImage())
.setIconFifeUrl(device.getImageUrl())
.setAntiSpoofingPublicKey(device.getAntiSpoofingKeyPair().getPublicKey())
- .setFastPairStrings(getFastPairStrings(observedDeviceResponse))
+ .setFastPairStrings(getFastPairStrings(observedDeviceResponse, deviceName, account))
.build();
}
/**
+ * Prints readable string for a {@link ScanFastPairStoreItem}.
+ */
+ public static String toString(ScanFastPairStoreItem item) {
+ return "ScanFastPairStoreItem=[address:" + item.getAddress()
+ + ", actionUr:" + item.getActionUrl()
+ + ", deviceName:" + item.getDeviceName()
+ + ", iconPng:" + item.getIconPng()
+ + ", iconFifeUrl:" + item.getIconFifeUrl()
+ + ", antiSpoofingKeyPair:" + item.getAntiSpoofingPublicKey()
+ + ", fastPairStrings:" + toString(item.getFastPairStrings())
+ + "]";
+ }
+
+ /**
* Prints readable string for a {@link FastPairStrings}
*/
public static String toString(FastPairStrings fastPairStrings) {
@@ -76,13 +93,17 @@
+ "]";
}
- private static FastPairStrings getFastPairStrings(GetObservedDeviceResponse response) {
+ private static FastPairStrings getFastPairStrings(GetObservedDeviceResponse response,
+ String deviceName, @Nullable String account) {
ObservedDeviceStrings strings = response.getStrings();
return FastPairStrings.newBuilder()
.setTapToPairWithAccount(strings.getInitialNotificationDescription())
.setTapToPairWithoutAccount(
strings.getInitialNotificationDescriptionNoAccount())
- .setInitialPairingDescription(strings.getInitialPairingDescription())
+ .setInitialPairingDescription(account == null
+ ? strings.getInitialNotificationDescriptionNoAccount()
+ : String.format(strings.getInitialPairingDescription(),
+ deviceName, account))
.setPairingFinishedCompanionAppInstalled(
strings.getConnectSuccessCompanionAppInstalled())
.setPairingFinishedCompanionAppNotInstalled(
diff --git a/nearby/service/java/com/android/server/nearby/util/Environment.java b/nearby/service/java/com/android/server/nearby/util/Environment.java
index e7277cd..d397862 100644
--- a/nearby/service/java/com/android/server/nearby/util/Environment.java
+++ b/nearby/service/java/com/android/server/nearby/util/Environment.java
@@ -33,7 +33,7 @@
/**
* The path where the Nearby apex is mounted.
- * Current value = "/apex/com.android.nearby"
+ * Current value = "/apex/com.android.tethering"
*/
private static final String NEARBY_APEX_PATH =
new File("/apex", NEARBY_APEX_NAME).getAbsolutePath();
@@ -55,7 +55,7 @@
/**
* Returns true if the app is in the nearby apex, false otherwise.
- * Checks if the app's path starts with "/apex/com.android.nearby".
+ * Checks if the app's path starts with "/apex/com.android.tethering".
*/
public static boolean isAppInNearbyApex(ApplicationInfo appInfo) {
return appInfo.sourceDir.startsWith(NEARBY_APEX_PATH);
diff --git a/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java b/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java
index 5bb76c9..6021ff6 100644
--- a/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java
+++ b/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java
@@ -145,7 +145,6 @@
return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER_NO_NOTIFICATION);
}
-
/**
* Get random resolvableData
*/
diff --git a/nearby/tests/cts/fastpair/Android.bp b/nearby/tests/cts/fastpair/Android.bp
index 9cbd7d0..599fe5c 100644
--- a/nearby/tests/cts/fastpair/Android.bp
+++ b/nearby/tests/cts/fastpair/Android.bp
@@ -29,7 +29,7 @@
],
libs: [
"android.test.base",
- "framework-connectivity-tiramisu.impl",
+ "framework-connectivity-t.impl",
],
srcs: ["src/**/*.java"],
test_suites: [
diff --git a/nearby/tests/multidevices/clients/proguard.flags b/nearby/tests/multidevices/clients/proguard.flags
index ec8f526..2e34dce 100644
--- a/nearby/tests/multidevices/clients/proguard.flags
+++ b/nearby/tests/multidevices/clients/proguard.flags
@@ -3,6 +3,11 @@
*;
}
+# Keep simulator reflection callback.
+-keep class com.android.server.nearby.common.bluetooth.fastpair.testing.** {
+ *;
+}
+
# Do not touch Mobly.
-keep class com.google.android.mobly.** {
*;
diff --git a/nearby/tests/unit/Android.bp b/nearby/tests/unit/Android.bp
index 50c395a..851dd08 100644
--- a/nearby/tests/unit/Android.bp
+++ b/nearby/tests/unit/Android.bp
@@ -39,12 +39,17 @@
"guava",
"junit",
"libprotobuf-java-lite",
- "mockito-target",
+ "mockito-target-extended-minus-junit4",
"platform-test-annotations",
"service-nearby",
"truth-prebuilt",
// "Robolectric_all-target",
],
+ // these are needed for Extended Mockito
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
test_suites: [
"general-tests",
"mts-tethering",
diff --git a/nearby/tests/unit/AndroidManifest.xml b/nearby/tests/unit/AndroidManifest.xml
index c756ff2..88c0f5f 100644
--- a/nearby/tests/unit/AndroidManifest.xml
+++ b/nearby/tests/unit/AndroidManifest.xml
@@ -20,8 +20,10 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
- <application>
+ <application android:debuggable="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java b/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java
new file mode 100644
index 0000000..c20cf22
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.content.Context;
+import android.nearby.IScanListener;
+import android.nearby.ScanRequest;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public final class NearbyServiceTest {
+
+ private Context mContext;
+ private NearbyService mService;
+ private ScanRequest mScanRequest;
+ @Mock
+ private IScanListener mScanListener;
+
+ @Before
+ public void setup() {
+ initMocks(this);
+
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ mService = new NearbyService(mContext);
+ mScanRequest = createScanRequest();
+ }
+
+ @Test
+ public void test_register() {
+ mService.registerScanListener(mScanRequest, mScanListener);
+ }
+
+ @Test
+ public void test_unregister() {
+ mService.unregisterScanListener(mScanListener);
+ }
+
+ private ScanRequest createScanRequest() {
+ return new ScanRequest.Builder()
+ .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR)
+ .setEnableBle(true)
+ .build();
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java
index 44bab71..a103a72 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java
@@ -27,6 +27,7 @@
import static org.mockito.ArgumentMatchers.anyShort;
import static org.mockito.Mockito.doNothing;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.platform.test.annotations.Presubmit;
@@ -38,7 +39,9 @@
import com.android.server.nearby.common.bluetooth.BluetoothGattException;
import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
+import com.google.protobuf.ByteString;
import junit.framework.TestCase;
@@ -47,6 +50,8 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
/**
@@ -56,11 +61,16 @@
@SmallTest
public class FastPairDualConnectionTest extends TestCase {
- private static final String BLE_ADDRESS = "BLE_ADDRESS";
+ private static final String BLE_ADDRESS = "00:11:22:33:FF:EE";
private static final String MASKED_BLE_ADDRESS = "MASKED_BLE_ADDRESS";
private static final short[] PROFILES = {Constants.A2DP_SINK_SERVICE_UUID};
private static final int NUM_CONNECTION_ATTEMPTS = 1;
private static final boolean ENABLE_PAIRING_BEHAVIOR = true;
+ private static final BluetoothDevice BLUETOOTH_DEVICE = BluetoothAdapter.getDefaultAdapter()
+ .getRemoteDevice("11:22:33:44:55:66");
+ private static final String DEVICE_NAME = "DEVICE_NAME";
+ private static final byte[] ACCOUNT_KEY = new byte[]{1, 3};
+ private static final byte[] HASH_VALUE = new byte[]{7};
private TestEventLogger mEventLogger;
@Mock private TimingLogger mTimingLogger;
@@ -70,6 +80,8 @@
public void setUp() throws Exception {
super.setUp();
+ BluetoothAudioPairer.enableTestMode();
+ FastPairDualConnection.enableTestMode();
MockitoAnnotations.initMocks(this);
doNothing().when(mBluetoothAudioPairer).connect(anyShort(), anyBoolean());
@@ -127,7 +139,7 @@
@SdkSuppress(minSdkVersion = 32, codeName = "T")
- public void appendMoreErrorCode_gattError() {
+ public void testAppendMoreErrorCode_gattError() {
assertThat(
appendMoreErrorCode(
GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED,
@@ -170,6 +182,131 @@
.isEqualTo(GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST);
}
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testUnpairNotCrash() {
+ try {
+ new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().build(),
+ mEventLogger,
+ mTimingLogger).unpair(BLUETOOTH_DEVICE);
+ } catch (ExecutionException | InterruptedException | ReflectionException
+ | TimeoutException | PairingException e) {
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testSetFastPairHistory() {
+ new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().build(),
+ mEventLogger,
+ mTimingLogger).setFastPairHistory(ImmutableList.of());
+ }
+
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testSetGetProviderDeviceName() {
+ FastPairDualConnection connection = new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().build(),
+ mEventLogger,
+ mTimingLogger);
+ connection.setProviderDeviceName(DEVICE_NAME);
+ connection.getProviderDeviceName();
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetExistingAccountKey() {
+ FastPairDualConnection connection = new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().build(),
+ mEventLogger,
+ mTimingLogger);
+ connection.getExistingAccountKey();
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testPair() {
+ FastPairDualConnection connection = new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().setNumSdpAttempts(0)
+ .setLogPairWithCachedModelId(false).build(),
+ mEventLogger,
+ mTimingLogger);
+ try {
+ connection.pair();
+ } catch (BluetoothException | InterruptedException | ReflectionException
+ | ExecutionException | TimeoutException | PairingException e) {
+ }
+ }
+
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetPublicAddress() {
+ FastPairDualConnection connection = new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().setNumSdpAttempts(0)
+ .setLogPairWithCachedModelId(false).build(),
+ mEventLogger,
+ mTimingLogger);
+ connection.getPublicAddress();
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testShouldWriteAccountKeyForExistingCase() {
+ FastPairDualConnection connection = new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().setNumSdpAttempts(0)
+ .setLogPairWithCachedModelId(false).build(),
+ mEventLogger,
+ mTimingLogger);
+ connection.shouldWriteAccountKeyForExistingCase(ACCOUNT_KEY);
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testReadFirmwareVersion() {
+ FastPairDualConnection connection = new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().setNumSdpAttempts(0)
+ .setLogPairWithCachedModelId(false).build(),
+ mEventLogger,
+ mTimingLogger);
+ try {
+ connection.readFirmwareVersion();
+ } catch (BluetoothException | InterruptedException | ExecutionException
+ | TimeoutException e) {
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testHistoryItem() {
+ FastPairDualConnection connection = new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().setNumSdpAttempts(0)
+ .setLogPairWithCachedModelId(false).build(),
+ mEventLogger,
+ mTimingLogger);
+ ImmutableList.Builder<FastPairHistoryItem> historyBuilder = ImmutableList.builder();
+ FastPairHistoryItem historyItem1 =
+ FastPairHistoryItem.create(
+ ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(HASH_VALUE));
+ historyBuilder.add(historyItem1);
+
+ connection.setFastPairHistory(historyBuilder.build());
+ assertThat(connection.mPairedHistoryFinder.isInPairedHistory("11:22:33:44:55:88"))
+ .isFalse();
+ }
+
static class TestEventLogger implements EventLogger {
private List<Item> mLogs = new ArrayList<>();
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManagerTest.java
new file mode 100644
index 0000000..2f80a30
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/GattConnectionManagerTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.nearby.common.bluetooth.BluetoothException;
+import com.android.server.nearby.common.bluetooth.BluetoothGattException;
+import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter;
+import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException;
+
+import com.google.common.collect.ImmutableSet;
+
+import junit.framework.TestCase;
+
+import java.time.Duration;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Unit tests for {@link GattConnectionManager}.
+ */
+@Presubmit
+@SmallTest
+public class GattConnectionManagerTest extends TestCase {
+
+ private static final String FAST_PAIR_ADDRESS = "BB:BB:BB:BB:BB:1E";
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ GattConnectionManager.enableTestMode();
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGattConnectionManagerConstructor() throws Exception {
+ GattConnectionManager manager = createManager(Preferences.builder());
+ try {
+ manager.getConnection();
+ } catch (ExecutionException e) {
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testIsNoRetryError() {
+ Preferences preferences =
+ Preferences.builder()
+ .setGattConnectionAndSecretHandshakeNoRetryGattError(
+ ImmutableSet.of(257, 999))
+ .build();
+
+ assertThat(
+ GattConnectionManager.isNoRetryError(
+ preferences, new BluetoothGattException("Test", 133)))
+ .isFalse();
+ assertThat(
+ GattConnectionManager.isNoRetryError(
+ preferences, new BluetoothGattException("Test", 257)))
+ .isTrue();
+ assertThat(
+ GattConnectionManager.isNoRetryError(
+ preferences, new BluetoothGattException("Test", 999)))
+ .isTrue();
+ assertThat(GattConnectionManager.isNoRetryError(
+ preferences, new BluetoothException("Test")))
+ .isFalse();
+ assertThat(
+ GattConnectionManager.isNoRetryError(
+ preferences, new BluetoothOperationTimeoutException("Test")))
+ .isFalse();
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetTimeoutNotOverShortRetryMaxSpentTimeGetShort() {
+ Preferences preferences = Preferences.builder().build();
+
+ assertThat(
+ createManager(Preferences.builder(), () -> {})
+ .getTimeoutMs(
+ preferences.getGattConnectShortTimeoutRetryMaxSpentTimeMs() - 1))
+ .isEqualTo(preferences.getGattConnectShortTimeoutMs());
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetTimeoutOverShortRetryMaxSpentTimeGetLong() {
+ Preferences preferences = Preferences.builder().build();
+
+ assertThat(
+ createManager(Preferences.builder(), () -> {})
+ .getTimeoutMs(
+ preferences.getGattConnectShortTimeoutRetryMaxSpentTimeMs() + 1))
+ .isEqualTo(preferences.getGattConnectLongTimeoutMs());
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetTimeoutRetryNotEnabledGetOrigin() {
+ Preferences preferences = Preferences.builder().build();
+
+ assertThat(
+ createManager(
+ Preferences.builder().setRetryGattConnectionAndSecretHandshake(false),
+ () -> {})
+ .getTimeoutMs(0))
+ .isEqualTo(Duration.ofSeconds(
+ preferences.getGattConnectionTimeoutSeconds()).toMillis());
+ }
+
+ private GattConnectionManager createManager(Preferences.Builder prefs) {
+ return createManager(prefs, () -> {});
+ }
+
+ private GattConnectionManager createManager(
+ Preferences.Builder prefs, ToggleBluetoothTask toggleBluetooth) {
+ return createManager(prefs, toggleBluetooth,
+ /* fastPairSignalChecker= */ null);
+ }
+
+ private GattConnectionManager createManager(
+ Preferences.Builder prefs,
+ ToggleBluetoothTask toggleBluetooth,
+ @Nullable FastPairConnection.FastPairSignalChecker fastPairSignalChecker) {
+ return new GattConnectionManager(
+ ApplicationProvider.getApplicationContext(),
+ prefs.build(),
+ new EventLoggerWrapper(null),
+ BluetoothAdapter.getDefaultAdapter(),
+ toggleBluetooth,
+ FAST_PAIR_ADDRESS,
+ new TimingLogger("GattConnectionManager", prefs.build()),
+ fastPairSignalChecker,
+ /* setMtu= */ false);
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java
new file mode 100644
index 0000000..58e4c47
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair.halfsheet;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
+
+import com.android.server.nearby.common.locator.Locator;
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
+import com.android.server.nearby.fastpair.FastPairController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import service.proto.Cache;
+
+public class FastPairHalfSheetManagerTest {
+ private static final String BLEADDRESS = "11:22:44:66";
+ private static final String NAME = "device_name";
+ private FastPairHalfSheetManager mFastPairHalfSheetManager;
+ private Cache.ScanFastPairStoreItem mScanFastPairStoreItem;
+ @Mock
+ LocatorContextWrapper mContextWrapper;
+ @Mock
+ ResolveInfo mResolveInfo;
+ @Mock
+ PackageManager mPackageManager;
+ @Mock
+ Locator mLocator;
+ @Mock
+ FastPairController mFastPairController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mScanFastPairStoreItem = Cache.ScanFastPairStoreItem.newBuilder()
+ .setAddress(BLEADDRESS)
+ .setDeviceName(NAME)
+ .build();
+ }
+
+ @Test
+ public void verifyFastPairHalfSheetManagerBehavior() {
+ mLocator.overrideBindingForTest(FastPairController.class, mFastPairController);
+ ResolveInfo resolveInfo = new ResolveInfo();
+ List<ResolveInfo> resolveInfoList = new ArrayList<>();
+
+ mPackageManager = mock(PackageManager.class);
+ when(mContextWrapper.getPackageManager()).thenReturn(mPackageManager);
+ resolveInfo.activityInfo = new ActivityInfo();
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.sourceDir = "/apex/com.android.tethering";
+ applicationInfo.packageName = "test.package";
+ resolveInfo.activityInfo.applicationInfo = applicationInfo;
+ resolveInfoList.add(resolveInfo);
+ when(mPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(resolveInfoList);
+ when(mPackageManager.canRequestPackageInstalls()).thenReturn(false);
+
+ mFastPairHalfSheetManager =
+ new FastPairHalfSheetManager(mContextWrapper);
+
+ when(mContextWrapper.getLocator()).thenReturn(mLocator);
+
+ ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+ mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+
+ verify(mContextWrapper, atLeastOnce())
+ .startActivityAsUser(intentArgumentCaptor.capture(), eq(UserHandle.CURRENT));
+ }
+
+ @Test
+ public void verifyFastPairHalfSheetManagerHalfSheetApkNotValidBehavior() {
+ mLocator.overrideBindingForTest(FastPairController.class, mFastPairController);
+ ResolveInfo resolveInfo = new ResolveInfo();
+ List<ResolveInfo> resolveInfoList = new ArrayList<>();
+
+ mPackageManager = mock(PackageManager.class);
+ when(mContextWrapper.getPackageManager()).thenReturn(mPackageManager);
+ resolveInfo.activityInfo = new ActivityInfo();
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ // application directory is wrong
+ applicationInfo.sourceDir = "/apex/com.android.nearby";
+ applicationInfo.packageName = "test.package";
+ resolveInfo.activityInfo.applicationInfo = applicationInfo;
+ resolveInfoList.add(resolveInfo);
+ when(mPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(resolveInfoList);
+ when(mPackageManager.canRequestPackageInstalls()).thenReturn(false);
+
+ mFastPairHalfSheetManager =
+ new FastPairHalfSheetManager(mContextWrapper);
+
+ when(mContextWrapper.getLocator()).thenReturn(mLocator);
+
+ ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+ mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem);
+
+ verify(mContextWrapper, never())
+ .startActivityAsUser(intentArgumentCaptor.capture(), eq(UserHandle.CURRENT));
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/metrics/NearbyMetricsTest.java b/nearby/tests/unit/src/com/android/server/nearby/metrics/NearbyMetricsTest.java
new file mode 100644
index 0000000..252bb90
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/metrics/NearbyMetricsTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.metrics;
+
+import static android.nearby.ScanRequest.SCAN_MODE_BALANCED;
+import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_SHARE;
+
+import android.nearby.NearbyDeviceParcelable;
+import android.nearby.ScanRequest;
+import android.os.WorkSource;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.nearby.proto.NearbyStatsLog;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+public class NearbyMetricsTest {
+ private static final int SESSION_ID = 11111;
+ private static final int WORK_SOURCE_UID = 2222;
+
+ private static final String DEVICE_NAME = "testDevice";
+ private static final int SCAN_MEDIUM = 1;
+ private static final int RSSI = -60;
+ private static final String FAST_PAIR_MODEL_ID = "1234";
+ private static final String BLUETOOTH_ADDRESS = "00:11:22:33:FF:EE";
+ private static final byte[] SCAN_DATA = new byte[]{1, 2, 3, 4};
+
+ private final WorkSource mWorkSource = new WorkSource(WORK_SOURCE_UID);
+ private final WorkSource mEmptyWorkSource = new WorkSource();
+
+ private final ScanRequest.Builder mScanRequestBuilder = new ScanRequest.Builder()
+ .setScanMode(SCAN_MODE_BALANCED)
+ .setScanType(SCAN_TYPE_NEARBY_SHARE);
+ private final ScanRequest mScanRequest = mScanRequestBuilder
+ .setWorkSource(mWorkSource)
+ .build();
+ private final ScanRequest mScanRequestWithEmptyWorkSource = mScanRequestBuilder
+ .setWorkSource(mEmptyWorkSource)
+ .build();
+
+ private final NearbyDeviceParcelable mNearbyDevice =
+ new NearbyDeviceParcelable.Builder()
+ .setName(DEVICE_NAME)
+ .setMedium(SCAN_MEDIUM)
+ .setRssi(RSSI)
+ .setFastPairModelId(FAST_PAIR_MODEL_ID)
+ .setBluetoothAddress(BLUETOOTH_ADDRESS)
+ .setData(SCAN_DATA).build();
+
+ private MockitoSession mSession;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mSession = ExtendedMockito.mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .mockStatic(NearbyStatsLog.class)
+ .startMocking();
+ }
+
+ @After
+ public void tearDown() {
+ mSession.finishMocking();
+ }
+
+ @Test
+ public void testLogScanStart() {
+ NearbyMetrics.logScanStarted(SESSION_ID, mScanRequest);
+ ExtendedMockito.verify(() -> NearbyStatsLog.write(
+ NearbyStatsLog.NEARBY_DEVICE_SCAN_STATE_CHANGED,
+ WORK_SOURCE_UID,
+ SESSION_ID,
+ NearbyStatsLog
+ .NEARBY_DEVICE_SCAN_STATE_CHANGED__SCAN_STATE__NEARBY_SCAN_STATE_STARTED,
+ SCAN_TYPE_NEARBY_SHARE,
+ 0,
+ 0,
+ "",
+ ""));
+ }
+
+ @Test
+ public void testLogScanStart_emptyWorkSource() {
+ NearbyMetrics.logScanStarted(SESSION_ID, mScanRequestWithEmptyWorkSource);
+ ExtendedMockito.verify(() -> NearbyStatsLog.write(
+ NearbyStatsLog.NEARBY_DEVICE_SCAN_STATE_CHANGED,
+ -1,
+ SESSION_ID,
+ NearbyStatsLog
+ .NEARBY_DEVICE_SCAN_STATE_CHANGED__SCAN_STATE__NEARBY_SCAN_STATE_STARTED,
+ SCAN_TYPE_NEARBY_SHARE,
+ 0,
+ 0,
+ "",
+ ""));
+ }
+
+ @Test
+ public void testLogScanStopped() {
+ NearbyMetrics.logScanStopped(SESSION_ID, mScanRequest);
+ ExtendedMockito.verify(() -> NearbyStatsLog.write(
+ NearbyStatsLog.NEARBY_DEVICE_SCAN_STATE_CHANGED,
+ WORK_SOURCE_UID,
+ SESSION_ID,
+ NearbyStatsLog
+ .NEARBY_DEVICE_SCAN_STATE_CHANGED__SCAN_STATE__NEARBY_SCAN_STATE_STOPPED,
+ SCAN_TYPE_NEARBY_SHARE,
+ 0,
+ 0,
+ "",
+ ""));
+ }
+
+ @Test
+ public void testLogScanStopped_emptyWorkSource() {
+ NearbyMetrics.logScanStopped(SESSION_ID, mScanRequestWithEmptyWorkSource);
+ ExtendedMockito.verify(() -> NearbyStatsLog.write(
+ NearbyStatsLog.NEARBY_DEVICE_SCAN_STATE_CHANGED,
+ -1,
+ SESSION_ID,
+ NearbyStatsLog
+ .NEARBY_DEVICE_SCAN_STATE_CHANGED__SCAN_STATE__NEARBY_SCAN_STATE_STOPPED,
+ SCAN_TYPE_NEARBY_SHARE,
+ 0,
+ 0,
+ "",
+ ""));
+ }
+
+ @Test
+ public void testLogScanDeviceDiscovered() {
+ NearbyMetrics.logScanDeviceDiscovered(SESSION_ID, mScanRequest, mNearbyDevice);
+ ExtendedMockito.verify(() -> NearbyStatsLog.write(
+ NearbyStatsLog.NEARBY_DEVICE_SCAN_STATE_CHANGED,
+ WORK_SOURCE_UID,
+ SESSION_ID,
+ NearbyStatsLog
+ .NEARBY_DEVICE_SCAN_STATE_CHANGED__SCAN_STATE__NEARBY_SCAN_STATE_DISCOVERED,
+ SCAN_TYPE_NEARBY_SHARE,
+ SCAN_MEDIUM,
+ RSSI,
+ FAST_PAIR_MODEL_ID,
+ ""));
+ }
+
+ @Test
+ public void testLogScanDeviceDiscovered_emptyWorkSource() {
+ NearbyMetrics.logScanDeviceDiscovered(SESSION_ID, mScanRequestWithEmptyWorkSource,
+ mNearbyDevice);
+ ExtendedMockito.verify(() -> NearbyStatsLog.write(
+ NearbyStatsLog.NEARBY_DEVICE_SCAN_STATE_CHANGED,
+ -1,
+ SESSION_ID,
+ NearbyStatsLog
+ .NEARBY_DEVICE_SCAN_STATE_CHANGED__SCAN_STATE__NEARBY_SCAN_STATE_DISCOVERED,
+ SCAN_TYPE_NEARBY_SHARE,
+ SCAN_MEDIUM,
+ RSSI,
+ FAST_PAIR_MODEL_ID,
+ ""));
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceDiscoveryResultTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceDiscoveryResultTest.java
new file mode 100644
index 0000000..dd94778
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceDiscoveryResultTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.presence;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.nearby.PresenceCredential;
+import android.nearby.PresenceDevice;
+import android.nearby.PresenceScanFilter;
+import android.nearby.PublicCredential;
+
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link PresenceDiscoveryResult}.
+ */
+public class PresenceDiscoveryResultTest {
+ private static final int PRESENCE_ACTION = 123;
+ private static final int TX_POWER = -1;
+ private static final int RSSI = -41;
+ private static final byte[] SALT = new byte[]{12, 34};
+ private static final byte[] SECRET_ID = new byte[]{1, 2, 3, 4};
+ private static final byte[] AUTHENTICITY_KEY = new byte[]{12, 13, 14};
+
+ private PresenceDiscoveryResult.Builder mBuilder;
+ private PublicCredential mCredential;
+
+ @Before
+ public void setUp() {
+ mCredential =
+ new PublicCredential.Builder(SECRET_ID, AUTHENTICITY_KEY)
+ .setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE)
+ .build();
+ mBuilder = new PresenceDiscoveryResult.Builder()
+ .setPublicCredential(mCredential)
+ .setSalt(SALT)
+ .setTxPower(TX_POWER)
+ .setRssi(RSSI)
+ .addPresenceAction(PRESENCE_ACTION);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testToDevice() {
+ PresenceDiscoveryResult discoveryResult = mBuilder.build();
+ PresenceDevice presenceDevice = discoveryResult.toPresenceDevice();
+
+ assertThat(presenceDevice.getRssi()).isEqualTo(RSSI);
+ assertThat(Arrays.equals(presenceDevice.getSalt(), SALT)).isTrue();
+ assertThat(Arrays.equals(presenceDevice.getSecretId(), SECRET_ID)).isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testMatches() {
+ PresenceScanFilter scanFilter = new PresenceScanFilter.Builder()
+ .setMaxPathLoss(80)
+ .addPresenceAction(PRESENCE_ACTION)
+ .addCredential(mCredential)
+ .build();
+
+ PresenceDiscoveryResult discoveryResult = mBuilder.build();
+ assertThat(discoveryResult.matches(scanFilter)).isTrue();
+ }
+
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java
new file mode 100644
index 0000000..4c78885
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.provider;
+
+import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.le.ScanRecord;
+import android.bluetooth.le.ScanResult;
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.nearby.injector.Injector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public final class BleDiscoveryProviderTest {
+
+ private BluetoothAdapter mBluetoothAdapter;
+ private BleDiscoveryProvider mBleDiscoveryProvider;
+ @Mock
+ private AbstractDiscoveryProvider.Listener mListener;
+
+ @Before
+ public void setup() {
+ initMocks(this);
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ Injector injector = new TestInjector();
+
+ mBluetoothAdapter = context.getSystemService(BluetoothManager.class).getAdapter();
+ mBleDiscoveryProvider = new BleDiscoveryProvider(context, injector);
+ }
+
+ @Test
+ public void test_callback() throws InterruptedException {
+ mBleDiscoveryProvider.getController().setListener(mListener);
+ mBleDiscoveryProvider.onStart();
+ mBleDiscoveryProvider.getScanCallback()
+ .onScanResult(CALLBACK_TYPE_ALL_MATCHES, createScanResult());
+
+ // Wait for callback to be invoked
+ Thread.sleep(500);
+ verify(mListener, times(1)).onNearbyDeviceDiscovered(any());
+ }
+
+ @Test
+ public void test_stopScan() {
+ mBleDiscoveryProvider.onStart();
+ mBleDiscoveryProvider.onStop();
+ }
+
+ private class TestInjector implements Injector {
+ @Override
+ public BluetoothAdapter getBluetoothAdapter() {
+ return mBluetoothAdapter;
+ }
+ }
+
+ private ScanResult createScanResult() {
+ BluetoothDevice bluetoothDevice = mBluetoothAdapter
+ .getRemoteDevice("11:22:33:44:55:66");
+ byte[] scanRecord = new byte[] {2, 1, 6, 6, 22, 44, -2, 113, -116, 23, 2, 10, -11, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ return new ScanResult(
+ bluetoothDevice,
+ /* eventType= */ 0,
+ /* primaryPhy= */ 0,
+ /* secondaryPhy= */ 0,
+ /* advertisingSid= */ 0,
+ -31,
+ -50,
+ /* periodicAdvertisingInterval= */ 0,
+ parseScanRecord(scanRecord),
+ 1645579363003L);
+ }
+
+ private static ScanRecord parseScanRecord(byte[] bytes) {
+ Class<?> scanRecordClass = ScanRecord.class;
+ try {
+ Method method = scanRecordClass
+ .getDeclaredMethod("parseFromBytes", byte[].class);
+ return (ScanRecord) method.invoke(null, bytes);
+ } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException e) {
+ return null;
+ }
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java
new file mode 100644
index 0000000..9152c07
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.util;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.protobuf.ByteString;
+
+import org.junit.Test;
+
+import service.proto.Cache;
+import service.proto.FastPairString.FastPairStrings;
+import service.proto.Rpcs;
+import service.proto.Rpcs.GetObservedDeviceResponse;
+
+public final class DataUtilsTest {
+ private static final String BLUETOOTH_ADDRESS = "00:11:22:33:FF:EE";
+ private static final String APP_PACKAGE = "test_package";
+ private static final String APP_ACTION_URL =
+ "intent:#Intent;action=cto_be_set%3AACTION_MAGIC_PAIR;"
+ + "package=to_be_set;"
+ + "component=to_be_set;"
+ + "to_be_set%3AEXTRA_COMPANION_APP="
+ + APP_PACKAGE
+ + ";end";
+ private static final long DEVICE_ID = 12;
+ private static final String DEVICE_NAME = "My device";
+ private static final byte[] DEVICE_PUBLIC_KEY = base16().decode("0123456789ABCDEF");
+ private static final String DEVICE_COMPANY = "Company name";
+ private static final byte[] DEVICE_IMAGE = new byte[] {0x00, 0x01, 0x10, 0x11};
+ private static final String DEVICE_IMAGE_URL = "device_image_url";
+ private static final String AUTHORITY = "com.android.test";
+ private static final String SIGNATURE_HASH = "as8dfbyu2duas7ikanvklpaclo2";
+ private static final String ACCOUNT = "test@gmail.com";
+
+ private static final String MESSAGE_INIT_NOTIFY_DESCRIPTION = "message 1";
+ private static final String MESSAGE_INIT_NOTIFY_DESCRIPTION_NO_ACCOUNT = "message 2";
+ private static final String MESSAGE_INIT_PAIR_DESCRIPTION = "message 3 %s";
+ private static final String MESSAGE_COMPANION_INSTALLED = "message 4";
+ private static final String MESSAGE_COMPANION_NOT_INSTALLED = "message 5";
+ private static final String MESSAGE_SUBSEQUENT_PAIR_DESCRIPTION = "message 6";
+ private static final String MESSAGE_RETROACTIVE_PAIR_DESCRIPTION = "message 7";
+ private static final String MESSAGE_WAIT_LAUNCH_COMPANION_APP_DESCRIPTION = "message 8";
+ private static final String MESSAGE_FAIL_CONNECT_DESCRIPTION = "message 9";
+ private static final String MESSAGE_FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION =
+ "message 10";
+ private static final String MESSAGE_ASSISTANT_HALF_SHEET_DESCRIPTION = "message 11";
+ private static final String MESSAGE_ASSISTANT_NOTIFICATION_DESCRIPTION = "message 12";
+
+ @Test
+ public void test_toScanFastPairStoreItem_withAccount() {
+ Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem(
+ createObservedDeviceResponse(), BLUETOOTH_ADDRESS, ACCOUNT);
+ assertThat(item.getAddress()).isEqualTo(BLUETOOTH_ADDRESS);
+ assertThat(item.getActionUrl()).isEqualTo(APP_ACTION_URL);
+ assertThat(item.getDeviceName()).isEqualTo(DEVICE_NAME);
+ assertThat(item.getIconPng()).isEqualTo(ByteString.copyFrom(DEVICE_IMAGE));
+ assertThat(item.getIconFifeUrl()).isEqualTo(DEVICE_IMAGE_URL);
+ assertThat(item.getAntiSpoofingPublicKey())
+ .isEqualTo(ByteString.copyFrom(DEVICE_PUBLIC_KEY));
+
+ FastPairStrings strings = item.getFastPairStrings();
+ assertThat(strings.getTapToPairWithAccount()).isEqualTo(MESSAGE_INIT_NOTIFY_DESCRIPTION);
+ assertThat(strings.getTapToPairWithoutAccount())
+ .isEqualTo(MESSAGE_INIT_NOTIFY_DESCRIPTION_NO_ACCOUNT);
+ assertThat(strings.getInitialPairingDescription())
+ .isEqualTo(String.format(MESSAGE_INIT_PAIR_DESCRIPTION, DEVICE_NAME));
+ assertThat(strings.getPairingFinishedCompanionAppInstalled())
+ .isEqualTo(MESSAGE_COMPANION_INSTALLED);
+ assertThat(strings.getPairingFinishedCompanionAppNotInstalled())
+ .isEqualTo(MESSAGE_COMPANION_NOT_INSTALLED);
+ assertThat(strings.getSubsequentPairingDescription())
+ .isEqualTo(MESSAGE_SUBSEQUENT_PAIR_DESCRIPTION);
+ assertThat(strings.getRetroactivePairingDescription())
+ .isEqualTo(MESSAGE_RETROACTIVE_PAIR_DESCRIPTION);
+ assertThat(strings.getWaitAppLaunchDescription())
+ .isEqualTo(MESSAGE_WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+ assertThat(strings.getPairingFailDescription())
+ .isEqualTo(MESSAGE_FAIL_CONNECT_DESCRIPTION);
+ assertThat(strings.getAssistantHalfSheetDescription())
+ .isEqualTo(MESSAGE_ASSISTANT_HALF_SHEET_DESCRIPTION);
+ assertThat(strings.getAssistantNotificationDescription())
+ .isEqualTo(MESSAGE_ASSISTANT_NOTIFICATION_DESCRIPTION);
+ assertThat(strings.getFastPairTvConnectDeviceNoAccountDescription())
+ .isEqualTo(MESSAGE_FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION);
+ }
+
+ @Test
+ public void test_toScanFastPairStoreItem_withoutAccount() {
+ Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem(
+ createObservedDeviceResponse(), BLUETOOTH_ADDRESS, /* account= */ null);
+ FastPairStrings strings = item.getFastPairStrings();
+ assertThat(strings.getInitialPairingDescription())
+ .isEqualTo(MESSAGE_INIT_NOTIFY_DESCRIPTION_NO_ACCOUNT);
+ }
+
+ @Test
+ public void test_toString() {
+ Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem(
+ createObservedDeviceResponse(), BLUETOOTH_ADDRESS, ACCOUNT);
+ FastPairStrings strings = item.getFastPairStrings();
+
+ assertThat(DataUtils.toString(strings))
+ .isEqualTo("FastPairStrings[tapToPairWithAccount=message 1, "
+ + "tapToPairWithoutAccount=message 2, "
+ + "initialPairingDescription=message 3 " + DEVICE_NAME + ", "
+ + "pairingFinishedCompanionAppInstalled=message 4, "
+ + "pairingFinishedCompanionAppNotInstalled=message 5, "
+ + "subsequentPairingDescription=message 6, "
+ + "retroactivePairingDescription=message 7, "
+ + "waitAppLaunchDescription=message 8, "
+ + "pairingFailDescription=message 9, "
+ + "assistantHalfSheetDescription=message 11, "
+ + "assistantNotificationDescription=message 12, "
+ + "fastPairTvConnectDeviceNoAccountDescription=message 10]");
+ }
+
+ private static GetObservedDeviceResponse createObservedDeviceResponse() {
+ return GetObservedDeviceResponse.newBuilder()
+ .setDevice(
+ Rpcs.Device.newBuilder()
+ .setId(DEVICE_ID)
+ .setName(DEVICE_NAME)
+ .setAntiSpoofingKeyPair(
+ Rpcs.AntiSpoofingKeyPair
+ .newBuilder()
+ .setPublicKey(
+ ByteString.copyFrom(DEVICE_PUBLIC_KEY)))
+ .setIntentUri(APP_ACTION_URL)
+ .setDataOnlyConnection(true)
+ .setAssistantSupported(false)
+ .setCompanionDetail(
+ Rpcs.CompanionAppDetails.newBuilder()
+ .setAuthority(AUTHORITY)
+ .setCertificateHash(SIGNATURE_HASH)
+ .build())
+ .setCompanyName(DEVICE_COMPANY)
+ .setImageUrl(DEVICE_IMAGE_URL))
+ .setImage(ByteString.copyFrom(DEVICE_IMAGE))
+ .setStrings(
+ Rpcs.ObservedDeviceStrings.newBuilder()
+ .setInitialNotificationDescription(MESSAGE_INIT_NOTIFY_DESCRIPTION)
+ .setInitialNotificationDescriptionNoAccount(
+ MESSAGE_INIT_NOTIFY_DESCRIPTION_NO_ACCOUNT)
+ .setInitialPairingDescription(MESSAGE_INIT_PAIR_DESCRIPTION)
+ .setConnectSuccessCompanionAppInstalled(MESSAGE_COMPANION_INSTALLED)
+ .setConnectSuccessCompanionAppNotInstalled(
+ MESSAGE_COMPANION_NOT_INSTALLED)
+ .setSubsequentPairingDescription(
+ MESSAGE_SUBSEQUENT_PAIR_DESCRIPTION)
+ .setRetroactivePairingDescription(
+ MESSAGE_RETROACTIVE_PAIR_DESCRIPTION)
+ .setWaitLaunchCompanionAppDescription(
+ MESSAGE_WAIT_LAUNCH_COMPANION_APP_DESCRIPTION)
+ .setFailConnectGoToSettingsDescription(
+ MESSAGE_FAIL_CONNECT_DESCRIPTION)
+ .setAssistantSetupHalfSheet(
+ MESSAGE_ASSISTANT_HALF_SHEET_DESCRIPTION)
+ .setAssistantSetupNotification(
+ MESSAGE_ASSISTANT_NOTIFICATION_DESCRIPTION)
+ .setFastPairTvConnectDeviceNoAccountDescription(
+ MESSAGE_FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION))
+ .build();
+ }
+}