Merge "Add overlayble.xml to enable RRO" into tm-dev
diff --git a/nearby/framework/java/android/nearby/FastPairDataProviderBase.java b/nearby/framework/java/android/nearby/FastPairDataProviderBase.java
index f7af7b8..57467b7 100644
--- a/nearby/framework/java/android/nearby/FastPairDataProviderBase.java
+++ b/nearby/framework/java/android/nearby/FastPairDataProviderBase.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.nearby.aidl.ByteArrayParcel;
import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel;
import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
import android.nearby.aidl.FastPairAntispoofkeyDeviceMetadataRequestParcel;
@@ -40,7 +41,9 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
/**
* Base class for fast pair providers outside the system server.
@@ -223,7 +226,18 @@
}
/**
- * Class for reading FastPairAccountDevicesMetadataRequest.
+ * Class for reading FastPairAccountDevicesMetadataRequest, which specifies the Fast Pair
+ * account and the allow list of the FastPair device keys saved to the account (i.e., FastPair
+ * accountKeys).
+ *
+ * A Fast Pair accountKey is created when a Fast Pair device is saved to an account. It is per
+ * Fast Pair device per account.
+ *
+ * To retrieve all Fast Pair accountKeys saved to an account, the caller needs to set
+ * account with an empty allow list.
+ *
+ * To retrieve metadata of a selected list of Fast Pair devices saved to an account, the caller
+ * needs to set account with a non-empty allow list.
*/
public static class FastPairAccountDevicesMetadataRequest {
@@ -233,10 +247,34 @@
final FastPairAccountDevicesMetadataRequestParcel metaDataRequestParcel) {
this.mMetadataRequestParcel = metaDataRequestParcel;
}
- /** Get account. */
+
+ /**
+ * Get FastPair account, whose Fast Pair devices' metadata is requested.
+ *
+ * @return a FastPair account.
+ */
public @NonNull Account getAccount() {
return this.mMetadataRequestParcel.account;
}
+
+ /**
+ * Get allowlist of Fast Pair devices using a collection of accountKeys.
+ * Note that as a special case, empty list actually means all FastPair devices under the
+ * account instead of none.
+ *
+ * @return allowlist of Fast Pair devices using a collection of accountKeys.
+ */
+ public @NonNull Collection<byte[]> getAccountKeys() {
+ if (this.mMetadataRequestParcel.accountKeys == null) {
+ return new ArrayList<byte[]>(0);
+ }
+ List<byte[]> accountKeys =
+ new ArrayList<>(this.mMetadataRequestParcel.accountKeys.length);
+ for (ByteArrayParcel accountKey : this.mMetadataRequestParcel.accountKeys) {
+ accountKeys.add(accountKey.byteArray);
+ }
+ return accountKeys;
+ }
}
/** Class for reading FastPairEligibleAccountsRequest. */
diff --git a/nearby/framework/java/android/nearby/aidl/ByteArrayParcel.aidl b/nearby/framework/java/android/nearby/aidl/ByteArrayParcel.aidl
new file mode 100644
index 0000000..53c73bd
--- /dev/null
+++ b/nearby/framework/java/android/nearby/aidl/ByteArrayParcel.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nearby.aidl;
+
+/**
+ * This is to support 2D byte arrays.
+ * {@hide}
+ */
+parcelable ByteArrayParcel {
+ byte[] byteArray;
+}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/aidl/FastPairAccountDevicesMetadataRequestParcel.aidl b/nearby/framework/java/android/nearby/aidl/FastPairAccountDevicesMetadataRequestParcel.aidl
index 2cff632..5953935 100644
--- a/nearby/framework/java/android/nearby/aidl/FastPairAccountDevicesMetadataRequestParcel.aidl
+++ b/nearby/framework/java/android/nearby/aidl/FastPairAccountDevicesMetadataRequestParcel.aidl
@@ -17,6 +17,7 @@
package android.nearby.aidl;
import android.accounts.Account;
+import android.nearby.aidl.ByteArrayParcel;
/**
* Request details for Metadata of Fast Pair devices associated with an account.
@@ -24,4 +25,5 @@
*/
parcelable FastPairAccountDevicesMetadataRequestParcel {
Account account;
+ ByteArrayParcel[] accountKeys;
}
\ No newline at end of file
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_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 74530de..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(getContext(), companionApp)
- && 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 fffb9e1..903ea90 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
@@ -38,8 +38,6 @@
*/
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(Context context, String packageName) {
try {
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/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 6a296c2..f41715a 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
@@ -89,7 +89,7 @@
} else {
// Start to process bloom filter
try {
- Log.d(TAG, "account list size" + accountList.size());
+ Log.d(TAG, "account list size " + accountList.size());
byte[] bloomFilterByteArray = FastPairDecoder
.getBloomFilter(fastPairDevice.getData());
byte[] bloomFilterSalt = FastPairDecoder
@@ -100,8 +100,8 @@
}
for (Account account : accountList) {
List<Data.FastPairDeviceWithAccountKey> listDevices =
- FastPairDataProvider.getInstance().loadFastPairDevicesWithAccountKey(
- account);
+ FastPairDataProvider.getInstance()
+ .loadFastPairDeviceWithAccountKey(account);
Data.FastPairDeviceWithAccountKey recognizedDevice =
findRecognizedDevice(listDevices,
new BloomFilter(bloomFilterByteArray,
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 42839b2..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,10 +29,14 @@
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.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;
@@ -47,27 +51,24 @@
* Fast Pair ux manager for half sheet.
*/
public class FastPairHalfSheetManager {
- static final String ACTIVITY_INTENT_ACTION = "android.nearby.SHOW_HALFSHEET";
+ 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 LocatorContextWrapper mLocatorContextWrapper;
+ private final LocatorContextWrapper mLocatorContextWrapper;
- /**
- * Construct function
- */
+ FastPairService mFastPairService;
+
public FastPairHalfSheetManager(Context context) {
- mContext = context;
- mLocatorContextWrapper = new LocatorContextWrapper(context);
+ this(new LocatorContextWrapper(context));
}
- /**
- * Construct function for test
- */
- public FastPairHalfSheetManager(LocatorContextWrapper locatorContextWrapper) {
+ @VisibleForTesting
+ FastPairHalfSheetManager(LocatorContextWrapper locatorContextWrapper) {
mLocatorContextWrapper = locatorContextWrapper;
+ mFastPairService = new FastPairService();
}
/**
@@ -79,14 +80,13 @@
if (mLocatorContextWrapper != null) {
String packageName = getHalfSheetApkPkgName();
if (packageName == null) {
- Log.e("FastPairHalfSheetManager", "package name is null");
+ Log.e(TAG, "package name is null");
return;
}
- HalfSheetCallback callback = new HalfSheetCallback();
- callback.setmFastPairController(
+ mFastPairService.setFastPairController(
mLocatorContextWrapper.getLocator().get(FastPairController.class));
Bundle bundle = new Bundle();
- bundle.putBinder(EXTRA_BINDER, callback);
+ bundle.putBinder(EXTRA_BINDER, mFastPairService);
mLocatorContextWrapper
.startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION)
.putExtra(EXTRA_HALF_SHEET_INFO,
@@ -97,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");
}
}
@@ -109,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");
+ }
}
/**
@@ -129,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");
+ }
}
/**
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
index 53c2c12..8c0d572 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairService.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairService.java
@@ -73,7 +73,13 @@
* Asks the Fast Pair service to pair the device.
*/
@Override
- public void connect(FastPairDevice fastPairDevice) {}
+ 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;
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/provider/FastPairDataProvider.java b/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
index 03484a0..f4db578 100644
--- a/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.nearby.FastPairDataProviderBase;
+import android.nearby.aidl.ByteArrayParcel;
import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel;
import android.nearby.aidl.FastPairAntispoofkeyDeviceMetadataRequestParcel;
import android.nearby.aidl.FastPairEligibleAccountsRequestParcel;
@@ -32,6 +33,7 @@
import com.android.server.nearby.common.bloomfilter.BloomFilter;
import com.android.server.nearby.fastpair.footprint.FastPairUploadInfo;
+import java.util.ArrayList;
import java.util.List;
import service.proto.Data;
@@ -142,16 +144,38 @@
}
/**
- * Loads FastPair devices for a given account.
+ * Loads FastPair device accountKeys for a given account, but not other detailed fields.
*
* @throws IllegalStateException If ProxyFastPairDataProvider is not available.
*/
- public List<Data.FastPairDeviceWithAccountKey> loadFastPairDevicesWithAccountKey(
+ public List<Data.FastPairDeviceWithAccountKey> loadFastPairDeviceWithAccountKey(
Account account) {
+ return loadFastPairDeviceWithAccountKey(account, new ArrayList<byte[]>(0));
+ }
+
+ /**
+ * Loads FastPair devices for a list of accountKeys of a given account.
+ *
+ * @param account The account of the FastPair devices.
+ * @param accountKeys The allow list of FastPair devices if it is not empty. Otherwise, the
+ * function returns accountKeys of all FastPair devices under the account,
+ * without detailed fields.
+ *
+ * @throws IllegalStateException If ProxyFastPairDataProvider is not available.
+ */
+ public List<Data.FastPairDeviceWithAccountKey> loadFastPairDeviceWithAccountKey(
+ Account account, List<byte[]> accountKeys) {
if (mProxyFastPairDataProvider != null) {
FastPairAccountDevicesMetadataRequestParcel requestParcel =
new FastPairAccountDevicesMetadataRequestParcel();
requestParcel.account = account;
+ requestParcel.accountKeys = new ByteArrayParcel[accountKeys.size()];
+ int i = 0;
+ for (byte[] accountKey : accountKeys) {
+ requestParcel.accountKeys[i] = new ByteArrayParcel();
+ requestParcel.accountKeys[i].byteArray = accountKey;
+ i = i + 1;
+ }
return Utils.convertToFastPairDevicesWithAccountKey(
mProxyFastPairDataProvider.loadFastPairAccountDevicesMetadata(requestParcel));
}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairDataProviderBaseTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairDataProviderBaseTest.java
index 71fc330..fd9e294 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairDataProviderBaseTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/FastPairDataProviderBaseTest.java
@@ -30,6 +30,7 @@
import android.nearby.FastPairDeviceMetadata;
import android.nearby.FastPairDiscoveryItem;
import android.nearby.FastPairEligibleAccount;
+import android.nearby.aidl.ByteArrayParcel;
import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel;
import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
import android.nearby.aidl.FastPairAntispoofkeyDeviceMetadataParcel;
@@ -85,6 +86,7 @@
private static final Account MANAGE_ACCOUNT = new Account("ghi@gmail.com", "type3");
private static final Account ACCOUNTDEVICES_METADATA_ACCOUNT =
new Account("jk@gmail.com", "type4");
+ private static final int NUM_ACCOUNT_DEVICES = 2;
private static final int ERROR_CODE_BAD_REQUEST =
FastPairDataProviderBase.ERROR_CODE_BAD_REQUEST;
@@ -125,6 +127,7 @@
private static final String WAIT_LAUNCH_COMPANION_APP_DESCRIPTION =
"WAIT_LAUNCH_COMPANION_APP_DESCRIPTION";
private static final byte[] ACCOUNT_KEY = new byte[] {3};
+ private static final byte[] ACCOUNT_KEY_2 = new byte[] {9, 3};
private static final byte[] SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS = new byte[] {2, 8};
private static final byte[] REQUEST_MODEL_ID = new byte[] {1, 2, 3};
private static final byte[] ANTI_SPOOFING_KEY = new byte[] {4, 5, 6};
@@ -533,6 +536,11 @@
new FastPairAccountDevicesMetadataRequestParcel();
requestParcel.account = ACCOUNTDEVICES_METADATA_ACCOUNT;
+ requestParcel.accountKeys = new ByteArrayParcel[NUM_ACCOUNT_DEVICES];
+ requestParcel.accountKeys[0] = new ByteArrayParcel();
+ requestParcel.accountKeys[1] = new ByteArrayParcel();
+ requestParcel.accountKeys[0].byteArray = ACCOUNT_KEY;
+ requestParcel.accountKeys[1].byteArray = ACCOUNT_KEY_2;
return requestParcel;
}
@@ -793,6 +801,9 @@
private static void ensureHappyPathAsExpected(
FastPairDataProviderBase.FastPairAccountDevicesMetadataRequest request) {
assertThat(request.getAccount()).isEqualTo(ACCOUNTDEVICES_METADATA_ACCOUNT);
+ assertThat(request.getAccountKeys().size()).isEqualTo(ACCOUNTKEY_DEVICE_NUM);
+ assertThat(request.getAccountKeys()).contains(ACCOUNT_KEY);
+ assertThat(request.getAccountKeys()).contains(ACCOUNT_KEY_2);
}
/* Verifies Happy Path FastPairEligibleAccountsRequest. */
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/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/BluetoothStateChangeReceiver.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/BluetoothStateChangeReceiver.kt
index 25cc637..f9c77f7 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/BluetoothStateChangeReceiver.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/BluetoothStateChangeReceiver.kt
@@ -77,7 +77,7 @@
when (intent.action) {
BluetoothAdapter.ACTION_SCAN_MODE_CHANGED -> {
val scanMode =
- intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, BluetoothAdapter.SCAN_MODE_NONE)
+ intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, SCAN_MODE_NONE)
val scanModeStr = scanModeToString(scanMode)
Log.i(TAG, "ACTION_SCAN_MODE_CHANGED, the new scanMode: $scanModeStr")
listener?.onScanModeChange(scanModeStr)
diff --git a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairSimulator.java b/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairSimulator.java
index acc13e3..cf8be76 100644
--- a/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairSimulator.java
+++ b/nearby/tests/multidevices/clients/src/com/android/server/nearby/common/bluetooth/fastpair/testing/FastPairSimulator.java
@@ -29,14 +29,15 @@
import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_NOTIFY;
import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_READ;
import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_WRITE;
-import static com.android.server.nearby.common.bluetooth.fastpair.testing.RfcommServer.State.CONNECTED;
import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH;
import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.encrypt;
import static com.android.server.nearby.common.bluetooth.fastpair.Bytes.toBytes;
import static com.android.server.nearby.common.bluetooth.fastpair.Constants.A2DP_SINK_SERVICE_UUID;
import static com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService.BLUETOOTH_SIG_ORGANIZATION_ID;
+import static com.android.server.nearby.common.bluetooth.fastpair.EllipticCurveDiffieHellmanExchange.PUBLIC_KEY_LENGTH;
import static com.android.server.nearby.common.bluetooth.fastpair.MessageStreamHmacEncoder.SECTION_NONCE_LENGTH;
+import static com.android.server.nearby.common.bluetooth.fastpair.testing.RfcommServer.State.CONNECTED;
import static com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothManager.wrap;
import static com.google.common.io.BaseEncoding.base16;
@@ -55,7 +56,12 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.nearby.multidevices.fastpair.EventStreamProtocol;
+import android.nearby.multidevices.fastpair.EventStreamProtocol.AcknowledgementEventCode;
+import android.nearby.multidevices.fastpair.EventStreamProtocol.DeviceActionEventCode;
+import android.nearby.multidevices.fastpair.EventStreamProtocol.DeviceCapabilitySyncEventCode;
+import android.nearby.multidevices.fastpair.EventStreamProtocol.DeviceConfigurationEventCode;
+import android.nearby.multidevices.fastpair.EventStreamProtocol.DeviceEventCode;
+import android.nearby.multidevices.fastpair.EventStreamProtocol.EventGroup;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
@@ -68,6 +74,8 @@
import androidx.annotation.VisibleForTesting;
import androidx.core.util.Consumer;
+import com.android.server.nearby.common.bloomfilter.BloomFilter;
+import com.android.server.nearby.common.bloomfilter.FastPairBloomFilterHasher;
import com.android.server.nearby.common.bluetooth.BluetoothException;
import com.android.server.nearby.common.bluetooth.BluetoothGattException;
import com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption;
@@ -83,6 +91,8 @@
import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.NameCharacteristic;
import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.PasskeyCharacteristic;
import com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService;
+import com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService.BrHandoverDataCharacteristic;
+import com.android.server.nearby.common.bluetooth.fastpair.Constants.TransportDiscoveryService.ControlPointCharacteristic;
import com.android.server.nearby.common.bluetooth.fastpair.EllipticCurveDiffieHellmanExchange;
import com.android.server.nearby.common.bluetooth.fastpair.Ltv;
import com.android.server.nearby.common.bluetooth.fastpair.MessageStreamHmacEncoder;
@@ -98,13 +108,8 @@
import com.google.common.base.Ascii;
import com.google.common.primitives.Bytes;
-
-import com.android.server.nearby.common.bloomfilter.BloomFilter;
-import com.android.server.nearby.common.bloomfilter.FastPairBloomFilterHasher;
-
import com.google.protobuf.ByteString;
-import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
@@ -250,12 +255,14 @@
return;
}
if (isDestroyed) {
- // Sometimes this receiver does not successfully unregister in destroy() which causes
- // events to occur after the simulator is stopped, so ignore those events.
+ // Sometimes this receiver does not successfully unregister in destroy()
+ // which causes events to occur after the simulator is stopped, so ignore
+ // those events.
logger.log("Intent received after simulator destroyed, ignoring");
return;
}
- BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ BluetoothDevice device = intent.getParcelableExtra(
+ BluetoothDevice.EXTRA_DEVICE);
switch (intent.getAction()) {
case BluetoothAdapter.ACTION_SCAN_MODE_CHANGED:
if (isDiscoverable()) {
@@ -263,28 +270,34 @@
}
break;
case BluetoothDevice.ACTION_PAIRING_REQUEST:
- int variant = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, ERROR);
+ int variant = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
+ ERROR);
int key = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, ERROR);
logger.log(
- "Pairing request, variant=%d, key=%s", variant, key == ERROR ? "(none)" : key);
+ "Pairing request, variant=%d, key=%s", variant,
+ key == ERROR ? "(none)" : key);
// Prevent Bluetooth Settings from getting the pairing request.
abortBroadcast();
pairingDevice = device;
if (secret == null) {
- // We haven't done the handshake over GATT to agree on the shared secret. For now,
- // just accept anyway (so we can still simulate old 1.0 model IDs).
+ // We haven't done the handshake over GATT to agree on the shared
+ // secret. For now, just accept anyway (so we can still simulate
+ // old 1.0 model IDs).
logger.log("No handshake, auto-accepting anyway.");
setPasskeyConfirmation(true);
- } else if (variant == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) {
- // Store the passkey. And check it, since there's a race (see method for why).
- // Usually this check is a no-op and we'll get the passkey later over GATT.
+ } else if (variant
+ == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) {
+ // Store the passkey. And check it, since there's a race (see
+ // method for why). Usually this check is a no-op and we'll get
+ // the passkey later over GATT.
localPasskey = key;
checkPasskey();
} else if (variant == PAIRING_VARIANT_DISPLAY_PASSKEY) {
if (passkeyEventCallback != null) {
- passkeyEventCallback.onPasskeyRequested(FastPairSimulator.this::enterPassKey);
+ passkeyEventCallback.onPasskeyRequested(
+ FastPairSimulator.this::enterPassKey);
} else {
logger.log("passkeyEventCallback is not set!");
enterPassKey(key);
@@ -295,20 +308,22 @@
} else if (variant == BluetoothDevice.PAIRING_VARIANT_PIN) {
if (passkeyEventCallback != null) {
passkeyEventCallback.onPasskeyRequested(
- (int pin) ->
- pairingDevice.setPin(
- convertPinToBytes(
- String.format(Locale.ENGLISH, "%d", pin))));
+ (int pin) -> {
+ byte[] newPin = convertPinToBytes(
+ String.format(Locale.ENGLISH, "%d", pin));
+ pairingDevice.setPin(newPin);
+ });
}
} else {
- // Reject the pairing request if it's not using the Numeric Comparison (aka Passkey
- // Confirmation) method.
+ // Reject the pairing request if it's not using the Numeric
+ // Comparison (aka Passkey Confirmation) method.
setPasskeyConfirmation(false);
}
break;
case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
int bondState =
- intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
+ intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.BOND_NONE);
logger.log("Bond state to %s changed to %d", device, bondState);
switch (bondState) {
case BluetoothDevice.BOND_BONDING:
@@ -323,7 +338,9 @@
setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
// If it is subsequent pair, we need to add paired device here.
- if (isSubsequentPair && secret != null && secret.length == AES_BLOCK_LENGTH) {
+ if (isSubsequentPair
+ && secret != null
+ && secret.length == AES_BLOCK_LENGTH) {
addAccountKey(secret, pairingDevice);
}
break;
@@ -357,8 +374,8 @@
}
break;
default:
- logger.log(
- new IllegalArgumentException(intent.toString()), "Received unexpected intent");
+ logger.log(new IllegalArgumentException(intent.toString()),
+ "Received unexpected intent");
break;
}
}
@@ -370,12 +387,7 @@
return null;
}
byte[] pinBytes;
- try {
- pinBytes = pin.getBytes("UTF-8");
- } catch (UnsupportedEncodingException uee) {
- logger.log("UTF-8 not supported?!?");
- return null;
- }
+ pinBytes = pin.getBytes(StandardCharsets.UTF_8);
if (pinBytes.length <= 0 || pinBytes.length > 16) {
return null;
}
@@ -395,7 +407,8 @@
}
@Override
- public void write(BluetoothGattServerConnection connection, int offset, byte[] value) {
+ public void write(
+ BluetoothGattServerConnection connection, int offset, byte[] value) {
logger.log("Got value from passkey servlet: %s", base16().encode(value));
if (secret == null) {
logger.log("Ignoring write to passkey characteristic, no pairing secret.");
@@ -403,8 +416,8 @@
}
try {
- remotePasskey =
- PasskeyCharacteristic.decrypt(PasskeyCharacteristic.Type.SEEKER, secret, value);
+ remotePasskey = PasskeyCharacteristic.decrypt(
+ PasskeyCharacteristic.Type.SEEKER, secret, value);
if (passkeyEventCallback != null) {
passkeyEventCallback.onRemotePasskeyReceived(remotePasskey);
}
@@ -430,17 +443,18 @@
}
@Override
- public void write(BluetoothGattServerConnection connection, int offset, byte[] value) {
+ public void write(
+ BluetoothGattServerConnection connection, int offset, byte[] value) {
logger.log("Got value from device naming servlet: %s", base16().encode(value));
if (secret == null) {
logger.log("Ignoring write to name characteristic, no pairing secret.");
return;
}
- // Parse the device name from seeker to write name into provider. See
- // go/fast-pair-naming-design-doc for the decryption detail to get the device name.
+ // Parse the device name from seeker to write name into provider.
logger.log("Got name byte array size = %d", value.length);
try {
- String decryptedDeviceName = NamingEncoder.decodeNamingPacket(secret, value);
+ String decryptedDeviceName =
+ NamingEncoder.decodeNamingPacket(secret, value);
if (decryptedDeviceName != null) {
setDeviceName(decryptedDeviceName.getBytes(StandardCharsets.UTF_8));
logger.log("write device name = %s", decryptedDeviceName);
@@ -458,7 +472,8 @@
private Value bluetoothAddress;
private final FastPairAdvertiser advertiser;
- private final Map<String, BluetoothGattServerHelper> bluetoothGattServerHelpers = new HashMap<>();
+ private final Map<String, BluetoothGattServerHelper> mBluetoothGattServerHelpers =
+ new HashMap<>();
private CountDownLatch isDiscoverableLatch = new CountDownLatch(1);
private ScheduledFuture<?> revertDiscoverableFuture;
private boolean shouldFailPairing = false;
@@ -532,9 +547,9 @@
}
/** Send Event Message on to rfcomm connected devices. */
- public void sendEventStreamMessageToRfcommDevices(EventStreamProtocol.EventGroup eventGroup) {
+ public void sendEventStreamMessageToRfcommDevices(EventGroup eventGroup) {
// Send fake log when event code is logging and type is not using Log_Full event.
- if (eventGroup == EventStreamProtocol.EventGroup.LOGGING && !useLogFullEvent) {
+ if (eventGroup == EventGroup.LOGGING && !useLogFullEvent) {
rfcommServer.sendFakeEventStreamLoggingMessage(
getDeviceName()
+ " "
@@ -568,8 +583,8 @@
}
/**
- * Callback when there comes a passkey input request from BT service, or receiving remote device's
- * passkey.
+ * Callback when there comes a passkey input request from BT service, or receiving remote
+ * device's passkey.
*/
public interface PasskeyEventCallback {
void onPasskeyRequested(KeyInputCallback keyInputCallback);
@@ -816,7 +831,8 @@
this.mCallback = option.mCallback;
this.mIncludeTransportDataDescriptor = option.mIncludeTransportDataDescriptor;
this.mAntiSpoofingPrivateKey = option.mAntiSpoofingPrivateKey;
- this.mUseRandomSaltForAccountKeyRotation = option.mUseRandomSaltForAccountKeyRotation;
+ this.mUseRandomSaltForAccountKeyRotation =
+ option.mUseRandomSaltForAccountKeyRotation;
this.mIsMemoryTest = option.mIsMemoryTest;
this.mBecomeDiscoverable = option.mBecomeDiscoverable;
this.mShowsPasskeyConfirmation = option.mShowsPasskeyConfirmation;
@@ -834,7 +850,10 @@
return this;
}
- /** Must be a 6-byte hex string (optionally with colons). Default is this device's BT MAC. */
+ /**
+ * Must be a 6-byte hex string (optionally with colons).
+ * Default is this device's BT MAC.
+ */
public Builder setBluetoothAddress(@Nullable String bluetoothAddress) {
this.mBluetoothAddress = bluetoothAddress;
return this;
@@ -864,10 +883,11 @@
/**
* Set whether to include the Transport Data descriptor, which has the list of supported
- * profiles. This is required by the spec, but if we can't get it, we recover gracefully by
- * assuming support for one of {A2DP, Headset}. Default is true.
+ * profiles. This is required by the spec, but if we can't get it, we recover gracefully
+ * by assuming support for one of {A2DP, Headset}. Default is true.
*/
- public Builder setIncludeTransportDataDescriptor(boolean includeTransportDataDescriptor) {
+ public Builder setIncludeTransportDataDescriptor(
+ boolean includeTransportDataDescriptor) {
this.mIncludeTransportDataDescriptor = includeTransportDataDescriptor;
return this;
}
@@ -915,7 +935,10 @@
return this;
}
- /** Non-public because this is required to create a builder. See {@link Options#builder}. */
+ /**
+ * Non-public because this is required to create a builder. See
+ * {@link Options#builder}.
+ */
public Builder setModelId(String modelId) {
this.mModelId = modelId;
return this;
@@ -959,7 +982,8 @@
String bluetoothAddress =
!TextUtils.isEmpty(options.getBluetoothAddress())
? options.getBluetoothAddress()
- : Settings.Secure.getString(context.getContentResolver(), "bluetooth_address");
+ : Settings.Secure.getString(context.getContentResolver(),
+ "bluetooth_address");
if (bluetoothAddress == null && VERSION.SDK_INT >= VERSION_CODES.O) {
// Requires a modified Android O build for access to bluetoothAdapter.getAddress().
// See http://google3/java/com/google/location/nearby/apps/fastpair/simulator/README.md.
@@ -979,12 +1003,12 @@
deviceName != null ? new String(deviceName, StandardCharsets.UTF_8) : null);
if (dataOnlyConnection) {
- // To get BLE address, we need to start advertising first, and then {@code #setBleAddress}
- // will be called with BLE address.
+ // To get BLE address, we need to start advertising first, and then
+ // {@code#setBleAddress} will be called with BLE address.
advertiser.startAdvertising(modelIdServiceData(/* forAdvertising= */ true));
} else {
- // TODO(jklinker): Make this so that the simulator doesn't start automatically. This is tricky
- // since the simulator is used in our integ tests as well.
+ // Make this so that the simulator doesn't start automatically.
+ // This is tricky since the simulator is used in our integ tests as well.
start(bleAddress != null ? bleAddress : bluetoothAddress);
}
}
@@ -1001,7 +1025,7 @@
(BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothGattServerHelper bluetoothGattServerHelper =
new BluetoothGattServerHelper(context, wrap(bluetoothManager));
- bluetoothGattServerHelpers.put(address, bluetoothGattServerHelper);
+ mBluetoothGattServerHelpers.put(address, bluetoothGattServerHelper);
if (options.getBecomeDiscoverable()) {
try {
@@ -1018,8 +1042,8 @@
}
/**
- * Regenerate service data on a fixed interval. This causes the bloom filter to be refreshed and a
- * different salt to be used for rotation.
+ * Regenerate service data on a fixed interval.
+ * This causes the bloom filter to be refreshed and a different salt to be used for rotation.
*/
@SuppressWarnings("FutureReturnValueIgnored")
private void scheduleAdvertisingRefresh() {
@@ -1044,7 +1068,7 @@
isDestroyed = true;
context.unregisterReceiver(broadcastReceiver);
advertiser.stopAdvertising();
- for (BluetoothGattServerHelper helper : bluetoothGattServerHelpers.values()) {
+ for (BluetoothGattServerHelper helper : mBluetoothGattServerHelpers.values()) {
helper.close();
}
stopRfcommServer();
@@ -1088,8 +1112,8 @@
// When BLE address changes, needs to send BLE address to the client again.
sendDeviceBleAddress(bleAddress);
- // If we are advertising something other than the model id (eg the bloom filter), restart the
- // advertisement so that it is updated with the new address.
+ // If we are advertising something other than the model id (e.g. the bloom filter), restart
+ // the advertisement so that it is updated with the new address.
if (isAdvertising() && !isDiscoverable()) {
advertiser.startAdvertising(getServiceData());
}
@@ -1123,7 +1147,8 @@
characteristic.addDescriptor(
new BluetoothGattDescriptor(
Constants.CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID,
- BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE));
+ BluetoothGattDescriptor.PERMISSION_READ
+ | BluetoothGattDescriptor.PERMISSION_WRITE));
return characteristic;
}
@@ -1157,9 +1182,9 @@
for (Map.Entry<BluetoothGattServerConnection, Notifier> entry :
connections.entrySet()) {
try {
- logger.log(
- "Sending notify %s to %s",
- getCharacteristic(), entry.getKey().getDevice().getAddress());
+ logger.log("Sending notify %s to %s",
+ getCharacteristic(),
+ entry.getKey().getDevice().getAddress());
entry.getValue().notify(data);
} catch (BluetoothException e) {
logger.log(
@@ -1174,60 +1199,63 @@
}
private void startRfcommServer() {
- rfcommServer.setRequestHandler(
- (int eventGroup, int eventCode, byte[] data) -> {
- switch (eventGroup) {
- case EventStreamProtocol.EventGroup.DEVICE_VALUE:
- if (eventCode == EventStreamProtocol.DeviceEventCode.DEVICE_CAPABILITY_VALUE
- && data != null) {
- logger.log("Received phone capability: %s", base16().encode(data));
- } else if (eventCode == EventStreamProtocol.DeviceEventCode.PLATFORM_TYPE_VALUE
- && data != null) {
- logger.log("Received platform type: %s", base16().encode(data));
- }
- break;
- case EventStreamProtocol.EventGroup.DEVICE_ACTION_VALUE:
- if (eventCode == EventStreamProtocol.DeviceActionEventCode.DEVICE_ACTION_RING_VALUE) {
- logger.log("receive device action with ring value, data = %d", data[0]);
- sendDeviceRingActionResponse();
- // Simulate notifying the seeker that the ringing has stopped due to user
- // interaction (such as tapping the bud).
- uiThreadHandler.postDelayed(this::sendDeviceRingStoppedAction, 5000);
- }
- break;
- case EventStreamProtocol.EventGroup.DEVICE_CONFIGURATION_VALUE:
- if (eventCode
- == EventStreamProtocol.DeviceConfigurationEventCode.CONFIGURATION_BUFFER_SIZE_VALUE) {
- logger.log(
- "receive device action with buffer size value, data = %s",
- base16().encode(data));
- sendSetBufferActionResponse(data);
- }
- break;
- case EventStreamProtocol.EventGroup.DEVICE_CAPABILITY_SYNC_VALUE:
- if (eventCode
- == EventStreamProtocol.DeviceCapabilitySyncEventCode.REQUEST_CAPABILITY_UPDATE_VALUE) {
- logger.log("receive device capability update request.");
- sendCapabilitySync();
- }
- break;
- default: // fall out
- }
- });
- rfcommServer.setStateMonitor(
- state -> {
- logger.log("RfcommServer is in %s state", state);
- if (CONNECTED.equals(state)) {
- sendModelId();
- sendDeviceBleAddress(bleAddress);
- sendFirmwareVersion();
- sendSessionNonce();
- }
- });
-
+ rfcommServer.setRequestHandler(this::handleRfcommServerRequest);
+ rfcommServer.setStateMonitor(state -> {
+ logger.log("RfcommServer is in %s state", state);
+ if (CONNECTED.equals(state)) {
+ sendModelId();
+ sendDeviceBleAddress(bleAddress);
+ sendFirmwareVersion();
+ sendSessionNonce();
+ }
+ });
rfcommServer.start();
}
+ private void handleRfcommServerRequest(int eventGroup, int eventCode, byte[] data) {
+ switch (eventGroup) {
+ case EventGroup.DEVICE_VALUE:
+ if (data == null) {
+ break;
+ }
+
+ String deviceValue = base16().encode(data);
+ if (eventCode == DeviceEventCode.DEVICE_CAPABILITY_VALUE) {
+ logger.log("Received phone capability: %s", deviceValue);
+ } else if (eventCode == DeviceEventCode.PLATFORM_TYPE_VALUE) {
+ logger.log("Received platform type: %s", deviceValue);
+ }
+ break;
+ case EventGroup.DEVICE_ACTION_VALUE:
+ if (eventCode == DeviceActionEventCode.DEVICE_ACTION_RING_VALUE) {
+ logger.log("receive device action with ring value, data = %d",
+ data[0]);
+ sendDeviceRingActionResponse();
+ // Simulate notifying the seeker that the ringing has stopped due
+ // to user interaction (such as tapping the bud).
+ uiThreadHandler.postDelayed(this::sendDeviceRingStoppedAction,
+ 5000);
+ }
+ break;
+ case EventGroup.DEVICE_CONFIGURATION_VALUE:
+ if (eventCode == DeviceConfigurationEventCode.CONFIGURATION_BUFFER_SIZE_VALUE) {
+ logger.log(
+ "receive device action with buffer size value, data = %s",
+ base16().encode(data));
+ sendSetBufferActionResponse(data);
+ }
+ break;
+ case EventGroup.DEVICE_CAPABILITY_SYNC_VALUE:
+ if (eventCode == DeviceCapabilitySyncEventCode.REQUEST_CAPABILITY_UPDATE_VALUE) {
+ logger.log("receive device capability update request.");
+ sendCapabilitySync();
+ }
+ break;
+ default: // fall out
+ break;
+ }
+ }
+
private void stopRfcommServer() {
rfcommServer.stop();
rfcommServer.setRequestHandler(null);
@@ -1237,18 +1265,17 @@
private void sendModelId() {
logger.log("Send model ID to the client");
rfcommServer.send(
- EventStreamProtocol.EventGroup.DEVICE_VALUE,
- EventStreamProtocol.DeviceEventCode.DEVICE_MODEL_ID_VALUE,
+ EventGroup.DEVICE_VALUE,
+ DeviceEventCode.DEVICE_MODEL_ID_VALUE,
modelIdServiceData(/* forAdvertising= */ false));
}
private void sendDeviceBleAddress(String bleAddress) {
logger.log("Send BLE address (%s) to the client", bleAddress);
- // TODO(b/134244147): to solve central address resolution problem, adds api for simulator app.
if (bleAddress != null) {
rfcommServer.send(
- EventStreamProtocol.EventGroup.DEVICE_VALUE,
- EventStreamProtocol.DeviceEventCode.DEVICE_BLE_ADDRESS_VALUE,
+ EventGroup.DEVICE_VALUE,
+ DeviceEventCode.DEVICE_BLE_ADDRESS_VALUE,
BluetoothAddress.decode(bleAddress));
}
}
@@ -1256,8 +1283,8 @@
private void sendFirmwareVersion() {
logger.log("Send Firmware Version (%s) to the client", deviceFirmwareVersion);
rfcommServer.send(
- EventStreamProtocol.EventGroup.DEVICE_VALUE,
- EventStreamProtocol.DeviceEventCode.FIRMWARE_VERSION_VALUE,
+ EventGroup.DEVICE_VALUE,
+ DeviceEventCode.FIRMWARE_VERSION_VALUE,
deviceFirmwareVersion.getBytes());
}
@@ -1266,18 +1293,18 @@
SecureRandom secureRandom = new SecureRandom();
sessionNonce = new byte[SECTION_NONCE_LENGTH];
secureRandom.nextBytes(sessionNonce);
- rfcommServer.send(EventStreamProtocol.EventGroup.DEVICE_VALUE,
- EventStreamProtocol.DeviceEventCode.SECTION_NONCE_VALUE, sessionNonce);
+ rfcommServer.send(
+ EventGroup.DEVICE_VALUE, DeviceEventCode.SECTION_NONCE_VALUE, sessionNonce);
}
private void sendDeviceRingActionResponse() {
logger.log("Send device ring action response to the client");
rfcommServer.send(
- EventStreamProtocol.EventGroup.ACKNOWLEDGEMENT_VALUE,
- EventStreamProtocol.AcknowledgementEventCode.ACKNOWLEDGEMENT_ACK_VALUE,
+ EventGroup.ACKNOWLEDGEMENT_VALUE,
+ AcknowledgementEventCode.ACKNOWLEDGEMENT_ACK_VALUE,
new byte[]{
- EventStreamProtocol.EventGroup.DEVICE_ACTION_VALUE,
- EventStreamProtocol.DeviceActionEventCode.DEVICE_ACTION_RING_VALUE
+ EventGroup.DEVICE_ACTION_VALUE,
+ DeviceActionEventCode.DEVICE_ACTION_RING_VALUE
});
}
@@ -1285,10 +1312,11 @@
boolean hmacPassed = false;
for (ByteString accountKey : getAccountKeys()) {
try {
- if (MessageStreamHmacEncoder.verifyHmac(accountKey.toByteArray(), sessionNonce, data)) {
+ if (MessageStreamHmacEncoder.verifyHmac(
+ accountKey.toByteArray(), sessionNonce, data)) {
hmacPassed = true;
- logger.log(
- "Buffer size data matches account key %s", base16().encode(accountKey.toByteArray()));
+ logger.log("Buffer size data matches account key %s",
+ base16().encode(accountKey.toByteArray()));
break;
}
} catch (GeneralSecurityException e) {
@@ -1298,11 +1326,11 @@
if (hmacPassed) {
logger.log("Send buffer size action response %s to the client", base16().encode(data));
rfcommServer.send(
- EventStreamProtocol.EventGroup.ACKNOWLEDGEMENT_VALUE,
- EventStreamProtocol.AcknowledgementEventCode.ACKNOWLEDGEMENT_ACK_VALUE,
+ EventGroup.ACKNOWLEDGEMENT_VALUE,
+ AcknowledgementEventCode.ACKNOWLEDGEMENT_ACK_VALUE,
new byte[]{
- EventStreamProtocol.EventGroup.DEVICE_CONFIGURATION_VALUE,
- EventStreamProtocol.DeviceConfigurationEventCode.CONFIGURATION_BUFFER_SIZE_VALUE,
+ EventGroup.DEVICE_CONFIGURATION_VALUE,
+ DeviceConfigurationEventCode.CONFIGURATION_BUFFER_SIZE_VALUE,
data[0],
data[1],
data[2]
@@ -1317,8 +1345,8 @@
if (supportDynamicBufferSize) {
logger.log("Send dynamic buffer size range to the client");
rfcommServer.send(
- EventStreamProtocol.EventGroup.DEVICE_CAPABILITY_SYNC_VALUE,
- EventStreamProtocol.DeviceCapabilitySyncEventCode.CONFIGURABLE_BUFFER_SIZE_RANGE_VALUE,
+ EventGroup.DEVICE_CAPABILITY_SYNC_VALUE,
+ DeviceCapabilitySyncEventCode.CONFIGURABLE_BUFFER_SIZE_RANGE_VALUE,
new byte[]{
0x00, 0x01, (byte) 0xf4, 0x00, 0x64, 0x00, (byte) 0xc8,
0x01, 0x00, (byte) 0xff, 0x00, 0x01, 0x00, (byte) 0x88,
@@ -1332,8 +1360,8 @@
private void sendDeviceRingStoppedAction() {
logger.log("Sending device ring stopped action to the client");
rfcommServer.send(
- EventStreamProtocol.EventGroup.DEVICE_ACTION_VALUE,
- EventStreamProtocol.DeviceActionEventCode.DEVICE_ACTION_RING_VALUE,
+ EventGroup.DEVICE_ACTION_VALUE,
+ DeviceActionEventCode.DEVICE_ACTION_RING_VALUE,
// Additional data for stopping ringing on all components.
new byte[]{0x00});
}
@@ -1343,16 +1371,16 @@
new NotifiableGattServlet() {
@Override
public BluetoothGattCharacteristic getBaseCharacteristic() {
- return new BluetoothGattCharacteristic(
- TransportDiscoveryService.ControlPointCharacteristic.ID,
- PROPERTY_WRITE | PROPERTY_INDICATE,
- PERMISSION_WRITE);
+ return new BluetoothGattCharacteristic(ControlPointCharacteristic.ID,
+ PROPERTY_WRITE | PROPERTY_INDICATE, PERMISSION_WRITE);
}
@Override
- public void write(BluetoothGattServerConnection connection, int offset, byte[] value)
+ public void write(
+ BluetoothGattServerConnection connection, int offset, byte[] value)
throws BluetoothGattException {
- logger.log("Requested TDS Control Point write, value=%s", base16().encode(value));
+ logger.log("Requested TDS Control Point write, value=%s",
+ base16().encode(value));
ResultCode resultCode = checkTdsControlPointRequest(value);
if (resultCode == ResultCode.SUCCESS) {
@@ -1369,7 +1397,10 @@
logger.log("Sending TDS Control Point response indication");
sendNotification(
Bytes.concat(
- new byte[]{getTdsControlPointOpCode(value), resultCode.byteValue},
+ new byte[]{
+ getTdsControlPointOpCode(value),
+ resultCode.byteValue,
+ },
resultCode == ResultCode.SUCCESS
? TDS_CONTROL_POINT_RESPONSE_PARAMETER
: new byte[0]));
@@ -1381,17 +1412,14 @@
@Override
public BluetoothGattCharacteristic getCharacteristic() {
- return new BluetoothGattCharacteristic(
- TransportDiscoveryService.BrHandoverDataCharacteristic.ID,
- PROPERTY_READ,
- PERMISSION_READ);
+ return new BluetoothGattCharacteristic(BrHandoverDataCharacteristic.ID,
+ PROPERTY_READ, PERMISSION_READ);
}
@Override
- public byte[] read(BluetoothGattServerConnection connection, int offset)
- throws BluetoothGattException {
+ public byte[] read(BluetoothGattServerConnection connection, int offset) {
return Bytes.concat(
- new byte[]{TransportDiscoveryService.BrHandoverDataCharacteristic.BR_EDR_FEATURES},
+ new byte[]{BrHandoverDataCharacteristic.BR_EDR_FEATURES},
bluetoothAddress.getBytes(ByteOrder.LITTLE_ENDIAN),
CLASS_OF_DEVICE.getBytes(ByteOrder.LITTLE_ENDIAN));
}
@@ -1441,14 +1469,18 @@
}
@Override
- public void write(BluetoothGattServerConnection connection, int offset, byte[] value) {
- logger.log("Got value from account key servlet: %s", base16().encode(value));
+ public void write(
+ BluetoothGattServerConnection connection, int offset, byte[] value) {
+ logger.log("Got value from account key servlet: %s",
+ base16().encode(value));
try {
- addAccountKey(AesEcbSingleBlockEncryption.decrypt(secret, value), pairingDevice);
+ addAccountKey(AesEcbSingleBlockEncryption.decrypt(secret, value),
+ pairingDevice);
} catch (GeneralSecurityException e) {
logger.log(e, "Failed to decrypt account key.");
}
- uiThreadHandler.post(() -> advertiser.startAdvertising(accountKeysServiceData()));
+ uiThreadHandler.post(
+ () -> advertiser.startAdvertising(accountKeysServiceData()));
}
};
@@ -1469,7 +1501,8 @@
BluetoothGattServlet keyBasedPairingServlet =
new NotifiableGattServlet() {
@Override
- // Simulating deprecated API {@code KeyBasedPairingCharacteristic.ID} for testing.
+ // Simulating deprecated API {@code KeyBasedPairingCharacteristic.ID} for
+ // testing.
@SuppressWarnings("deprecation")
public BluetoothGattCharacteristic getBaseCharacteristic() {
return new BluetoothGattCharacteristic(
@@ -1479,9 +1512,10 @@
}
@Override
- public void write(BluetoothGattServerConnection connection, int offset, byte[] value)
- throws BluetoothGattException {
- logger.log("Requesting key based pairing handshake, value=%s", base16().encode(value));
+ public void write(
+ BluetoothGattServerConnection connection, int offset, byte[] value) {
+ logger.log("Requesting key based pairing handshake, value=%s",
+ base16().encode(value));
secret = null;
byte[] seekerPublicAddress = null;
@@ -1495,18 +1529,19 @@
isSubsequentPair = true;
break;
} catch (GeneralSecurityException e) {
- logger.log(e, "Failed to decrypt with %s", base16().encode(candidateSecret));
+ logger.log(e, "Failed to decrypt with %s",
+ base16().encode(candidateSecret));
}
}
- } else if (value.length
- == AES_BLOCK_LENGTH + EllipticCurveDiffieHellmanExchange.PUBLIC_KEY_LENGTH
+ } else if (value.length == AES_BLOCK_LENGTH + PUBLIC_KEY_LENGTH
&& options.getAntiSpoofingPrivateKey() != null) {
try {
byte[] encryptedRequest = Arrays.copyOf(value, AES_BLOCK_LENGTH);
byte[] receivedPublicKey =
Arrays.copyOfRange(value, AES_BLOCK_LENGTH, value.length);
byte[] candidateSecret =
- EllipticCurveDiffieHellmanExchange.create(options.getAntiSpoofingPrivateKey())
+ EllipticCurveDiffieHellmanExchange.create(
+ options.getAntiSpoofingPrivateKey())
.generateSecret(receivedPublicKey);
seekerPublicAddress = handshake(candidateSecret, encryptedRequest);
secret = candidateSecret;
@@ -1530,54 +1565,61 @@
byte[] salt = new byte[9];
new Random().nextBytes(salt);
try {
- byte[] encryptedAddress =
- encrypt(
- secret,
- Bytes.concat(
- new byte[]{KeyBasedPairingCharacteristic.Response.TYPE},
- bluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN),
- salt));
+ byte[] data = concat(
+ new byte[]{KeyBasedPairingCharacteristic.Response.TYPE},
+ bluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN), salt);
+ byte[] encryptedAddress = encrypt(secret, data);
logger.log(
"Sending handshake response %s with size %d",
base16().encode(encryptedAddress), encryptedAddress.length);
sendNotification(encryptedAddress);
- // Notify seeker for NameCharacteristic to get provider device name when seeker
- // request device name flag is true.
- if (options.getEnableNameCharacteristic() && handshakeRequest.requestDeviceName()) {
+ // Notify seeker for NameCharacteristic to get provider device name
+ // when seeker request device name flag is true.
+ if (options.getEnableNameCharacteristic()
+ && handshakeRequest.requestDeviceName()) {
byte[] encryptedResponse =
- getDeviceNameInBytes() != null ? createEncryptedDeviceName() : new byte[0];
+ getDeviceNameInBytes() != null ? createEncryptedDeviceName()
+ : new byte[0];
logger.log(
"Sending device name response %s with size %d",
- base16().encode(encryptedResponse), encryptedResponse.length);
+ base16().encode(encryptedResponse),
+ encryptedResponse.length);
deviceNameServlet.sendNotification(encryptedResponse);
}
- // Disconnects the current connection to allow the following pairing request.
- // Needs to be on a separate thread to avoid deadlocking and timing out (waits for a
- // callback from OS, which happens on this thread).
+ // Disconnects the current connection to allow the following pairing
+ // request. Needs to be on a separate thread to avoid deadlocking and
+ // timing out (waits for a callback from OS, which happens on this
+ // thread).
//
- // Note: The spec does not require you to disconnect from other devices at this point.
- // If headphones support multiple simultaneous connections, they should stay
- // connected. But Android fails to pair with the new device if we don't first
- // disconnect from any other device.
- logger.log("Skip remove bond, value=%s", options.getRemoveAllDevicesDuringPairing());
+ // Note: The spec does not require you to disconnect from other
+ // devices at this point.
+ // If headphones support multiple simultaneous connections, they
+ // should stay connected. But Android fails to pair with the new
+ // device if we don't first disconnect from any other device.
+ logger.log("Skip remove bond, value=%s",
+ options.getRemoveAllDevicesDuringPairing());
if (options.getRemoveAllDevicesDuringPairing()
- && handshakeRequest.getType() == HandshakeRequest.Type.KEY_BASED_PAIRING_REQUEST
+ && handshakeRequest.getType()
+ == HandshakeRequest.Type.KEY_BASED_PAIRING_REQUEST
&& !handshakeRequest.requestRetroactivePair()) {
executor.execute(() -> disconnect());
}
- if (handshakeRequest.getType() == HandshakeRequest.Type.KEY_BASED_PAIRING_REQUEST
+ if (handshakeRequest.getType()
+ == HandshakeRequest.Type.KEY_BASED_PAIRING_REQUEST
&& handshakeRequest.requestProviderInitialBonding()) {
- // Run on executor to ensure it doesn't happen until after the notify (which tells
- // the remote device what address to expect).
- String seekerPublicAddressString = BluetoothAddress.encode(seekerPublicAddress);
- executor.execute(
- () -> {
- logger.log("Sending pairing request to %s", seekerPublicAddressString);
- bluetoothAdapter.getRemoteDevice(seekerPublicAddressString).createBond();
- });
+ // Run on executor to ensure it doesn't happen until after the
+ // notify (which tells the remote device what address to expect).
+ String seekerPublicAddressString =
+ BluetoothAddress.encode(seekerPublicAddress);
+ executor.execute(() -> {
+ logger.log("Sending pairing request to %s",
+ seekerPublicAddressString);
+ bluetoothAdapter.getRemoteDevice(
+ seekerPublicAddressString).createBond();
+ });
}
} catch (GeneralSecurityException e) {
logger.log(e, "Failed to notify of static mac address");
@@ -1591,9 +1633,10 @@
byte[] decryptedAddress = handshakeRequest.getVerificationData();
if (bleAddress != null
- && Arrays.equals(decryptedAddress, BluetoothAddress.decode(bleAddress))
- || (Arrays.equals(
- decryptedAddress, bluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN)))) {
+ && Arrays.equals(decryptedAddress,
+ BluetoothAddress.decode(bleAddress))
+ || Arrays.equals(decryptedAddress,
+ bluetoothAddress.getBytes(ByteOrder.BIG_ENDIAN))) {
logger.log("Address matches: %s", base16().encode(decryptedAddress));
} else {
throw new GeneralSecurityException(
@@ -1626,7 +1669,8 @@
}
logger.log(
- "KeyBasedPairing: initialBonding=%s, requestDeviceName=%s, retroactivePair=%s",
+ "KeyBasedPairing: initialBonding=%s, requestDeviceName=%s, "
+ + "retroactivePair=%s",
handshakeRequest.requestProviderInitialBonding(),
handshakeRequest.requestDeviceName(),
handshakeRequest.requestRetroactivePair());
@@ -1659,7 +1703,8 @@
logger.log("Requesting action over BLE, device action");
} else if (handshakeRequest.requestFollowedByAdditionalData()) {
logger.log(
- "Requesting action over BLE, followed by additional data, type:%s",
+ "Requesting action over BLE, followed by additional data, "
+ + "type:%s",
handshakeRequest.getAdditionalDataType());
} else {
logger.log("Requesting action over BLE");
@@ -1668,8 +1713,7 @@
}
/**
- * @return The encrypted device name from provider for seeker to use. See
- * go/fast-pair-naming-design-doc for the encryption detail to encrypt device name.
+ * @return The encrypted device name from provider for seeker to use.
*/
private byte[] createEncryptedDeviceName() throws GeneralSecurityException {
byte[] deviceName = getDeviceNameInBytes();
@@ -1703,7 +1747,8 @@
sha256 = MessageDigest.getInstance("SHA-256");
sha256.reset();
} catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("System missing SHA-256 implementation.", e);
+ throw new IllegalStateException(
+ "System missing SHA-256 implementation.", e);
}
}
@@ -1725,12 +1770,15 @@
}
@Override
- public void write(BluetoothGattServerConnection connection, int offset, byte[] value)
+ public void write(
+ BluetoothGattServerConnection connection, int offset, byte[] value)
throws BluetoothGattException {
- logger.log("Got value from beacon actions servlet: %s", base16().encode(value));
+ logger.log("Got value from beacon actions servlet: %s",
+ base16().encode(value));
if (value.length == 0) {
logger.log("Packet length invalid, %d", value.length);
- throw new BluetoothGattException("Packet length invalid", GATT_ERROR_INVALID_VALUE);
+ throw new BluetoothGattException("Packet length invalid",
+ GATT_ERROR_INVALID_VALUE);
}
switch (value[0]) {
case BeaconActionType.READ_BEACON_PARAMETERS:
@@ -1747,10 +1795,12 @@
case BeaconActionType.RING:
case BeaconActionType.READ_RINGING_STATE:
throw new BluetoothGattException(
- "Unimplemented beacon action", BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
+ "Unimplemented beacon action",
+ BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
default:
throw new BluetoothGattException(
- "Unknown beacon action", BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
+ "Unknown beacon action",
+ BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED);
}
}
@@ -1758,7 +1808,8 @@
throws BluetoothGattException {
if (value.length < ONE_TIME_AUTH_KEY_LENGTH + ONE_TIME_AUTH_KEY_OFFSET) {
logger.log("Packet length invalid, %d", value.length);
- throw new BluetoothGattException("Packet length invalid", GATT_ERROR_INVALID_VALUE);
+ throw new BluetoothGattException(
+ "Packet length invalid", GATT_ERROR_INVALID_VALUE);
}
byte[] hashedAccountKey =
Arrays.copyOfRange(
@@ -1766,7 +1817,8 @@
ONE_TIME_AUTH_KEY_OFFSET,
ONE_TIME_AUTH_KEY_LENGTH + ONE_TIME_AUTH_KEY_OFFSET);
if (lastNonce == null) {
- throw new BluetoothGattException("Nonce wasn't set", GATT_ERROR_UNAUTHENTICATED);
+ throw new BluetoothGattException(
+ "Nonce wasn't set", GATT_ERROR_UNAUTHENTICATED);
}
if (ownerOnly) {
ByteString accountKey = getOwnerAccountKey();
@@ -1774,7 +1826,8 @@
sha256.update(accountKey.toByteArray());
sha256.update(lastNonce);
return Arrays.equals(
- hashedAccountKey, Arrays.copyOf(sha256.digest(), ONE_TIME_AUTH_KEY_LENGTH));
+ hashedAccountKey,
+ Arrays.copyOf(sha256.digest(), ONE_TIME_AUTH_KEY_LENGTH));
}
} else {
Set<ByteString> accountKeys = getAccountKeys();
@@ -1782,7 +1835,8 @@
sha256.update(accountKey.toByteArray());
sha256.update(lastNonce);
if (Arrays.equals(
- hashedAccountKey, Arrays.copyOf(sha256.digest(), ONE_TIME_AUTH_KEY_LENGTH))) {
+ hashedAccountKey,
+ Arrays.copyOf(sha256.digest(), ONE_TIME_AUTH_KEY_LENGTH))) {
return true;
}
}
@@ -1807,24 +1861,29 @@
return data;
}
- private void handleReadBeaconParameters(byte[] value) throws BluetoothGattException {
+ private void handleReadBeaconParameters(byte[] value)
+ throws BluetoothGattException {
if (!verifyAccountKeyToken(value, /* ownerOnly= */ false)) {
throw new BluetoothGattException(
- "failed to authenticate account key", GATT_ERROR_UNAUTHENTICATED);
+ "failed to authenticate account key",
+ GATT_ERROR_UNAUTHENTICATED);
}
sendNotification(
fromBytes(
(byte) BeaconActionType.READ_BEACON_PARAMETERS,
(byte) 5 /* data length */,
TRANSMISSION_POWER)
- .concat(ByteString.copyFrom(intToByteArray(getBeaconClock())))
+ .concat(ByteString.copyFrom(
+ intToByteArray(getBeaconClock())))
.toByteArray());
}
- private void handleReadProvisioningState(byte[] value) throws BluetoothGattException {
+ private void handleReadProvisioningState(byte[] value)
+ throws BluetoothGattException {
if (!verifyAccountKeyToken(value, /* ownerOnly= */ false)) {
throw new BluetoothGattException(
- "failed to authenticate account key", GATT_ERROR_UNAUTHENTICATED);
+ "failed to authenticate account key",
+ GATT_ERROR_UNAUTHENTICATED);
}
byte flags = 0;
if (verifyAccountKeyToken(value, /* ownerOnly= */ true)) {
@@ -1846,30 +1905,36 @@
flags)
.concat(
E2eeCalculator.computeE2eeEid(
- identityKey, /* exponent= */ 10, getBeaconClock()))
+ identityKey, /* exponent= */ 10,
+ getBeaconClock()))
.toByteArray());
}
}
- private void handleSetEphemeralIdentityKey(byte[] value) throws BluetoothGattException {
+ private void handleSetEphemeralIdentityKey(byte[] value)
+ throws BluetoothGattException {
if (!verifyAccountKeyToken(value, /* ownerOnly= */ true)) {
throw new BluetoothGattException(
- "failed to authenticate owner account key", GATT_ERROR_UNAUTHENTICATED);
+ "failed to authenticate owner account key",
+ GATT_ERROR_UNAUTHENTICATED);
}
if (value.length
- != ONE_TIME_AUTH_KEY_LENGTH + ONE_TIME_AUTH_KEY_OFFSET + IDENTITY_KEY_LENGTH) {
+ != ONE_TIME_AUTH_KEY_LENGTH + ONE_TIME_AUTH_KEY_OFFSET
+ + IDENTITY_KEY_LENGTH) {
logger.log("Packet length invalid, %d", value.length);
- throw new BluetoothGattException("Packet length invalid", GATT_ERROR_INVALID_VALUE);
+ throw new BluetoothGattException("Packet length invalid",
+ GATT_ERROR_INVALID_VALUE);
}
if (identityKey != null) {
throw new BluetoothGattException(
- "Device is already provisioned as Eddystone", GATT_ERROR_UNAUTHENTICATED);
+ "Device is already provisioned as Eddystone",
+ GATT_ERROR_UNAUTHENTICATED);
}
- identityKey =
- Crypto.aesEcbNoPaddingDecrypt(
- ByteString.copyFrom(ownerAccountKey),
- ByteString.copyFrom(value)
- .substring(ONE_TIME_AUTH_KEY_LENGTH + ONE_TIME_AUTH_KEY_OFFSET));
+ identityKey = Crypto.aesEcbNoPaddingDecrypt(
+ ByteString.copyFrom(ownerAccountKey),
+ ByteString.copyFrom(value)
+ .substring(ONE_TIME_AUTH_KEY_LENGTH
+ + ONE_TIME_AUTH_KEY_OFFSET));
}
};
@@ -1916,7 +1981,8 @@
logger.log("enterPassKey called with passkey %d.", passkey);
try {
boolean result =
- (Boolean) Reflect.on(pairingDevice).withMethod("setPasskey", int.class).get(passkey);
+ (Boolean) Reflect.on(pairingDevice).withMethod("setPasskey", int.class).get(
+ passkey);
logger.log("enterPassKey called with result %b", result);
} catch (ReflectionException e) {
logger.log("enterPassKey meet Exception %s.", e.getMessage());
@@ -1938,7 +2004,8 @@
logger.log("Checking localPasskey %s == remotePasskey %s", localPasskey, remotePasskey);
boolean passkeysMatched = localPasskey == remotePasskey;
- if (options.getShowsPasskeyConfirmation() && passkeysMatched && passkeyEventCallback != null) {
+ if (options.getShowsPasskeyConfirmation() && passkeysMatched
+ && passkeyEventCallback != null) {
logger.log("callbacks the UI for passkey confirmation.");
passkeyEventCallback.onPasskeyConfirmation(localPasskey, this::setPasskeyConfirmation);
} else {
@@ -1949,7 +2016,8 @@
private void sendPasskeyToRemoteDevice(int passkey) {
try {
passkeyServlet.sendNotification(
- PasskeyCharacteristic.encrypt(PasskeyCharacteristic.Type.PROVIDER, secret, passkey));
+ PasskeyCharacteristic.encrypt(
+ PasskeyCharacteristic.Type.PROVIDER, secret, passkey));
} catch (GeneralSecurityException e) {
logger.log(e, "Failed to encrypt passkey response.");
}
@@ -1996,7 +2064,8 @@
setDiscoverable(false);
}
- private void setDiscoverable(boolean discoverable) throws InterruptedException, TimeoutException {
+ private void setDiscoverable(boolean discoverable)
+ throws InterruptedException, TimeoutException {
isDiscoverableLatch = new CountDownLatch(1);
setScanMode(discoverable ? SCAN_MODE_CONNECTABLE_DISCOVERABLE : SCAN_MODE_CONNECTABLE);
// If we're already discoverable, count down the latch right away. Otherwise,
@@ -2049,11 +2118,12 @@
private ResultCode checkTdsControlPointRequest(byte[] request) {
if (request.length < 2) {
logger.log(
- new IllegalArgumentException(), "Expected length >= 2 for %s", base16().encode(request));
+ new IllegalArgumentException(), "Expected length >= 2 for %s",
+ base16().encode(request));
return ResultCode.INVALID_PARAMETER;
}
byte opCode = getTdsControlPointOpCode(request);
- if (opCode != TransportDiscoveryService.ControlPointCharacteristic.ACTIVATE_TRANSPORT_OP_CODE) {
+ if (opCode != ControlPointCharacteristic.ACTIVATE_TRANSPORT_OP_CODE) {
logger.log(
new IllegalArgumentException(),
"Expected Activate Transport op code (0x01), got %d",
@@ -2067,7 +2137,6 @@
request[1]);
return ResultCode.UNSUPPORTED_ORGANIZATION_ID;
}
- // TODO(jfarfel): Parse out the requested service UUIDs, and if they don't include A2DP, fail.
return ResultCode.SUCCESS;
}
@@ -2082,10 +2151,11 @@
private byte[] modelIdServiceData(boolean forAdvertising) {
// Note: This used to be little-endian but is now big-endian. See b/78229467 for details.
byte[] modelIdPacket =
- base16().decode(forAdvertising ? options.getAdvertisingModelId() : options.getModelId());
+ base16().decode(
+ forAdvertising ? options.getAdvertisingModelId() : options.getModelId());
if (!batteryValues.isEmpty()) {
- // If we are going to advertise battery values with the packet, then switch to the non-3-byte
- // model ID format from go/fast-pair-service-data.
+ // If we are going to advertise battery values with the packet, then switch to the
+ // non-3-byte model ID format.
modelIdPacket = concat(new byte[]{0b00000110}, modelIdPacket);
}
return modelIdPacket;
@@ -2115,15 +2185,15 @@
}
BloomFilter bloomFilter =
new BloomFilter(
- new byte[(int) (1.2 * accountKeys.size()) + 3], new FastPairBloomFilterHasher());
+ new byte[(int) (1.2 * accountKeys.size()) + 3],
+ new FastPairBloomFilterHasher());
String address = bleAddress == null ? SIMULATOR_FAKE_BLE_ADDRESS : bleAddress;
- // Simulator supports Central Address Resolution characteristic, so when paired, the BLE address
- // in Seeker will be resolved to BR/EDR address. This caused Seeker fails on checking the bloom
- // filter due to different address is used for salting. In order to let battery values
- // notification be shown on paired device, we use random salt to workaround it.
- // TODO(tonyysliu): Remove this workaround when simulator does not support Central Address
- // Resolution characteristic.
+ // Simulator supports Central Address Resolution characteristic, so when paired, the BLE
+ // address in Seeker will be resolved to BR/EDR address. This caused Seeker fails on
+ // checking the bloom filter due to different address is used for salting. In order to
+ // let battery values notification be shown on paired device, we use random salt to
+ // workaround it.
boolean advertisingBatteryValues = !batteryValues.isEmpty();
byte[] salt;
if (options.getUseRandomSaltForAccountKeyRotation() || advertisingBatteryValues) {
@@ -2157,9 +2227,10 @@
}
/**
- * Creates a new field for the packet. The header is formatted 0xLLLLTTTT where LLLL is the length
- * of the field and TTTT is the type (0 for bloom filter, 1 for salt). See go/fast-pair-2-spec for
- * more information.
+ * Creates a new field for the packet.
+ *
+ * The header is formatted 0xLLLLTTTT where LLLL is the
+ * length of the field and TTTT is the type (0 for bloom filter, 1 for salt).
*/
private byte[] createField(byte header, byte[] value) {
return concat(new byte[]{header}, value);
@@ -2188,13 +2259,13 @@
}
private byte[] generateBatteryData() {
- // Byte 0: Battery length and type, first 4 bits are the number of battery values, second 4 are
- // the type. See go/fast-pair-2-service-data for the battery type definitions.
+ // Byte 0: Battery length and type, first 4 bits are the number of battery values, second
+ // 4 are the type.
// Byte 1 - length: Battery values, the first bit is charging status, the remaining bits are
// the actual value between 0 and 100, or -1 for unknown.
byte[] batteryData = new byte[batteryValues.size() + 1];
- batteryData[0] =
- (byte) (batteryValues.size() << 4 | (suppressBatteryNotification ? 0b0100 : 0b0011));
+ batteryData[0] = (byte) (batteryValues.size() << 4
+ | (suppressBatteryNotification ? 0b0100 : 0b0011));
int batteryValueIndex = 1;
for (BatteryValue batteryValue : batteryValues) {
@@ -2208,13 +2279,12 @@
}
private byte[] generateAccountKeyData(BloomFilter bloomFilter) {
- // Byte 0: length and type, first 4 bits are the length of bloom filter, second 4 are the type
- // which inditcating the subsequent pairing notification is suppressed or not.
+ // Byte 0: length and type, first 4 bits are the length of bloom filter, second 4 are the
+ // type which indicating the subsequent pairing notification is suppressed or not.
// The following bytes are the data of bloom filter.
byte[] filterBytes = bloomFilter.asBytes();
- byte lengthAndType =
- (byte)
- (filterBytes.length << 4 | (suppressSubsequentPairingNotification ? 0b0010 : 0b0000));
+ byte lengthAndType = (byte) (filterBytes.length << 4
+ | (suppressSubsequentPairingNotification ? 0b0010 : 0b0000));
logger.log(
"Generate bloom filter with suppress subsequent pairing notification:%b",
suppressSubsequentPairingNotification);
diff --git a/nearby/tests/multidevices/host/Android.bp b/nearby/tests/multidevices/host/Android.bp
index 5aace96..b0adfba 100644
--- a/nearby/tests/multidevices/host/Android.bp
+++ b/nearby/tests/multidevices/host/Android.bp
@@ -16,7 +16,7 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-// Run the tests: atest -v CtsSeekerDiscoverProviderTest -- --replicate-parent-setup --multi-device-count 2
+// Run the tests: atest -v CtsSeekerDiscoverProviderTest
python_test_host {
name: "CtsSeekerDiscoverProviderTest",
main: "seeker_discover_provider_test.py",
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
index ab18f54..58e4c47 100644
--- 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
@@ -16,11 +16,6 @@
package com.android.server.nearby.fastpair.halfsheet;
-
-import static com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager.ACTIVITY_INTENT_ACTION;
-
-import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -72,7 +67,6 @@
public void setup() {
MockitoAnnotations.initMocks(this);
-
mScanFastPairStoreItem = Cache.ScanFastPairStoreItem.newBuilder()
.setAddress(BLEADDRESS)
.setDeviceName(NAME)
@@ -85,7 +79,6 @@
ResolveInfo resolveInfo = new ResolveInfo();
List<ResolveInfo> resolveInfoList = new ArrayList<>();
-
mPackageManager = mock(PackageManager.class);
when(mContextWrapper.getPackageManager()).thenReturn(mPackageManager);
resolveInfo.activityInfo = new ActivityInfo();
@@ -108,8 +101,6 @@
verify(mContextWrapper, atLeastOnce())
.startActivityAsUser(intentArgumentCaptor.capture(), eq(UserHandle.CURRENT));
- Intent launchIntent = intentArgumentCaptor.getValue();
- assertThat(launchIntent.getAction()).isEqualTo(ACTIVITY_INTENT_ACTION);
}
@Test
@@ -118,7 +109,6 @@
ResolveInfo resolveInfo = new ResolveInfo();
List<ResolveInfo> resolveInfoList = new ArrayList<>();
-
mPackageManager = mock(PackageManager.class);
when(mContextWrapper.getPackageManager()).thenReturn(mPackageManager);
resolveInfo.activityInfo = new ActivityInfo();
@@ -142,6 +132,5 @@
verify(mContextWrapper, never())
.startActivityAsUser(intentArgumentCaptor.capture(), eq(UserHandle.CURRENT));
-
}
}