diff --git a/api/current.txt b/api/current.txt index d837786a10b5c..ab7a163b4e593 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8183,18 +8183,17 @@ package android.bluetooth.le { package android.companion { - public final class AssociationRequest 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 CREATOR; } - public static final class AssociationRequest.Builder { - method public android.companion.AssociationRequest build(); - method public static android.companion.AssociationRequest.Builder createForBluetoothDevice(); - method public static android.companion.AssociationRequest.Builder createForBluetoothLEDevice(); - method public android.companion.AssociationRequest.Builder setDeviceFilter(F); - method public android.companion.AssociationRequest.Builder 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 { @@ -8213,6 +8212,7 @@ package android.companion { public final class BluetoothLEDeviceFilter implements android.companion.DeviceFilter { method public int describeContents(); + method public static int getRenamePrefixLengthLimit(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } @@ -8221,11 +8221,13 @@ package android.companion { 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 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 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 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 b95f27a8df60c..98ba7ebc64192 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 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 CREATOR; } - public static final class AssociationRequest.Builder { - method public android.companion.AssociationRequest build(); - method public static android.companion.AssociationRequest.Builder createForBluetoothDevice(); - method public static android.companion.AssociationRequest.Builder createForBluetoothLEDevice(); - method public android.companion.AssociationRequest.Builder setDeviceFilter(F); - method public android.companion.AssociationRequest.Builder 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 { @@ -8713,6 +8712,7 @@ package android.companion { public final class BluetoothLEDeviceFilter implements android.companion.DeviceFilter { method public int describeContents(); + method public static int getRenamePrefixLengthLimit(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } @@ -8721,11 +8721,13 @@ package android.companion { 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 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 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 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 51ff912569134..8ece524a77a8f 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 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 CREATOR; } - public static final class AssociationRequest.Builder { - method public android.companion.AssociationRequest build(); - method public static android.companion.AssociationRequest.Builder createForBluetoothDevice(); - method public static android.companion.AssociationRequest.Builder createForBluetoothLEDevice(); - method public android.companion.AssociationRequest.Builder setDeviceFilter(F); - method public android.companion.AssociationRequest.Builder 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 { @@ -8240,6 +8239,7 @@ package android.companion { public final class BluetoothLEDeviceFilter implements android.companion.DeviceFilter { method public int describeContents(); + method public static int getRenamePrefixLengthLimit(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } @@ -8248,11 +8248,13 @@ package android.companion { 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 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 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 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 b89c64a8cac67..457096bf843ef 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 d477f43ac8c27..56f5d4483270a 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 Device filter type */ -public final class AssociationRequest 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> mDeviceFilters; - private AssociationRequest(boolean singleDevice, int mMediumType, F deviceFilter) { + private AssociationRequest( + boolean singleDevice, @Nullable List> 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 implements Parcela } /** @hide */ - @MediumType - public int getMediumType() { - return mMediumType; - } - - /** @hide */ - @Nullable - public F getDeviceFilter() { - return mDeviceFilter; + @NonNull + public List> 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 implements Parcela /** * A builder for {@link AssociationRequest} - * - * @param the type of filter for the request. */ - public static final class Builder - extends OneTimeUseBuilder> { + public static final class Builder extends OneTimeUseBuilder { private boolean mSingleDevice = false; - @MediumType private int mMediumType; - @Nullable private F mDeviceFilter = null; + @Nullable private ArrayList> mDeviceFilters = null; - private Builder() {} - - /** - * Create a new builder for an association request with a Bluetooth LE device - */ - @NonNull - public static Builder createForBluetoothLEDevice() { - return new Builder() - .setMediumType(MEDIUM_TYPE_BLUETOOTH_LE); - } - - /** - * Create a new builder for an association request with a Bluetooth(non-LE) device - */ - @NonNull - public static Builder createForBluetoothDevice() { - return new Builder() - .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 setSingleDevice(boolean singleDevice) { + public Builder setSingleDevice(boolean singleDevice) { checkNotUsed(); this.mSingleDevice = singleDevice; return this; @@ -163,29 +115,20 @@ public final class AssociationRequest implements Parcela * user */ @NonNull - public Builder 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 setMediumType(@MediumType int deviceType) { - mMediumType = deviceType; + if (deviceFilter != null) { + mDeviceFilters = ArrayUtils.add(mDeviceFilters, deviceFilter); + } return this; } /** @inheritDoc */ @NonNull @Override - public AssociationRequest 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 5a69955429aad..0f16b7b90165a 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 { - private static BluetoothDeviceFilter NO_OP; - private final Pattern mNamePattern; private final String mAddress; private final List mServiceUuids; @@ -67,22 +66,7 @@ public final class BluetoothDeviceFilter implements DeviceFilter readUuids(Parcel in) { - final ArrayList 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 { +public final class BluetoothLEDeviceFilter implements DeviceFilter { - 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() { @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 { 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= 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 6fa32b4b6944e..5710ad1318d7c 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 8362b2dab8fd8..9b4fdfdf51087 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 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 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 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 extends Parcelable { static boolean matches(@Nullable DeviceFilter 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 0000000000000..1ab9ce11cb0f3 --- /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 { + + 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 CREATOR + = new Creator() { + @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 { + 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 f94e89a2785d8..7a39d239f84bf 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 void readParcelableList(List list, ClassLoader cl) { + public final List readParcelableList(List 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 List filter(@Nullable List list, Class c) { + if (isEmpty(list)) return Collections.emptyList(); + ArrayList 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 boolean any(@Nullable List items, + java.util.function.Predicate predicate) { + return find(items, predicate) != null; + } + + @Nullable + public static T find(@Nullable List items, + java.util.function.Predicate 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 0000000000000..a208ccb8f35f1 --- /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 65cac09a09b3c..34bc4ebcd0aa2 100644 --- a/packages/CompanionDeviceManager/AndroidManifest.xml +++ b/packages/CompanionDeviceManager/AndroidManifest.xml @@ -26,6 +26,8 @@ + + mRequest; - List mDevicesFound; - BluetoothDevice mSelectedDevice; + private List> mFilters; + private List mBLEFilters; + private List mBluetoothFilters; + private List mWifiFilters; + private List mBLEScanFilters; + AssociationRequest mRequest; + List 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 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 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 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 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 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 { + class DevicesAdapter extends ArrayAdapter { + //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 device type + */ + static class DeviceFilterPair { + public final T device; + @Nullable + public final DeviceFilter filter; + + private DeviceFilterPair(T device, @Nullable DeviceFilter 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 DeviceFilterPair findMatch( + T dev, @Nullable List> filters) { + if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null); + final DeviceFilter 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 ad64e4e6e64da..e6e2cb3d99c9e 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() {