summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugene Susla <eugenesusla@google.com>2017-02-23 18:24:39 -0800
committerEugene Susla <eugenesusla@google.com>2017-03-08 16:52:14 -0800
commite70e6aa62c6f3a9a79624a4f9d97df95edda0364 (patch)
treec75658771072c342ce6b35d132a2e4363721bcd0
parent35fe82fd542bd030dfdacae848acbcaa6c781a0b (diff)
Support multiple filters per association request
By supporting multiple filters per one request we should be able to cover multiple kinds of use cases such as: - Letting the user select from a list of devices of more then one medium type (e.g. Bluetooth and BLE) - Allowing to provide multiple criteria for any field (e.g. filtering by more than one service UUID) Bug: 30932767 Test: Provide multiple filters and ensure that devices matching either are shown in the list to choose from. Ensure wifi SSIDs are shown in the list if wifi filter is provided Change-Id: I6621da388e2bf4ed97c5af2692629a321d0b63c7
-rw-r--r--api/current.txt30
-rw-r--r--api/system-current.txt30
-rw-r--r--api/test-current.txt30
-rw-r--r--core/java/android/bluetooth/le/ScanFilter.java12
-rw-r--r--core/java/android/companion/AssociationRequest.java107
-rw-r--r--core/java/android/companion/BluetoothDeviceFilter.java28
-rw-r--r--core/java/android/companion/BluetoothDeviceFilterUtils.java41
-rw-r--r--core/java/android/companion/BluetoothLEDeviceFilter.java191
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java2
-rw-r--r--core/java/android/companion/DeviceFilter.java24
-rw-r--r--core/java/android/companion/WifiDeviceFilter.java125
-rw-r--r--core/java/android/os/Parcel.java15
-rw-r--r--core/java/android/text/TextUtils.java5
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java29
-rw-r--r--core/java/com/android/internal/util/BitUtils.java58
-rw-r--r--packages/CompanionDeviceManager/AndroidManifest.xml2
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java16
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java197
-rw-r--r--services/print/java/com/android/server/print/CompanionDeviceManagerService.java2
19 files changed, 730 insertions, 214 deletions
diff --git a/api/current.txt b/api/current.txt
index 1a5ea853b365..0d38bc6e3d56 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8183,18 +8183,17 @@ package android.bluetooth.le {
package android.companion {
- public final class AssociationRequest<F extends android.companion.DeviceFilter> implements android.os.Parcelable {
+ public final class AssociationRequest implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.companion.AssociationRequest> CREATOR;
}
- public static final class AssociationRequest.Builder<F extends android.companion.DeviceFilter> {
- method public android.companion.AssociationRequest<F> build();
- method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothDeviceFilter> createForBluetoothDevice();
- method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothLEDeviceFilter> createForBluetoothLEDevice();
- method public android.companion.AssociationRequest.Builder<F> setDeviceFilter(F);
- method public android.companion.AssociationRequest.Builder<F> setSingleDevice(boolean);
+ public static final class AssociationRequest.Builder {
+ ctor public AssociationRequest.Builder();
+ method public android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>);
+ method public android.companion.AssociationRequest build();
+ method public android.companion.AssociationRequest.Builder setSingleDevice(boolean);
}
public final class BluetoothDeviceFilter implements android.companion.DeviceFilter {
@@ -8215,17 +8214,20 @@ package android.companion {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.companion.BluetoothLEDeviceFilter> CREATOR;
+ field public static final int RENAME_PREFIX_LENGTH_LIMIT = 10; // 0xa
}
public static final class BluetoothLEDeviceFilter.Builder {
ctor public BluetoothLEDeviceFilter.Builder();
method public android.companion.BluetoothLEDeviceFilter build();
method public android.companion.BluetoothLEDeviceFilter.Builder setNamePattern(java.util.regex.Pattern);
+ method public android.companion.BluetoothLEDeviceFilter.Builder setRawDataFilter(byte[], byte[]);
+ method public android.companion.BluetoothLEDeviceFilter.Builder setRename(java.lang.String, java.lang.String, int, int, boolean);
method public android.companion.BluetoothLEDeviceFilter.Builder setScanFilter(android.bluetooth.le.ScanFilter);
}
public final class CompanionDeviceManager {
- method public void associate(android.companion.AssociationRequest<?>, android.companion.CompanionDeviceManager.Callback, android.os.Handler);
+ method public void associate(android.companion.AssociationRequest, android.companion.CompanionDeviceManager.Callback, android.os.Handler);
method public void disassociate(java.lang.String);
method public java.util.List<java.lang.String> getAssociations();
field public static final java.lang.String EXTRA_DEVICE = "android.companion.extra.DEVICE";
@@ -8240,6 +8242,18 @@ package android.companion {
public abstract interface DeviceFilter<D extends android.os.Parcelable> implements android.os.Parcelable {
}
+ public final class WifiDeviceFilter implements android.companion.DeviceFilter {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.companion.WifiDeviceFilter> CREATOR;
+ }
+
+ public static final class WifiDeviceFilter.Builder {
+ ctor public WifiDeviceFilter.Builder();
+ method public android.companion.WifiDeviceFilter build();
+ method public android.companion.WifiDeviceFilter.Builder setNamePattern(java.util.regex.Pattern);
+ }
+
}
package android.content {
diff --git a/api/system-current.txt b/api/system-current.txt
index b5c20e52e913..16de51039ff4 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8683,18 +8683,17 @@ package android.bluetooth.le {
package android.companion {
- public final class AssociationRequest<F extends android.companion.DeviceFilter> implements android.os.Parcelable {
+ public final class AssociationRequest implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.companion.AssociationRequest> CREATOR;
}
- public static final class AssociationRequest.Builder<F extends android.companion.DeviceFilter> {
- method public android.companion.AssociationRequest<F> build();
- method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothDeviceFilter> createForBluetoothDevice();
- method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothLEDeviceFilter> createForBluetoothLEDevice();
- method public android.companion.AssociationRequest.Builder<F> setDeviceFilter(F);
- method public android.companion.AssociationRequest.Builder<F> setSingleDevice(boolean);
+ public static final class AssociationRequest.Builder {
+ ctor public AssociationRequest.Builder();
+ method public android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>);
+ method public android.companion.AssociationRequest build();
+ method public android.companion.AssociationRequest.Builder setSingleDevice(boolean);
}
public final class BluetoothDeviceFilter implements android.companion.DeviceFilter {
@@ -8715,17 +8714,20 @@ package android.companion {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.companion.BluetoothLEDeviceFilter> CREATOR;
+ field public static final int RENAME_PREFIX_LENGTH_LIMIT = 10; // 0xa
}
public static final class BluetoothLEDeviceFilter.Builder {
ctor public BluetoothLEDeviceFilter.Builder();
method public android.companion.BluetoothLEDeviceFilter build();
method public android.companion.BluetoothLEDeviceFilter.Builder setNamePattern(java.util.regex.Pattern);
+ method public android.companion.BluetoothLEDeviceFilter.Builder setRawDataFilter(byte[], byte[]);
+ method public android.companion.BluetoothLEDeviceFilter.Builder setRename(java.lang.String, java.lang.String, int, int, boolean);
method public android.companion.BluetoothLEDeviceFilter.Builder setScanFilter(android.bluetooth.le.ScanFilter);
}
public final class CompanionDeviceManager {
- method public void associate(android.companion.AssociationRequest<?>, android.companion.CompanionDeviceManager.Callback, android.os.Handler);
+ method public void associate(android.companion.AssociationRequest, android.companion.CompanionDeviceManager.Callback, android.os.Handler);
method public void disassociate(java.lang.String);
method public java.util.List<java.lang.String> getAssociations();
field public static final java.lang.String EXTRA_DEVICE = "android.companion.extra.DEVICE";
@@ -8740,6 +8742,18 @@ package android.companion {
public abstract interface DeviceFilter<D extends android.os.Parcelable> implements android.os.Parcelable {
}
+ public final class WifiDeviceFilter implements android.companion.DeviceFilter {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.companion.WifiDeviceFilter> CREATOR;
+ }
+
+ public static final class WifiDeviceFilter.Builder {
+ ctor public WifiDeviceFilter.Builder();
+ method public android.companion.WifiDeviceFilter build();
+ method public android.companion.WifiDeviceFilter.Builder setNamePattern(java.util.regex.Pattern);
+ }
+
}
package android.content {
diff --git a/api/test-current.txt b/api/test-current.txt
index 57cc998ffb5d..62f76bde349d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -8210,18 +8210,17 @@ package android.bluetooth.le {
package android.companion {
- public final class AssociationRequest<F extends android.companion.DeviceFilter> implements android.os.Parcelable {
+ public final class AssociationRequest implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.companion.AssociationRequest> CREATOR;
}
- public static final class AssociationRequest.Builder<F extends android.companion.DeviceFilter> {
- method public android.companion.AssociationRequest<F> build();
- method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothDeviceFilter> createForBluetoothDevice();
- method public static android.companion.AssociationRequest.Builder<android.companion.BluetoothLEDeviceFilter> createForBluetoothLEDevice();
- method public android.companion.AssociationRequest.Builder<F> setDeviceFilter(F);
- method public android.companion.AssociationRequest.Builder<F> setSingleDevice(boolean);
+ public static final class AssociationRequest.Builder {
+ ctor public AssociationRequest.Builder();
+ method public android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>);
+ method public android.companion.AssociationRequest build();
+ method public android.companion.AssociationRequest.Builder setSingleDevice(boolean);
}
public final class BluetoothDeviceFilter implements android.companion.DeviceFilter {
@@ -8242,17 +8241,20 @@ package android.companion {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.companion.BluetoothLEDeviceFilter> CREATOR;
+ field public static final int RENAME_PREFIX_LENGTH_LIMIT = 10; // 0xa
}
public static final class BluetoothLEDeviceFilter.Builder {
ctor public BluetoothLEDeviceFilter.Builder();
method public android.companion.BluetoothLEDeviceFilter build();
method public android.companion.BluetoothLEDeviceFilter.Builder setNamePattern(java.util.regex.Pattern);
+ method public android.companion.BluetoothLEDeviceFilter.Builder setRawDataFilter(byte[], byte[]);
+ method public android.companion.BluetoothLEDeviceFilter.Builder setRename(java.lang.String, java.lang.String, int, int, boolean);
method public android.companion.BluetoothLEDeviceFilter.Builder setScanFilter(android.bluetooth.le.ScanFilter);
}
public final class CompanionDeviceManager {
- method public void associate(android.companion.AssociationRequest<?>, android.companion.CompanionDeviceManager.Callback, android.os.Handler);
+ method public void associate(android.companion.AssociationRequest, android.companion.CompanionDeviceManager.Callback, android.os.Handler);
method public void disassociate(java.lang.String);
method public java.util.List<java.lang.String> getAssociations();
field public static final java.lang.String EXTRA_DEVICE = "android.companion.extra.DEVICE";
@@ -8267,6 +8269,18 @@ package android.companion {
public abstract interface DeviceFilter<D extends android.os.Parcelable> implements android.os.Parcelable {
}
+ public final class WifiDeviceFilter implements android.companion.DeviceFilter {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.companion.WifiDeviceFilter> CREATOR;
+ }
+
+ public static final class WifiDeviceFilter.Builder {
+ ctor public WifiDeviceFilter.Builder();
+ method public android.companion.WifiDeviceFilter build();
+ method public android.companion.WifiDeviceFilter.Builder setNamePattern(java.util.regex.Pattern);
+ }
+
}
package android.content {
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index b89c64a8cac6..457096bf843e 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -23,6 +23,8 @@ import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
+import com.android.internal.util.BitUtils;
+
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -345,15 +347,7 @@ public final class ScanFilter implements Parcelable {
// Check if the uuid pattern matches the particular service uuid.
private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) {
- if (mask == null) {
- return uuid.equals(data);
- }
- if ((uuid.getLeastSignificantBits() & mask.getLeastSignificantBits()) !=
- (data.getLeastSignificantBits() & mask.getLeastSignificantBits())) {
- return false;
- }
- return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits()) ==
- (data.getMostSignificantBits() & mask.getMostSignificantBits()));
+ return BitUtils.maskedEquals(data, uuid, mask);
}
// Check whether the data pattern matches the parsed data.
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index d477f43ac8c2..56f5d4483270 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -16,20 +16,21 @@
package android.companion;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.provider.OneTimeUseBuilder;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* A request for the user to select a companion device to associate with.
*
- * You can optionally set a {@link Builder#setDeviceFilter filter} for which devices to show to the
+ * You can optionally set {@link Builder#addDeviceFilter filters} for which devices to show to the
* user to select from.
* The exact type and fields of the filter you can set depend on the
* medium type. See {@link Builder}'s static factory methods for specific protocols that are
@@ -37,38 +38,22 @@ import java.lang.annotation.RetentionPolicy;
*
* You can also set {@link Builder#setSingleDevice single device} to request a popup with single
* device to be shown instead of a list to choose from
- *
- * @param <F> Device filter type
*/
-public final class AssociationRequest<F extends DeviceFilter> implements Parcelable {
-
- /** @hide */
- public static final int MEDIUM_TYPE_BLUETOOTH = 0;
- /** @hide */
- public static final int MEDIUM_TYPE_BLUETOOTH_LE = 1;
- /** @hide */
- public static final int MEDIUM_TYPE_WIFI = 2;
-
- /** @hide */
- @IntDef({MEDIUM_TYPE_BLUETOOTH, MEDIUM_TYPE_BLUETOOTH_LE, MEDIUM_TYPE_WIFI})
- @Retention(RetentionPolicy.SOURCE)
- public @interface MediumType {}
+public final class AssociationRequest implements Parcelable {
private final boolean mSingleDevice;
- private final int mMediumType;
- private final F mDeviceFilter;
+ private final List<DeviceFilter<?>> mDeviceFilters;
- private AssociationRequest(boolean singleDevice, int mMediumType, F deviceFilter) {
+ private AssociationRequest(
+ boolean singleDevice, @Nullable List<DeviceFilter<?>> deviceFilters) {
this.mSingleDevice = singleDevice;
- this.mMediumType = mMediumType;
- this.mDeviceFilter = deviceFilter;
+ this.mDeviceFilters = ArrayUtils.emptyIfNull(deviceFilters);
}
private AssociationRequest(Parcel in) {
this(
in.readByte() != 0,
- in.readInt(),
- in.readParcelable(AssociationRequest.class.getClassLoader()));
+ in.readParcelableList(new ArrayList<>(), AssociationRequest.class.getClassLoader()));
}
/** @hide */
@@ -77,22 +62,15 @@ public final class AssociationRequest<F extends DeviceFilter> implements Parcela
}
/** @hide */
- @MediumType
- public int getMediumType() {
- return mMediumType;
- }
-
- /** @hide */
- @Nullable
- public F getDeviceFilter() {
- return mDeviceFilter;
+ @NonNull
+ public List<DeviceFilter<?>> getDeviceFilters() {
+ return mDeviceFilters;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (mSingleDevice ? 1 : 0));
- dest.writeInt(mMediumType);
- dest.writeParcelable(mDeviceFilter, flags);
+ dest.writeParcelableList(mDeviceFilters, flags);
}
@Override
@@ -114,45 +92,19 @@ public final class AssociationRequest<F extends DeviceFilter> implements Parcela
/**
* A builder for {@link AssociationRequest}
- *
- * @param <F> the type of filter for the request.
*/
- public static final class Builder<F extends DeviceFilter>
- extends OneTimeUseBuilder<AssociationRequest<F>> {
+ public static final class Builder extends OneTimeUseBuilder<AssociationRequest> {
private boolean mSingleDevice = false;
- @MediumType private int mMediumType;
- @Nullable private F mDeviceFilter = null;
-
- private Builder() {}
-
- /**
- * Create a new builder for an association request with a Bluetooth LE device
- */
- @NonNull
- public static Builder<BluetoothLEDeviceFilter> createForBluetoothLEDevice() {
- return new Builder<BluetoothLEDeviceFilter>()
- .setMediumType(MEDIUM_TYPE_BLUETOOTH_LE);
- }
-
- /**
- * Create a new builder for an association request with a Bluetooth(non-LE) device
- */
- @NonNull
- public static Builder<BluetoothDeviceFilter> createForBluetoothDevice() {
- return new Builder<BluetoothDeviceFilter>()
- .setMediumType(MEDIUM_TYPE_BLUETOOTH);
- }
+ @Nullable private ArrayList<DeviceFilter<?>> mDeviceFilters = null;
- //TODO implement, once specific filter classes are available
-// public static Builder<> createForWiFiDevice()
-// public static Builder<> createForNanDevice()
+ public Builder() {}
/**
* @param singleDevice if true, scanning for a device will stop as soon as at least one
* fitting device is found
*/
@NonNull
- public Builder<F> setSingleDevice(boolean singleDevice) {
+ public Builder setSingleDevice(boolean singleDevice) {
checkNotUsed();
this.mSingleDevice = singleDevice;
return this;
@@ -163,29 +115,20 @@ public final class AssociationRequest<F extends DeviceFilter> implements Parcela
* user
*/
@NonNull
- public Builder<F> setDeviceFilter(@Nullable F deviceFilter) {
+ public Builder addDeviceFilter(@Nullable DeviceFilter<?> deviceFilter) {
checkNotUsed();
- this.mDeviceFilter = deviceFilter;
- return this;
- }
-
- /**
- * @param deviceType A type of medium over which to discover devices
- *
- * @see MediumType
- */
- @NonNull
- private Builder<F> setMediumType(@MediumType int deviceType) {
- mMediumType = deviceType;
+ if (deviceFilter != null) {
+ mDeviceFilters = ArrayUtils.add(mDeviceFilters, deviceFilter);
+ }
return this;
}
/** @inheritDoc */
@NonNull
@Override
- public AssociationRequest<F> build() {
+ public AssociationRequest build() {
markUsed();
- return new AssociationRequest<>(mSingleDevice, mMediumType, mDeviceFilter);
+ return new AssociationRequest(mSingleDevice, mDeviceFilters);
}
}
}
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
index 5a69955429aa..0f16b7b90165 100644
--- a/core/java/android/companion/BluetoothDeviceFilter.java
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -16,6 +16,7 @@
package android.companion;
+import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal;
import static android.companion.BluetoothDeviceFilterUtils.matchesAddress;
import static android.companion.BluetoothDeviceFilterUtils.matchesName;
import static android.companion.BluetoothDeviceFilterUtils.matchesServiceUuids;
@@ -40,8 +41,6 @@ import java.util.regex.Pattern;
*/
public final class BluetoothDeviceFilter implements DeviceFilter<BluetoothDevice> {
- private static BluetoothDeviceFilter NO_OP;
-
private final Pattern mNamePattern;
private final String mAddress;
private final List<ParcelUuid> mServiceUuids;
@@ -67,30 +66,27 @@ public final class BluetoothDeviceFilter implements DeviceFilter<BluetoothDevice
}
private static List<ParcelUuid> readUuids(Parcel in) {
- final ArrayList<ParcelUuid> list = new ArrayList<>();
- in.readParcelableList(list, ParcelUuid.class.getClassLoader());
- return list;
+ return in.readParcelableList(new ArrayList<>(), ParcelUuid.class.getClassLoader());
}
/** @hide */
- @NonNull
- public static BluetoothDeviceFilter nullsafe(@Nullable BluetoothDeviceFilter nullable) {
- return nullable != null ? nullable : noOp();
+ @Override
+ public boolean matches(BluetoothDevice device) {
+ return matchesAddress(mAddress, device)
+ && matchesServiceUuids(mServiceUuids, mServiceUuidMasks, device)
+ && matchesName(getNamePattern(), device);
}
/** @hide */
- @NonNull
- public static BluetoothDeviceFilter noOp() {
- if (NO_OP == null) NO_OP = new Builder().build();
- return NO_OP;
+ @Override
+ public String getDeviceDisplayName(BluetoothDevice device) {
+ return getDeviceDisplayNameInternal(device);
}
/** @hide */
@Override
- public boolean matches(BluetoothDevice device) {
- return matchesAddress(mAddress, device)
- && matchesServiceUuids(mServiceUuids, mServiceUuidMasks, device)
- && matchesName(getNamePattern(), device);
+ public int getMediumType() {
+ return DeviceFilter.MEDIUM_TYPE_BLUETOOTH;
}
/** @hide */
diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java
index 289f9953a068..8a316f19af8e 100644
--- a/core/java/android/companion/BluetoothDeviceFilterUtils.java
+++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java
@@ -23,7 +23,9 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.ScanFilter;
+import android.net.wifi.ScanResult;
import android.os.ParcelUuid;
+import android.os.Parcelable;
import android.util.Log;
import java.util.Arrays;
@@ -96,12 +98,47 @@ public class BluetoothDeviceFilterUtils {
return result;
}
+ static boolean matchesName(@Nullable Pattern namePattern, ScanResult device) {
+ boolean result;
+ if (namePattern == null) {
+ result = true;
+ } else if (device == null) {
+ result = false;
+ } else {
+ final String name = device.SSID;
+ result = name != null && namePattern.matcher(name).find();
+ }
+ if (DEBUG) debugLogMatchResult(result, device, namePattern);
+ return result;
+ }
+
private static void debugLogMatchResult(
boolean result, BluetoothDevice device, Object criteria) {
- Log.i(LOG_TAG, getDeviceDisplayName(device) + (result ? " ~ " : " !~ ") + criteria);
+ Log.i(LOG_TAG, getDeviceDisplayNameInternal(device) + (result ? " ~ " : " !~ ") + criteria);
}
- public static String getDeviceDisplayName(@NonNull BluetoothDevice device) {
+ private static void debugLogMatchResult(
+ boolean result, ScanResult device, Object criteria) {
+ Log.i(LOG_TAG, getDeviceDisplayNameInternal(device) + (result ? " ~ " : " !~ ") + criteria);
+ }
+
+ public static String getDeviceDisplayNameInternal(@NonNull BluetoothDevice device) {
return firstNotEmpty(device.getAliasName(), device.getAddress());
}
+
+ public static String getDeviceDisplayNameInternal(@NonNull ScanResult device) {
+ return firstNotEmpty(device.SSID, device.BSSID);
+ }
+
+ public static String getDeviceMacAddress(@NonNull Parcelable device) {
+ if (device instanceof BluetoothDevice) {
+ return ((BluetoothDevice) device).getAddress();
+ } else if (device instanceof ScanResult) {
+ return ((ScanResult) device).BSSID;
+ } else if (device instanceof android.bluetooth.le.ScanResult) {
+ return getDeviceMacAddress(((android.bluetooth.le.ScanResult) device).getDevice());
+ } else {
+ throw new IllegalArgumentException("Unknown device type: " + device);
+ }
+ }
}
diff --git a/core/java/android/companion/BluetoothLEDeviceFilter.java b/core/java/android/companion/BluetoothLEDeviceFilter.java
index 4a481ca80045..e057fbcc901a 100644
--- a/core/java/android/companion/BluetoothLEDeviceFilter.java
+++ b/core/java/android/companion/BluetoothLEDeviceFilter.java
@@ -16,18 +16,25 @@
package android.companion;
+import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal;
import static android.companion.BluetoothDeviceFilterUtils.patternFromString;
import static android.companion.BluetoothDeviceFilterUtils.patternToString;
+import static com.android.internal.util.Preconditions.checkArgument;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanRecord;
+import android.bluetooth.le.ScanResult;
import android.os.Parcel;
import android.provider.OneTimeUseBuilder;
+import android.text.TextUtils;
+import com.android.internal.util.BitUtils;
import com.android.internal.util.ObjectUtils;
+import com.android.internal.util.Preconditions;
import java.util.regex.Pattern;
@@ -36,57 +43,122 @@ import java.util.regex.Pattern;
*
* @see ScanFilter
*/
-public final class BluetoothLEDeviceFilter implements DeviceFilter<BluetoothDevice> {
+public final class BluetoothLEDeviceFilter implements DeviceFilter<ScanResult> {
- private static BluetoothLEDeviceFilter NO_OP;
+ private static final int RENAME_PREFIX_LENGTH_LIMIT = 10;
private final Pattern mNamePattern;
private final ScanFilter mScanFilter;
+ private final byte[] mRawDataFilter;
+ private final byte[] mRawDataFilterMask;
+ private final String mRenamePrefix;
+ private final String mRenameSuffix;
+ private final int mRenameBytesFrom;
+ private final int mRenameBytesTo;
+ private final boolean mRenameBytesReverseOrder;
- private BluetoothLEDeviceFilter(Pattern namePattern, ScanFilter scanFilter) {
+ private BluetoothLEDeviceFilter(Pattern namePattern, ScanFilter scanFilter,
+ byte[] rawDataFilter, byte[] rawDataFilterMask, String renamePrefix,
+ String renameSuffix, int renameBytesFrom, int renameBytesTo,
+ boolean renameBytesReverseOrder) {
mNamePattern = namePattern;
mScanFilter = ObjectUtils.firstNotNull(scanFilter, ScanFilter.EMPTY);
+ mRawDataFilter = rawDataFilter;
+ mRawDataFilterMask = rawDataFilterMask;
+ mRenamePrefix = renamePrefix;
+ mRenameSuffix = renameSuffix;
+ mRenameBytesFrom = renameBytesFrom;
+ mRenameBytesTo = renameBytesTo;
+ mRenameBytesReverseOrder = renameBytesReverseOrder;
}
- @SuppressLint("ParcelClassLoader")
- private BluetoothLEDeviceFilter(Parcel in) {
- this(
- patternFromString(in.readString()),
- in.readParcelable(null));
+ /** @hide */
+ @Nullable
+ public Pattern getNamePattern() {
+ return mNamePattern;
}
/** @hide */
@NonNull
- public static BluetoothLEDeviceFilter nullsafe(@Nullable BluetoothLEDeviceFilter nullable) {
- return nullable != null ? nullable : noOp();
+ public ScanFilter getScanFilter() {
+ return mScanFilter;
}
/** @hide */
- @NonNull
- public static BluetoothLEDeviceFilter noOp() {
- if (NO_OP == null) NO_OP = new Builder().build();
- return NO_OP;
+ @Nullable
+ public byte[] getRawDataFilter() {
+ return mRawDataFilter;
}
/** @hide */
@Nullable
- public Pattern getNamePattern() {
- return mNamePattern;
+ public byte[] getRawDataFilterMask() {
+ return mRawDataFilterMask;
}
/** @hide */
- @NonNull
- public ScanFilter getScanFilter() {
- return mScanFilter;
+ @Nullable
+ public String getRenamePrefix() {
+ return mRenamePrefix;
+ }
+
+ /** @hide */
+ @Nullable
+ public String getRenameSuffix() {
+ return mRenameSuffix;
+ }
+
+ /** @hide */
+ public int getRenameBytesFrom() {
+ return mRenameBytesFrom;
+ }
+
+ /** @hide */
+ public int getRenameBytesTo() {
+ return mRenameBytesTo;
+ }
+
+ /** @hide */
+ public boolean isRenameBytesReverseOrder() {
+ return mRenameBytesReverseOrder;
+ }
+
+ /** @hide */
+ @Override
+ @Nullable
+ public String getDeviceDisplayName(ScanResult sr) {
+ if (mRenameBytesFrom < 0) return getDeviceDisplayNameInternal(sr.getDevice());
+ final byte[] bytes = sr.getScanRecord().getBytes();
+ final StringBuilder sb = new StringBuilder(TextUtils.emptyIfNull(mRenamePrefix));
+ int startInclusive = mRenameBytesFrom;
+ int endInclusive = mRenameBytesTo - 1;
+ int initial = mRenameBytesReverseOrder ? endInclusive : startInclusive;
+ int step = mRenameBytesReverseOrder ? -1 : 1;
+ for (int i = initial; startInclusive <= i && i <= endInclusive; i+=step) {
+ sb.append(Byte.toHexString(bytes[i], true));
+ }
+ return sb.append(TextUtils.emptyIfNull(mRenameSuffix)).toString();
}
/** @hide */
@Override
- public boolean matches(BluetoothDevice device) {
+ public boolean matches(ScanResult device) {
+ return matches(device.getDevice())
+ && BitUtils.maskedEquals(device.getScanRecord().getBytes(),
+ mRawDataFilter, mRawDataFilterMask);
+ }
+
+ private boolean matches(BluetoothDevice device) {
return BluetoothDeviceFilterUtils.matches(getScanFilter(), device)
&& BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device);
}
+ /** @hide */
+ @Override
+ public int getMediumType() {
+ return DeviceFilter.MEDIUM_TYPE_BLUETOOTH_LE;
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(patternToString(getNamePattern()));
@@ -102,7 +174,13 @@ public final class BluetoothLEDeviceFilter implements DeviceFilter<BluetoothDevi
= new Creator<BluetoothLEDeviceFilter>() {
@Override
public BluetoothLEDeviceFilter createFromParcel(Parcel in) {
- return new BluetoothLEDeviceFilter(in);
+ return new BluetoothLEDeviceFilter.Builder()
+ .setNamePattern(patternFromString(in.readString()))
+ .setScanFilter(in.readParcelable(null))
+ .setRawDataFilter(in.readBlob(), in.readBlob())
+ .setRename(in.readString(), in.readString(),
+ in.readInt(), in.readInt(), in.readBoolean())
+ .build();
}
@Override
@@ -111,16 +189,28 @@ public final class BluetoothLEDeviceFilter implements DeviceFilter<BluetoothDevi
}
};
+ public static int getRenamePrefixLengthLimit() {
+ return RENAME_PREFIX_LENGTH_LIMIT;
+ }
+
/**
* Builder for {@link BluetoothLEDeviceFilter}
*/
public static final class Builder extends OneTimeUseBuilder<BluetoothLEDeviceFilter> {
private ScanFilter mScanFilter;
private Pattern mNamePattern;
+ private byte[] mRawDataFilter;
+ private byte[] mRawDataFilterMask;
+ private String mRenamePrefix;
+ private String mRenameSuffix;
+ private int mRenameBytesFrom = -1;
+ private int mRenameBytesTo;
+ private boolean mRenameBytesReverseOrder = false;
/**
* @param regex if set, only devices with {@link BluetoothDevice#getName name} matching the
* given regular expression will be shown
+ * @return self for chaining
*/
public Builder setNamePattern(@Nullable Pattern regex) {
checkNotUsed();
@@ -131,6 +221,7 @@ public final class BluetoothLEDeviceFilter implements DeviceFilter<BluetoothDevi
/**
* @param scanFilter a {@link ScanFilter} to filter devices by
*
+ * @return self for chaining
* @see ScanFilter for specific details on its various fields
*/
@NonNull
@@ -140,12 +231,66 @@ public final class BluetoothLEDeviceFilter implements DeviceFilter<BluetoothDevi
return this;
}
+ /**
+ * Filter devices by raw advertisement data, as obtained by {@link ScanRecord#getBytes}
+ *
+ * @param rawDataFilter bit values that have to match against advertized data
+ * @param rawDataFilterMask bits that have to be matched
+ * @return self for chaining
+ */
+ @NonNull
+ public Builder setRawDataFilter(@NonNull byte[] rawDataFilter,
+ @NonNull byte[] rawDataFilterMask) {
+ checkNotUsed();
+ checkArgument(rawDataFilter.length == rawDataFilterMask.length,
+ "Mask and filter should be the same length");
+ mRawDataFilter = Preconditions.checkNotNull(rawDataFilter);
+ mRawDataFilterMask = Preconditions.checkNotNull(rawDataFilterMask);
+ return this;
+ }
+
+ /**
+ * Rename the devices shown in the list, using specific bytes from the raw advertisement
+ * data ({@link ScanRecord#getBytes}) in hexadecimal format, as well as a custom
+ * prefix/suffix around them
+ *
+ * Note that the prefix length is limited to {@link #getRenamePrefixLengthLimit} characters
+ * to ensure that there's enough space to display the byte data
+ *
+ * The range of bytes to be displayed cannot be empty
+ *
+ * @param prefix to be displayed before the byte data
+ * @param suffix to be displayed after the byte data
+ * @param bytesFrom the start byte index to be displayed (inclusive)
+ * @param bytesTo the end byte index to be displayed (exclusive)
+ * @param bytesReverseOrder if true, the byte order of the provided range will be flipped
+ * when displaying
+ * @return self for chaining
+ */
+ @NonNull
+ public Builder setRename(@NonNull String prefix, @NonNull String suffix,
+ int bytesFrom, int bytesTo, boolean bytesReverseOrder) {
+ checkNotUsed();
+ checkArgument(TextUtils.length(prefix) >= getRenamePrefixLengthLimit(),
+ "Prefix is too short");
+ mRenamePrefix = prefix;
+ mRenameSuffix = suffix;
+ checkArgument(bytesFrom < bytesTo, "Byte range must be non-empty");
+ mRenameBytesFrom = bytesFrom;
+ mRenameBytesTo = bytesTo;
+ mRenameBytesReverseOrder = bytesReverseOrder;
+ return this;
+ }
+
/** @inheritDoc */
@Override
@NonNull
public BluetoothLEDeviceFilter build() {
markUsed();
- return new BluetoothLEDeviceFilter(mNamePattern, mScanFilter);
+ return new BluetoothLEDeviceFilter(mNamePattern, mScanFilter,
+ mRawDataFilter, mRawDataFilterMask,
+ mRenamePrefix, mRenameSuffix,
+ mRenameBytesFrom, mRenameBytesTo, mRenameBytesReverseOrder);
}
}
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 6fa32b4b6944..5710ad1318d7 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -117,7 +117,7 @@ public final class CompanionDeviceManager {
* @see AssociationRequest
*/
public void associate(
- @NonNull AssociationRequest<?> request,
+ @NonNull AssociationRequest request,
@NonNull Callback callback,
@Nullable Handler handler) {
final Handler finalHandler = handler != null
diff --git a/core/java/android/companion/DeviceFilter.java b/core/java/android/companion/DeviceFilter.java
index 8362b2dab8fd..9b4fdfdf5108 100644
--- a/core/java/android/companion/DeviceFilter.java
+++ b/core/java/android/companion/DeviceFilter.java
@@ -17,17 +17,28 @@
package android.companion;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A filter for companion devices of type {@code D}
*
* @param <D> Type of devices, filtered by this filter,
- * e.g. {@link android.bluetooth.BluetoothDevice}, {@link android.net.wifi.WifiInfo}
+ * e.g. {@link android.bluetooth.BluetoothDevice}, {@link android.net.wifi.ScanResult}
*/
public interface DeviceFilter<D extends Parcelable> extends Parcelable {
+ /** @hide */
+ int MEDIUM_TYPE_BLUETOOTH = 0;
+ /** @hide */
+ int MEDIUM_TYPE_BLUETOOTH_LE = 1;
+ /** @hide */
+ int MEDIUM_TYPE_WIFI = 2;
+
/**
* @return whether the given device matches this filter
*
@@ -35,6 +46,12 @@ public interface DeviceFilter<D extends Parcelable> extends Parcelable {
*/
boolean matches(D device);
+ /** @hide */
+ String getDeviceDisplayName(D device);
+
+ /** @hide */
+ @MediumType int getMediumType();
+
/**
* A nullsafe {@link #matches(Parcelable)}, returning true if the filter is null
*
@@ -43,4 +60,9 @@ public interface DeviceFilter<D extends Parcelable> extends Parcelable {
static <D extends Parcelable> boolean matches(@Nullable DeviceFilter<D> filter, D device) {
return filter == null || filter.matches(device);
}
+
+ /** @hide */
+ @IntDef({MEDIUM_TYPE_BLUETOOTH, MEDIUM_TYPE_BLUETOOTH_LE, MEDIUM_TYPE_WIFI})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface MediumType {}
}
diff --git a/core/java/android/companion/WifiDeviceFilter.java b/core/java/android/companion/WifiDeviceFilter.java
new file mode 100644
index 000000000000..1ab9ce11cb0f
--- /dev/null
+++ b/core/java/android/companion/WifiDeviceFilter.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 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.companion;
+
+import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal;
+import static android.companion.BluetoothDeviceFilterUtils.patternFromString;
+import static android.companion.BluetoothDeviceFilterUtils.patternToString;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.ScanFilter;
+import android.net.wifi.ScanResult;
+import android.os.Parcel;
+import android.provider.OneTimeUseBuilder;
+
+import java.util.regex.Pattern;
+
+/**
+ * A filter for Wifi devices
+ *
+ * @see ScanFilter
+ */
+public final class WifiDeviceFilter implements DeviceFilter<ScanResult> {
+
+ private final Pattern mNamePattern;
+
+ private WifiDeviceFilter(Pattern namePattern) {
+ mNamePattern = namePattern;
+ }
+
+ @SuppressLint("ParcelClassLoader")
+ private WifiDeviceFilter(Parcel in) {
+ this(patternFromString(in.readString()));
+ }
+
+ /** @hide */
+ @Nullable
+ public Pattern getNamePattern() {
+ return mNamePattern;
+ }
+
+
+ /** @hide */
+ @Override
+ public boolean matches(ScanResult device) {
+ return BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device);
+ }
+
+ /** @hide */
+ @Override
+ public String getDeviceDisplayName(ScanResult device) {
+ return getDeviceDisplayNameInternal(device);
+ }
+
+ /** @hide */
+ @Override
+ public int getMediumType() {
+ return MEDIUM_TYPE_WIFI;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(patternToString(getNamePattern()));
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<WifiDeviceFilter> CREATOR
+ = new Creator<WifiDeviceFilter>() {
+ @Override
+ public WifiDeviceFilter createFromParcel(Parcel in) {
+ return new WifiDeviceFilter(in);
+ }
+
+ @Override
+ public WifiDeviceFilter[] newArray(int size) {
+ return new WifiDeviceFilter[size];
+ }
+ };
+
+ /**
+ * Builder for {@link WifiDeviceFilter}
+ */
+ public static final class Builder extends OneTimeUseBuilder<WifiDeviceFilter> {
+ private Pattern mNamePattern;
+
+ /**
+ * @param regex if set, only devices with {@link BluetoothDevice#getName name} matching the
+ * given regular expression will be shown
+ * @return self for chaining
+ */
+ public Builder setNamePattern(@Nullable Pattern regex) {
+ checkNotUsed();
+ mNamePattern = regex;
+ return this;
+ }
+
+ /** @inheritDoc */
+ @Override
+ @NonNull
+ public WifiDeviceFilter build() {
+ markUsed();
+ return new WifiDeviceFilter(mNamePattern);
+ }
+ }
+}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index f94e89a2785d..7a39d239f84b 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -601,6 +601,11 @@ public final class Parcel {
nativeWriteString(mNativePtr, val);
}
+ /** @hide */
+ public final void writeBoolean(boolean val) {
+ writeInt(val ? 1 : 0);
+ }
+
/**
* Write a CharSequence value into the parcel at the current dataPosition(),
* growing dataCapacity() if needed.
@@ -1964,6 +1969,11 @@ public final class Parcel {
return nativeReadString(mNativePtr);
}
+ /** @hide */
+ public final boolean readBoolean() {
+ return readInt() != 0;
+ }
+
/**
* Read a CharSequence value from the parcel at the current dataPosition().
* @hide
@@ -2490,11 +2500,11 @@ public final class Parcel {
* @see #writeParcelableList(List, int)
* @hide
*/
- public final <T extends Parcelable> void readParcelableList(List<T> list, ClassLoader cl) {
+ public final <T extends Parcelable> List<T> readParcelableList(List<T> list, ClassLoader cl) {
final int N = readInt();
if (N == -1) {
list.clear();
- return;
+ return list;
}
final int M = list.size();
@@ -2508,6 +2518,7 @@ public final class Parcel {
for (; i<M; i++) {
list.remove(N);
}
+ return list;
}
/**
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index ac9c0d782c53..ee2b38e4f390 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -474,6 +474,11 @@ public class TextUtils {
return !isEmpty(a) ? a : Preconditions.checkStringNotEmpty(b);
}
+ /** {@hide} */
+ public static int length(@Nullable String s) {
+ return isEmpty(s) ? 0 : s.length();
+ }
+
/**
* Returns the length that the specified CharSequence would have if
* spaces and ASCII control characters were trimmed from the start and end,
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index be69d9f808e2..d0fbe7c8a666 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -237,6 +237,35 @@ public class ArrayUtils {
return false;
}
+ @NonNull
+ public static <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
+ if (isEmpty(list)) return Collections.emptyList();
+ ArrayList<T> result = null;
+ for (int i = 0; i < list.size(); i++) {
+ final Object item = list.get(i);
+ if (c.isInstance(item)) {
+ result = add(result, (T) item);
+ }
+ }
+ return emptyIfNull(result);
+ }
+
+ public static <T> boolean any(@Nullable List<T> items,
+ java.util.function.Predicate<T> predicate) {
+ return find(items, predicate) != null;
+ }
+
+ @Nullable
+ public static <T> T find(@Nullable List<T> items,
+ java.util.function.Predicate<T> predicate) {
+ if (isEmpty(items)) return null;
+ for (int i = 0; i < items.size(); i++) {
+ final T item = items.get(i);
+ if (predicate.test(item)) return item;
+ }
+ return null;
+ }
+
public static long total(@Nullable long[] array) {
long total = 0;
if (array != null) {
diff --git a/core/java/com/android/internal/util/BitUtils.java b/core/java/com/android/internal/util/BitUtils.java
new file mode 100644
index 000000000000..a208ccb8f35f
--- /dev/null
+++ b/core/java/com/android/internal/util/BitUtils.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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.internal.util;
+
+import android.annotation.Nullable;
+
+import libcore.util.Objects;
+
+import java.util.Arrays;
+import java.util.UUID;
+
+public class BitUtils {
+ private BitUtils() {}
+
+ public static boolean maskedEquals(long a, long b, long mask) {
+ return (a & mask) == (b & mask);
+ }
+
+ public static boolean maskedEquals(byte a, byte b, byte mask) {
+ return (a & mask) == (b & mask);
+ }
+
+ public static boolean maskedEquals(byte[] a, byte[] b, @Nullable byte[] mask) {
+ if (a == null || b == null) return a == b;
+ Preconditions.checkArgument(a.length == b.length, "Inputs must be of same size");
+ if (mask == null) return Arrays.equals(a, b);
+ Preconditions.checkArgument(a.length == mask.length, "Mask must be of same size as inputs");
+ for (int i = 0; i < mask.length; i++) {
+ if (!maskedEquals(a[i], b[i], mask[i])) return false;
+ }
+ return true;
+ }
+
+ public static boolean maskedEquals(UUID a, UUID b, @Nullable UUID mask) {
+ if (mask == null) {
+ return Objects.equal(a, b);
+ }
+ return maskedEquals(a.getLeastSignificantBits(), b.getLeastSignificantBits(),
+ mask.getLeastSignificantBits())
+ && maskedEquals(a.getMostSignificantBits(), b.getMostSignificantBits(),
+ mask.getMostSignificantBits());
+ }
+}
diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml
index 65cac09a09b3..34bc4ebcd0aa 100644
--- a/packages/CompanionDeviceManager/AndroidManifest.xml
+++ b/packages/CompanionDeviceManager/AndroidManifest.xml
@@ -26,6 +26,8 @@
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+ <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<application
android:allowClearUserData="true"
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
index 12bab18c88c9..14b9de59bf5a 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java
@@ -16,10 +16,9 @@
package com.android.companiondevicemanager;
-import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayName;
+import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
import android.app.Activity;
-import android.bluetooth.BluetoothDevice;
import android.companion.CompanionDeviceManager;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -34,6 +33,8 @@ import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.android.companiondevicemanager.DeviceDiscoveryService.DeviceFilterPair;
+
public class DeviceChooserActivity extends Activity {
private static final boolean DEBUG = false;
@@ -55,11 +56,11 @@ public class DeviceChooserActivity extends Activity {
if (getService().mRequest.isSingleDevice()) {
setContentView(R.layout.device_confirmation);
- final BluetoothDevice selectedDevice = getService().mDevicesFound.get(0);
+ final DeviceFilterPair selectedDevice = getService().mDevicesFound.get(0);
setTitle(Html.fromHtml(getString(
R.string.confirmation_title,
getCallingAppName(),
- getDeviceDisplayName(selectedDevice)), 0));
+ selectedDevice.getDisplayName()), 0));
getService().mSelectedDevice = selectedDevice;
} else {
setContentView(R.layout.device_chooser);
@@ -127,10 +128,11 @@ public class DeviceChooserActivity extends Activity {
return DeviceDiscoveryService.sInstance;
}
- protected void onPairTapped(BluetoothDevice selectedDevice) {
- getService().onDeviceSelected(getCallingPackage(), selectedDevice.getAddress());
+ protected void onPairTapped(DeviceFilterPair selectedDevice) {
+ getService().onDeviceSelected(
+ getCallingPackage(), getDeviceMacAddress(selectedDevice.device));
setResult(RESULT_OK,
- new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice));
+ new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice.device));
finish();
}
} \ No newline at end of file
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
index f0f910848943..e1e60bb99374 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java
@@ -16,8 +16,10 @@
package com.android.companiondevicemanager;
-import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayName;
-import static android.companion.BluetoothLEDeviceFilter.nullsafe;
+import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal;
+import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
+
+import static com.android.internal.util.ArrayUtils.isEmpty;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -32,28 +34,38 @@ import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.companion.AssociationRequest;
+import android.companion.BluetoothDeviceFilter;
+import android.companion.BluetoothDeviceFilterUtils;
import android.companion.BluetoothLEDeviceFilter;
import android.companion.CompanionDeviceManager;
+import android.companion.DeviceFilter;
import android.companion.ICompanionDeviceDiscoveryService;
import android.companion.ICompanionDeviceDiscoveryServiceCallback;
import android.companion.IFindDeviceCallback;
+import android.companion.WifiDeviceFilter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
+import android.net.wifi.WifiManager;
import android.os.IBinder;
+import android.os.Parcelable;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
+import java.util.Objects;
public class DeviceDiscoveryService extends Service {
@@ -63,12 +75,16 @@ public class DeviceDiscoveryService extends Service {
static DeviceDiscoveryService sInstance;
private BluetoothAdapter mBluetoothAdapter;
- private BluetoothLEDeviceFilter mFilter;
- private ScanFilter mScanFilter;
+ private WifiManager mWifiManager;
private ScanSettings mDefaultScanSettings = new ScanSettings.Builder().build();
- AssociationRequest<?> mRequest;
- List<BluetoothDevice> mDevicesFound;
- BluetoothDevice mSelectedDevice;
+ private List<DeviceFilter<?>> mFilters;
+ private List<BluetoothLEDeviceFilter> mBLEFilters;
+ private List<BluetoothDeviceFilter> mBluetoothFilters;
+ private List<WifiDeviceFilter> mWifiFilters;
+ private List<ScanFilter> mBLEScanFilters;
+ AssociationRequest mRequest;
+ List<DeviceFilterPair> mDevicesFound;
+ DeviceFilterPair mSelectedDevice;
DevicesAdapter mDevicesAdapter;
IFindDeviceCallback mFindCallback;
ICompanionDeviceDiscoveryServiceCallback mServiceCallback;
@@ -95,11 +111,13 @@ public class DeviceDiscoveryService extends Service {
private final ScanCallback mBLEScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
- final BluetoothDevice device = result.getDevice();
+ final DeviceFilterPair<ScanResult> deviceFilterPair
+ = DeviceFilterPair.findMatch(result, mBLEFilters);
+ if (deviceFilterPair == null) return;
if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
- onDeviceLost(device);
+ onDeviceLost(deviceFilterPair);
} else {
- onDeviceFound(device);
+ onDeviceFound(deviceFilterPair);
}
}
};
@@ -109,15 +127,35 @@ public class DeviceDiscoveryService extends Service {
private BroadcastReceiver mBluetoothDeviceFoundBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- final BluetoothDevice device = intent.getParcelableExtra(
- BluetoothDevice.EXTRA_DEVICE);
- if (!mFilter.matches(device)) return; // ignore device
-
+ final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ final DeviceFilterPair<BluetoothDevice> deviceFilterPair
+ = DeviceFilterPair.findMatch(device, mBluetoothFilters);
+ if (deviceFilterPair == null) return;
if (intent.getAction().equals(BluetoothDevice.ACTION_FOUND)) {
- onDeviceFound(device);
+ onDeviceFound(deviceFilterPair);
} else {
- onDeviceLost(device);
+ onDeviceLost(deviceFilterPair);
+ }
+ }
+ };
+
+ private BroadcastReceiver mWifiDeviceFoundBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+ List<android.net.wifi.ScanResult> scanResults = mWifiManager.getScanResults();
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Wifi scan results: " + TextUtils.join("\n", scanResults));
+ }
+
+ for (int i = 0; i < scanResults.size(); i++) {
+ DeviceFilterPair<android.net.wifi.ScanResult> deviceFilterPair =
+ DeviceFilterPair.findMatch(scanResults.get(i), mWifiFilters);
+ if (deviceFilterPair != null) onDeviceFound(deviceFilterPair);
+ }
}
+
}
};
@@ -135,6 +173,7 @@ public class DeviceDiscoveryService extends Service {
mBluetoothAdapter = getSystemService(BluetoothManager.class).getAdapter();
mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
+ mWifiManager = getSystemService(WifiManager.class);
mDevicesFound = new ArrayList<>();
mDevicesAdapter = new DevicesAdapter();
@@ -142,23 +181,39 @@ public class DeviceDiscoveryService extends Service {
sInstance = this;
}
- private void startDiscovery(AssociationRequest<?> request) {
- //TODO support other protocols as well
+ private void startDiscovery(AssociationRequest request) {
mRequest = request;
- mFilter = nullsafe((BluetoothLEDeviceFilter) request.getDeviceFilter());
- mScanFilter = mFilter.getScanFilter();
+
+ mFilters = request.getDeviceFilters();
+ mWifiFilters = ArrayUtils.filter(mFilters, WifiDeviceFilter.class);
+ mBluetoothFilters = ArrayUtils.filter(mFilters, BluetoothDeviceFilter.class);
+ mBLEFilters = ArrayUtils.filter(mFilters, BluetoothLEDeviceFilter.class);
+ mBLEScanFilters = ArrayUtils.map(mBLEFilters, BluetoothLEDeviceFilter::getScanFilter);
reset();
- final IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
- intentFilter.addAction(BluetoothDevice.ACTION_DISAPPEARED);
+ if (shouldScan(mBluetoothFilters)) {
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
+ intentFilter.addAction(BluetoothDevice.ACTION_DISAPPEARED);
- registerReceiver(mBluetoothDeviceFoundBroadcastReceiver, intentFilter);
- mBluetoothAdapter.startDiscovery();
+ registerReceiver(mBluetoothDeviceFoundBroadcastReceiver, intentFilter);
+ mBluetoothAdapter.startDiscovery();
+ }
- mBLEScanner.startScan(
- Collections.singletonList(mScanFilter), mDefaultScanSettings, mBLEScanCallback);
+ if (shouldScan(mBLEFilters)) {
+ mBLEScanner.startScan(mBLEScanFilters, mDefaultScanSettings, mBLEScanCallback);
+ }
+
+ if (shouldScan(mWifiFilters)) {
+ registerReceiver(mWifiDeviceFoundBroadcastReceiver,
+ new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
+ mWifiManager.startScan();
+ }
+ }
+
+ private boolean shouldScan(List<? extends DeviceFilter> mediumSpecificFilters) {
+ return !isEmpty(mediumSpecificFilters) || isEmpty(mFilters);
}
private void reset() {
@@ -178,25 +233,18 @@ public class DeviceDiscoveryService extends Service {
mBluetoothAdapter.cancelDiscovery();
mBLEScanner.stopScan(mBLEScanCallback);
unregisterReceiver(mBluetoothDeviceFoundBroadcastReceiver);
+ unregisterReceiver(mWifiDeviceFoundBroadcastReceiver);
stopSelf();
}
- private void onDeviceFound(BluetoothDevice device) {
+ private void onDeviceFound(@Nullable DeviceFilterPair device) {
if (mDevicesFound.contains(device)) {
return;
}
- if (DEBUG) {
- Log.i(LOG_TAG, "Considering device " + getDeviceDisplayName(device));
- }
+ if (DEBUG) Log.i(LOG_TAG, "Found device " + device.getDisplayName() + " "
+ + getDeviceMacAddress(device.device));
- if (!mFilter.matches(device)) {
- return;
- }
-
- if (DEBUG) {
- Log.i(LOG_TAG, "Found device " + getDeviceDisplayName(device));
- }
if (mDevicesFound.isEmpty()) {
onReadyToShowUI();
}
@@ -217,12 +265,10 @@ public class DeviceDiscoveryService extends Service {
}
}
- private void onDeviceLost(BluetoothDevice device) {
+ private void onDeviceLost(@Nullable DeviceFilterPair device) {
mDevicesFound.remove(device);
mDevicesAdapter.notifyDataSetChanged();
- if (DEBUG) {
- Log.i(LOG_TAG, "Lost device " + getDeviceDisplayName(device));
- }
+ if (DEBUG) Log.i(LOG_TAG, "Lost device " + device.getDisplayName());
}
void onDeviceSelected(String callingPackage, String deviceAddress) {
@@ -236,7 +282,8 @@ public class DeviceDiscoveryService extends Service {
}
}
- class DevicesAdapter extends ArrayAdapter<BluetoothDevice> {
+ class DevicesAdapter extends ArrayAdapter<DeviceFilterPair> {
+ //TODO wifi icon
private Drawable BLUETOOTH_ICON = icon(android.R.drawable.stat_sys_data_bluetooth);
private Drawable icon(int drawableRes) {
@@ -261,8 +308,8 @@ public class DeviceDiscoveryService extends Service {
return view;
}
- private void bind(TextView textView, BluetoothDevice device) {
- textView.setText(getDeviceDisplayName(device));
+ private void bind(TextView textView, DeviceFilterPair device) {
+ textView.setText(device.getDisplayName());
textView.setBackgroundColor(
device.equals(mSelectedDevice)
? Color.GRAY
@@ -285,4 +332,62 @@ public class DeviceDiscoveryService extends Service {
return textView;
}
}
+
+ /**
+ * A pair of device and a filter that matched this device if any.
+ *
+ * @param <T> device type
+ */
+ static class DeviceFilterPair<T extends Parcelable> {
+ public final T device;
+ @Nullable
+ public final DeviceFilter<T> filter;
+
+ private DeviceFilterPair(T device, @Nullable DeviceFilter<T> filter) {
+ this.device = device;
+ this.filter = filter;
+ }
+
+ /**
+ * {@code (device, null)} if the filters list is empty or null
+ * {@code null} if none of the provided filters match the device
+ * {@code (device, filter)} where filter is among the list of filters and matches the device
+ */
+ @Nullable
+ public static <T extends Parcelable> DeviceFilterPair<T> findMatch(
+ T dev, @Nullable List<? extends DeviceFilter<T>> filters) {
+ if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null);
+ final DeviceFilter<T> matchingFilter = ArrayUtils.find(filters, (f) -> f.matches(dev));
+ return matchingFilter != null ? new DeviceFilterPair<>(dev, matchingFilter) : null;
+ }
+
+ public String getDisplayName() {
+ if (filter == null) {
+ Preconditions.checkNotNull(device);
+ if (device instanceof BluetoothDevice) {
+ return getDeviceDisplayNameInternal((BluetoothDevice) device);
+ } else if (device instanceof android.net.wifi.ScanResult) {
+ return getDeviceDisplayNameInternal((android.net.wifi.ScanResult) device);
+ } else if (device instanceof ScanResult) {
+ return getDeviceDisplayNameInternal(((ScanResult) device).getDevice());
+ } else {
+ throw new IllegalArgumentException("Unknown device type: " + device.getClass());
+ }
+ }
+ return filter.getDeviceDisplayName(device);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ DeviceFilterPair<?> that = (DeviceFilterPair<?>) o;
+ return Objects.equals(getDeviceMacAddress(device), getDeviceMacAddress(that.device));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getDeviceMacAddress(device));
+ }
+ }
}
diff --git a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
index ad64e4e6e64d..e6e2cb3d99c9 100644
--- a/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
+++ b/services/print/java/com/android/server/print/CompanionDeviceManagerService.java
@@ -141,7 +141,7 @@ public class CompanionDeviceManagerService extends SystemService {
}
private ServiceConnection getServiceConnection(
- final AssociationRequest<?> request,
+ final AssociationRequest request,
final IFindDeviceCallback findDeviceCallback,
final String callingPackage) {
return new ServiceConnection() {