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
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
@Nullable private ArrayList<DeviceFilter<?>> mDeviceFilters = 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);
|
||||
}
|
||||
|
||||
//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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,22 +66,7 @@ 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;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@NonNull
|
||||
public static BluetoothDeviceFilter nullsafe(@Nullable BluetoothDeviceFilter nullable) {
|
||||
return nullable != null ? nullable : noOp();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@NonNull
|
||||
public static BluetoothDeviceFilter noOp() {
|
||||
if (NO_OP == null) NO_OP = new Builder().build();
|
||||
return NO_OP;
|
||||
return in.readParcelableList(new ArrayList<>(), ParcelUuid.class.getClassLoader());
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@@ -93,6 +77,18 @@ public final class BluetoothDeviceFilter implements DeviceFilter<BluetoothDevice
|
||||
&& matchesName(getNamePattern(), device);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public String getDeviceDisplayName(BluetoothDevice device) {
|
||||
return getDeviceDisplayNameInternal(device);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public int getMediumType() {
|
||||
return DeviceFilter.MEDIUM_TYPE_BLUETOOTH;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Nullable
|
||||
public Pattern getNamePattern() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
private static void debugLogMatchResult(
|
||||
boolean result, BluetoothDevice device, Object criteria) {
|
||||
Log.i(LOG_TAG, getDeviceDisplayName(device) + (result ? " ~ " : " !~ ") + criteria);
|
||||
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;
|
||||
}
|
||||
|
||||
public static String getDeviceDisplayName(@NonNull BluetoothDevice device) {
|
||||
private static void debugLogMatchResult(
|
||||
boolean result, BluetoothDevice device, Object criteria) {
|
||||
Log.i(LOG_TAG, getDeviceDisplayNameInternal(device) + (result ? " ~ " : " !~ ") + criteria);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,36 +43,33 @@ 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);
|
||||
}
|
||||
|
||||
@SuppressLint("ParcelClassLoader")
|
||||
private BluetoothLEDeviceFilter(Parcel in) {
|
||||
this(
|
||||
patternFromString(in.readString()),
|
||||
in.readParcelable(null));
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@NonNull
|
||||
public static BluetoothLEDeviceFilter nullsafe(@Nullable BluetoothLEDeviceFilter nullable) {
|
||||
return nullable != null ? nullable : noOp();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@NonNull
|
||||
public static BluetoothLEDeviceFilter noOp() {
|
||||
if (NO_OP == null) NO_OP = new Builder().build();
|
||||
return NO_OP;
|
||||
mRawDataFilter = rawDataFilter;
|
||||
mRawDataFilterMask = rawDataFilterMask;
|
||||
mRenamePrefix = renamePrefix;
|
||||
mRenameSuffix = renameSuffix;
|
||||
mRenameBytesFrom = renameBytesFrom;
|
||||
mRenameBytesTo = renameBytesTo;
|
||||
mRenameBytesReverseOrder = renameBytesReverseOrder;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@@ -80,13 +84,81 @@ public final class BluetoothLEDeviceFilter implements DeviceFilter<BluetoothDevi
|
||||
return mScanFilter;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Nullable
|
||||
public byte[] getRawDataFilter() {
|
||||
return mRawDataFilter;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Nullable
|
||||
public byte[] getRawDataFilterMask() {
|
||||
return mRawDataFilterMask;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@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
|
||||
public boolean matches(BluetoothDevice device) {
|
||||
@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(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {}
|
||||
}
|
||||
|
||||
125
core/java/android/companion/WifiDeviceFilter.java
Normal file
125
core/java/android/companion/WifiDeviceFilter.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
58
core/java/com/android/internal/util/BitUtils.java
Normal file
58
core/java/com/android/internal/util/BitUtils.java
Normal file
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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,18 +127,38 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
if (DEBUG) Log.i(LOG_TAG, "onBind(" + intent + ")");
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user