Merge "Rename framework-connectivity-tiramisu to framework-connectivity-t" into tm-dev
diff --git a/nearby/framework/java/android/nearby/INearbyManager.aidl b/nearby/framework/java/android/nearby/INearbyManager.aidl
index cd05106..4fff563 100644
--- a/nearby/framework/java/android/nearby/INearbyManager.aidl
+++ b/nearby/framework/java/android/nearby/INearbyManager.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007, The Android Open Source Project
+ * Copyright (C) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/nearby/framework/java/android/nearby/IScanListener.aidl b/nearby/framework/java/android/nearby/IScanListener.aidl
index 8747b07..54033aa 100644
--- a/nearby/framework/java/android/nearby/IScanListener.aidl
+++ b/nearby/framework/java/android/nearby/IScanListener.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007, The Android Open Source Project
+ * Copyright (C) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
index 3b0a776..e9a29b7 100644
--- a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
+++ b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
@@ -76,6 +76,8 @@
return new NearbyDeviceParcelable[size];
}
};
+
+ @ScanRequest.ScanType int mScanType;
@Nullable
private final String mName;
@NearbyDevice.Medium
@@ -89,9 +91,10 @@
@Nullable
private final byte[] mData;
- private NearbyDeviceParcelable(@Nullable String name, int medium, int rssi,
- @Nullable String fastPairModelId, @Nullable String bluetoothAddress,
- @Nullable byte[] data) {
+ private NearbyDeviceParcelable(@ScanRequest.ScanType int scanType, @Nullable String name,
+ int medium, int rssi, @Nullable String fastPairModelId,
+ @Nullable String bluetoothAddress, @Nullable byte[] data) {
+ mScanType = scanType;
mName = name;
mMedium = medium;
mRssi = rssi;
@@ -176,6 +179,16 @@
}
/**
+ * Returns the type of the scan.
+ *
+ * @hide
+ */
+ @ScanRequest.ScanType
+ public int getScanType() {
+ return mScanType;
+ }
+
+ /**
* Gets the name of the NearbyDeviceParcelable. Returns {@code null} If there is no name.
*/
@Nullable
@@ -235,6 +248,7 @@
@NearbyDevice.Medium
private int mMedium;
private int mRssi;
+ @ScanRequest.ScanType int mScanType;
@Nullable
private String mFastPairModelId;
@Nullable
@@ -243,6 +257,16 @@
private byte[] mData;
/**
+ * Sets the scan type of the NearbyDeviceParcelable.
+ *
+ * @hide
+ */
+ public Builder setScanType(@ScanRequest.ScanType int scanType) {
+ mScanType = scanType;
+ return this;
+ }
+
+ /**
* Sets the name of the scanned device.
*
* @param name The local name of the scanned device.
@@ -314,7 +338,7 @@
*/
@NonNull
public NearbyDeviceParcelable build() {
- return new NearbyDeviceParcelable(mName, mMedium, mRssi, mFastPairModelId,
+ return new NearbyDeviceParcelable(mScanType, mName, mMedium, mRssi, mFastPairModelId,
mBluetoothAddress, mData);
}
}
diff --git a/nearby/framework/java/android/nearby/ScanRequest.aidl b/nearby/framework/java/android/nearby/ScanRequest.aidl
index 0aaf3af..438dfed 100644
--- a/nearby/framework/java/android/nearby/ScanRequest.aidl
+++ b/nearby/framework/java/android/nearby/ScanRequest.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012, The Android Open Source Project
+ * Copyright (C) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/nearby/halfsheet/res/layout/fast_pair_consent_fragment.xml b/nearby/halfsheet/res/layout/fast_pair_consent_fragment.xml
deleted file mode 100644
index aba9a32..0000000
--- a/nearby/halfsheet/res/layout/fast_pair_consent_fragment.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<android.support.v7.widget.LinearLayoutCompat
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:orientation="vertical"
- android:layout_width="match_parent" android:layout_height="match_parent">
-
- <android.support.constraint.ConstraintLayout
- android:id="@+id/image_view"
- android:layout_width="match_parent"
- android:layout_height="340dp"
- android:paddingStart="12dp"
- android:paddingEnd="12dp"
- android:paddingTop="12dp">
- <TextView
- android:id="@+id/header_subtitle"
- android:textColor="@color/fast_pair_half_sheet_subtitle_color"
- android:fontFamily="google-sans"
- android:textSize="14sp"
- android:gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent" />
-
- <ImageView
- android:id="@+id/pairing_pic"
- android:layout_width="@dimen/fast_pair_half_sheet_image_size"
- android:layout_height="@dimen/fast_pair_half_sheet_image_size"
- android:paddingTop="18dp"
- android:paddingBottom="18dp"
- android:importantForAccessibility="no"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/header_subtitle" />
-
- <ProgressBar
- android:id="@+id/connect_progressbar"
- android:layout_width="@dimen/fast_pair_half_sheet_image_size"
- android:layout_height="2dp"
- android:indeterminate="true"
- android:indeterminateTint="@color/fast_pair_progress_color"
- android:indeterminateTintMode="src_in"
- style="?android:attr/progressBarStyleHorizontal"
- app:layout_constraintTop_toBottomOf="@+id/pairing_pic"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"/>
-
- <Button
- android:id="@+id/connect_btn"
- android:text="@string/fast_pair_app_launch_button"
- android:layout_height="@dimen/fast_pair_connect_button_height"
- android:layout_width="@dimen/fast_pair_half_sheet_image_size"
- android:background="@color/fast_pair_half_sheet_button_color"
- android:paddingTop="6dp"
- android:paddingBottom="6dp"
- app:layout_constraintTop_toBottomOf="@+id/pairing_pic"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- style="@style/HalfSheetButton" />
-
- <Button
- android:id="@+id/result_action_btn"
- android:text="@string/common_done"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:paddingTop="6dp"
- android:paddingBottom="6dp"
- app:layout_constraintTop_toBottomOf="@+id/pairing_pic"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- style="@style/HalfSheetButtonBorderless" />
-
- </android.support.constraint.ConstraintLayout>
-
-</android.support.v7.widget.LinearLayoutCompat>
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
index c99c130..74530de 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/fragment/DevicePairingFragment.java
@@ -324,7 +324,7 @@
private Intent createCompletionIntent(@Nullable String companionApp, @Nullable String address) {
if (isEmpty(companionApp)) {
return null;
- } else if (FastPairUtils.isAppInstalled(companionApp, getContext())
+ } else if (FastPairUtils.isAppInstalled(getContext(), companionApp)
&& isLaunchable(companionApp)) {
mOpenCompanionAppIntent = createCompanionAppIntent(companionApp, address);
return mOpenCompanionAppIntent;
diff --git a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
index ee869ba..fffb9e1 100644
--- a/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
+++ b/nearby/halfsheet/src/com/android/nearby/halfsheet/utils/FastPairUtils.java
@@ -18,11 +18,15 @@
import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_COMPANION_APP;
import static com.android.server.nearby.fastpair.UserActionHandler.ACTION_FAST_PAIR;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.net.URISyntaxException;
@@ -37,7 +41,7 @@
public static final String TAG = "HalfSheetActivity";
/** FastPair util method check certain app is install on the device or not. */
- public static boolean isAppInstalled(String packageName, Context context) {
+ public static boolean isAppInstalled(Context context, String packageName) {
try {
context.getPackageManager().getPackageInfo(packageName, 0);
return true;
@@ -71,7 +75,8 @@
}
/**
- * Converts a {@link ScanFastPairStoreItem} to a {@link StoredDiscoveryItem}.
+ * Converts a {@link service.proto.Cache.ScanFastPairStoreItem}
+ * to a {@link service.proto.Cache.StoredDiscoveryItem}.
*
* <p>This is needed to make the new Fast Pair scanning stack compatible with the rest of the
* legacy Fast Pair code.
@@ -110,7 +115,39 @@
.build();
}
- private FastPairUtils() {
-
+ /**
+ * Returns true the application is installed and can be opened on device.
+ */
+ public static boolean isLaunchable(@NonNull Context context, String companionApp) {
+ return isAppInstalled(context, companionApp)
+ && createCompanionAppIntent(context, companionApp, null) != null;
}
+
+ /**
+ * Returns an intent to launch given the package name and bluetooth address (if provided).
+ * Returns null if no such an intent can be found.
+ */
+ @Nullable
+ public static Intent createCompanionAppIntent(@NonNull Context context, String packageName,
+ @Nullable String address) {
+ Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
+ if (intent == null) {
+ return null;
+ }
+ if (address != null) {
+ BluetoothAdapter adapter = getBluetoothAdapter(context);
+ if (adapter != null) {
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, adapter.getRemoteDevice(address));
+ }
+ }
+ return intent;
+ }
+
+ @Nullable
+ private static BluetoothAdapter getBluetoothAdapter(@NonNull Context context) {
+ BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class);
+ return bluetoothManager == null ? null : bluetoothManager.getAdapter();
+ }
+
+ private FastPairUtils() {}
}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java
index a7c05d6..c963aa6 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairConnection.java
@@ -18,7 +18,6 @@
import android.annotation.WorkerThread;
import android.bluetooth.BluetoothDevice;
-import android.content.Intent;
import androidx.annotation.Nullable;
import androidx.core.util.Consumer;
@@ -135,13 +134,6 @@
@Nullable
public abstract String getPublicAddress();
- /**
- * Creates cloud syncing intent which saves the Fast Pair device to the account.
- *
- * @param accountKey account key which is written into the Fast Pair device
- * @return cloud syncing intent
- */
- public abstract Intent createCloudSyncingIntent(byte[] accountKey);
/** Callback for getting notifications when pairing has completed. */
public interface OnPairedCallback {
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
index 8b466fa..789ef59 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnection.java
@@ -20,7 +20,6 @@
import static android.bluetooth.BluetoothDevice.BOND_BONDING;
import static android.bluetooth.BluetoothDevice.BOND_NONE;
-import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH;
import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress.maskBluetoothAddress;
import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.get16BitUuid;
import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid;
@@ -28,7 +27,6 @@
import static com.android.server.nearby.common.bluetooth.fastpair.Bytes.toShorts;
import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.base.Verify.verifyNotNull;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.primitives.Bytes.concat;
@@ -221,6 +219,12 @@
},
REQUESTED_SERVICES_LTV);
+ private static boolean sTestMode = false;
+
+ static void enableTestMode() {
+ sTestMode = true;
+ }
+
/**
* Operation Result Code.
*/
@@ -493,6 +497,7 @@
// Lazily initialize a new connection manager for each pairing request.
initGattConnectionManager();
boolean isSecretHandshakeCompleted = true;
+
try {
if (key != null && key.length > 0) {
// GATT_CONNECTION_AND_SECRET_HANDSHAKE start.
@@ -731,11 +736,6 @@
}
}
- @VisibleForTesting
- void setBeforeDirectlyConnectProfileFromCacheForTest(Runnable runnable) {
- this.mBeforeDirectlyConnectProfileFromCacheForTest = runnable;
- }
-
/**
* Logs for user retry, check go/fastpairquality21q3 for more details.
*/
@@ -898,6 +898,7 @@
mBluetoothAdapter.getRemoteDevice(mBleAddress).unwrap(),
mPreferences.getNumSdpAttempts()));
}
+
BluetoothDevice device =
mBluetoothAdapter.getRemoteDevice(brEdrHandoverInformation.mBluetoothAddress)
.unwrap();
@@ -910,6 +911,7 @@
? null
: new KeyBasedPairingInfo(
mPairingSecret, mGattConnectionManager, mProviderInitiatesBonding);
+
BluetoothAudioPairer pairer =
new BluetoothAudioPairer(
mContext,
@@ -928,7 +930,9 @@
// normally do and we can finish early. It is also more reliable than tearing down the bond
// and recreating it.
try {
- attemptDirectConnectionIfBonded(device, pairer);
+ if (!sTestMode) {
+ attemptDirectConnectionIfBonded(device, pairer);
+ }
callbackOnPaired();
return maybeWriteAccountKey(device);
} catch (PairingException e) {
@@ -1299,26 +1303,6 @@
}
/**
- * Creates cloud syncing intent which saves the Fast Pair device to the account.
- *
- * @param accountKey account key which is written into the Fast Pair device
- * @return cloud syncing intent
- */
- public Intent createCloudSyncingIntent(byte[] accountKey) {
- Intent intent = new Intent(BroadcastConstants.ACTION_FAST_PAIR_DEVICE_ADDED);
- intent.setClassName(BroadcastConstants.PACKAGE_NAME, BroadcastConstants.SERVICE_NAME);
- intent.putExtra(BroadcastConstants.EXTRA_ADDRESS, mBleAddress);
- if (mPublicAddress != null) {
- intent.putExtra(BroadcastConstants.EXTRA_PUBLIC_ADDRESS, mPublicAddress);
- }
- intent.putExtra(BroadcastConstants.EXTRA_ACCOUNT_KEY, accountKey);
- intent.putExtra(
- BroadcastConstants.EXTRA_RETROACTIVE_PAIR, mPreferences.getIsRetroactivePairing());
-
- return intent;
- }
-
- /**
* Checks whether or not an account key should be written to the device and writes it if so.
* This is called after handle notifying the pairedCallback that we've finished pairing, because
* at this point the headset is ready to use.
@@ -1328,7 +1312,9 @@
throws InterruptedException, ExecutionException, TimeoutException,
NoSuchAlgorithmException,
BluetoothException {
- Locator.get(mContext, FastPairController.class).setShouldUpload(false);
+ if (!sTestMode) {
+ Locator.get(mContext, FastPairController.class).setShouldUpload(false);
+ }
if (!shouldWriteAccountKey()) {
// For FastPair 2.0, here should be a subsequent pairing case.
return null;
@@ -1611,95 +1597,6 @@
waitForBluetoothStateUsingPolling(state);
}
- /**
- * Update device name to provider.
- *
- * <pre>
- * A) Connect GATT
- * B) Handshake with provider to get the pairing secret and public address
- * C) Write new device name into provider through name characteristic in GATT
- * D) Disconnect GATT
- * </pre>
- *
- * Synchronous: Blocks until until the name has finished being written. Throws on any error.
- *
- * @param key is a 16-byte account key. See go/fast-pair-2-spec for how these keys are used.
- * @return true if the task is done, i.e. name is written successfully or it is skipped because
- * of unsupported Name characteristic, false if some error happens and may need to re-try.
- */
- @WorkerThread
- public boolean updateProviderName(@Nullable byte[] key, @Nullable String deviceName)
- throws BluetoothException, InterruptedException, TimeoutException, ExecutionException,
- PairingException, GeneralSecurityException {
- if (!mPreferences.getEnableNamingCharacteristic()) {
- Log.i(TAG, "Disable NamingCharacteristic feature, ignoring.");
- return false;
- }
- if (isNullOrEmpty(deviceName)) {
- Log.i(TAG, "Provider name is null or empty, ignoring.");
- return false;
- }
- if (key == null || key.length != AES_BLOCK_LENGTH) {
- Log.i(TAG, "key is null or key length is not account key size.");
- return false;
- }
-
- Log.i(TAG, "Start to update device name for provider.");
- boolean result = false;
- if (mPreferences.getExtraLoggingInformation() != null) {
- this.mEventLogger.bind(
- mContext, mBleAddress, mPreferences.getExtraLoggingInformation());
- }
-
- // Lazily initialize a new connection manager for each renaming request.
- mGattConnectionManager =
- new GattConnectionManager(
- mContext,
- mPreferences,
- mEventLogger,
- mBluetoothAdapter,
- this::toggleBluetooth,
- mBleAddress,
- mTimingLogger,
- mFastPairSignalChecker,
- /* setMtu= */ true);
-
- try (BluetoothGattConnection connection = mGattConnectionManager.getConnection()) {
- UUID characteristicUuid = NameCharacteristic.getId(connection);
- if (!validateBluetoothGattCharacteristic(connection, characteristicUuid)) {
- Log.i(TAG, "Can't find name characteristic, skip to write name with retry times.");
- mGattConnectionManager.closeConnection();
- // Returns true because the task is done with no name characteristic in device.
- return true;
- }
- mEventLogger.setCurrentEvent(EventCode.SECRET_HANDSHAKE);
- // Handshake to get pairing secret for name characteristic decryption and encryption.
- try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger, "Handshake")) {
- handshakeForActionOverBle(key, AdditionalDataType.PERSONALIZED_NAME);
- }
- mEventLogger.logCurrentEventSucceeded();
- // After handshake to get secret, write the name back to provider.
- try (ScopedTiming scopedTiming = new ScopedTiming(mTimingLogger,
- "WriteNameToProvider")) {
- result = writeNameToProvider(deviceName, mPublicAddress);
- }
- } catch (BluetoothException
- | InterruptedException
- | TimeoutException
- | ExecutionException
- | PairingException
- | GeneralSecurityException e) {
- if (mEventLogger.isCurrentEvent()) {
- mEventLogger.logCurrentEventFailed(e);
- }
- throw e;
- } finally {
- mTimingLogger.dump();
- }
- mGattConnectionManager.closeConnection();
- return result;
- }
-
private void waitForBluetoothStateUsingPolling(int state) throws TimeoutException {
// There's a bug where we (pretty often!) never get the broadcast for STATE_ON or STATE_OFF.
// So poll instead.
@@ -1882,9 +1779,13 @@
/* setMtu= */ true);
mGattConnectionManager.closeConnection();
}
+ if (sTestMode) {
+ return null;
+ }
BluetoothGattConnection connection = mGattConnectionManager.getConnection();
connection.setOperationTimeout(
TimeUnit.SECONDS.toMillis(mPreferences.getGattOperationTimeoutSeconds()));
+
try {
String firmwareVersion =
new String(
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
index f8d5a62..b12e263 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
@@ -16,6 +16,8 @@
package com.android.server.nearby.fastpair;
+import static com.android.server.nearby.fastpair.Constant.TAG;
+
import static com.google.common.primitives.Bytes.concat;
import android.accounts.Account;
@@ -45,7 +47,6 @@
public class FastPairAdvHandler {
Context mContext;
String mBleAddress;
- private static final String TAG = "FastPairAdvHandler";
/** The types about how the bloomfilter is processed. */
public enum ProcessBloomFilterType {
@@ -69,26 +70,26 @@
public void handleBroadcast(NearbyDevice device) {
FastPairDevice fastPairDevice = (FastPairDevice) device;
mBleAddress = fastPairDevice.getBluetoothAddress();
+ List<Account> accountList =
+ FastPairDataProvider.getInstance().loadFastPairEligibleAccounts();
if (FastPairDecoder.checkModelId(fastPairDevice.getData())) {
byte[] model = FastPairDecoder.getModelId(fastPairDevice.getData());
- Log.d("FastPairService",
- "On discovery model id " + Hex.bytesToStringLowercase(model));
+ Log.d(TAG, "On discovery model id " + Hex.bytesToStringLowercase(model));
// Use api to get anti spoofing key from model id.
try {
Rpcs.GetObservedDeviceResponse response =
FastPairDataProvider.getInstance()
.loadFastPairAntispoofkeyDeviceMetadata(model);
Locator.get(mContext, FastPairHalfSheetManager.class).showHalfSheet(
- DataUtils.toScanFastPairStoreItem(response, mBleAddress));
+ DataUtils.toScanFastPairStoreItem(
+ response, mBleAddress,
+ accountList.isEmpty() ? null : accountList.get(0).name));
} catch (IllegalStateException e) {
Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
}
-
} else {
// Start to process bloom filter
try {
- List<Account> accountList =
- FastPairDataProvider.getInstance().loadFastPairEligibleAccounts();
Log.d(TAG, "account list size" + accountList.size());
byte[] bloomFilterByteArray = FastPairDecoder
.getBloomFilter(fastPairDevice.getData());
diff --git a/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java b/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java
new file mode 100644
index 0000000..c355df2
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.presence;
+
+import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid;
+
+import java.util.UUID;
+
+/**
+ * Constants for Nearby Presence operations.
+ */
+public class PresenceConstants {
+
+ /** Presence advertisement service data uuid. */
+ public static final UUID PRESENCE_UUID = to128BitUuid((short) 0xFCF1);
+}
diff --git a/nearby/service/java/com/android/server/nearby/presence/PresenceDiscoveryResult.java b/nearby/service/java/com/android/server/nearby/presence/PresenceDiscoveryResult.java
new file mode 100644
index 0000000..7a77aa9
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/presence/PresenceDiscoveryResult.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.presence;
+
+import android.nearby.NearbyDevice;
+import android.nearby.PresenceDevice;
+import android.nearby.PresenceScanFilter;
+import android.nearby.PublicCredential;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a Presence discovery result.
+ */
+public class PresenceDiscoveryResult {
+
+ /**
+ * Creates a {@link PresenceDiscoveryResult} from the scan data.
+ */
+ public static PresenceDiscoveryResult fromScanData(byte[] scanData, int rssi) {
+ return new PresenceDiscoveryResult.Builder().setRssi(rssi).build();
+ }
+
+ private final int mTxPower;
+ private final int mRssi;
+ private final byte[] mSalt;
+ private final List<Integer> mPresenceActions;
+ private final PublicCredential mPublicCredential;
+
+ private PresenceDiscoveryResult(int txPower, int rssi, byte[] salt,
+ List<Integer> presenceActions, PublicCredential publicCredential) {
+ mTxPower = txPower;
+ mRssi = rssi;
+ mSalt = salt;
+ mPresenceActions = presenceActions;
+ mPublicCredential = publicCredential;
+ }
+
+ /**
+ * Returns whether the discovery result matches the scan filter.
+ */
+ public boolean matches(PresenceScanFilter scanFilter) {
+ return pathLossMatches(scanFilter.getMaxPathLoss())
+ && actionMatches(scanFilter.getPresenceActions())
+ && credentialMatches(scanFilter.getCredentials());
+ }
+
+ private boolean pathLossMatches(int maxPathLoss) {
+ return (mTxPower - mRssi) <= maxPathLoss;
+ }
+
+ private boolean actionMatches(List<Integer> filterActions) {
+ if (filterActions.isEmpty()) {
+ return true;
+ }
+ return filterActions.stream().anyMatch(mPresenceActions::contains);
+ }
+
+ private boolean credentialMatches(List<PublicCredential> credentials) {
+ return credentials.contains(mPublicCredential);
+ }
+
+ /**
+ * Converts a presence device from the discovery result.
+ */
+ public PresenceDevice toPresenceDevice() {
+ return new PresenceDevice.Builder()
+ .setRssi(mRssi)
+ .setSalt(mSalt)
+ .setSecretId(mPublicCredential.getSecretId())
+ .addMedium(NearbyDevice.Medium.BLE).build();
+ }
+
+ /**
+ * Builder for {@link PresenceDiscoveryResult}.
+ */
+ public static class Builder {
+ private int mTxPower;
+ private int mRssi;
+ private byte[] mSalt;
+
+ private PublicCredential mPublicCredential;
+ private final List<Integer> mPresenceActions;
+
+ public Builder() {
+ mPresenceActions = new ArrayList<>();
+ }
+
+ /**
+ * Sets the calibrated tx power for the discovery result.
+ */
+ public Builder setTxPower(int txPower) {
+ mTxPower = txPower;
+ return this;
+ }
+
+ /**
+ * Sets the rssi for the discovery result.
+ */
+ public Builder setRssi(int rssi) {
+ mRssi = rssi;
+ return this;
+ }
+
+ /**
+ * Sets the salt for the discovery result.
+ */
+ public Builder setSalt(byte[] salt) {
+ mSalt = salt;
+ return this;
+ }
+
+ /**
+ * Sets the public credential for the discovery result.
+ */
+ public Builder setPublicCredential(PublicCredential publicCredential) {
+ mPublicCredential = publicCredential;
+ return this;
+ }
+
+ /**
+ * Adds presence action of the discovery result.
+ */
+ public Builder addPresenceAction(int presenceAction) {
+ mPresenceActions.add(presenceAction);
+ return this;
+ }
+
+ /**
+ * Builds a {@link PresenceDiscoveryResult}.
+ */
+ public PresenceDiscoveryResult build() {
+ return new PresenceDiscoveryResult(mTxPower, mRssi, mSalt, mPresenceActions,
+ mPublicCredential);
+ }
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
index ae7f133..fc2e4e8 100644
--- a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
@@ -34,6 +34,7 @@
import com.android.server.nearby.common.bluetooth.fastpair.Constants;
import com.android.server.nearby.injector.Injector;
+import com.android.server.nearby.presence.PresenceConstants;
import com.android.server.nearby.util.ForegroundThread;
import java.util.ArrayList;
@@ -47,6 +48,8 @@
public class BleDiscoveryProvider extends AbstractDiscoveryProvider {
private static final ParcelUuid FAST_PAIR_UUID = new ParcelUuid(Constants.FastPairService.ID);
+ private static final ParcelUuid PRESENCE_UUID = new ParcelUuid(PresenceConstants.PRESENCE_UUID);
+
// Don't block the thread as it may be used by other services.
private static final Executor NEARBY_EXECUTOR = ForegroundThread.getExecutor();
private final Injector mInjector;
@@ -69,6 +72,11 @@
byte[] fastPairData = serviceDataMap.get(FAST_PAIR_UUID);
if (fastPairData != null) {
builder.setData(serviceDataMap.get(FAST_PAIR_UUID));
+ } else {
+ byte [] presenceData = serviceDataMap.get(PRESENCE_UUID);
+ if (presenceData != null) {
+ builder.setData(serviceDataMap.get(PRESENCE_UUID));
+ }
}
}
mExecutor.execute(() -> mListener.onNearbyDeviceDiscovered(builder.build()));
diff --git a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
index 27f3acb..825a4ab 100644
--- a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
+++ b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
@@ -16,11 +16,15 @@
package com.android.server.nearby.provider;
+import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE;
+
import static com.android.server.nearby.NearbyService.TAG;
import android.content.Context;
import android.nearby.IScanListener;
import android.nearby.NearbyDeviceParcelable;
+import android.nearby.PresenceScanFilter;
+import android.nearby.ScanFilter;
import android.nearby.ScanRequest;
import android.os.IBinder;
import android.os.RemoteException;
@@ -29,10 +33,13 @@
import com.android.internal.annotations.GuardedBy;
import com.android.server.nearby.injector.Injector;
import com.android.server.nearby.metrics.NearbyMetrics;
+import com.android.server.nearby.presence.PresenceDiscoveryResult;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.stream.Collectors;
/**
* Manages all aspects of discovery providers.
@@ -56,6 +63,16 @@
Log.w(TAG, "DiscoveryProviderManager cannot find the scan record.");
continue;
}
+ if (nearbyDevice.getScanType() == SCAN_TYPE_NEARBY_PRESENCE) {
+ List<ScanFilter> presenceFilters =
+ record.getScanRequest().getScanFilters().stream().filter(
+ scanFilter -> scanFilter.getType()
+ == SCAN_TYPE_NEARBY_PRESENCE).collect(
+ Collectors.toList());
+ if (!presenceFilterMatches(nearbyDevice, presenceFilters)) {
+ continue;
+ }
+ }
try {
record.getScanListener().onDiscovered(
PrivacyFilter.filter(record.getScanRequest().getScanType(),
@@ -175,6 +192,22 @@
mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode);
}
+ private static boolean presenceFilterMatches(NearbyDeviceParcelable device,
+ List<ScanFilter> scanFilters) {
+ if (scanFilters.isEmpty()) {
+ return true;
+ }
+ PresenceDiscoveryResult discoveryResult = PresenceDiscoveryResult.fromScanData(
+ device.getData(), device.getRssi());
+ for (ScanFilter scanFilter : scanFilters) {
+ PresenceScanFilter presenceScanFilter = (PresenceScanFilter) scanFilter;
+ if (discoveryResult.matches(presenceScanFilter)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static class ScanListenerRecord {
private final ScanRequest mScanRequest;
diff --git a/nearby/service/java/com/android/server/nearby/util/DataUtils.java b/nearby/service/java/com/android/server/nearby/util/DataUtils.java
index ce738c8..0b01bc0 100644
--- a/nearby/service/java/com/android/server/nearby/util/DataUtils.java
+++ b/nearby/service/java/com/android/server/nearby/util/DataUtils.java
@@ -17,6 +17,7 @@
package com.android.server.nearby.util;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import service.proto.Cache.ScanFastPairStoreItem;
import service.proto.Cache.StoredDiscoveryItem;
@@ -36,20 +37,36 @@
* Converts a {@link GetObservedDeviceResponse} to a {@link ScanFastPairStoreItem}.
*/
public static ScanFastPairStoreItem toScanFastPairStoreItem(
- GetObservedDeviceResponse observedDeviceResponse, @NonNull String bleAddress) {
+ GetObservedDeviceResponse observedDeviceResponse,
+ @NonNull String bleAddress, @Nullable String account) {
Device device = observedDeviceResponse.getDevice();
+ String deviceName = device.getName();
return ScanFastPairStoreItem.newBuilder()
.setAddress(bleAddress)
.setActionUrl(device.getIntentUri())
- .setDeviceName(device.getName())
+ .setDeviceName(deviceName)
.setIconPng(observedDeviceResponse.getImage())
.setIconFifeUrl(device.getImageUrl())
.setAntiSpoofingPublicKey(device.getAntiSpoofingKeyPair().getPublicKey())
- .setFastPairStrings(getFastPairStrings(observedDeviceResponse))
+ .setFastPairStrings(getFastPairStrings(observedDeviceResponse, deviceName, account))
.build();
}
/**
+ * Prints readable string for a {@link ScanFastPairStoreItem}.
+ */
+ public static String toString(ScanFastPairStoreItem item) {
+ return "ScanFastPairStoreItem=[address:" + item.getAddress()
+ + ", actionUr:" + item.getActionUrl()
+ + ", deviceName:" + item.getDeviceName()
+ + ", iconPng:" + item.getIconPng()
+ + ", iconFifeUrl:" + item.getIconFifeUrl()
+ + ", antiSpoofingKeyPair:" + item.getAntiSpoofingPublicKey()
+ + ", fastPairStrings:" + toString(item.getFastPairStrings())
+ + "]";
+ }
+
+ /**
* Prints readable string for a {@link FastPairStrings}
*/
public static String toString(FastPairStrings fastPairStrings) {
@@ -76,13 +93,17 @@
+ "]";
}
- private static FastPairStrings getFastPairStrings(GetObservedDeviceResponse response) {
+ private static FastPairStrings getFastPairStrings(GetObservedDeviceResponse response,
+ String deviceName, @Nullable String account) {
ObservedDeviceStrings strings = response.getStrings();
return FastPairStrings.newBuilder()
.setTapToPairWithAccount(strings.getInitialNotificationDescription())
.setTapToPairWithoutAccount(
strings.getInitialNotificationDescriptionNoAccount())
- .setInitialPairingDescription(strings.getInitialPairingDescription())
+ .setInitialPairingDescription(account == null
+ ? strings.getInitialNotificationDescriptionNoAccount()
+ : String.format(strings.getInitialPairingDescription(),
+ deviceName, account))
.setPairingFinishedCompanionAppInstalled(
strings.getConnectSuccessCompanionAppInstalled())
.setPairingFinishedCompanionAppNotInstalled(
diff --git a/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java b/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java
index 5bb76c9..6021ff6 100644
--- a/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java
+++ b/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java
@@ -145,7 +145,6 @@
return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER_NO_NOTIFICATION);
}
-
/**
* Get random resolvableData
*/
diff --git a/nearby/tests/multidevices/host/Android.bp b/nearby/tests/multidevices/host/Android.bp
index b0adfba..5aace96 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
+// Run the tests: atest -v CtsSeekerDiscoverProviderTest -- --replicate-parent-setup --multi-device-count 2
python_test_host {
name: "CtsSeekerDiscoverProviderTest",
main: "seeker_discover_provider_test.py",
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java
index 44bab71..a103a72 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java
@@ -27,6 +27,7 @@
import static org.mockito.ArgumentMatchers.anyShort;
import static org.mockito.Mockito.doNothing;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.platform.test.annotations.Presubmit;
@@ -38,7 +39,9 @@
import com.android.server.nearby.common.bluetooth.BluetoothGattException;
import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
+import com.google.protobuf.ByteString;
import junit.framework.TestCase;
@@ -47,6 +50,8 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
/**
@@ -56,11 +61,16 @@
@SmallTest
public class FastPairDualConnectionTest extends TestCase {
- private static final String BLE_ADDRESS = "BLE_ADDRESS";
+ private static final String BLE_ADDRESS = "00:11:22:33:FF:EE";
private static final String MASKED_BLE_ADDRESS = "MASKED_BLE_ADDRESS";
private static final short[] PROFILES = {Constants.A2DP_SINK_SERVICE_UUID};
private static final int NUM_CONNECTION_ATTEMPTS = 1;
private static final boolean ENABLE_PAIRING_BEHAVIOR = true;
+ private static final BluetoothDevice BLUETOOTH_DEVICE = BluetoothAdapter.getDefaultAdapter()
+ .getRemoteDevice("11:22:33:44:55:66");
+ private static final String DEVICE_NAME = "DEVICE_NAME";
+ private static final byte[] ACCOUNT_KEY = new byte[]{1, 3};
+ private static final byte[] HASH_VALUE = new byte[]{7};
private TestEventLogger mEventLogger;
@Mock private TimingLogger mTimingLogger;
@@ -70,6 +80,8 @@
public void setUp() throws Exception {
super.setUp();
+ BluetoothAudioPairer.enableTestMode();
+ FastPairDualConnection.enableTestMode();
MockitoAnnotations.initMocks(this);
doNothing().when(mBluetoothAudioPairer).connect(anyShort(), anyBoolean());
@@ -127,7 +139,7 @@
@SdkSuppress(minSdkVersion = 32, codeName = "T")
- public void appendMoreErrorCode_gattError() {
+ public void testAppendMoreErrorCode_gattError() {
assertThat(
appendMoreErrorCode(
GATT_ERROR_CODE_FAST_PAIR_ADDRESS_ROTATED,
@@ -170,6 +182,131 @@
.isEqualTo(GATT_ERROR_CODE_FAST_PAIR_SIGNAL_LOST);
}
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testUnpairNotCrash() {
+ try {
+ new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().build(),
+ mEventLogger,
+ mTimingLogger).unpair(BLUETOOTH_DEVICE);
+ } catch (ExecutionException | InterruptedException | ReflectionException
+ | TimeoutException | PairingException e) {
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testSetFastPairHistory() {
+ new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().build(),
+ mEventLogger,
+ mTimingLogger).setFastPairHistory(ImmutableList.of());
+ }
+
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testSetGetProviderDeviceName() {
+ FastPairDualConnection connection = new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().build(),
+ mEventLogger,
+ mTimingLogger);
+ connection.setProviderDeviceName(DEVICE_NAME);
+ connection.getProviderDeviceName();
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetExistingAccountKey() {
+ FastPairDualConnection connection = new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().build(),
+ mEventLogger,
+ mTimingLogger);
+ connection.getExistingAccountKey();
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testPair() {
+ FastPairDualConnection connection = new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().setNumSdpAttempts(0)
+ .setLogPairWithCachedModelId(false).build(),
+ mEventLogger,
+ mTimingLogger);
+ try {
+ connection.pair();
+ } catch (BluetoothException | InterruptedException | ReflectionException
+ | ExecutionException | TimeoutException | PairingException e) {
+ }
+ }
+
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetPublicAddress() {
+ FastPairDualConnection connection = new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().setNumSdpAttempts(0)
+ .setLogPairWithCachedModelId(false).build(),
+ mEventLogger,
+ mTimingLogger);
+ connection.getPublicAddress();
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testShouldWriteAccountKeyForExistingCase() {
+ FastPairDualConnection connection = new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().setNumSdpAttempts(0)
+ .setLogPairWithCachedModelId(false).build(),
+ mEventLogger,
+ mTimingLogger);
+ connection.shouldWriteAccountKeyForExistingCase(ACCOUNT_KEY);
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testReadFirmwareVersion() {
+ FastPairDualConnection connection = new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().setNumSdpAttempts(0)
+ .setLogPairWithCachedModelId(false).build(),
+ mEventLogger,
+ mTimingLogger);
+ try {
+ connection.readFirmwareVersion();
+ } catch (BluetoothException | InterruptedException | ExecutionException
+ | TimeoutException e) {
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testHistoryItem() {
+ FastPairDualConnection connection = new FastPairDualConnection(
+ ApplicationProvider.getApplicationContext(),
+ BLE_ADDRESS,
+ Preferences.builder().setNumSdpAttempts(0)
+ .setLogPairWithCachedModelId(false).build(),
+ mEventLogger,
+ mTimingLogger);
+ ImmutableList.Builder<FastPairHistoryItem> historyBuilder = ImmutableList.builder();
+ FastPairHistoryItem historyItem1 =
+ FastPairHistoryItem.create(
+ ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(HASH_VALUE));
+ historyBuilder.add(historyItem1);
+
+ connection.setFastPairHistory(historyBuilder.build());
+ assertThat(connection.mPairedHistoryFinder.isInPairedHistory("11:22:33:44:55:88"))
+ .isFalse();
+ }
+
static class TestEventLogger implements EventLogger {
private List<Item> mLogs = new ArrayList<>();
diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceDiscoveryResultTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceDiscoveryResultTest.java
new file mode 100644
index 0000000..dd94778
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceDiscoveryResultTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.presence;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.nearby.PresenceCredential;
+import android.nearby.PresenceDevice;
+import android.nearby.PresenceScanFilter;
+import android.nearby.PublicCredential;
+
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link PresenceDiscoveryResult}.
+ */
+public class PresenceDiscoveryResultTest {
+ private static final int PRESENCE_ACTION = 123;
+ private static final int TX_POWER = -1;
+ private static final int RSSI = -41;
+ private static final byte[] SALT = new byte[]{12, 34};
+ private static final byte[] SECRET_ID = new byte[]{1, 2, 3, 4};
+ private static final byte[] AUTHENTICITY_KEY = new byte[]{12, 13, 14};
+
+ private PresenceDiscoveryResult.Builder mBuilder;
+ private PublicCredential mCredential;
+
+ @Before
+ public void setUp() {
+ mCredential =
+ new PublicCredential.Builder(SECRET_ID, AUTHENTICITY_KEY)
+ .setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE)
+ .build();
+ mBuilder = new PresenceDiscoveryResult.Builder()
+ .setPublicCredential(mCredential)
+ .setSalt(SALT)
+ .setTxPower(TX_POWER)
+ .setRssi(RSSI)
+ .addPresenceAction(PRESENCE_ACTION);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testToDevice() {
+ PresenceDiscoveryResult discoveryResult = mBuilder.build();
+ PresenceDevice presenceDevice = discoveryResult.toPresenceDevice();
+
+ assertThat(presenceDevice.getRssi()).isEqualTo(RSSI);
+ assertThat(Arrays.equals(presenceDevice.getSalt(), SALT)).isTrue();
+ assertThat(Arrays.equals(presenceDevice.getSecretId(), SECRET_ID)).isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testMatches() {
+ PresenceScanFilter scanFilter = new PresenceScanFilter.Builder()
+ .setMaxPathLoss(80)
+ .addPresenceAction(PRESENCE_ACTION)
+ .addCredential(mCredential)
+ .build();
+
+ PresenceDiscoveryResult discoveryResult = mBuilder.build();
+ assertThat(discoveryResult.matches(scanFilter)).isTrue();
+ }
+
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java
new file mode 100644
index 0000000..9152c07
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.util;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.protobuf.ByteString;
+
+import org.junit.Test;
+
+import service.proto.Cache;
+import service.proto.FastPairString.FastPairStrings;
+import service.proto.Rpcs;
+import service.proto.Rpcs.GetObservedDeviceResponse;
+
+public final class DataUtilsTest {
+ private static final String BLUETOOTH_ADDRESS = "00:11:22:33:FF:EE";
+ private static final String APP_PACKAGE = "test_package";
+ private static final String APP_ACTION_URL =
+ "intent:#Intent;action=cto_be_set%3AACTION_MAGIC_PAIR;"
+ + "package=to_be_set;"
+ + "component=to_be_set;"
+ + "to_be_set%3AEXTRA_COMPANION_APP="
+ + APP_PACKAGE
+ + ";end";
+ private static final long DEVICE_ID = 12;
+ private static final String DEVICE_NAME = "My device";
+ private static final byte[] DEVICE_PUBLIC_KEY = base16().decode("0123456789ABCDEF");
+ private static final String DEVICE_COMPANY = "Company name";
+ private static final byte[] DEVICE_IMAGE = new byte[] {0x00, 0x01, 0x10, 0x11};
+ private static final String DEVICE_IMAGE_URL = "device_image_url";
+ private static final String AUTHORITY = "com.android.test";
+ private static final String SIGNATURE_HASH = "as8dfbyu2duas7ikanvklpaclo2";
+ private static final String ACCOUNT = "test@gmail.com";
+
+ private static final String MESSAGE_INIT_NOTIFY_DESCRIPTION = "message 1";
+ private static final String MESSAGE_INIT_NOTIFY_DESCRIPTION_NO_ACCOUNT = "message 2";
+ private static final String MESSAGE_INIT_PAIR_DESCRIPTION = "message 3 %s";
+ private static final String MESSAGE_COMPANION_INSTALLED = "message 4";
+ private static final String MESSAGE_COMPANION_NOT_INSTALLED = "message 5";
+ private static final String MESSAGE_SUBSEQUENT_PAIR_DESCRIPTION = "message 6";
+ private static final String MESSAGE_RETROACTIVE_PAIR_DESCRIPTION = "message 7";
+ private static final String MESSAGE_WAIT_LAUNCH_COMPANION_APP_DESCRIPTION = "message 8";
+ private static final String MESSAGE_FAIL_CONNECT_DESCRIPTION = "message 9";
+ private static final String MESSAGE_FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION =
+ "message 10";
+ private static final String MESSAGE_ASSISTANT_HALF_SHEET_DESCRIPTION = "message 11";
+ private static final String MESSAGE_ASSISTANT_NOTIFICATION_DESCRIPTION = "message 12";
+
+ @Test
+ public void test_toScanFastPairStoreItem_withAccount() {
+ Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem(
+ createObservedDeviceResponse(), BLUETOOTH_ADDRESS, ACCOUNT);
+ assertThat(item.getAddress()).isEqualTo(BLUETOOTH_ADDRESS);
+ assertThat(item.getActionUrl()).isEqualTo(APP_ACTION_URL);
+ assertThat(item.getDeviceName()).isEqualTo(DEVICE_NAME);
+ assertThat(item.getIconPng()).isEqualTo(ByteString.copyFrom(DEVICE_IMAGE));
+ assertThat(item.getIconFifeUrl()).isEqualTo(DEVICE_IMAGE_URL);
+ assertThat(item.getAntiSpoofingPublicKey())
+ .isEqualTo(ByteString.copyFrom(DEVICE_PUBLIC_KEY));
+
+ FastPairStrings strings = item.getFastPairStrings();
+ assertThat(strings.getTapToPairWithAccount()).isEqualTo(MESSAGE_INIT_NOTIFY_DESCRIPTION);
+ assertThat(strings.getTapToPairWithoutAccount())
+ .isEqualTo(MESSAGE_INIT_NOTIFY_DESCRIPTION_NO_ACCOUNT);
+ assertThat(strings.getInitialPairingDescription())
+ .isEqualTo(String.format(MESSAGE_INIT_PAIR_DESCRIPTION, DEVICE_NAME));
+ assertThat(strings.getPairingFinishedCompanionAppInstalled())
+ .isEqualTo(MESSAGE_COMPANION_INSTALLED);
+ assertThat(strings.getPairingFinishedCompanionAppNotInstalled())
+ .isEqualTo(MESSAGE_COMPANION_NOT_INSTALLED);
+ assertThat(strings.getSubsequentPairingDescription())
+ .isEqualTo(MESSAGE_SUBSEQUENT_PAIR_DESCRIPTION);
+ assertThat(strings.getRetroactivePairingDescription())
+ .isEqualTo(MESSAGE_RETROACTIVE_PAIR_DESCRIPTION);
+ assertThat(strings.getWaitAppLaunchDescription())
+ .isEqualTo(MESSAGE_WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+ assertThat(strings.getPairingFailDescription())
+ .isEqualTo(MESSAGE_FAIL_CONNECT_DESCRIPTION);
+ assertThat(strings.getAssistantHalfSheetDescription())
+ .isEqualTo(MESSAGE_ASSISTANT_HALF_SHEET_DESCRIPTION);
+ assertThat(strings.getAssistantNotificationDescription())
+ .isEqualTo(MESSAGE_ASSISTANT_NOTIFICATION_DESCRIPTION);
+ assertThat(strings.getFastPairTvConnectDeviceNoAccountDescription())
+ .isEqualTo(MESSAGE_FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION);
+ }
+
+ @Test
+ public void test_toScanFastPairStoreItem_withoutAccount() {
+ Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem(
+ createObservedDeviceResponse(), BLUETOOTH_ADDRESS, /* account= */ null);
+ FastPairStrings strings = item.getFastPairStrings();
+ assertThat(strings.getInitialPairingDescription())
+ .isEqualTo(MESSAGE_INIT_NOTIFY_DESCRIPTION_NO_ACCOUNT);
+ }
+
+ @Test
+ public void test_toString() {
+ Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem(
+ createObservedDeviceResponse(), BLUETOOTH_ADDRESS, ACCOUNT);
+ FastPairStrings strings = item.getFastPairStrings();
+
+ assertThat(DataUtils.toString(strings))
+ .isEqualTo("FastPairStrings[tapToPairWithAccount=message 1, "
+ + "tapToPairWithoutAccount=message 2, "
+ + "initialPairingDescription=message 3 " + DEVICE_NAME + ", "
+ + "pairingFinishedCompanionAppInstalled=message 4, "
+ + "pairingFinishedCompanionAppNotInstalled=message 5, "
+ + "subsequentPairingDescription=message 6, "
+ + "retroactivePairingDescription=message 7, "
+ + "waitAppLaunchDescription=message 8, "
+ + "pairingFailDescription=message 9, "
+ + "assistantHalfSheetDescription=message 11, "
+ + "assistantNotificationDescription=message 12, "
+ + "fastPairTvConnectDeviceNoAccountDescription=message 10]");
+ }
+
+ private static GetObservedDeviceResponse createObservedDeviceResponse() {
+ return GetObservedDeviceResponse.newBuilder()
+ .setDevice(
+ Rpcs.Device.newBuilder()
+ .setId(DEVICE_ID)
+ .setName(DEVICE_NAME)
+ .setAntiSpoofingKeyPair(
+ Rpcs.AntiSpoofingKeyPair
+ .newBuilder()
+ .setPublicKey(
+ ByteString.copyFrom(DEVICE_PUBLIC_KEY)))
+ .setIntentUri(APP_ACTION_URL)
+ .setDataOnlyConnection(true)
+ .setAssistantSupported(false)
+ .setCompanionDetail(
+ Rpcs.CompanionAppDetails.newBuilder()
+ .setAuthority(AUTHORITY)
+ .setCertificateHash(SIGNATURE_HASH)
+ .build())
+ .setCompanyName(DEVICE_COMPANY)
+ .setImageUrl(DEVICE_IMAGE_URL))
+ .setImage(ByteString.copyFrom(DEVICE_IMAGE))
+ .setStrings(
+ Rpcs.ObservedDeviceStrings.newBuilder()
+ .setInitialNotificationDescription(MESSAGE_INIT_NOTIFY_DESCRIPTION)
+ .setInitialNotificationDescriptionNoAccount(
+ MESSAGE_INIT_NOTIFY_DESCRIPTION_NO_ACCOUNT)
+ .setInitialPairingDescription(MESSAGE_INIT_PAIR_DESCRIPTION)
+ .setConnectSuccessCompanionAppInstalled(MESSAGE_COMPANION_INSTALLED)
+ .setConnectSuccessCompanionAppNotInstalled(
+ MESSAGE_COMPANION_NOT_INSTALLED)
+ .setSubsequentPairingDescription(
+ MESSAGE_SUBSEQUENT_PAIR_DESCRIPTION)
+ .setRetroactivePairingDescription(
+ MESSAGE_RETROACTIVE_PAIR_DESCRIPTION)
+ .setWaitLaunchCompanionAppDescription(
+ MESSAGE_WAIT_LAUNCH_COMPANION_APP_DESCRIPTION)
+ .setFailConnectGoToSettingsDescription(
+ MESSAGE_FAIL_CONNECT_DESCRIPTION)
+ .setAssistantSetupHalfSheet(
+ MESSAGE_ASSISTANT_HALF_SHEET_DESCRIPTION)
+ .setAssistantSetupNotification(
+ MESSAGE_ASSISTANT_NOTIFICATION_DESCRIPTION)
+ .setFastPairTvConnectDeviceNoAccountDescription(
+ MESSAGE_FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION))
+ .build();
+ }
+}