summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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() {