Scanner client side code
Add NearbyManager, IScanListener.aidl, INearbyManager.aidl,
NearbyDevice, FastPairDevice, ScanCallback which returns NearbyDevice to callers
and NearbyDeviceParcelable for service and client communication.
Test: Successfully built. Unit test will be added.
Bug: 189954300
Change-Id: I71366f888e245f1175a67b6d4040898e35f49c35
diff --git a/nearby/framework/java/android/nearby/FastPairDevice.java b/nearby/framework/java/android/nearby/FastPairDevice.java
new file mode 100644
index 0000000..3f1fd42
--- /dev/null
+++ b/nearby/framework/java/android/nearby/FastPairDevice.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nearby;
+
+import android.annotation.Hide;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A class represents a Fast Pair device that can be discovered by multiple mediums.
+ *
+ * @hide
+ */
+public class FastPairDevice extends NearbyDevice implements Parcelable {
+ public static final Creator<FastPairDevice> CREATOR = new Creator<FastPairDevice>() {
+ @Override
+ public FastPairDevice createFromParcel(Parcel in) {
+ FastPairDevice.Builder builder = new FastPairDevice.Builder();
+ if (in.readInt() == 1) {
+ builder.setName(in.readString());
+ }
+ builder.setMedium(in.readInt());
+ builder.setRssi(in.readInt());
+ if (in.readInt() == 1) {
+ builder.setModelId(in.readString());
+ }
+ builder.setBluetoothAddress(in.readString());
+ if (in.readInt() == 1) {
+ int dataLength = in.readInt();
+ byte[] data = new byte[dataLength];
+ in.readByteArray(data);
+ builder.setData(data);
+ }
+ return builder.build();
+ }
+
+ @Override
+ public FastPairDevice[] newArray(int size) {
+ return new FastPairDevice[size];
+ }
+ };
+
+ // Some OEM devices devices don't have model Id.
+ @Nullable private final String mModelId;
+
+ // Bluetooth hardware address as string. Can be read from BLE ScanResult.
+ private final String mBluetoothAddress;
+
+ @Nullable
+ private final byte[] mData;
+
+ public FastPairDevice(@Nullable String name,
+ @Medium int medium,
+ int rssi,
+ @Nullable String modelId,
+ @NonNull String bluetoothAddress,
+ @Nullable byte[] data) {
+ super(name, medium, rssi);
+ this.mModelId = modelId;
+ this.mBluetoothAddress = bluetoothAddress;
+ this.mData = data;
+ }
+
+ @Nullable
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public int getMedium() {
+ return mMedium;
+ }
+
+ @Override
+ public float getRssi() {
+ return mRssi;
+ }
+
+ @Nullable
+ public String getModelId() {
+ return this.mModelId;
+ }
+
+ @NonNull
+ public String getBluetoothAddress() {
+ return mBluetoothAddress;
+ }
+
+ // Only visible to system clients.
+ @Nullable
+ @Hide
+ public byte[] getData() {
+ return mData;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("FastPairDevice [");
+ if (mName != null && !mName.isEmpty()) {
+ stringBuilder.append("name=").append(mName).append(", ");
+ }
+ stringBuilder.append("medium=").append(mediumToString(mMedium));
+ stringBuilder.append(" rssi=").append(mRssi);
+ stringBuilder.append(" modelId=").append(mModelId);
+ stringBuilder.append(" bluetoothAddress=").append(mBluetoothAddress);
+ stringBuilder.append("]");
+ return stringBuilder.toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof FastPairDevice) {
+ FastPairDevice otherDevice = (FastPairDevice) other;
+ if (!super.equals(other)) {
+ return false;
+ }
+ return Objects.equals(mModelId, otherDevice.mModelId)
+ && Objects.equals(mBluetoothAddress, otherDevice.mBluetoothAddress)
+ && Arrays.equals(mData, otherDevice.mData);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mName, mMedium, mRssi, mModelId, mBluetoothAddress, Arrays.hashCode(mData));
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mName == null ? 0 : 1);
+ if (mName != null) {
+ dest.writeString(mName);
+ }
+ dest.writeInt(mMedium);
+ dest.writeInt(mRssi);
+ dest.writeInt(mModelId == null ? 0 : 1);
+ if (mModelId != null) {
+ dest.writeString(mModelId);
+ }
+ dest.writeString(mBluetoothAddress);
+ dest.writeInt(mData == null ? 0 : 1);
+ if (mData != null) {
+ dest.writeInt(mData.length);
+ dest.writeByteArray(mData);
+ }
+ }
+
+ /**
+ * A builder class for {@link FastPairDevice}
+ *
+ * @hide
+ */
+ public static final class Builder extends NearbyDevice.Builder {
+
+ @Nullable private String mName;
+ @Medium private int mMedium;
+ private int mRssi;
+ @Nullable private String mModelId;
+ private String mBluetoothAddress;
+ @Nullable private byte[] mData;
+ /**
+ * Sets the name of the Fast Pair device.
+ */
+ @NonNull
+ public Builder setName(@Nullable String name) {
+ mName = name;
+ return this;
+ }
+
+ /**
+ * Sets the medium over which the Fast Pair device is discovered.
+ */
+ @NonNull
+ public Builder setMedium(@Medium int medium) {
+ mMedium = medium;
+ return this;
+ }
+
+ /**
+ * Sets the RSSI between the scan device and the discovered Fast Pair device.
+ */
+ @NonNull
+ public Builder setRssi(int rssi) {
+ mRssi = rssi;
+ return this;
+ }
+
+ /**
+ * Sets the model Id of this Fast Pair device.
+ */
+ @NonNull
+ public Builder setModelId(String modelId) {
+ mModelId = modelId;
+ return this;
+ }
+
+ /**
+ * Sets the hardware address of this BluetoothDevice.
+ */
+ @NonNull
+ public Builder setBluetoothAddress(@NonNull String maskedBluetoothAddress) {
+ mBluetoothAddress = maskedBluetoothAddress;
+ return this;
+ }
+
+ /**
+ * Sets the raw data. Only visible to system API.
+ * @hide
+ */
+ @Hide
+ @NonNull
+ public Builder setData(byte[] data) {
+ mData = data;
+ return this;
+ }
+
+ /**
+ * Builds a FastPairDevice and return it.
+ */
+ @NonNull
+ public FastPairDevice build() {
+ return new FastPairDevice(mName, mMedium, mRssi, mModelId,
+ mBluetoothAddress, mData);
+ }
+ }
+}
diff --git a/nearby/framework/java/android/nearby/INearbyManager.aidl b/nearby/framework/java/android/nearby/INearbyManager.aidl
index 031c31d..cd05106 100644
--- a/nearby/framework/java/android/nearby/INearbyManager.aidl
+++ b/nearby/framework/java/android/nearby/INearbyManager.aidl
@@ -1,23 +1,32 @@
-// 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.
+/*
+ * Copyright (C) 2007, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package android.nearby;
+import android.nearby.IScanListener;
+import android.nearby.ScanRequest;
+
/**
* Interface for communicating with the nearby services.
*
* @hide
*/
interface INearbyManager {
+
+ void registerScanListener(in ScanRequest scanRequest, in IScanListener listener);
+
+ void unregisterScanListener(in IScanListener listener);
}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/IScanListener.aidl b/nearby/framework/java/android/nearby/IScanListener.aidl
new file mode 100644
index 0000000..8747b07
--- /dev/null
+++ b/nearby/framework/java/android/nearby/IScanListener.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2007, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nearby;
+
+import android.nearby.NearbyDeviceParcelable;
+
+/**
+ * Binder callback for ScanCallback.
+ *
+ * {@hide}
+ */
+oneway interface IScanListener {
+ /** Reports a {@link NearbyDevice} being discovered. */
+ void onDiscovered(in NearbyDeviceParcelable nearbyDeviceParcelable);
+
+ /** Reports a {@link NearbyDevice} information(distance, packet, and etc) changed. */
+ void onUpdated(in NearbyDeviceParcelable nearbyDeviceParcelable);
+
+ /** Reports a {@link NearbyDevice} is no longer within range. */
+ void onLost(in NearbyDeviceParcelable nearbyDeviceParcelable);
+}
diff --git a/nearby/framework/java/android/nearby/NearbyDevice.java b/nearby/framework/java/android/nearby/NearbyDevice.java
new file mode 100644
index 0000000..0d82530
--- /dev/null
+++ b/nearby/framework/java/android/nearby/NearbyDevice.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nearby;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * A class represents a device that can be discovered by multiple mediums.
+ *
+ * @hide
+ */
+public abstract class NearbyDevice {
+
+ @Nullable
+ final String mName;
+
+ @Medium
+ final int mMedium;
+
+ final int mRssi;
+
+ /**
+ * @hide
+ */
+ public NearbyDevice(@Nullable String name, @Medium int medium, int rssi) {
+ Preconditions.checkState(isValidMedium(medium),
+ "Not supported medium: " + medium
+ + ", scan medium must be one of NearbyDevice#Medium.");
+ mName = name;
+ mMedium = medium;
+ mRssi = rssi;
+ }
+
+ static String mediumToString(@Medium int medium) {
+ switch (medium) {
+ case Medium.BLE:
+ return "BLE";
+ case Medium.BLUETOOTH:
+ return "Bluetooth Classic";
+ default:
+ return "Unknown";
+ }
+ }
+
+ /**
+ * True if the medium is defined in {@link Medium}.
+ */
+ public static boolean isValidMedium(@Medium int medium) {
+ return medium == Medium.BLE
+ || medium == Medium.BLUETOOTH;
+ }
+
+ /**
+ * The name of the device, or null if not available.
+ *
+ * @hide
+ */
+ @Nullable
+ public String getName() {
+ return mName;
+ }
+
+ /** The medium over which this device was discovered. */
+ @Medium
+ public int getMedium() {
+ return mMedium;
+ }
+
+ /**
+ * The RSSI this device is away.
+ */
+ public float getRssi() {
+ return mRssi;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("NearbyDevice [");
+ if (mName != null && !mName.isEmpty()) {
+ stringBuilder.append("name=").append(mName).append(", ");
+ }
+ stringBuilder.append("medium=").append(mediumToString(mMedium));
+ stringBuilder.append(" rssi=").append(mRssi);
+ stringBuilder.append("]");
+ return stringBuilder.toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof NearbyDevice) {
+ NearbyDevice otherDevice = (NearbyDevice) other;
+ return Objects.equals(mName, otherDevice.mName)
+ && mMedium == otherDevice.mMedium
+ && mRssi == otherDevice.mRssi;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mName, mMedium, mRssi);
+ }
+
+ /**
+ * The medium where a NearbyDevice was discovered on.
+ *
+ * @hide
+ */
+ @IntDef({Medium.BLE, Medium.BLUETOOTH})
+ public @interface Medium {
+ int BLE = 1;
+ int BLUETOOTH = 2;
+ }
+
+ /**
+ * Builder for a NearbyDevice.
+ */
+ public abstract static class Builder {
+
+ /**
+ * Sets the name of Nearby Device.
+ */
+ public abstract Builder setName(String name);
+
+ /**
+ * Sets the medium over which the Nearby Device is discovered.
+ */
+ public abstract Builder setMedium(int medium);
+
+ /**
+ * Sets the RSSI between scanned device and the discovered device.
+ */
+ public abstract Builder setRssi(int rssi);
+
+ /**
+ * Builds the Nearby Device.
+ */
+ public abstract NearbyDevice build();
+ }
+}
+
diff --git a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.aidl b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.aidl
new file mode 100644
index 0000000..e211187
--- /dev/null
+++ b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2012, 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;
+
+parcelable NearbyDeviceParcelable;
+
diff --git a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
new file mode 100644
index 0000000..6910d13
--- /dev/null
+++ b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nearby;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A data class representing scan result from Nearby Service. Scan result can come from multiple
+ * mediums like BLE, Wi-Fi Aware, and etc.
+ * A scan result consists of
+ * An encapsulation of various parameters for requesting nearby scans.
+ *
+ * <p>All scan results generated through {@link NearbyManager} are guaranteed to have a valid
+ * medium, identifier, timestamp (both UTC time and elapsed real-time since boot), and accuracy.
+ * All other parameters are optional.
+ *
+ * @hide
+ */
+public final class NearbyDeviceParcelable implements Parcelable {
+ public static final Creator<NearbyDeviceParcelable> CREATOR =
+ new Creator<NearbyDeviceParcelable>() {
+ @Override
+ public NearbyDeviceParcelable createFromParcel(Parcel in) {
+ Builder builder = new Builder();
+ if (in.readInt() == 1) {
+ builder.setName(in.readString());
+ }
+ builder.setMedium(in.readInt());
+ builder.setRssi(in.readInt());
+ if (in.readInt() == 1) {
+ builder.setFastPairModelId(in.readString());
+ }
+ if (in.readInt() == 1) {
+ int dataLength = in.readInt();
+ byte[] data = new byte[dataLength];
+ in.readByteArray(data);
+ builder.setData(data);
+ }
+ return builder.build();
+ }
+
+ @Override
+ public NearbyDeviceParcelable[] newArray(int size) {
+ return new NearbyDeviceParcelable[size];
+ }
+ };
+ @Nullable
+ private final String mName;
+ @NearbyDevice.Medium
+ private final int mMedium;
+ private final int mRssi;
+
+ @Nullable
+ private final String mBluetoothAddress;
+ @Nullable
+ private final String mFastPairModelId;
+ @Nullable
+ private final byte[] mData;
+
+ private NearbyDeviceParcelable(@Nullable String name, int medium, int rssi,
+ @Nullable String fastPairModelId, @Nullable String bluetoothAddress, byte[] data) {
+ mName = name;
+ mMedium = medium;
+ mRssi = rssi;
+ mFastPairModelId = fastPairModelId;
+ mBluetoothAddress = bluetoothAddress;
+ mData = data;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mName == null ? 0 : 1);
+ if (mName != null) {
+ dest.writeString(mName);
+ }
+ dest.writeInt(mMedium);
+ dest.writeInt(mRssi);
+ dest.writeInt(mFastPairModelId == null ? 0 : 1);
+ if (mFastPairModelId != null) {
+ dest.writeString(mFastPairModelId);
+ }
+ dest.writeInt(mBluetoothAddress == null ? 0 : 1);
+ if (mFastPairModelId != null) {
+ dest.writeString(mBluetoothAddress);
+ }
+ dest.writeInt(mData == null ? 0 : 1);
+ if (mData != null) {
+ dest.writeInt(mData.length);
+ dest.writeByteArray(mData);
+ }
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public int getMedium() {
+ return mMedium;
+ }
+
+ public int getRssi() {
+ return mRssi;
+ }
+
+ @Nullable
+ public String getFastPairModelId() {
+ return mFastPairModelId;
+ }
+
+ @Nullable
+ public String getBluetoothAddress() {
+ return mBluetoothAddress;
+ }
+
+ public byte[] getData() {
+ return mData;
+ }
+
+ /**
+ * Builder class for {@link NearbyDeviceParcelable}.
+ */
+ public static final class Builder {
+ @Nullable
+ private String mName;
+ @NearbyDevice.Medium
+ private int mMedium;
+ private int mRssi;
+ @Nullable
+ private String mFastPairModelId;
+ @Nullable
+ private String mBluetoothAddress;
+ @Nullable
+ private byte[] mData;
+
+ /**
+ * Sets the name of the scanned device.
+ */
+ public Builder setName(String name) {
+ mName = name;
+ return this;
+ }
+
+ /**
+ * Sets the medium over which the device is discovered.
+ */
+ public Builder setMedium(int medium) {
+ mMedium = medium;
+ return this;
+ }
+
+ /**
+ * Sets the RSSI between scanned device and the discovered device.
+ */
+ public Builder setRssi(int rssi) {
+ mRssi = rssi;
+ return this;
+ }
+
+ /**
+ * Sets the identifier of the device.
+ */
+ public Builder setFastPairModelId(String fastPairModelId) {
+ mFastPairModelId = fastPairModelId;
+ return this;
+ }
+
+ /**
+ * Sets the scanned Fast Pair data.
+ */
+ public Builder setBluetoothAddress(String bluetoothAddress) {
+ mBluetoothAddress = bluetoothAddress;
+ return this;
+ }
+
+ /**
+ * Sets the raw data.
+ */
+ public Builder setData(byte[] data) {
+ mData = data;
+ return this;
+ }
+
+ /**
+ * Builds a ScanResult.
+ */
+ public NearbyDeviceParcelable build() {
+ return new NearbyDeviceParcelable(mName, mMedium, mRssi, mFastPairModelId,
+ mBluetoothAddress, mData);
+ }
+ }
+}
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index b75e640..d8f6917 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -16,6 +16,19 @@
package android.nearby;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+import java.util.WeakHashMap;
+import java.util.concurrent.Executor;
+
/**
* This class provides a way to perform Nearby related operations such as scanning and connecting
* to nearby devices.
@@ -25,6 +38,139 @@
*
* @hide
*/
-// TODO(b/189954300): implement NearyManager API and unhide it.
public class NearbyManager {
+
+ private static final String TAG = "NearbyManager";
+ @GuardedBy("sScanListeners")
+ private static final WeakHashMap<ScanCallback, WeakReference<ScanListenerTransport>>
+ sScanListeners = new WeakHashMap<>();
+ private final INearbyManager mService;
+
+ public NearbyManager(@NonNull INearbyManager service) {
+ mService = service;
+ }
+
+ private static NearbyDevice toClientNearbyDevice(
+ NearbyDeviceParcelable nearbyDeviceParcelable,
+ @ScanRequest.ScanType int scanType) {
+ if (scanType == ScanRequest.SCAN_TYPE_FAST_PAIR) {
+ return new FastPairDevice.Builder()
+ .setName(nearbyDeviceParcelable.getName())
+ .setMedium(nearbyDeviceParcelable.getMedium())
+ .setRssi(nearbyDeviceParcelable.getRssi())
+ .setModelId(nearbyDeviceParcelable.getFastPairModelId())
+ .setBluetoothAddress(nearbyDeviceParcelable.getBluetoothAddress())
+ .setData(nearbyDeviceParcelable.getData()).build();
+ }
+ return null;
+ }
+
+ /**
+ * Start scan for nearby devices with given parameters. Devices matching {@link ScanRequest}
+ * will be delivered through the given callback.
+ *
+ * @param executor Executor where the listener method is called.
+ */
+ public void startScan(ScanRequest scanRequest, ScanCallback scanCallback, @CallbackExecutor
+ Executor executor) {
+ Objects.requireNonNull(scanRequest, "scanRequest must not be null");
+ Objects.requireNonNull(scanCallback, "scanCallback must not be null");
+ Objects.requireNonNull(executor, "executor must not be null");
+
+ try {
+ synchronized (sScanListeners) {
+ WeakReference<ScanListenerTransport> reference = sScanListeners.get(scanCallback);
+ ScanListenerTransport transport = reference != null ? reference.get() : null;
+ if (transport == null) {
+ transport = new ScanListenerTransport(scanRequest.getScanType(), scanCallback,
+ executor);
+ } else {
+ Preconditions.checkState(transport.isRegistered());
+ transport.setExecutor(executor);
+ }
+ mService.registerScanListener(scanRequest, transport);
+ sScanListeners.put(scanCallback, new WeakReference<>(transport));
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Stops the nearby device scan for the specified callback. The given callback
+ * is guaranteed not to receive any invocations that happen after this method
+ * is invoked.
+ */
+ public void stopScan(@NonNull ScanCallback scanCallback) {
+ Preconditions.checkArgument(scanCallback != null,
+ "invalid null scanCallback");
+ try {
+ synchronized (sScanListeners) {
+ WeakReference<ScanListenerTransport> reference = sScanListeners.remove(
+ scanCallback);
+ ScanListenerTransport transport = reference != null ? reference.get() : null;
+ if (transport != null) {
+ transport.unregister();
+ mService.unregisterScanListener(transport);
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static class ScanListenerTransport extends IScanListener.Stub {
+
+ private @ScanRequest.ScanType int mScanType;
+ private volatile @Nullable ScanCallback mScanCallback;
+ private Executor mExecutor;
+
+ ScanListenerTransport(@ScanRequest.ScanType int scanType, ScanCallback scanCallback,
+ @CallbackExecutor Executor executor) {
+ Preconditions.checkArgument(scanCallback != null,
+ "invalid null callback");
+ Preconditions.checkState(ScanRequest.isValidScanType(mScanType),
+ "invalid scan type : " + mScanType
+ + ", scan type must be one of ScanRequest#SCAN_TYPE_");
+ mScanType = scanType;
+ mScanCallback = scanCallback;
+ mExecutor = executor;
+ }
+
+ void setExecutor(Executor executor) {
+ Preconditions.checkArgument(
+ executor != null, "invalid null executor");
+ mExecutor = executor;
+ }
+
+ boolean isRegistered() {
+ return mScanCallback != null;
+ }
+
+ void unregister() {
+ mScanCallback = null;
+ }
+
+ @Override
+ public void onDiscovered(NearbyDeviceParcelable nearbyDeviceParcelable)
+ throws RemoteException {
+ mExecutor.execute(() -> mScanCallback.onDiscovered(
+ toClientNearbyDevice(nearbyDeviceParcelable, mScanType)));
+ }
+
+ @Override
+ public void onUpdated(NearbyDeviceParcelable nearbyDeviceParcelable)
+ throws RemoteException {
+ mExecutor.execute(
+ () -> mScanCallback.onUpdated(
+ toClientNearbyDevice(nearbyDeviceParcelable, mScanType)));
+ }
+
+ @Override
+ public void onLost(NearbyDeviceParcelable nearbyDeviceParcelable) throws RemoteException {
+ mExecutor.execute(
+ () -> mScanCallback.onLost(
+ toClientNearbyDevice(nearbyDeviceParcelable, mScanType)));
+ }
+ }
}
diff --git a/nearby/framework/java/android/nearby/ScanCallback.java b/nearby/framework/java/android/nearby/ScanCallback.java
new file mode 100644
index 0000000..498e672
--- /dev/null
+++ b/nearby/framework/java/android/nearby/ScanCallback.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nearby;
+
+import android.annotation.NonNull;
+
+/**
+ * Reports newly discovered devices.
+ * Note: The frequency of the callback is dependent on whether the caller
+ * is in the foreground or background. Foreground callbacks will occur
+ * as fast as the underlying medium supports, whereas background
+ * use cases will be rate limited to improve performance (ie, only on
+ * found/lost/significant changes).
+ *
+ * @hide
+ */
+public interface ScanCallback {
+ /** Reports a {@link NearbyDevice} being discovered. */
+ void onDiscovered(@NonNull NearbyDevice device);
+
+ /** Reports a {@link NearbyDevice} information(distance, packet, and etc) changed. */
+ void onUpdated(@NonNull NearbyDevice device);
+
+ /** Reports a {@link NearbyDevice} is no longer within range. */
+ void onLost(@NonNull NearbyDevice device);
+}
diff --git a/nearby/framework/java/android/nearby/ScanRequest.aidl b/nearby/framework/java/android/nearby/ScanRequest.aidl
new file mode 100644
index 0000000..0aaf3af
--- /dev/null
+++ b/nearby/framework/java/android/nearby/ScanRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2012, 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;
+
+parcelable ScanRequest;
diff --git a/nearby/framework/java/android/nearby/ScanRequest.java b/nearby/framework/java/android/nearby/ScanRequest.java
index 56da371..c9a4072 100644
--- a/nearby/framework/java/android/nearby/ScanRequest.java
+++ b/nearby/framework/java/android/nearby/ScanRequest.java
@@ -38,12 +38,6 @@
*/
public final class ScanRequest implements Parcelable {
- /** @hide **/
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({SCAN_TYPE_FAST_PAIR, SCAN_TYPE_NEARBY_SHARE, SCAN_TYPE_NEARBY_PRESENCE,
- SCAN_TYPE_EXPOSURE_NOTIFICATION})
- public @interface ScanType{}
-
/** Scan type for scanning devices using fast pair protocol. */
public static final int SCAN_TYPE_FAST_PAIR = 1;
/** Scan type for scanning devices using nearby share protocol. */
@@ -52,16 +46,87 @@
public static final int SCAN_TYPE_NEARBY_PRESENCE = 3;
/** Scan type for scanning devices using exposure notification protocol. */
public static final int SCAN_TYPE_EXPOSURE_NOTIFICATION = 4;
+ /** Scan mode uses highest duty cycle. */
+ public static final int SCAN_MODE_LOW_LATENCY = 2;
+ /** Scan in balanced power mode.
+ * Scan results are returned at a rate that provides a good trade-off between scan
+ * frequency and power consumption.
+ */
+ public static final int SCAN_MODE_BALANCED = 1;
+ /** Perform scan in low power mode. This is the default scan mode. */
+ public static final int SCAN_MODE_LOW_POWER = 0;
+ /**
+ * A special scan mode. Applications using this scan mode will passively listen for other scan
+ * results without starting BLE scans themselves.
+ */
+ public static final int SCAN_MODE_NO_POWER = -1;
+ public static final Creator<ScanRequest> CREATOR = new Creator<ScanRequest>() {
+ @Override
+ public ScanRequest createFromParcel(Parcel in) {
+ return new ScanRequest(
+ /* scanType= */ in.readInt(),
+ /* scanMode= */ in.readInt(),
+ /* enableBle= */ in.readBoolean(),
+ /* workSource= */ in.readTypedObject(WorkSource.CREATOR));
+ }
+ @Override
+ public ScanRequest[] newArray(int size) {
+ return new ScanRequest[size];
+ }
+ };
private final @ScanType int mScanType;
- private final @Nullable WorkSource mWorkSource;
+ private final @ScanMode int mScanMode;
+ private final boolean mEnableBle;
+ private final WorkSource mWorkSource;
- private ScanRequest(@ScanType int scanType, @Nullable WorkSource workSource) {
+ private ScanRequest(@ScanType int scanType, @ScanMode int scanMode, boolean enableBle,
+ WorkSource workSource) {
mScanType = scanType;
+ mScanMode = scanMode;
+ mEnableBle = enableBle;
mWorkSource = workSource;
}
/**
+ * Convert scan mode to readable string.
+ */
+ public static String scanModeToString(@ScanMode int scanMode) {
+ switch (scanMode) {
+ case SCAN_MODE_LOW_LATENCY:
+ return "SCAN_MODE_LOW_LATENCY";
+ case SCAN_MODE_BALANCED:
+ return "SCAN_MODE_BALANCED";
+ case SCAN_MODE_LOW_POWER:
+ return "SCAN_MODE_LOW_POWER";
+ case SCAN_MODE_NO_POWER:
+ return "SCAN_MODE_NO_POWER";
+ default:
+ return "SCAN_MODE_INVALID";
+ }
+ }
+
+ /**
+ * Returns true if an integer is a defined scan type.
+ */
+ public static boolean isValidScanType(int scanType) {
+ return scanType == SCAN_TYPE_FAST_PAIR
+ || scanType == SCAN_TYPE_NEARBY_SHARE
+ || scanType == SCAN_TYPE_NEARBY_PRESENCE
+ || scanType == SCAN_TYPE_EXPOSURE_NOTIFICATION;
+ }
+
+ /**
+ * Returns true if an integer is a defined scan mode.
+ */
+ public static boolean isValidScanMode(int scanMode) {
+ return scanMode == SCAN_MODE_LOW_LATENCY
+ || scanMode == SCAN_MODE_BALANCED
+ || scanMode == SCAN_MODE_LOW_POWER
+ || scanMode == SCAN_MODE_NO_POWER;
+ }
+
+ /**
* Returns the scan type for this request.
*/
public @ScanType int getScanType() {
@@ -69,28 +134,28 @@
}
/**
+ * Returns the scan mode for this request.
+ */
+ public @ScanMode int getScanMode() {
+ return mScanMode;
+ }
+
+ /**
+ * Returns if Bluetooth Low Energy enabled for scanning.
+ */
+ public boolean isEnableBle() {
+ return mEnableBle;
+ }
+
+ /**
* Returns the work source used for power attribution of this request.
*
* @hide
*/
- public @Nullable WorkSource getWorkSource() {
+ public WorkSource getWorkSource() {
return mWorkSource;
}
- public static final Creator<ScanRequest> CREATOR = new Creator<ScanRequest>() {
- @Override
- public ScanRequest createFromParcel(Parcel in) {
- return new ScanRequest(
- /* scanType= */ in.readInt(),
- /* workSource= */ in.readTypedObject(WorkSource.CREATOR));
- }
-
- @Override
- public ScanRequest[] newArray(int size) {
- return new ScanRequest[size];
- }
- };
-
@Override
public int describeContents() {
return 0;
@@ -101,9 +166,9 @@
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Request[")
.append("scanType=").append(mScanType);
- if (mWorkSource != null && !mWorkSource.isEmpty()) {
- stringBuilder.append(", workSource=").append(mWorkSource);
- }
+ stringBuilder.append(", scanMode=").append(scanModeToString(mScanMode));
+ stringBuilder.append(", enableBle=").append(mEnableBle);
+ stringBuilder.append(", workSource=").append(mWorkSource);
stringBuilder.append("]");
return stringBuilder.toString();
}
@@ -111,6 +176,8 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mScanType);
+ dest.writeInt(mScanMode);
+ dest.writeBoolean(mEnableBle);
dest.writeTypedObject(mWorkSource, /* parcelableFlags= */0);
}
@@ -119,26 +186,46 @@
if (other instanceof ScanRequest) {
ScanRequest otherRequest = (ScanRequest) other;
return mScanType == otherRequest.mScanType
- && Objects.equals(mWorkSource, otherRequest.mWorkSource);
+ && (mScanMode == otherRequest.mScanMode)
+ && (mEnableBle == otherRequest.mEnableBle)
+ && (Objects.equals(mWorkSource, otherRequest.mWorkSource));
}
return false;
}
@Override
public int hashCode() {
- return Objects.hash(mScanType, mWorkSource);
+ return Objects.hash(mScanType, mScanMode, mEnableBle, mWorkSource);
}
+ /** @hide **/
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SCAN_TYPE_FAST_PAIR, SCAN_TYPE_NEARBY_SHARE, SCAN_TYPE_NEARBY_PRESENCE,
+ SCAN_TYPE_EXPOSURE_NOTIFICATION})
+ public @interface ScanType {
+ }
+
+ /** @hide **/
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SCAN_MODE_LOW_LATENCY, SCAN_MODE_BALANCED,
+ SCAN_MODE_LOW_POWER,
+ SCAN_MODE_NO_POWER})
+ public @interface ScanMode {}
+
/** A builder class for {@link ScanRequest}. */
public static final class Builder {
private static final int INVALID_SCAN_TYPE = -1;
private @ScanType int mScanType;
- private @Nullable WorkSource mWorkSource;
+ private @ScanMode int mScanMode;
+
+ private boolean mEnableBle;
+ private WorkSource mWorkSource;
/** Creates a new Builder with the given scan type. */
public Builder() {
mScanType = INVALID_SCAN_TYPE;
- mWorkSource = null;
+ mEnableBle = true;
+ mWorkSource = new WorkSource();
}
/**
@@ -149,6 +236,25 @@
mScanType = scanType;
return this;
}
+
+ /**
+ * Sets the scan mode for the request. The scan type must be one of the SCAN_MODE_ constants
+ * in {@link ScanRequest}.
+ */
+ public Builder setScanMode(@ScanMode int scanMode) {
+ mScanMode = scanMode;
+ return this;
+ }
+
+ /**
+ * Sets if the ble is enabled for scanning.
+ * in {@link ScanRequest}.
+ */
+ public Builder setEnableBle(boolean enableBle) {
+ mEnableBle = enableBle;
+ return this;
+ }
+
/**
* Sets the work source to use for power attribution for this scan request. Defaults to
* empty work source, which implies the caller that sends the scan request will be used
@@ -160,30 +266,32 @@
* @hide
*/
@RequiresPermission(Manifest.permission.UPDATE_DEVICE_STATS)
- public @NonNull Builder setWorkSource(@Nullable WorkSource workSource) {
- this.mWorkSource = workSource;
+ @NonNull
+ public Builder setWorkSource(@Nullable WorkSource workSource) {
+ if (workSource == null) {
+ mWorkSource = new WorkSource();
+ } else {
+ mWorkSource = workSource;
+ }
return this;
}
/**
* Builds a scan request from this builder.
*
- * @throws IllegalStateException if the scanType is not one of the SCAN_TYPE_ constants in
- * {@link ScanRequest}.
* @return a new nearby scan request.
+ * @throws IllegalStateException if the scanType is not one of the SCAN_TYPE_ constants in
+ * {@link ScanRequest}.
*/
- public @NonNull ScanRequest build() {
+ @NonNull
+ public ScanRequest build() {
Preconditions.checkState(isValidScanType(mScanType),
"invalid scan type : " + mScanType
+ ", scan type must be one of ScanRequest#SCAN_TYPE_");
- return new ScanRequest(mScanType, mWorkSource);
- }
-
- private static boolean isValidScanType(int scanType) {
- return scanType == SCAN_TYPE_FAST_PAIR
- || scanType == SCAN_TYPE_NEARBY_SHARE
- || scanType == SCAN_TYPE_NEARBY_PRESENCE
- || scanType == SCAN_TYPE_EXPOSURE_NOTIFICATION;
+ Preconditions.checkState(isValidScanMode(mScanMode),
+ "invalid scan mode : " + mScanMode
+ + ", scan mode must be one of ScanMode#SCAN_MODE_");
+ return new ScanRequest(mScanType, mScanMode, mEnableBle, mWorkSource);
}
}
}
diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java
index 0691d21..764978f 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyService.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyService.java
@@ -37,8 +37,6 @@
private LocatorContextWrapper mLocatorContextWrapper;
-
-
public NearbyService(Context contextBase) {
super(contextBase);
mImpl = new NearbyServiceImpl(contextBase);
diff --git a/nearby/service/java/com/android/server/nearby/NearbyServiceImpl.java b/nearby/service/java/com/android/server/nearby/NearbyServiceImpl.java
index 959d12d..c84169e 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyServiceImpl.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyServiceImpl.java
@@ -18,6 +18,8 @@
import android.content.Context;
import android.nearby.INearbyManager;
+import android.nearby.IScanListener;
+import android.nearby.ScanRequest;
/**
* Implementation of {@link NearbyService}.
@@ -26,4 +28,12 @@
public NearbyServiceImpl(Context context) {
}
+
+ @Override
+ public void registerScanListener(ScanRequest scanRequest, IScanListener listener) {
+ }
+
+ @Override
+ public void unregisterScanListener(IScanListener listener) {
+ }
}
diff --git a/nearby/tests/src/android/nearby/ScanRequestTest.java b/nearby/tests/src/android/nearby/ScanRequestTest.java
index ab9f161..fe80a15 100644
--- a/nearby/tests/src/android/nearby/ScanRequestTest.java
+++ b/nearby/tests/src/android/nearby/ScanRequestTest.java
@@ -16,6 +16,8 @@
package android.nearby;
+import static android.nearby.ScanRequest.SCAN_MODE_BALANCED;
+import static android.nearby.ScanRequest.SCAN_MODE_LOW_POWER;
import static android.nearby.ScanRequest.SCAN_TYPE_EXPOSURE_NOTIFICATION;
import static android.nearby.ScanRequest.SCAN_TYPE_FAST_PAIR;
import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE;
@@ -39,6 +41,12 @@
@RunWith(AndroidJUnit4.class)
public class ScanRequestTest {
+ private static WorkSource getWorkSource() {
+ final int uid = 1001;
+ final String appName = "android.nearby.tests";
+ return new WorkSource(uid, appName);
+ }
+
/** Test creating a scan request. */
@Test
public void testScanRequestBuilder() {
@@ -46,20 +54,29 @@
ScanRequest request = new ScanRequest.Builder().setScanType(scanType).build();
assertThat(request.getScanType()).isEqualTo(scanType);
+ assertThat(request.getScanMode()).isEqualTo(SCAN_MODE_LOW_POWER);
// Work source is null if not set.
- assertThat(request.getWorkSource()).isNull();
+ assertThat(request.getWorkSource().isEmpty()).isTrue();
}
/** Verify RuntimeException is thrown when creating scan request with invalid scan type. */
@Test(expected = RuntimeException.class)
public void testScanRequestBuilder_invalidScanType() {
final int invalidScanType = -1;
- ScanRequest.Builder builder = new ScanRequest.Builder().setScanType(
- invalidScanType);
+ ScanRequest.Builder builder = new ScanRequest.Builder().setScanType(invalidScanType);
builder.build();
}
+ /** Verify RuntimeException is thrown when creating scan mode with invalid scan mode. */
+ @Test(expected = RuntimeException.class)
+ public void testScanModeBuilder_invalidScanType() {
+ final int invalidScanMode = -5;
+ ScanRequest.Builder builder = new ScanRequest.Builder().setScanType(
+ SCAN_TYPE_FAST_PAIR).setScanMode(invalidScanMode);
+ builder.build();
+ }
+
/** Verify setting work source in the scan request. */
@Test
public void testSetWorkSource() {
@@ -81,7 +98,7 @@
.build();
// Null work source is allowed.
- assertThat(request.getWorkSource()).isNull();
+ assertThat(request.getWorkSource().isEmpty()).isTrue();
}
/** Verify toString returns expected string. */
@@ -90,20 +107,24 @@
WorkSource workSource = getWorkSource();
ScanRequest request = new ScanRequest.Builder()
.setScanType(SCAN_TYPE_NEARBY_SHARE)
+ .setScanMode(SCAN_MODE_BALANCED)
+ .setEnableBle(true)
.setWorkSource(workSource)
.build();
assertThat(request.toString()).isEqualTo(
- "Request[scanType=2, workSource=WorkSource{1001 android.nearby.tests}]");
+ "Request[scanType=2, scanMode=SCAN_MODE_BALANCED, "
+ + "enableBle=true, workSource=WorkSource{1001 android.nearby.tests}]");
}
/** Verify toString works correctly with null WorkSource. */
@Test
public void testToString_nullWorkSource() {
ScanRequest request = new ScanRequest.Builder().setScanType(
- SCAN_TYPE_FAST_PAIR).build();
+ SCAN_TYPE_FAST_PAIR).setWorkSource(null).build();
- assertThat(request.toString()).isEqualTo("Request[scanType=1]");
+ assertThat(request.toString()).isEqualTo("Request[scanType=1, "
+ + "scanMode=SCAN_MODE_LOW_POWER, enableBle=true, workSource=WorkSource{}]");
}
/** Verify writing and reading from parcel for scan request. */
@@ -113,6 +134,8 @@
WorkSource workSource = getWorkSource();
ScanRequest originalRequest = new ScanRequest.Builder()
.setScanType(scanType)
+ .setScanMode(SCAN_MODE_BALANCED)
+ .setEnableBle(true)
.setWorkSource(workSource)
.build();
@@ -141,10 +164,4 @@
parcel.setDataPosition(0);
return ScanRequest.CREATOR.createFromParcel(parcel);
}
-
- private static WorkSource getWorkSource() {
- final int uid = 1001;
- final String appName = "android.nearby.tests";
- return new WorkSource(uid, appName);
- }
}