Merge "Merge history of Nearby" into tm-dev

This commit is contained in:
Chun Zhang
2022-03-22 21:26:11 +00:00
committed by Android (Google) Code Review
473 changed files with 70112 additions and 2 deletions

View File

@@ -114,7 +114,7 @@ java_sdk_library {
// In preparation for future move
"//packages/modules/Connectivity/apex",
"//packages/modules/Connectivity/service-t",
"//packages/modules/Nearby/service",
"//packages/modules/Connectivity/nearby/service",
"//frameworks/base",
// Tests using hidden APIs
@@ -131,10 +131,10 @@ java_sdk_library {
"//frameworks/opt/telephony/tests/telephonytests",
"//packages/modules/CaptivePortalLogin/tests",
"//packages/modules/Connectivity/Tethering/tests:__subpackages__",
"//packages/modules/Connectivity/nearby/tests:__subpackages__",
"//packages/modules/Connectivity/tests:__subpackages__",
"//packages/modules/IPsec/tests/iketests",
"//packages/modules/NetworkStack/tests:__subpackages__",
"//packages/modules/Nearby/tests:__subpackages__",
"//packages/modules/Wifi/service/tests/wifitests",
],
}

8
nearby/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
# Eclipse project
**/.classpath
**/.project
# IntelliJ project
**/.idea
**/*.iml
**/*.ipr

10
nearby/PREUPLOAD.cfg Normal file
View File

@@ -0,0 +1,10 @@
[Builtin Hooks]
xmllint = true
clang_format = true
commit_msg_changeid_field = true
[Builtin Hooks Options]
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}

42
nearby/README.md Normal file
View File

@@ -0,0 +1,42 @@
# Nearby Mainline Module
This directory contains code for the AOSP Nearby mainline module.
##Directory Structure
`apex`
- Files associated with the Nearby mainline module APEX.
`framework`
- Contains client side APIs and AIDL files.
`jni`
- JNI wrapper for invoking Android APIs from native code.
`native`
- Native code implementation for nearby module services.
`service`
- Server side implementation for nearby module services.
`tests`
- Unit/Multi devices tests for Nearby module (both Java and native code).
## IDE setup
```sh
$ source build/envsetup.sh && lunch <TARGET>
$ cd packages/modules/Nearby
$ aidegen .
# This will launch Intellij project for Nearby module.
```
## Build and Install
```sh
$ source build/envsetup.sh && lunch <TARGET>
$ m com.google.android.tethering.next deapexer
$ $ANDROID_BUILD_TOP/out/host/linux-x86/bin/deapexer decompress --input \
${ANDROID_PRODUCT_OUT}/system/apex/com.google.android.tethering.next.capex \
--output /tmp/tethering.apex
$ adb install -r /tmp/tethering.apex
```

24
nearby/TEST_MAPPING Normal file
View File

@@ -0,0 +1,24 @@
{
"presubmit": [
{
"name": "NearbyUnitTests"
},
{
"name": "NearbyIntegrationPrivilegedTests"
},
{
"name": "NearbyIntegrationUntrustedTests"
}
],
"postsubmit": [
{
"name": "NearbyUnitTests"
}
]
// TODO(b/193602229): uncomment once it's supported.
//"mainline-presubmit": [
// {
// "name": "NearbyUnitTests[com.google.android.nearby.apex]"
// }
//]
}

21
nearby/apex/Android.bp Normal file
View File

@@ -0,0 +1,21 @@
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
filegroup {
name: "nearby-jarjar-rules",
srcs: ["jarjar-rules.txt"],
}

View File

@@ -0,0 +1 @@
rule com.android.internal.** com.android.nearby.jarjar.@0

View File

@@ -0,0 +1,4 @@
{
"name": "com.android.nearby",
"version": 1
}

View File

@@ -0,0 +1,55 @@
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
// Sources included in the framework-connectivity-t jar
// TODO: consider moving files to packages/modules/Connectivity
filegroup {
name: "framework-nearby-java-sources",
srcs: [
"java/**/*.java",
"java/**/*.aidl",
],
path: "java",
visibility: [
"//packages/modules/Connectivity/framework-t:__subpackages__",
],
}
filegroup {
name: "framework-nearby-sources",
srcs: [
":framework-nearby-java-sources",
],
visibility: ["//frameworks/base"],
}
// Build of only framework-nearby (not as part of connectivity) for
// unit tests
java_library {
name: "framework-nearby-static",
srcs: [":framework-nearby-java-sources"],
sdk_version: "module_current",
libs: [
"framework-annotations-lib",
"framework-bluetooth",
],
static_libs: [
"modules-utils-preconditions",
],
visibility: ["//packages/modules/Connectivity/nearby/tests:__subpackages__"],
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.IntDef;
import android.annotation.SystemApi;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Callback when broadcasting request using nearby specification.
*
* @hide
*/
@SystemApi
public interface BroadcastCallback {
/** Broadcast was successful. */
int STATUS_OK = 0;
/** General status code when broadcast failed. */
int STATUS_FAILURE = 1;
/**
* Broadcast failed as the callback was already registered.
*/
int STATUS_FAILURE_ALREADY_REGISTERED = 2;
/**
* Broadcast failed as the request contains excessive data.
*/
int STATUS_FAILURE_SIZE_EXCEED_LIMIT = 3;
/**
* Broadcast failed as the client doesn't hold required permissions.
*/
int STATUS_FAILURE_MISSING_PERMISSIONS = 4;
/** @hide **/
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATUS_OK, STATUS_FAILURE, STATUS_FAILURE_ALREADY_REGISTERED,
STATUS_FAILURE_SIZE_EXCEED_LIMIT, STATUS_FAILURE_MISSING_PERMISSIONS})
@interface BroadcastStatus {
}
/**
* Called when broadcast status changes.
*/
void onStatusChanged(@BroadcastStatus int status);
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a {@link BroadcastRequest}.
*
* @hide
*/
@SystemApi
public abstract class BroadcastRequest {
/** An unknown nearby broadcast request type. */
public static final int BROADCAST_TYPE_UNKNOWN = -1;
/** Broadcast type for advertising using nearby presence protocol. */
public static final int BROADCAST_TYPE_NEARBY_PRESENCE = 3;
/** @hide **/
// Currently, only Nearby Presence broadcast is supported, in the future
// broadcasting using other nearby specifications will be added.
@Retention(RetentionPolicy.SOURCE)
@IntDef({BROADCAST_TYPE_UNKNOWN, BROADCAST_TYPE_NEARBY_PRESENCE})
public @interface BroadcastType {
}
/**
* Tx Power when the value is not set in the broadcast.
*/
public static final int UNKNOWN_TX_POWER = -127;
/**
* An unknown version of presence broadcast request.
*/
public static final int PRESENCE_VERSION_UNKNOWN = -1;
/**
* A legacy presence version that is only suitable for legacy (31 bytes) BLE advertisements.
* This exists to support legacy presence version, and not recommended for use.
*/
public static final int PRESENCE_VERSION_V0 = 0;
/**
* V1 of Nearby Presence Protocol. This version supports both legacy (31 bytes) BLE
* advertisements, and extended BLE advertisements.
*/
public static final int PRESENCE_VERSION_V1 = 1;
/** @hide **/
@Retention(RetentionPolicy.SOURCE)
@IntDef({PRESENCE_VERSION_UNKNOWN, PRESENCE_VERSION_V0, PRESENCE_VERSION_V1})
public @interface BroadcastVersion {
}
/**
* The medium where the broadcast request should be sent.
*
* @hide
*/
@IntDef({Medium.BLE, Medium.MDNS})
public @interface Medium {
int BLE = 1;
int MDNS = 2;
}
/**
* Creates a {@link BroadcastRequest} from parcel.
*
* @hide
*/
@NonNull
public static BroadcastRequest createFromParcel(Parcel in) {
int type = in.readInt();
switch (type) {
case BroadcastRequest.BROADCAST_TYPE_NEARBY_PRESENCE:
return PresenceBroadcastRequest.createFromParcelBody(in);
default:
throw new IllegalStateException(
"Unexpected broadcast type (value " + type + ") in parcel.");
}
}
private final @BroadcastType int mType;
private final @BroadcastVersion int mVersion;
private final int mTxPower;
private final @Medium List<Integer> mMediums;
BroadcastRequest(@BroadcastType int type, @BroadcastVersion int version, int txPower,
@Medium List<Integer> mediums) {
this.mType = type;
this.mVersion = version;
this.mTxPower = txPower;
this.mMediums = mediums;
}
BroadcastRequest(@BroadcastType int type, Parcel in) {
mType = type;
mVersion = in.readInt();
mTxPower = in.readInt();
mMediums = new ArrayList<>();
in.readList(mMediums, Integer.class.getClassLoader(), Integer.class);
}
/**
* Returns the type of the broadcast.
*/
public @BroadcastType int getType() {
return mType;
}
/**
* Returns the version of the broadcast.
*/
public @BroadcastVersion int getVersion() {
return mVersion;
}
/**
* Returns the calibrated TX power when this request is broadcast.
*/
@IntRange(from = -127, to = 126)
public int getTxPower() {
return mTxPower;
}
/**
* Returns the list of broadcast mediums.
*/
@NonNull
@Medium
public List<Integer> getMediums() {
return mMediums;
}
/**
* Writes the BroadcastRequest to the parcel.
*
* @hide
*/
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeInt(mVersion);
dest.writeInt(mTxPower);
dest.writeList(mMediums);
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
parcelable BroadcastRequestParcelable;

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
/**
* A wrapper of {@link BroadcastRequest} that is parcelable.
*
* @hide
*/
public class BroadcastRequestParcelable implements Parcelable {
private final BroadcastRequest mBroadcastRequest;
public static final Creator<BroadcastRequestParcelable> CREATOR =
new Creator<BroadcastRequestParcelable>() {
@Override
public BroadcastRequestParcelable createFromParcel(Parcel in) {
return new BroadcastRequestParcelable(BroadcastRequest.createFromParcel(in));
}
@Override
public BroadcastRequestParcelable[] newArray(int size) {
return new BroadcastRequestParcelable[size];
}
};
BroadcastRequestParcelable(BroadcastRequest broadcastRequest) {
mBroadcastRequest = broadcastRequest;
}
/**
* Returns the broadcastRequest.
*/
public BroadcastRequest getBroadcastRequest() {
return mBroadcastRequest;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
mBroadcastRequest.writeToParcel(dest, flags);
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
/**
* Represents an element in {@link PresenceCredential}.
*
* @hide
*/
@SystemApi
public final class CredentialElement implements Parcelable {
private final String mKey;
private final byte[] mValue;
/**
* Constructs a {@link CredentialElement}.
*/
public CredentialElement(@NonNull String key, @NonNull byte[] value) {
Preconditions.checkState(key != null && value != null,
"neither key or value can be null");
mKey = key;
mValue = value;
}
@NonNull
public static final Parcelable.Creator<CredentialElement> CREATOR =
new Parcelable.Creator<CredentialElement>() {
@Override
public CredentialElement createFromParcel(Parcel in) {
String key = in.readString();
byte[] value = new byte[in.readInt()];
in.readByteArray(value);
return new CredentialElement(key, value);
}
@Override
public CredentialElement[] newArray(int size) {
return new CredentialElement[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mKey);
dest.writeInt(mValue.length);
dest.writeByteArray(mValue);
}
/**
* Returns the key of the credential element.
*/
@NonNull
public String getKey() {
return mKey;
}
/**
* Returns the value of the credential element.
*/
@NonNull
public byte[] getValue() {
return mValue;
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
/**
* Represents a data element in Nearby Presence.
*
* @hide
*/
@SystemApi
public final class DataElement implements Parcelable {
private final int mKey;
private final byte[] mValue;
/**
* Constructs a {@link DataElement}.
*/
public DataElement(int key, @NonNull byte[] value) {
Preconditions.checkState(value != null, "value cannot be null");
mKey = key;
mValue = value;
}
@NonNull
public static final Creator<DataElement> CREATOR = new Creator<DataElement>() {
@Override
public DataElement createFromParcel(Parcel in) {
int key = in.readInt();
byte[] value = new byte[in.readInt()];
in.readByteArray(value);
return new DataElement(key, value);
}
@Override
public DataElement[] newArray(int size) {
return new DataElement[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mKey);
dest.writeInt(mValue.length);
dest.writeByteArray(mValue);
}
/**
* Returns the key of the data element, as defined in the nearby presence specification.
*/
public int getKey() {
return mKey;
}
/**
* Returns the value of the data element.
*/
@NonNull
public byte[] getValue() {
return mValue;
}
}

View File

@@ -0,0 +1,196 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
/**
* Class for metadata of a Fast Pair device associated with an account.
*
* @hide
*/
@SystemApi
public class FastPairAccountKeyDeviceMetadata {
FastPairAccountKeyDeviceMetadataParcel mMetadataParcel;
FastPairAccountKeyDeviceMetadata(FastPairAccountKeyDeviceMetadataParcel metadataParcel) {
this.mMetadataParcel = metadataParcel;
}
/**
* Get Device Account Key, which uniquely identifies a Fast Pair device associated with an
* account. AccountKey is 16 bytes: first byte is 0x04. Other 15 bytes are randomly generated.
*
* @return 16-byte Account Key.
* @hide
*/
@SystemApi
@Nullable
public byte[] getDeviceAccountKey() {
return mMetadataParcel.deviceAccountKey;
}
/**
* Get a hash value of device's account key and public bluetooth address without revealing the
* public bluetooth address. Sha256 hash value is 32 bytes.
*
* @return 32-byte Sha256 hash value.
* @hide
*/
@SystemApi
@Nullable
public byte[] getSha256DeviceAccountKeyPublicAddress() {
return mMetadataParcel.sha256DeviceAccountKeyPublicAddress;
}
/**
* Get metadata of a Fast Pair device type.
*
* @hide
*/
@SystemApi
@Nullable
public FastPairDeviceMetadata getFastPairDeviceMetadata() {
if (mMetadataParcel.metadata == null) {
return null;
}
return new FastPairDeviceMetadata(mMetadataParcel.metadata);
}
/**
* Get Fast Pair discovery item, which is tied to both the device type and the account.
*
* @hide
*/
@SystemApi
@Nullable
public FastPairDiscoveryItem getFastPairDiscoveryItem() {
if (mMetadataParcel.discoveryItem == null) {
return null;
}
return new FastPairDiscoveryItem(mMetadataParcel.discoveryItem);
}
/**
* Builder used to create FastPairAccountKeyDeviceMetadata.
*
* @hide
*/
@SystemApi
public static final class Builder {
private final FastPairAccountKeyDeviceMetadataParcel mBuilderParcel;
/**
* Default constructor of Builder.
*
* @hide
*/
@SystemApi
public Builder() {
mBuilderParcel = new FastPairAccountKeyDeviceMetadataParcel();
mBuilderParcel.deviceAccountKey = null;
mBuilderParcel.sha256DeviceAccountKeyPublicAddress = null;
mBuilderParcel.metadata = null;
mBuilderParcel.discoveryItem = null;
}
/**
* Set Account Key.
*
* @param deviceAccountKey Fast Pair device account key, which is 16 bytes: first byte is
* 0x04. Next 15 bytes are randomly generated.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setDeviceAccountKey(@Nullable byte[] deviceAccountKey) {
mBuilderParcel.deviceAccountKey = deviceAccountKey;
return this;
}
/**
* Set sha256 hash value of account key and public bluetooth address.
*
* @param sha256DeviceAccountKeyPublicAddress 32-byte sha256 hash value of account key and
* public bluetooth address.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setSha256DeviceAccountKeyPublicAddress(
@Nullable byte[] sha256DeviceAccountKeyPublicAddress) {
mBuilderParcel.sha256DeviceAccountKeyPublicAddress =
sha256DeviceAccountKeyPublicAddress;
return this;
}
/**
* Set Fast Pair metadata.
*
* @param metadata Fast Pair metadata.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setFastPairDeviceMetadata(@Nullable FastPairDeviceMetadata metadata) {
if (metadata == null) {
mBuilderParcel.metadata = null;
} else {
mBuilderParcel.metadata = metadata.mMetadataParcel;
}
return this;
}
/**
* Set Fast Pair discovery item.
*
* @param discoveryItem Fast Pair discovery item.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setFastPairDiscoveryItem(@Nullable FastPairDiscoveryItem discoveryItem) {
if (discoveryItem == null) {
mBuilderParcel.discoveryItem = null;
} else {
mBuilderParcel.discoveryItem = discoveryItem.mMetadataParcel;
}
return this;
}
/**
* Build {@link FastPairAccountKeyDeviceMetadata} with the currently set configuration.
*
* @hide
*/
@SystemApi
@NonNull
public FastPairAccountKeyDeviceMetadata build() {
return new FastPairAccountKeyDeviceMetadata(mBuilderParcel);
}
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel;
/**
* Class for a type of registered Fast Pair device keyed by modelID, or antispoofKey.
*
* @hide
*/
@SystemApi
public class FastPairAntispoofKeyDeviceMetadata {
FastPairAntispoofKeyDeviceMetadataParcel mMetadataParcel;
FastPairAntispoofKeyDeviceMetadata(
FastPairAntispoofKeyDeviceMetadataParcel metadataParcel) {
this.mMetadataParcel = metadataParcel;
}
/**
* Get Antispoof public key.
*
* @hide
*/
@SystemApi
@Nullable
public byte[] getAntispoofPublicKey() {
return this.mMetadataParcel.antispoofPublicKey;
}
/**
* Get metadata of a Fast Pair device type.
*
* @hide
*/
@SystemApi
@Nullable
public FastPairDeviceMetadata getFastPairDeviceMetadata() {
if (this.mMetadataParcel.deviceMetadata == null) {
return null;
}
return new FastPairDeviceMetadata(this.mMetadataParcel.deviceMetadata);
}
/**
* Builder used to create FastPairAntispoofkeyDeviceMetadata.
*
* @hide
*/
@SystemApi
public static final class Builder {
private final FastPairAntispoofKeyDeviceMetadataParcel mBuilderParcel;
/**
* Default constructor of Builder.
*
* @hide
*/
@SystemApi
public Builder() {
mBuilderParcel = new FastPairAntispoofKeyDeviceMetadataParcel();
mBuilderParcel.antispoofPublicKey = null;
mBuilderParcel.deviceMetadata = null;
}
/**
* Set AntiSpoof public key, which uniquely identify a Fast Pair device type.
*
* @param antispoofPublicKey is 64 bytes, see <a href="https://developers.google.com/nearby/fast-pair/spec#data_format">Data Format</a>.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setAntispoofPublicKey(@Nullable byte[] antispoofPublicKey) {
mBuilderParcel.antispoofPublicKey = antispoofPublicKey;
return this;
}
/**
* Set Fast Pair metadata, which is the property of a Fast Pair device type, including
* device images and strings.
*
* @param metadata Fast Pair device meta data.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setFastPairDeviceMetadata(@Nullable FastPairDeviceMetadata metadata) {
if (metadata != null) {
mBuilderParcel.deviceMetadata = metadata.mMetadataParcel;
} else {
mBuilderParcel.deviceMetadata = null;
}
return this;
}
/**
* Build {@link FastPairAntispoofKeyDeviceMetadata} with the currently set configuration.
*
* @hide
*/
@SystemApi
@NonNull
public FastPairAntispoofKeyDeviceMetadata build() {
return new FastPairAntispoofKeyDeviceMetadata(mBuilderParcel);
}
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.BinderThread;
import android.content.Context;
import android.nearby.aidl.IFastPairClient;
import android.nearby.aidl.IFastPairStatusCallback;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import java.lang.ref.WeakReference;
/**
* 0p API for controlling Fast Pair. It communicates between main thread and service.
*
* @hide
*/
public class FastPairClient {
private static final String TAG = "FastPairClient";
private final IBinder mBinder;
private final WeakReference<Context> mWeakContext;
IFastPairClient mFastPairClient;
PairStatusCallbackIBinder mPairStatusCallbackIBinder;
/**
* The Ibinder instance should be from
* {@link com.android.server.nearby.fastpair.halfsheet.FastPairService} so that the client can
* talk with the service.
*/
public FastPairClient(Context context, IBinder binder) {
mBinder = binder;
mFastPairClient = IFastPairClient.Stub.asInterface(mBinder);
mWeakContext = new WeakReference<>(context);
}
/**
* Registers a callback at service to get UI updates.
*/
public void registerHalfSheet(FastPairStatusCallback fastPairStatusCallback) {
if (mPairStatusCallbackIBinder != null) {
return;
}
mPairStatusCallbackIBinder = new PairStatusCallbackIBinder(fastPairStatusCallback);
try {
mFastPairClient.registerHalfSheet(mPairStatusCallbackIBinder);
} catch (RemoteException e) {
Log.w(TAG, "Failed to register fastPairStatusCallback", e);
}
}
/**
* Pairs the device at service.
*/
public void connect(FastPairDevice fastPairDevice) {
try {
mFastPairClient.connect(fastPairDevice);
} catch (RemoteException e) {
Log.w(TAG, "Failed to connect Fast Pair device" + fastPairDevice, e);
}
}
private class PairStatusCallbackIBinder extends IFastPairStatusCallback.Stub {
private final FastPairStatusCallback mStatusCallback;
private PairStatusCallbackIBinder(FastPairStatusCallback fastPairStatusCallback) {
mStatusCallback = fastPairStatusCallback;
}
@BinderThread
@Override
public synchronized void onPairUpdate(FastPairDevice fastPairDevice,
PairStatusMetadata pairStatusMetadata) {
Context context = mWeakContext.get();
if (context != null) {
Handler handler = new Handler(context.getMainLooper());
handler.post(() ->
mStatusCallback.onPairUpdate(fastPairDevice, pairStatusMetadata));
}
}
}
}

View File

@@ -0,0 +1,761 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.accounts.Account;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
import android.nearby.aidl.ByteArrayParcel;
import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel;
import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel;
import android.nearby.aidl.FastPairEligibleAccountParcel;
import android.nearby.aidl.FastPairEligibleAccountsRequestParcel;
import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel;
import android.nearby.aidl.FastPairManageAccountRequestParcel;
import android.nearby.aidl.IFastPairAccountDevicesMetadataCallback;
import android.nearby.aidl.IFastPairAntispoofKeyDeviceMetadataCallback;
import android.nearby.aidl.IFastPairDataProvider;
import android.nearby.aidl.IFastPairEligibleAccountsCallback;
import android.nearby.aidl.IFastPairManageAccountCallback;
import android.nearby.aidl.IFastPairManageAccountDeviceCallback;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* A service class for fast pair data providers outside the system server.
*
* Fast pair providers should be wrapped in a non-exported service which returns the result of
* {@link #getBinder()} from the service's {@link android.app.Service#onBind(Intent)} method. The
* service should not be exported so that components other than the system server cannot bind to it.
* Alternatively, the service may be guarded by a permission that only system server can obtain.
*
* <p>Fast Pair providers are identified by their UID / package name.
*
* @hide
*/
@SystemApi
public abstract class FastPairDataProviderService extends Service {
/**
* The action the wrapping service should have in its intent filter to implement the
* {@link android.nearby.FastPairDataProviderBase}.
*
* @hide
*/
@SystemApi
public static final String ACTION_FAST_PAIR_DATA_PROVIDER =
"android.nearby.action.FAST_PAIR_DATA_PROVIDER";
/**
* Manage request type to add, or opt-in.
*
* @hide
*/
@SystemApi
public static final int MANAGE_REQUEST_ADD = 0;
/**
* Manage request type to remove, or opt-out.
*
* @hide
*/
@SystemApi
public static final int MANAGE_REQUEST_REMOVE = 1;
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
MANAGE_REQUEST_ADD,
MANAGE_REQUEST_REMOVE})
@interface ManageRequestType {}
/**
* Error code for bad request.
*
* @hide
*/
@SystemApi
public static final int ERROR_CODE_BAD_REQUEST = 0;
/**
* Error code for internal error.
*
* @hide
*/
@SystemApi
public static final int ERROR_CODE_INTERNAL_ERROR = 1;
/**
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
ERROR_CODE_BAD_REQUEST,
ERROR_CODE_INTERNAL_ERROR})
@interface ErrorCode {}
private final IBinder mBinder;
private final String mTag;
/**
* Constructor of FastPairDataProviderService.
*
* @param tag TAG for on device logging.
* @hide
*/
@SystemApi
public FastPairDataProviderService(@NonNull String tag) {
mBinder = new Service();
mTag = tag;
}
@Override
@NonNull
public final IBinder onBind(@NonNull Intent intent) {
return mBinder;
}
/**
* Callback to be invoked when an AntispoofKeyed device metadata is loaded.
*
* @hide
*/
@SystemApi
public interface FastPairAntispoofKeyDeviceMetadataCallback {
/**
* Invoked once the meta data is loaded.
*
* @hide
*/
@SystemApi
void onFastPairAntispoofKeyDeviceMetadataReceived(
@NonNull FastPairAntispoofKeyDeviceMetadata metadata);
/** Invoked in case of error.
*
* @hide
*/
@SystemApi
void onError(@ErrorCode int code, @Nullable String message);
}
/**
* Callback to be invoked when Fast Pair devices of a given account is loaded.
*
* @hide
*/
@SystemApi
public interface FastPairAccountDevicesMetadataCallback {
/**
* Should be invoked once the metadatas are loaded.
*
* @hide
*/
@SystemApi
void onFastPairAccountDevicesMetadataReceived(
@NonNull Collection<FastPairAccountKeyDeviceMetadata> metadatas);
/**
* Invoked in case of error.
*
* @hide
*/
@SystemApi
void onError(@ErrorCode int code, @Nullable String message);
}
/**
* Callback to be invoked when FastPair eligible accounts are loaded.
*
* @hide
*/
@SystemApi
public interface FastPairEligibleAccountsCallback {
/**
* Should be invoked once the eligible accounts are loaded.
*
* @hide
*/
@SystemApi
void onFastPairEligibleAccountsReceived(
@NonNull Collection<FastPairEligibleAccount> accounts);
/**
* Invoked in case of error.
*
* @hide
*/
@SystemApi
void onError(@ErrorCode int code, @Nullable String message);
}
/**
* Callback to be invoked when a management action is finished.
*
* @hide
*/
@SystemApi
public interface FastPairManageActionCallback {
/**
* Should be invoked once the manage action is successful.
*
* @hide
*/
@SystemApi
void onSuccess();
/**
* Invoked in case of error.
*
* @hide
*/
@SystemApi
void onError(@ErrorCode int code, @Nullable String message);
}
/**
* Fulfills the Fast Pair device metadata request by using callback to send back the
* device meta data of a given modelId.
*
* @hide
*/
@SystemApi
public abstract void onLoadFastPairAntispoofKeyDeviceMetadata(
@NonNull FastPairAntispoofKeyDeviceMetadataRequest request,
@NonNull FastPairAntispoofKeyDeviceMetadataCallback callback);
/**
* Fulfills the account tied Fast Pair devices metadata request by using callback to send back
* all Fast Pair device's metadata of a given account.
*
* @hide
*/
@SystemApi
public abstract void onLoadFastPairAccountDevicesMetadata(
@NonNull FastPairAccountDevicesMetadataRequest request,
@NonNull FastPairAccountDevicesMetadataCallback callback);
/**
* Fulfills the Fast Pair eligible accounts request by using callback to send back Fast Pair
* eligible accounts.
*
* @hide
*/
@SystemApi
public abstract void onLoadFastPairEligibleAccounts(
@NonNull FastPairEligibleAccountsRequest request,
@NonNull FastPairEligibleAccountsCallback callback);
/**
* Fulfills the Fast Pair account management request by using callback to send back result.
*
* @hide
*/
@SystemApi
public abstract void onManageFastPairAccount(
@NonNull FastPairManageAccountRequest request,
@NonNull FastPairManageActionCallback callback);
/**
* Fulfills the request to manage device-account mapping by using callback to send back result.
*
* @hide
*/
@SystemApi
public abstract void onManageFastPairAccountDevice(
@NonNull FastPairManageAccountDeviceRequest request,
@NonNull FastPairManageActionCallback callback);
/**
* Class for reading FastPairAntispoofKeyDeviceMetadataRequest, which specifies the model ID of
* a Fast Pair device. To fulfill this request, corresponding
* {@link FastPairAntispoofKeyDeviceMetadata} should be fetched and returned.
*
* @hide
*/
@SystemApi
public static class FastPairAntispoofKeyDeviceMetadataRequest {
private final FastPairAntispoofKeyDeviceMetadataRequestParcel mMetadataRequestParcel;
private FastPairAntispoofKeyDeviceMetadataRequest(
final FastPairAntispoofKeyDeviceMetadataRequestParcel metaDataRequestParcel) {
this.mMetadataRequestParcel = metaDataRequestParcel;
}
/**
* Get modelId (24 bit), the key for FastPairAntispoofKeyDeviceMetadata in the same format
* returned by Google at device registration time.
*
* ModelId format is defined at device registration time, see
* <a href="https://developers.google.com/nearby/fast-pair/spec#model_id">Model ID</a>.
* @return raw bytes of modelId in the same format returned by Google at device registration
* time.
* @hide
*/
@SystemApi
public @NonNull byte[] getModelId() {
return this.mMetadataRequestParcel.modelId;
}
}
/**
* Class for reading FastPairAccountDevicesMetadataRequest, which specifies the Fast Pair
* account and the allow list of the FastPair device keys saved to the account (i.e., FastPair
* accountKeys).
*
* A Fast Pair accountKey is created when a Fast Pair device is saved to an account. It is per
* Fast Pair device per account.
*
* To retrieve all Fast Pair accountKeys saved to an account, the caller needs to set
* account with an empty allow list.
*
* To retrieve metadata of a selected list of Fast Pair devices saved to an account, the caller
* needs to set account with a non-empty allow list.
* @hide
*/
@SystemApi
public static class FastPairAccountDevicesMetadataRequest {
private final FastPairAccountDevicesMetadataRequestParcel mMetadataRequestParcel;
private FastPairAccountDevicesMetadataRequest(
final FastPairAccountDevicesMetadataRequestParcel metaDataRequestParcel) {
this.mMetadataRequestParcel = metaDataRequestParcel;
}
/**
* Get FastPair account, whose Fast Pair devices' metadata is requested.
*
* @return a FastPair account.
* @hide
*/
@SystemApi
public @NonNull Account getAccount() {
return this.mMetadataRequestParcel.account;
}
/**
* Get allowlist of Fast Pair devices using a collection of deviceAccountKeys.
* Note that as a special case, empty list actually means all FastPair devices under the
* account instead of none.
*
* DeviceAccountKey is 16 bytes: first byte is 0x04. Other 15 bytes are randomly generated.
*
* @return allowlist of Fast Pair devices using a collection of deviceAccountKeys.
* @hide
*/
@SystemApi
public @NonNull Collection<byte[]> getDeviceAccountKeys() {
if (this.mMetadataRequestParcel.deviceAccountKeys == null) {
return new ArrayList<byte[]>(0);
}
List<byte[]> deviceAccountKeys =
new ArrayList<>(this.mMetadataRequestParcel.deviceAccountKeys.length);
for (ByteArrayParcel deviceAccountKey : this.mMetadataRequestParcel.deviceAccountKeys) {
deviceAccountKeys.add(deviceAccountKey.byteArray);
}
return deviceAccountKeys;
}
}
/**
* Class for reading FastPairEligibleAccountsRequest. Upon receiving this request, Fast Pair
* eligible accounts should be returned to bind Fast Pair devices.
*
* @hide
*/
@SystemApi
public static class FastPairEligibleAccountsRequest {
@SuppressWarnings("UnusedVariable")
private final FastPairEligibleAccountsRequestParcel mAccountsRequestParcel;
private FastPairEligibleAccountsRequest(
final FastPairEligibleAccountsRequestParcel accountsRequestParcel) {
this.mAccountsRequestParcel = accountsRequestParcel;
}
}
/**
* Class for reading FastPairManageAccountRequest. If the request type is MANAGE_REQUEST_ADD,
* the account is enabled to bind Fast Pair devices; If the request type is
* MANAGE_REQUEST_REMOVE, the account is disabled to bind more Fast Pair devices. Furthermore,
* all existing bounded Fast Pair devices are unbounded.
*
* @hide
*/
@SystemApi
public static class FastPairManageAccountRequest {
private final FastPairManageAccountRequestParcel mAccountRequestParcel;
private FastPairManageAccountRequest(
final FastPairManageAccountRequestParcel accountRequestParcel) {
this.mAccountRequestParcel = accountRequestParcel;
}
/**
* Get request type: MANAGE_REQUEST_ADD, or MANAGE_REQUEST_REMOVE.
*
* @hide
*/
@SystemApi
public @ManageRequestType int getRequestType() {
return this.mAccountRequestParcel.requestType;
}
/**
* Get account.
*
* @hide
*/
@SystemApi
public @NonNull Account getAccount() {
return this.mAccountRequestParcel.account;
}
}
/**
* Class for reading FastPairManageAccountDeviceRequest. If the request type is
* MANAGE_REQUEST_ADD, then a Fast Pair device is bounded to a Fast Pair account. If the
* request type is MANAGE_REQUEST_REMOVE, then a Fast Pair device is removed from a Fast Pair
* account.
*
* @hide
*/
@SystemApi
public static class FastPairManageAccountDeviceRequest {
private final FastPairManageAccountDeviceRequestParcel mRequestParcel;
private FastPairManageAccountDeviceRequest(
final FastPairManageAccountDeviceRequestParcel requestParcel) {
this.mRequestParcel = requestParcel;
}
/**
* Get request type: MANAGE_REQUEST_ADD, or MANAGE_REQUEST_REMOVE.
*
* @hide
*/
@SystemApi
public @ManageRequestType int getRequestType() {
return this.mRequestParcel.requestType;
}
/**
* Get account.
*
* @hide
*/
@SystemApi
public @NonNull Account getAccount() {
return this.mRequestParcel.account;
}
/**
* Get BleAddress.
*
* @hide
*/
@SystemApi
public @Nullable String getBleAddress() {
return this.mRequestParcel.bleAddress;
}
/**
* Get account key device metadata.
*
* @hide
*/
@SystemApi
public @NonNull FastPairAccountKeyDeviceMetadata getAccountKeyDeviceMetadata() {
return new FastPairAccountKeyDeviceMetadata(
this.mRequestParcel.accountKeyDeviceMetadata);
}
}
/**
* Callback class that sends back FastPairAntispoofKeyDeviceMetadata.
*/
private final class WrapperFastPairAntispoofKeyDeviceMetadataCallback implements
FastPairAntispoofKeyDeviceMetadataCallback {
private IFastPairAntispoofKeyDeviceMetadataCallback mCallback;
private WrapperFastPairAntispoofKeyDeviceMetadataCallback(
IFastPairAntispoofKeyDeviceMetadataCallback callback) {
mCallback = callback;
}
/**
* Sends back FastPairAntispoofKeyDeviceMetadata.
*/
@Override
public void onFastPairAntispoofKeyDeviceMetadataReceived(
@NonNull FastPairAntispoofKeyDeviceMetadata metadata) {
try {
mCallback.onFastPairAntispoofKeyDeviceMetadataReceived(metadata.mMetadataParcel);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
Log.w(mTag, e);
}
}
@Override
public void onError(@ErrorCode int code, @Nullable String message) {
try {
mCallback.onError(code, message);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
Log.w(mTag, e);
}
}
}
/**
* Callback class that sends back collection of FastPairAccountKeyDeviceMetadata.
*/
private final class WrapperFastPairAccountDevicesMetadataCallback implements
FastPairAccountDevicesMetadataCallback {
private IFastPairAccountDevicesMetadataCallback mCallback;
private WrapperFastPairAccountDevicesMetadataCallback(
IFastPairAccountDevicesMetadataCallback callback) {
mCallback = callback;
}
/**
* Sends back collection of FastPairAccountKeyDeviceMetadata.
*/
@Override
public void onFastPairAccountDevicesMetadataReceived(
@NonNull Collection<FastPairAccountKeyDeviceMetadata> metadatas) {
FastPairAccountKeyDeviceMetadataParcel[] metadataParcels =
new FastPairAccountKeyDeviceMetadataParcel[metadatas.size()];
int i = 0;
for (FastPairAccountKeyDeviceMetadata metadata : metadatas) {
metadataParcels[i] = metadata.mMetadataParcel;
i = i + 1;
}
try {
mCallback.onFastPairAccountDevicesMetadataReceived(metadataParcels);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
Log.w(mTag, e);
}
}
@Override
public void onError(@ErrorCode int code, @Nullable String message) {
try {
mCallback.onError(code, message);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
Log.w(mTag, e);
}
}
}
/**
* Callback class that sends back eligible Fast Pair accounts.
*/
private final class WrapperFastPairEligibleAccountsCallback implements
FastPairEligibleAccountsCallback {
private IFastPairEligibleAccountsCallback mCallback;
private WrapperFastPairEligibleAccountsCallback(
IFastPairEligibleAccountsCallback callback) {
mCallback = callback;
}
/**
* Sends back the eligible Fast Pair accounts.
*/
@Override
public void onFastPairEligibleAccountsReceived(
@NonNull Collection<FastPairEligibleAccount> accounts) {
int i = 0;
FastPairEligibleAccountParcel[] accountParcels =
new FastPairEligibleAccountParcel[accounts.size()];
for (FastPairEligibleAccount account: accounts) {
accountParcels[i] = account.mAccountParcel;
i = i + 1;
}
try {
mCallback.onFastPairEligibleAccountsReceived(accountParcels);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
Log.w(mTag, e);
}
}
@Override
public void onError(@ErrorCode int code, @Nullable String message) {
try {
mCallback.onError(code, message);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
Log.w(mTag, e);
}
}
}
/**
* Callback class that sends back Fast Pair account management result.
*/
private final class WrapperFastPairManageAccountCallback implements
FastPairManageActionCallback {
private IFastPairManageAccountCallback mCallback;
private WrapperFastPairManageAccountCallback(
IFastPairManageAccountCallback callback) {
mCallback = callback;
}
/**
* Sends back Fast Pair account opt in result.
*/
@Override
public void onSuccess() {
try {
mCallback.onSuccess();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
Log.w(mTag, e);
}
}
@Override
public void onError(@ErrorCode int code, @Nullable String message) {
try {
mCallback.onError(code, message);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
Log.w(mTag, e);
}
}
}
/**
* Call back class that sends back account-device mapping management result.
*/
private final class WrapperFastPairManageAccountDeviceCallback implements
FastPairManageActionCallback {
private IFastPairManageAccountDeviceCallback mCallback;
private WrapperFastPairManageAccountDeviceCallback(
IFastPairManageAccountDeviceCallback callback) {
mCallback = callback;
}
/**
* Sends back the account-device mapping management result.
*/
@Override
public void onSuccess() {
try {
mCallback.onSuccess();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
Log.w(mTag, e);
}
}
@Override
public void onError(@ErrorCode int code, @Nullable String message) {
try {
mCallback.onError(code, message);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {
Log.w(mTag, e);
}
}
}
private final class Service extends IFastPairDataProvider.Stub {
Service() {
}
@Override
public void loadFastPairAntispoofKeyDeviceMetadata(
@NonNull FastPairAntispoofKeyDeviceMetadataRequestParcel requestParcel,
IFastPairAntispoofKeyDeviceMetadataCallback callback) {
onLoadFastPairAntispoofKeyDeviceMetadata(
new FastPairAntispoofKeyDeviceMetadataRequest(requestParcel),
new WrapperFastPairAntispoofKeyDeviceMetadataCallback(callback));
}
@Override
public void loadFastPairAccountDevicesMetadata(
@NonNull FastPairAccountDevicesMetadataRequestParcel requestParcel,
IFastPairAccountDevicesMetadataCallback callback) {
onLoadFastPairAccountDevicesMetadata(
new FastPairAccountDevicesMetadataRequest(requestParcel),
new WrapperFastPairAccountDevicesMetadataCallback(callback));
}
@Override
public void loadFastPairEligibleAccounts(
@NonNull FastPairEligibleAccountsRequestParcel requestParcel,
IFastPairEligibleAccountsCallback callback) {
onLoadFastPairEligibleAccounts(new FastPairEligibleAccountsRequest(requestParcel),
new WrapperFastPairEligibleAccountsCallback(callback));
}
@Override
public void manageFastPairAccount(
@NonNull FastPairManageAccountRequestParcel requestParcel,
IFastPairManageAccountCallback callback) {
onManageFastPairAccount(new FastPairManageAccountRequest(requestParcel),
new WrapperFastPairManageAccountCallback(callback));
}
@Override
public void manageFastPairAccountDevice(
@NonNull FastPairManageAccountDeviceRequestParcel requestParcel,
IFastPairManageAccountDeviceCallback callback) {
onManageFastPairAccountDevice(new FastPairManageAccountDeviceRequest(requestParcel),
new WrapperFastPairManageAccountDeviceCallback(callback));
}
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
/**
* A class represents a Fast Pair device that can be discovered by multiple mediums.
*
* {@hide}
*/
parcelable FastPairDevice;

View File

@@ -0,0 +1,299 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* A class represents a Fast Pair device that can be discovered by multiple mediums.
*
* @hide
*/
public class FastPairDevice extends NearbyDevice implements Parcelable {
/**
* Used to read a FastPairDevice from a Parcel.
*/
public static final Creator<FastPairDevice> CREATOR = new Creator<FastPairDevice>() {
@Override
public FastPairDevice createFromParcel(Parcel in) {
FastPairDevice.Builder builder = new FastPairDevice.Builder();
if (in.readInt() == 1) {
builder.setName(in.readString());
}
int size = in.readInt();
for (int i = 0; i < size; i++) {
builder.addMedium(in.readInt());
}
builder.setRssi(in.readInt());
if (in.readInt() == 1) {
builder.setModelId(in.readString());
}
builder.setBluetoothAddress(in.readString());
if (in.readInt() == 1) {
int dataLength = in.readInt();
byte[] data = new byte[dataLength];
in.readByteArray(data);
builder.setData(data);
}
return builder.build();
}
@Override
public FastPairDevice[] newArray(int size) {
return new FastPairDevice[size];
}
};
// Some OEM devices devices don't have model Id.
@Nullable private final String mModelId;
// Bluetooth hardware address as string. Can be read from BLE ScanResult.
private final String mBluetoothAddress;
@Nullable
private final byte[] mData;
/**
* Creates a new FastPairDevice.
*
* @param name Name of the FastPairDevice. Can be {@code null} if there is no name.
* @param mediums The {@link Medium}s over which the device is discovered.
* @param rssi The received signal strength in dBm.
* @param modelId The identifier of the Fast Pair device.
* Can be {@code null} if there is no Model ID.
* @param bluetoothAddress The hardware address of this BluetoothDevice.
* @param data Extra data for a Fast Pair device.
*/
public FastPairDevice(@Nullable String name,
List<Integer> mediums,
int rssi,
@Nullable String modelId,
@NonNull String bluetoothAddress,
@Nullable byte[] data) {
super(name, mediums, rssi);
this.mModelId = modelId;
this.mBluetoothAddress = bluetoothAddress;
this.mData = data;
}
/**
* Gets the identifier of the Fast Pair device. Can be {@code null} if there is no Model ID.
*/
@Nullable
public String getModelId() {
return this.mModelId;
}
/**
* Gets the hardware address of this BluetoothDevice.
*/
@NonNull
public String getBluetoothAddress() {
return mBluetoothAddress;
}
/**
* Gets the extra data for a Fast Pair device. Can be {@code null} if there is extra data.
*
* @hide
*/
@Nullable
public byte[] getData() {
return mData;
}
/**
* No special parcel contents.
*/
@Override
public int describeContents() {
return 0;
}
/**
* Returns a string representation of this FastPairDevice.
*/
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("FastPairDevice [");
String name = getName();
if (getName() != null && !name.isEmpty()) {
stringBuilder.append("name=").append(name).append(", ");
}
stringBuilder.append("medium={");
for (int medium: getMediums()) {
stringBuilder.append(mediumToString(medium));
}
stringBuilder.append("} rssi=").append(getRssi());
stringBuilder.append(" modelId=").append(mModelId);
stringBuilder.append(" bluetoothAddress=").append(mBluetoothAddress);
stringBuilder.append("]");
return stringBuilder.toString();
}
@Override
public boolean equals(Object other) {
if (other instanceof FastPairDevice) {
FastPairDevice otherDevice = (FastPairDevice) other;
if (!super.equals(other)) {
return false;
}
return Objects.equals(mModelId, otherDevice.mModelId)
&& Objects.equals(mBluetoothAddress, otherDevice.mBluetoothAddress)
&& Arrays.equals(mData, otherDevice.mData);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(
getName(), getMediums(), getRssi(), mModelId, mBluetoothAddress,
Arrays.hashCode(mData));
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
String name = getName();
dest.writeInt(name == null ? 0 : 1);
if (name != null) {
dest.writeString(name);
}
List<Integer> mediums = getMediums();
dest.writeInt(mediums.size());
for (int medium : mediums) {
dest.writeInt(medium);
}
dest.writeInt(getRssi());
dest.writeInt(mModelId == null ? 0 : 1);
if (mModelId != null) {
dest.writeString(mModelId);
}
dest.writeString(mBluetoothAddress);
dest.writeInt(mData == null ? 0 : 1);
if (mData != null) {
dest.writeInt(mData.length);
dest.writeByteArray(mData);
}
}
/**
* A builder class for {@link FastPairDevice}
*
* @hide
*/
public static final class Builder {
private final List<Integer> mMediums;
@Nullable private String mName;
private int mRssi;
@Nullable private String mModelId;
private String mBluetoothAddress;
@Nullable private byte[] mData;
public Builder() {
mMediums = new ArrayList<>();
}
/**
* Sets the name of the Fast Pair device.
*
* @param name Name of the FastPairDevice. Can be {@code null} if there is no name.
*/
@NonNull
public Builder setName(@Nullable String name) {
mName = name;
return this;
}
/**
* Sets the medium over which the Fast Pair device is discovered.
*
* @param medium The {@link Medium} over which the device is discovered.
*/
@NonNull
public Builder addMedium(@Medium int medium) {
mMediums.add(medium);
return this;
}
/**
* Sets the RSSI between the scan device and the discovered Fast Pair device.
*
* @param rssi The received signal strength in dBm.
*/
@NonNull
public Builder setRssi(@IntRange(from = -127, to = 126) int rssi) {
mRssi = rssi;
return this;
}
/**
* Sets the model Id of this Fast Pair device.
*
* @param modelId The identifier of the Fast Pair device. Can be {@code null}
* if there is no Model ID.
*/
@NonNull
public Builder setModelId(@Nullable String modelId) {
mModelId = modelId;
return this;
}
/**
* Sets the hardware address of this BluetoothDevice.
*
* @param bluetoothAddress The hardware address of this BluetoothDevice.
*/
@NonNull
public Builder setBluetoothAddress(@NonNull String bluetoothAddress) {
Objects.requireNonNull(bluetoothAddress);
mBluetoothAddress = bluetoothAddress;
return this;
}
/**
* Sets the raw data for a FastPairDevice. Can be {@code null} if there is no extra data.
*
* @hide
*/
@NonNull
public Builder setData(@Nullable byte[] data) {
mData = data;
return this;
}
/**
* Builds a FastPairDevice and return it.
*/
@NonNull
public FastPairDevice build() {
return new FastPairDevice(mName, mMediums, mRssi, mModelId,
mBluetoothAddress, mData);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,825 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.nearby.aidl.FastPairDiscoveryItemParcel;
/**
* Class for FastPairDiscoveryItem and its builder.
*
* @hide
*/
@SystemApi
public class FastPairDiscoveryItem {
FastPairDiscoveryItemParcel mMetadataParcel;
FastPairDiscoveryItem(
FastPairDiscoveryItemParcel metadataParcel) {
this.mMetadataParcel = metadataParcel;
}
/**
* Get Id.
*
* @hide
*/
@SystemApi
@Nullable
public String getId() {
return mMetadataParcel.id;
}
/**
* Get Type.
*
* @hide
*/
@SystemApi
public int getType() {
return mMetadataParcel.type;
}
/**
* Get MacAddress.
*
* @hide
*/
@SystemApi
@Nullable
public String getMacAddress() {
return mMetadataParcel.macAddress;
}
/**
* Get ActionUrl.
*
* @hide
*/
@SystemApi
@Nullable
public String getActionUrl() {
return mMetadataParcel.actionUrl;
}
/**
* Get DeviceName.
*
* @hide
*/
@SystemApi
@Nullable
public String getDeviceName() {
return mMetadataParcel.deviceName;
}
/**
* Get Title.
*
* @hide
*/
@SystemApi
@Nullable
public String getTitle() {
return mMetadataParcel.title;
}
/**
* Get Description.
*
* @hide
*/
@SystemApi
@Nullable
public String getDescription() {
return mMetadataParcel.description;
}
/**
* Get DisplayUrl.
*
* @hide
*/
@SystemApi
@Nullable
public String getDisplayUrl() {
return mMetadataParcel.displayUrl;
}
/**
* Get LastObservationTimestampMillis.
*
* @hide
*/
@SystemApi
public long getLastObservationTimestampMillis() {
return mMetadataParcel.lastObservationTimestampMillis;
}
/**
* Get FirstObservationTimestampMillis.
*
* @hide
*/
@SystemApi
public long getFirstObservationTimestampMillis() {
return mMetadataParcel.firstObservationTimestampMillis;
}
/**
* Get State.
*
* @hide
*/
@SystemApi
public int getState() {
return mMetadataParcel.state;
}
/**
* Get ActionUrlType.
*
* @hide
*/
@SystemApi
public int getActionUrlType() {
return mMetadataParcel.actionUrlType;
}
/**
* Get Rssi.
*
* @hide
*/
@SystemApi
public int getRssi() {
return mMetadataParcel.rssi;
}
/**
* Get PendingAppInstallTimestampMillis.
*
* @hide
*/
@SystemApi
public long getPendingAppInstallTimestampMillis() {
return mMetadataParcel.pendingAppInstallTimestampMillis;
}
/**
* Get TxPower.
*
* @hide
*/
@SystemApi
public int getTxPower() {
return mMetadataParcel.txPower;
}
/**
* Get AppName.
*
* @hide
*/
@SystemApi
@Nullable
public String getAppName() {
return mMetadataParcel.appName;
}
/**
* Get GroupId.
*
* @hide
*/
@SystemApi
@Nullable
public String getGroupId() {
return mMetadataParcel.groupId;
}
/**
* Get AttachmentType.
*
* @hide
*/
@SystemApi
public int getAttachmentType() {
return mMetadataParcel.attachmentType;
}
/**
* Get PackageName.
*
* @hide
*/
@SystemApi
@Nullable
public String getPackageName() {
return mMetadataParcel.packageName;
}
/**
* Get FeatureGraphicUrl.
*
* @hide
*/
@SystemApi
@Nullable
public String getFeatureGraphicUrl() {
return mMetadataParcel.featureGraphicUrl;
}
/**
* Get TriggerId.
*
* @hide
*/
@SystemApi
@Nullable
public String getTriggerId() {
return mMetadataParcel.triggerId;
}
/**
* Get IconPng, which is submitted at device registration time to display on notification. It is
* a 32-bit PNG with dimensions of 512px by 512px.
*
* @return IconPng in 32-bit PNG with dimensions of 512px by 512px.
* @hide
*/
@SystemApi
@Nullable
public byte[] getIconPng() {
return mMetadataParcel.iconPng;
}
/**
* Get IconFifeUrl.
*
* @hide
*/
@SystemApi
@Nullable
public String getIconFfeUrl() {
return mMetadataParcel.iconFifeUrl;
}
/**
* Get DebugMessage.
*
* @hide
*/
@SystemApi
@Nullable
public String getDebugMessage() {
return mMetadataParcel.debugMessage;
}
/**
* Get DebugCategory.
*
* @hide
*/
@SystemApi
public int getDebugCategory() {
return mMetadataParcel.debugCategory;
}
/**
* Get LostMillis.
*/
public long getLostMillis() {
return mMetadataParcel.lostMillis;
}
/**
* Get LastUserExperience.
*
* @hide
*/
@SystemApi
public int getLastUserExperience() {
return mMetadataParcel.lastUserExperience;
}
/**
* Get BleRecordBytes. Raw bytes of {@link android.bluetooth.le.ScanRecord}.
* It is the most recent BLE advertisement related to this item.
*
* @return the most recent BLE advertisement in raw bytes of
* {@link android.bluetooth.le.ScanRecord}.
* @hide
*/
@SystemApi
@Nullable
public byte[] getBleRecordBytes() {
return mMetadataParcel.bleRecordBytes;
}
/**
* Get EntityId.
*
* @hide
*/
@SystemApi
@Nullable
public String getEntityId() {
return mMetadataParcel.entityId;
}
/**
* Get authenticationPublicKeySecp256r1, which is same as AntiSpoof public key, see
* <a href="https://developers.google.com/nearby/fast-pair/spec#data_format">Data Format</a>.
*
* @return 64-byte authenticationPublicKeySecp256r1.
* @hide
*/
@SystemApi
@Nullable
public byte[] getAuthenticationPublicKeySecp256r1() {
return mMetadataParcel.authenticationPublicKeySecp256r1;
}
/**
* Builder used to create FastPairDiscoveryItem.
*
* @hide
*/
@SystemApi
public static final class Builder {
private final FastPairDiscoveryItemParcel mBuilderParcel;
/**
* Default constructor of Builder.
*
* @hide
*/
@SystemApi
public Builder() {
mBuilderParcel = new FastPairDiscoveryItemParcel();
}
/**
* Set Id.
*
* @param id Unique id.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
*
* @hide
*/
@SystemApi
@NonNull
public Builder setId(@Nullable String id) {
mBuilderParcel.id = id;
return this;
}
/**
* Set Nearby Type.
*
* @param type Nearby type.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setType(int type) {
mBuilderParcel.type = type;
return this;
}
/**
* Set MacAddress.
*
* @param macAddress Fast Pair device rotating mac address.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setMacAddress(@Nullable String macAddress) {
mBuilderParcel.macAddress = macAddress;
return this;
}
/**
* Set ActionUrl.
*
* @param actionUrl Action Url of Fast Pair device.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setActionUrl(@Nullable String actionUrl) {
mBuilderParcel.actionUrl = actionUrl;
return this;
}
/**
* Set DeviceName.
* @param deviceName Fast Pair device name.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setDeviceName(@Nullable String deviceName) {
mBuilderParcel.deviceName = deviceName;
return this;
}
/**
* Set Title.
*
* @param title Title of Fast Pair device.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setTitle(@Nullable String title) {
mBuilderParcel.title = title;
return this;
}
/**
* Set Description.
*
* @param description Description of Fast Pair device.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setDescription(@Nullable String description) {
mBuilderParcel.description = description;
return this;
}
/**
* Set DisplayUrl.
*
* @param displayUrl Display Url of Fast Pair device.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setDisplayUrl(@Nullable String displayUrl) {
mBuilderParcel.displayUrl = displayUrl;
return this;
}
/**
* Set LastObservationTimestampMillis.
*
* @param lastObservationTimestampMillis Last observed timestamp of Fast Pair device, keyed
* by a rotating id.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setLastObservationTimestampMillis(
long lastObservationTimestampMillis) {
mBuilderParcel.lastObservationTimestampMillis = lastObservationTimestampMillis;
return this;
}
/**
* Set FirstObservationTimestampMillis.
*
* @param firstObservationTimestampMillis First observed timestamp of Fast Pair device,
* keyed by a rotating id.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setFirstObservationTimestampMillis(
long firstObservationTimestampMillis) {
mBuilderParcel.firstObservationTimestampMillis = firstObservationTimestampMillis;
return this;
}
/**
* Set State.
*
* @param state Item's current state. e.g. if the item is blocked.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setState(int state) {
mBuilderParcel.state = state;
return this;
}
/**
* Set ActionUrlType.
*
* @param actionUrlType The resolved url type for the action_url.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setActionUrlType(int actionUrlType) {
mBuilderParcel.actionUrlType = actionUrlType;
return this;
}
/**
* Set Rssi.
*
* @param rssi Beacon's RSSI value.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setRssi(int rssi) {
mBuilderParcel.rssi = rssi;
return this;
}
/**
* Set PendingAppInstallTimestampMillis.
*
* @param pendingAppInstallTimestampMillis The timestamp when the user is redirected to App
* Store after clicking on the item.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setPendingAppInstallTimestampMillis(long pendingAppInstallTimestampMillis) {
mBuilderParcel.pendingAppInstallTimestampMillis = pendingAppInstallTimestampMillis;
return this;
}
/**
* Set TxPower.
*
* @param txPower Beacon's tx power.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setTxPower(int txPower) {
mBuilderParcel.txPower = txPower;
return this;
}
/**
* Set AppName.
*
* @param appName Human readable name of the app designated to open the uri.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setAppName(@Nullable String appName) {
mBuilderParcel.appName = appName;
return this;
}
/**
* Set GroupId.
*
* @param groupId ID used for associating several DiscoveryItems. These items may be
* visually displayed together.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setGroupId(@Nullable String groupId) {
mBuilderParcel.groupId = groupId;
return this;
}
/**
* Set AttachmentType.
*
* @param attachmentType Whether the attachment is created in debug namespace.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setAttachmentType(int attachmentType) {
mBuilderParcel.attachmentType = attachmentType;
return this;
}
/**
* Set PackageName.
*
* @param packageName Package name of the App that owns this item.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setPackageName(@Nullable String packageName) {
mBuilderParcel.packageName = packageName;
return this;
}
/**
* Set FeatureGraphicUrl.
*
* @param featureGraphicUrl The "feature" graphic image url used for large sized list view
* entries.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setFeatureGraphicUrl(@Nullable String featureGraphicUrl) {
mBuilderParcel.featureGraphicUrl = featureGraphicUrl;
return this;
}
/**
* Set TriggerId.
*
* @param triggerId TriggerId identifies the trigger/beacon that is attached with a message.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setTriggerId(@Nullable String triggerId) {
mBuilderParcel.triggerId = triggerId;
return this;
}
/**
* Set IconPng.
*
* @param iconPng Bytes of item icon in PNG format displayed in Discovery item list.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setIconPng(@Nullable byte[] iconPng) {
mBuilderParcel.iconPng = iconPng;
return this;
}
/**
* Set IconFifeUrl.
*
* @param iconFifeUrl A FIFE URL of the item icon displayed in Discovery item list.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setIconFfeUrl(@Nullable String iconFifeUrl) {
mBuilderParcel.iconFifeUrl = iconFifeUrl;
return this;
}
/**
* Set DebugMessage.
*
* @param debugMessage Message written to bugreport for 3P developers.(No sensitive info)
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setDebugMessage(@Nullable String debugMessage) {
mBuilderParcel.debugMessage = debugMessage;
return this;
}
/**
* Set DebugCategory.
*
* @param debugCategory Weather the item is filtered out on server.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setDebugCategory(int debugCategory) {
mBuilderParcel.debugCategory = debugCategory;
return this;
}
/**
* Set LostMillis.
*
* @param lostMillis Client timestamp when the trigger (e.g. beacon) was last lost
* (e.g. when Messages told us the beacon's no longer nearby).
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setLostMillis(long lostMillis) {
mBuilderParcel.lostMillis = lostMillis;
return this;
}
/**
* Set LastUserExperience.
*
* @param lastUserExperience The kind of experience the user last had with this (e.g. if
* they dismissed the notification, that's bad; but if they tapped
* it, that's good).
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setLastUserExperience(int lastUserExperience) {
mBuilderParcel.lastUserExperience = lastUserExperience;
return this;
}
/**
* Set BleRecordBytes.
*
* @param bleRecordBytes The most recent BLE advertisement related to this item. Raw bytes
* of {@link android.bluetooth.le.ScanRecord}.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setBleRecordBytes(@Nullable byte[] bleRecordBytes) {
mBuilderParcel.bleRecordBytes = bleRecordBytes;
return this;
}
/**
* Set EntityId.
*
* @param entityId An ID generated on the server to uniquely identify content.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setEntityId(@Nullable String entityId) {
mBuilderParcel.entityId = entityId;
return this;
}
/**
* Set authenticationPublicKeySecp256r1, which is same as AntiSpoof public key, see
* <a href="https://developers.google.com/nearby/fast-pair/spec#data_format">Data Format</a>
*
* @param authenticationPublicKeySecp256r1 64-byte Fast Pair device public key.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setAuthenticationPublicKeySecp256r1(
@Nullable byte[] authenticationPublicKeySecp256r1) {
mBuilderParcel.authenticationPublicKeySecp256r1 = authenticationPublicKeySecp256r1;
return this;
}
/**
* Build {@link FastPairDiscoveryItem} with the currently set configuration.
*
* @hide
*/
@SystemApi
@NonNull
public FastPairDiscoveryItem build() {
return new FastPairDiscoveryItem(mBuilderParcel);
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.accounts.Account;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.nearby.aidl.FastPairEligibleAccountParcel;
/**
* Class for FastPairEligibleAccount and its builder.
*
* @hide
*/
@SystemApi
public class FastPairEligibleAccount {
FastPairEligibleAccountParcel mAccountParcel;
FastPairEligibleAccount(FastPairEligibleAccountParcel accountParcel) {
this.mAccountParcel = accountParcel;
}
/**
* Get Account.
*
* @hide
*/
@SystemApi
@Nullable
public Account getAccount() {
return this.mAccountParcel.account;
}
/**
* Get OptIn Status.
*
* @hide
*/
@SystemApi
public boolean isOptIn() {
return this.mAccountParcel.optIn;
}
/**
* Builder used to create FastPairEligibleAccount.
*
* @hide
*/
@SystemApi
public static final class Builder {
private final FastPairEligibleAccountParcel mBuilderParcel;
/**
* Default constructor of Builder.
*
* @hide
*/
@SystemApi
public Builder() {
mBuilderParcel = new FastPairEligibleAccountParcel();
mBuilderParcel.account = null;
mBuilderParcel.optIn = false;
}
/**
* Set Account.
*
* @param account Fast Pair eligible account.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setAccount(@Nullable Account account) {
mBuilderParcel.account = account;
return this;
}
/**
* Set whether the account is opt into Fast Pair.
*
* @param optIn Whether the Fast Pair eligible account opts into Fast Pair.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
* @hide
*/
@SystemApi
@NonNull
public Builder setOptIn(boolean optIn) {
mBuilderParcel.optIn = optIn;
return this;
}
/**
* Build {@link FastPairEligibleAccount} with the currently set configuration.
*
* @hide
*/
@SystemApi
@NonNull
public FastPairEligibleAccount build() {
return new FastPairEligibleAccount(mBuilderParcel);
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.NonNull;
/**
* Reports the pair status for an ongoing pair with a {@link FastPairDevice}.
* @hide
*/
public interface FastPairStatusCallback {
/** Reports a pair status related metadata associated with a {@link FastPairDevice} */
void onPairUpdate(@NonNull FastPairDevice fastPairDevice,
PairStatusMetadata pairStatusMetadata);
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
/**
* Callback when brodacast status changes.
*
* {@hide}
*/
oneway interface IBroadcastListener {
/** Called when the broadcast status changes. */
void onStatusChanged(int status);
}

View File

@@ -0,0 +1,25 @@
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package android.nearby;
import android.content.Intent;
/**
* Provides callback interface for halfsheet to send FastPair call back.
*
* {@hide}
*/
interface IFastPairHalfSheetCallback {
void onHalfSheetConnectionConfirm(in Intent intent);
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.nearby.IBroadcastListener;
import android.nearby.IScanListener;
import android.nearby.BroadcastRequestParcelable;
import android.nearby.ScanRequest;
/**
* Interface for communicating with the nearby services.
*
* @hide
*/
interface INearbyManager {
int registerScanListener(in ScanRequest scanRequest, in IScanListener listener);
void unregisterScanListener(in IScanListener listener);
void startBroadcast(in BroadcastRequestParcelable broadcastRequest,
in IBroadcastListener callback);
void stopBroadcast(in IBroadcastListener callback);
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.nearby.NearbyDeviceParcelable;
/**
* Binder callback for ScanCallback.
*
* {@hide}
*/
oneway interface IScanListener {
/** Reports a {@link NearbyDevice} being discovered. */
void onDiscovered(in NearbyDeviceParcelable nearbyDeviceParcelable);
/** Reports a {@link NearbyDevice} information(distance, packet, and etc) changed. */
void onUpdated(in NearbyDeviceParcelable nearbyDeviceParcelable);
/** Reports a {@link NearbyDevice} is no longer within range. */
void onLost(in NearbyDeviceParcelable nearbyDeviceParcelable);
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import com.android.internal.util.Preconditions;
import java.util.List;
import java.util.Objects;
/**
* A class represents a device that can be discovered by multiple mediums.
*
* @hide
*/
@SystemApi
public abstract class NearbyDevice {
@Nullable
private final String mName;
@Medium
private final List<Integer> mMediums;
private final int mRssi;
/**
* Creates a new NearbyDevice.
*
* @param name Local device name. Can be {@code null} if there is no name.
* @param mediums The {@link Medium}s over which the device is discovered.
* @param rssi The received signal strength in dBm.
* @hide
*/
public NearbyDevice(@Nullable String name, List<Integer> mediums, int rssi) {
for (int medium : mediums) {
Preconditions.checkState(isValidMedium(medium),
"Not supported medium: " + medium
+ ", scan medium must be one of NearbyDevice#Medium.");
}
mName = name;
mMediums = mediums;
mRssi = rssi;
}
static String mediumToString(@Medium int medium) {
switch (medium) {
case Medium.BLE:
return "BLE";
case Medium.BLUETOOTH:
return "Bluetooth Classic";
default:
return "Unknown";
}
}
/**
* True if the medium is defined in {@link Medium}.
*
* @param medium Integer that may represent a medium type.
*/
public static boolean isValidMedium(@Medium int medium) {
return medium == Medium.BLE
|| medium == Medium.BLUETOOTH;
}
/**
* The name of the device, or null if not available.
*/
@Nullable
public String getName() {
return mName;
}
/** The medium over which this device was discovered. */
@NonNull
@Medium public List<Integer> getMediums() {
return mMediums;
}
/**
* Returns the received signal strength in dBm.
*/
@IntRange(from = -127, to = 126)
public int getRssi() {
return mRssi;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("NearbyDevice [");
if (mName != null && !mName.isEmpty()) {
stringBuilder.append("name=").append(mName).append(", ");
}
stringBuilder.append("medium={");
for (int medium : mMediums) {
stringBuilder.append(mediumToString(medium));
}
stringBuilder.append("} rssi=").append(mRssi);
stringBuilder.append("]");
return stringBuilder.toString();
}
@Override
public boolean equals(Object other) {
if (other instanceof NearbyDevice) {
NearbyDevice otherDevice = (NearbyDevice) other;
return Objects.equals(mName, otherDevice.mName)
&& mMediums == otherDevice.mMediums
&& mRssi == otherDevice.mRssi;
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(mName, mMediums, mRssi);
}
/**
* The medium where a NearbyDevice was discovered on.
*
* @hide
*/
@IntDef({Medium.BLE, Medium.BLUETOOTH})
public @interface Medium {
int BLE = 1;
int BLUETOOTH = 2;
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2012, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
parcelable NearbyDeviceParcelable;

View File

@@ -0,0 +1,343 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.le.ScanRecord;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Arrays;
import java.util.Objects;
/**
* A data class representing scan result from Nearby Service. Scan result can come from multiple
* mediums like BLE, Wi-Fi Aware, and etc.
* A scan result consists of
* An encapsulation of various parameters for requesting nearby scans.
*
* <p>All scan results generated through {@link NearbyManager} are guaranteed to have a valid
* medium, identifier, timestamp (both UTC time and elapsed real-time since boot), and accuracy.
* All other parameters are optional.
*
* @hide
*/
public final class NearbyDeviceParcelable implements Parcelable {
/**
* Used to read a NearbyDeviceParcelable from a Parcel.
*/
@NonNull
public static final Creator<NearbyDeviceParcelable> CREATOR =
new Creator<NearbyDeviceParcelable>() {
@Override
public NearbyDeviceParcelable createFromParcel(Parcel in) {
Builder builder = new Builder();
if (in.readInt() == 1) {
builder.setName(in.readString());
}
builder.setMedium(in.readInt());
builder.setRssi(in.readInt());
if (in.readInt() == 1) {
builder.setFastPairModelId(in.readString());
}
if (in.readInt() == 1) {
builder.setBluetoothAddress(in.readString());
}
if (in.readInt() == 1) {
int dataLength = in.readInt();
byte[] data = new byte[dataLength];
in.readByteArray(data);
builder.setData(data);
}
return builder.build();
}
@Override
public NearbyDeviceParcelable[] newArray(int size) {
return new NearbyDeviceParcelable[size];
}
};
@ScanRequest.ScanType int mScanType;
@Nullable
private final String mName;
@NearbyDevice.Medium
private final int mMedium;
private final int mRssi;
@Nullable
private final String mBluetoothAddress;
@Nullable
private final String mFastPairModelId;
@Nullable
private final byte[] mData;
private NearbyDeviceParcelable(@ScanRequest.ScanType int scanType, @Nullable String name,
int medium, int rssi, @Nullable String fastPairModelId,
@Nullable String bluetoothAddress, @Nullable byte[] data) {
mScanType = scanType;
mName = name;
mMedium = medium;
mRssi = rssi;
mFastPairModelId = fastPairModelId;
mBluetoothAddress = bluetoothAddress;
mData = data;
}
/**
* No special parcel contents.
*/
@Override
public int describeContents() {
return 0;
}
/**
* Flatten this NearbyDeviceParcelable in to a Parcel.
*
* @param dest The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written.
*/
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mName == null ? 0 : 1);
if (mName != null) {
dest.writeString(mName);
}
dest.writeInt(mMedium);
dest.writeInt(mRssi);
dest.writeInt(mFastPairModelId == null ? 0 : 1);
if (mFastPairModelId != null) {
dest.writeString(mFastPairModelId);
}
dest.writeInt(mBluetoothAddress == null ? 0 : 1);
if (mBluetoothAddress != null) {
dest.writeString(mBluetoothAddress);
}
dest.writeInt(mData == null ? 0 : 1);
if (mData != null) {
dest.writeInt(mData.length);
dest.writeByteArray(mData);
}
}
/**
* Returns a string representation of this ScanRequest.
*/
@Override
public String toString() {
return "NearbyDeviceParcelable["
+ "name=" + mName
+ ", medium=" + NearbyDevice.mediumToString(mMedium)
+ ", rssi=" + mRssi
+ ", bluetoothAddress=" + mBluetoothAddress
+ ", fastPairModelId=" + mFastPairModelId
+ ", data=" + Arrays.toString(mData)
+ "]";
}
@Override
public boolean equals(Object other) {
if (other instanceof NearbyDeviceParcelable) {
NearbyDeviceParcelable otherNearbyDeviceParcelable = (NearbyDeviceParcelable) other;
return Objects.equals(mName, otherNearbyDeviceParcelable.mName)
&& (mMedium == otherNearbyDeviceParcelable.mMedium)
&& (mRssi == otherNearbyDeviceParcelable.mRssi)
&& (Objects.equals(
mBluetoothAddress, otherNearbyDeviceParcelable.mBluetoothAddress))
&& (Objects.equals(
mFastPairModelId, otherNearbyDeviceParcelable.mFastPairModelId))
&& (Arrays.equals(mData, otherNearbyDeviceParcelable.mData));
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(
mName, mMedium, mRssi, mBluetoothAddress, mFastPairModelId, Arrays.hashCode(mData));
}
/**
* Returns the type of the scan.
*
* @hide
*/
@ScanRequest.ScanType
public int getScanType() {
return mScanType;
}
/**
* Gets the name of the NearbyDeviceParcelable. Returns {@code null} If there is no name.
*/
@Nullable
public String getName() {
return mName;
}
/**
* Gets the {@link android.nearby.NearbyDevice.Medium} of the NearbyDeviceParcelable over which
* it is discovered.
*/
@NearbyDevice.Medium
public int getMedium() {
return mMedium;
}
/**
* Gets the received signal strength in dBm.
*/
@IntRange(from = -127, to = 126)
public int getRssi() {
return mRssi;
}
/**
* Gets the Fast Pair identifier. Returns {@code null} if there is no Model ID or this is not a
* Fast Pair device.
*/
@Nullable
public String getFastPairModelId() {
return mFastPairModelId;
}
/**
* Gets the Bluetooth device hardware address. Returns {@code null} if the device is not
* discovered by Bluetooth.
*/
@Nullable
public String getBluetoothAddress() {
return mBluetoothAddress;
}
/**
* Gets the raw data from the scanning. Returns {@code null} if there is no extra data.
*/
@Nullable
public byte[] getData() {
return mData;
}
/**
* Builder class for {@link NearbyDeviceParcelable}.
*/
public static final class Builder {
@Nullable
private String mName;
@NearbyDevice.Medium
private int mMedium;
private int mRssi;
@ScanRequest.ScanType int mScanType;
@Nullable
private String mFastPairModelId;
@Nullable
private String mBluetoothAddress;
@Nullable
private byte[] mData;
/**
* Sets the scan type of the NearbyDeviceParcelable.
*
* @hide
*/
public Builder setScanType(@ScanRequest.ScanType int scanType) {
mScanType = scanType;
return this;
}
/**
* Sets the name of the scanned device.
*
* @param name The local name of the scanned device.
*/
@NonNull
public Builder setName(@Nullable String name) {
mName = name;
return this;
}
/**
* Sets the medium over which the device is discovered.
*
* @param medium The {@link NearbyDevice.Medium} over which the device is discovered.
*/
@NonNull
public Builder setMedium(@NearbyDevice.Medium int medium) {
mMedium = medium;
return this;
}
/**
* Sets the RSSI between scanned device and the discovered device.
*
* @param rssi The received signal strength in dBm.
*/
@NonNull
public Builder setRssi(int rssi) {
mRssi = rssi;
return this;
}
/**
* Sets the Fast Pair model Id.
*
* @param fastPairModelId Fast Pair device identifier.
*/
@NonNull
public Builder setFastPairModelId(@Nullable String fastPairModelId) {
mFastPairModelId = fastPairModelId;
return this;
}
/**
* Sets the bluetooth address.
*
* @param bluetoothAddress The hardware address of the bluetooth device.
*/
@NonNull
public Builder setBluetoothAddress(@Nullable String bluetoothAddress) {
mBluetoothAddress = bluetoothAddress;
return this;
}
/**
* Sets the scanned raw data.
*
* @param data Data the scan.
* For example, {@link ScanRecord#getServiceData()} if scanned by Bluetooth.
*/
@NonNull
public Builder setData(@Nullable byte[] data) {
mData = data;
return this;
}
/**
* Builds a ScanResult.
*/
@NonNull
public NearbyDeviceParcelable build() {
return new NearbyDeviceParcelable(mScanType, mName, mMedium, mRssi, mFastPairModelId,
mBluetoothAddress, mData);
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.SystemApi;
import android.app.SystemServiceRegistry;
import android.content.Context;
/**
* Class for performing registration for all Nearby services.
*
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final class NearbyFrameworkInitializer {
private NearbyFrameworkInitializer() {}
/**
* Called by {@link SystemServiceRegistry}'s static initializer and registers all
* Nearby services to {@link Context}, so that {@link Context#getSystemService} can return them.
*
* @throws IllegalStateException if this is called from anywhere besides
* {@link SystemServiceRegistry}
*/
public static void registerServiceWrappers() {
SystemServiceRegistry.registerContextAwareService(
Context.NEARBY_SERVICE,
NearbyManager.class,
(context, serviceBinder) -> {
INearbyManager service = INearbyManager.Stub.asInterface(serviceBinder);
return new NearbyManager(service);
}
);
}
}

View File

@@ -0,0 +1,355 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.os.RemoteException;
import android.provider.Settings;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.concurrent.Executor;
/**
* This class provides a way to perform Nearby related operations such as scanning, broadcasting
* and connecting to nearby devices.
*
* <p> To get a {@link NearbyManager} instance, call the
* <code>Context.getSystemService(NearbyManager.class)</code>.
*
* @hide
*/
@SystemApi
@SystemService(Context.NEARBY_SERVICE)
public class NearbyManager {
/**
* Represents the scanning state.
*
* @hide
*/
@IntDef({
ScanStatus.UNKNOWN,
ScanStatus.SUCCESS,
ScanStatus.ERROR,
})
public @interface ScanStatus {
// Default, invalid state.
int UNKNOWN = 0;
// The successful state.
int SUCCESS = 1;
// Failed state.
int ERROR = 2;
}
/**
* Whether allows Fast Pair to scan.
*
* (0 = disabled, 1 = enabled)
*
* @hide
*/
public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled";
@GuardedBy("sScanListeners")
private static final WeakHashMap<ScanCallback, WeakReference<ScanListenerTransport>>
sScanListeners = new WeakHashMap<>();
@GuardedBy("sBroadcastListeners")
private static final WeakHashMap<BroadcastCallback, WeakReference<BroadcastListenerTransport>>
sBroadcastListeners = new WeakHashMap<>();
private final INearbyManager mService;
/**
* Creates a new NearbyManager.
*
* @param service the service object
*/
NearbyManager(@NonNull INearbyManager service) {
mService = service;
}
private static NearbyDevice toClientNearbyDevice(
NearbyDeviceParcelable nearbyDeviceParcelable,
@ScanRequest.ScanType int scanType) {
if (scanType == ScanRequest.SCAN_TYPE_FAST_PAIR) {
return new FastPairDevice.Builder()
.setName(nearbyDeviceParcelable.getName())
.addMedium(nearbyDeviceParcelable.getMedium())
.setRssi(nearbyDeviceParcelable.getRssi())
.setModelId(nearbyDeviceParcelable.getFastPairModelId())
.setBluetoothAddress(nearbyDeviceParcelable.getBluetoothAddress())
.setData(nearbyDeviceParcelable.getData()).build();
}
return null;
}
/**
* Start scan for nearby devices with given parameters. Devices matching {@link ScanRequest}
* will be delivered through the given callback.
*
* @param scanRequest various parameters clients send when requesting scanning
* @param executor executor where the listener method is called
* @param scanCallback the callback to notify clients when there is a scan result
*
* @return whether scanning was successfully started
*/
@RequiresPermission(allOf = {android.Manifest.permission.BLUETOOTH_SCAN,
android.Manifest.permission.BLUETOOTH_PRIVILEGED})
@ScanStatus
public int startScan(@NonNull ScanRequest scanRequest,
@CallbackExecutor @NonNull Executor executor,
@NonNull ScanCallback scanCallback) {
Objects.requireNonNull(scanRequest, "scanRequest must not be null");
Objects.requireNonNull(scanCallback, "scanCallback must not be null");
Objects.requireNonNull(executor, "executor must not be null");
try {
synchronized (sScanListeners) {
WeakReference<ScanListenerTransport> reference = sScanListeners.get(scanCallback);
ScanListenerTransport transport = reference != null ? reference.get() : null;
if (transport == null) {
transport = new ScanListenerTransport(scanRequest.getScanType(), scanCallback,
executor);
} else {
Preconditions.checkState(transport.isRegistered());
transport.setExecutor(executor);
}
@ScanStatus int status = mService.registerScanListener(scanRequest, transport);
if (status != ScanStatus.SUCCESS) {
return status;
}
sScanListeners.put(scanCallback, new WeakReference<>(transport));
return ScanStatus.SUCCESS;
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Stops the nearby device scan for the specified callback. The given callback
* is guaranteed not to receive any invocations that happen after this method
* is invoked.
*
* Suppressed lint: Registration methods should have overload that accepts delivery Executor.
* Already have executor in startScan() method.
*
* @param scanCallback the callback that was used to start the scan
*/
@SuppressLint("ExecutorRegistration")
@RequiresPermission(allOf = {android.Manifest.permission.BLUETOOTH_SCAN,
android.Manifest.permission.BLUETOOTH_PRIVILEGED})
public void stopScan(@NonNull ScanCallback scanCallback) {
Preconditions.checkArgument(scanCallback != null,
"invalid null scanCallback");
try {
synchronized (sScanListeners) {
WeakReference<ScanListenerTransport> reference = sScanListeners.remove(
scanCallback);
ScanListenerTransport transport = reference != null ? reference.get() : null;
if (transport != null) {
transport.unregister();
mService.unregisterScanListener(transport);
}
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Start broadcasting the request using nearby specification.
*
* @param broadcastRequest request for the nearby broadcast
* @param executor executor for running the callback
* @param callback callback for notifying the client
*/
@RequiresPermission(allOf = {Manifest.permission.BLUETOOTH_ADVERTISE,
android.Manifest.permission.BLUETOOTH_PRIVILEGED})
public void startBroadcast(@NonNull BroadcastRequest broadcastRequest,
@CallbackExecutor @NonNull Executor executor, @NonNull BroadcastCallback callback) {
try {
synchronized (sBroadcastListeners) {
WeakReference<BroadcastListenerTransport> reference = sBroadcastListeners.get(
callback);
BroadcastListenerTransport transport = reference != null ? reference.get() : null;
if (transport == null) {
transport = new BroadcastListenerTransport(callback, executor);
} else {
Preconditions.checkState(transport.isRegistered());
transport.setExecutor(executor);
}
mService.startBroadcast(new BroadcastRequestParcelable(broadcastRequest),
transport);
sBroadcastListeners.put(callback, new WeakReference<>(transport));
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Stop the broadcast associated with the given callback.
*
* @param callback the callback that was used for starting the broadcast
*/
@SuppressLint("ExecutorRegistration")
@RequiresPermission(allOf = {Manifest.permission.BLUETOOTH_ADVERTISE,
android.Manifest.permission.BLUETOOTH_PRIVILEGED})
public void stopBroadcast(@NonNull BroadcastCallback callback) {
try {
synchronized (sBroadcastListeners) {
WeakReference<BroadcastListenerTransport> reference = sBroadcastListeners.remove(
callback);
BroadcastListenerTransport transport = reference != null ? reference.get() : null;
if (transport != null) {
transport.unregister();
mService.stopBroadcast(transport);
}
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Read from {@link Settings} whether Fast Pair scan is enabled.
*
* @param context the {@link Context} to query the setting
* @param def the default value if no setting value
* @return whether the Fast Pair is enabled
*/
public static boolean getFastPairScanEnabled(@NonNull Context context, boolean def) {
final int enabled = Settings.Secure.getInt(
context.getContentResolver(), FAST_PAIR_SCAN_ENABLED, (def ? 1 : 0));
return enabled != 0;
}
/**
* Write into {@link Settings} whether Fast Pair scan is enabled
*
* @param context the {@link Context} to set the setting
* @param enable whether the Fast Pair scan should be enabled
*/
public static void setFastPairScanEnabled(@NonNull Context context, boolean enable) {
Settings.Secure.putInt(
context.getContentResolver(), FAST_PAIR_SCAN_ENABLED, enable ? 1 : 0);
}
private static class ScanListenerTransport extends IScanListener.Stub {
private @ScanRequest.ScanType int mScanType;
private volatile @Nullable ScanCallback mScanCallback;
private Executor mExecutor;
ScanListenerTransport(@ScanRequest.ScanType int scanType, ScanCallback scanCallback,
@CallbackExecutor Executor executor) {
Preconditions.checkArgument(scanCallback != null,
"invalid null callback");
Preconditions.checkState(ScanRequest.isValidScanType(scanType),
"invalid scan type : " + scanType
+ ", scan type must be one of ScanRequest#SCAN_TYPE_");
mScanType = scanType;
mScanCallback = scanCallback;
mExecutor = executor;
}
void setExecutor(Executor executor) {
Preconditions.checkArgument(
executor != null, "invalid null executor");
mExecutor = executor;
}
boolean isRegistered() {
return mScanCallback != null;
}
void unregister() {
mScanCallback = null;
}
@Override
public void onDiscovered(NearbyDeviceParcelable nearbyDeviceParcelable)
throws RemoteException {
mExecutor.execute(() -> mScanCallback.onDiscovered(
toClientNearbyDevice(nearbyDeviceParcelable, mScanType)));
}
@Override
public void onUpdated(NearbyDeviceParcelable nearbyDeviceParcelable)
throws RemoteException {
mExecutor.execute(
() -> mScanCallback.onUpdated(
toClientNearbyDevice(nearbyDeviceParcelable, mScanType)));
}
@Override
public void onLost(NearbyDeviceParcelable nearbyDeviceParcelable) throws RemoteException {
mExecutor.execute(
() -> mScanCallback.onLost(
toClientNearbyDevice(nearbyDeviceParcelable, mScanType)));
}
}
private static class BroadcastListenerTransport extends IBroadcastListener.Stub {
private volatile @Nullable BroadcastCallback mBroadcastCallback;
private Executor mExecutor;
BroadcastListenerTransport(BroadcastCallback broadcastCallback,
@CallbackExecutor Executor executor) {
mBroadcastCallback = broadcastCallback;
mExecutor = executor;
}
void setExecutor(Executor executor) {
Preconditions.checkArgument(
executor != null, "invalid null executor");
mExecutor = executor;
}
boolean isRegistered() {
return mBroadcastCallback != null;
}
void unregister() {
mBroadcastCallback = null;
}
@Override
public void onStatusChanged(int status) {
mExecutor.execute(() -> {
if (mBroadcastCallback != null) {
mBroadcastCallback.onStatusChanged(status);
}
});
}
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
/**
* Metadata about an ongoing paring. Wraps transient data like status and progress.
*
* @hide
*/
parcelable PairStatusMetadata;

View File

@@ -0,0 +1,117 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
/**
* Metadata about an ongoing paring. Wraps transient data like status and progress.
*
* @hide
*/
public final class PairStatusMetadata implements Parcelable {
@Status
private final int mStatus;
/** The status of the pairing. */
@IntDef({
Status.UNKNOWN,
Status.SUCCESS,
Status.FAIL,
Status.DISMISS
})
public @interface Status {
int UNKNOWN = 1000;
int SUCCESS = 1001;
int FAIL = 1002;
int DISMISS = 1003;
}
/** Converts the status to readable string. */
public static String statusToString(@Status int status) {
switch (status) {
case Status.SUCCESS:
return "SUCCESS";
case Status.FAIL:
return "FAIL";
case Status.DISMISS:
return "DISMISS";
case Status.UNKNOWN:
default:
return "UNKNOWN";
}
}
public int getStatus() {
return mStatus;
}
@Override
public String toString() {
return "PairStatusMetadata[ status=" + statusToString(mStatus) + "]";
}
@Override
public boolean equals(Object other) {
if (other instanceof PairStatusMetadata) {
return mStatus == ((PairStatusMetadata) other).mStatus;
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(mStatus);
}
public PairStatusMetadata(@Status int status) {
mStatus = status;
}
public static final Creator<PairStatusMetadata> CREATOR = new Creator<PairStatusMetadata>() {
@Override
public PairStatusMetadata createFromParcel(Parcel in) {
return new PairStatusMetadata(in.readInt());
}
@Override
public PairStatusMetadata[] newArray(int size) {
return new PairStatusMetadata[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public int getStability() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mStatus);
}
}

View File

@@ -0,0 +1,208 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Request for Nearby Presence Broadcast.
*
* @hide
*/
@SystemApi
public final class PresenceBroadcastRequest extends BroadcastRequest implements Parcelable {
private final byte[] mSalt;
private final List<Integer> mActions;
private final PrivateCredential mCredential;
private final List<DataElement> mExtendedProperties;
private PresenceBroadcastRequest(@BroadcastVersion int version, int txPower,
List<Integer> mediums, byte[] salt, List<Integer> actions,
PrivateCredential credential, List<DataElement> extendedProperties) {
super(BROADCAST_TYPE_NEARBY_PRESENCE, version, txPower, mediums);
mSalt = salt;
mActions = actions;
mCredential = credential;
mExtendedProperties = extendedProperties;
}
private PresenceBroadcastRequest(Parcel in) {
super(BROADCAST_TYPE_NEARBY_PRESENCE, in);
mSalt = new byte[in.readInt()];
in.readByteArray(mSalt);
mActions = new ArrayList<>();
in.readList(mActions, Integer.class.getClassLoader(), Integer.class);
mCredential = in.readParcelable(PrivateCredential.class.getClassLoader(),
PrivateCredential.class);
mExtendedProperties = new ArrayList<>();
in.readList(mExtendedProperties, DataElement.class.getClassLoader(), DataElement.class);
}
@NonNull
public static final Creator<PresenceBroadcastRequest> CREATOR =
new Creator<PresenceBroadcastRequest>() {
@Override
public PresenceBroadcastRequest createFromParcel(Parcel in) {
// Skip Broadcast request type - it's used by parent class.
in.readInt();
return createFromParcelBody(in);
}
@Override
public PresenceBroadcastRequest[] newArray(int size) {
return new PresenceBroadcastRequest[size];
}
};
static PresenceBroadcastRequest createFromParcelBody(Parcel in) {
return new PresenceBroadcastRequest(in);
}
/**
* Returns the salt associated with this broadcast request.
*/
@NonNull
public byte[] getSalt() {
return mSalt;
}
/**
* Returns actions associated with this broadcast request.
*/
@NonNull
public List<Integer> getActions() {
return mActions;
}
/**
* Returns the private credential associated with this broadcast request.
*/
@NonNull
public PrivateCredential getCredential() {
return mCredential;
}
/**
* Returns extended property information associated with this broadcast request.
*/
@NonNull
public List<DataElement> getExtendedProperties() {
return mExtendedProperties;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mSalt.length);
dest.writeByteArray(mSalt);
dest.writeList(mActions);
dest.writeParcelable(mCredential, /** parcelableFlags= */0);
dest.writeList(mExtendedProperties);
}
/**
* Builder for {@link PresenceBroadcastRequest}.
*/
public static final class Builder {
private final List<Integer> mMediums;
private final List<Integer> mActions;
private final List<DataElement> mExtendedProperties;
private final byte[] mSalt;
private final PrivateCredential mCredential;
private int mVersion;
private int mTxPower;
public Builder(@NonNull List<Integer> mediums, @NonNull byte[] salt,
@NonNull PrivateCredential credential) {
Preconditions.checkState(!mediums.isEmpty(), "mediums cannot be empty");
Preconditions.checkState(salt != null && salt.length > 0, "salt cannot be empty");
mVersion = PRESENCE_VERSION_V0;
mTxPower = UNKNOWN_TX_POWER;
mCredential = credential;
mActions = new ArrayList<>();
mExtendedProperties = new ArrayList<>();
mSalt = salt;
mMediums = mediums;
}
/**
* Sets the version for this request.
*/
@NonNull
public Builder setVersion(@BroadcastVersion int version) {
mVersion = version;
return this;
}
/**
* Sets the calibrated tx power level in dBm for this request. The tx power level should
* be between -127 dBm and 126 dBm.
*/
@NonNull
public Builder setTxPower(@IntRange(from = -127, to = 126) int txPower) {
mTxPower = txPower;
return this;
}
/**
* Adds an action for the presence broadcast request.
*/
@NonNull
public Builder addAction(@IntRange(from = 1, to = 255) int action) {
mActions.add(action);
return this;
}
/**
* Adds an extended property for the presence broadcast request.
*/
@NonNull
public Builder addExtendedProperty(@NonNull DataElement dataElement) {
Objects.requireNonNull(dataElement);
mExtendedProperties.add(dataElement);
return this;
}
/**
* Builds a {@link PresenceBroadcastRequest}.
*/
@NonNull
public PresenceBroadcastRequest build() {
return new PresenceBroadcastRequest(mVersion, mTxPower, mMediums, mSalt, mActions,
mCredential, mExtendedProperties);
}
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a credential for Nearby Presence.
*
* @hide
*/
@SystemApi
public abstract class PresenceCredential {
/**
* Private credential type.
*/
public static final int CREDENTIAL_TYPE_PRIVATE = 0;
/**
* Public credential type.
*/
public static final int CREDENTIAL_TYPE_PUBLIC = 1;
/** @hide **/
@Retention(RetentionPolicy.SOURCE)
@IntDef({CREDENTIAL_TYPE_PUBLIC, CREDENTIAL_TYPE_PRIVATE})
public @interface CredentialType {
}
/**
* Unknown identity type.
*/
public static final int IDENTITY_TYPE_UNKNOWN = 0;
/**
* Private identity type.
*/
public static final int IDENTITY_TYPE_PRIVATE = 1;
/**
* Provisioned identity type.
*/
public static final int IDENTITY_TYPE_PROVISIONED = 2;
/**
* Trusted identity type.
*/
public static final int IDENTITY_TYPE_TRUSTED = 3;
/**
* Public identity type.
*/
public static final int IDENTITY_TYPE_PUBLIC = 4;
/** @hide **/
@Retention(RetentionPolicy.SOURCE)
@IntDef({IDENTITY_TYPE_UNKNOWN, IDENTITY_TYPE_PRIVATE, IDENTITY_TYPE_PROVISIONED,
IDENTITY_TYPE_TRUSTED, IDENTITY_TYPE_PUBLIC})
public @interface IdentityType {
}
private final @CredentialType int mType;
private final @IdentityType int mIdentityType;
private final byte[] mSecretId;
private final byte[] mAuthenticityKey;
private final List<CredentialElement> mCredentialElements;
PresenceCredential(@CredentialType int type, @IdentityType int identityType,
byte[] secretId, byte[] authenticityKey, List<CredentialElement> credentialElements) {
mType = type;
mIdentityType = identityType;
mSecretId = secretId;
mAuthenticityKey = authenticityKey;
mCredentialElements = credentialElements;
}
PresenceCredential(@CredentialType int type, Parcel in) {
mType = type;
mIdentityType = in.readInt();
mSecretId = new byte[in.readInt()];
in.readByteArray(mSecretId);
mAuthenticityKey = new byte[in.readInt()];
in.readByteArray(mAuthenticityKey);
mCredentialElements = new ArrayList<>();
in.readList(mCredentialElements, CredentialElement.class.getClassLoader(),
CredentialElement.class);
}
/**
* Returns the type of the credential.
*/
public @CredentialType int getType() {
return mType;
}
/**
* Returns the identity type of the credential.
*/
public @IdentityType int getIdentityType() {
return mIdentityType;
}
/**
* Returns the secret id of the credential.
*/
@NonNull
public byte[] getSecretId() {
return mSecretId;
}
/**
* Returns the authenticity key of the credential.
*/
@NonNull
public byte[] getAuthenticityKey() {
return mAuthenticityKey;
}
/**
* Returns the elements of the credential.
*/
@NonNull
public List<CredentialElement> getCredentialElements() {
return mCredentialElements;
}
/**
* Writes the presence credential to the parcel.
*
* @hide
*/
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeInt(mIdentityType);
dest.writeInt(mSecretId.length);
dest.writeByteArray(mSecretId);
dest.writeInt(mAuthenticityKey.length);
dest.writeByteArray(mAuthenticityKey);
dest.writeList(mCredentialElements);
}
}

View File

@@ -0,0 +1,373 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Represents a Presence device from nearby scans.
*
* @hide
*/
@SystemApi
public final class PresenceDevice extends NearbyDevice implements Parcelable {
/** The type of presence device. */
/** @hide **/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
DeviceType.UNKNOWN,
DeviceType.PHONE,
DeviceType.TABLET,
DeviceType.DISPLAY,
DeviceType.LAPTOP,
DeviceType.TV,
DeviceType.WATCH,
})
public @interface DeviceType {
/** The type of the device is unknown. */
int UNKNOWN = 0;
/** The device is a phone. */
int PHONE = 1;
/** The device is a tablet. */
int TABLET = 2;
/** The device is a display. */
int DISPLAY = 3;
/** The device is a laptop. */
int LAPTOP = 4;
/** The device is a TV. */
int TV = 5;
/** The device is a watch. */
int WATCH = 6;
}
private final String mDeviceId;
private final byte[] mSalt;
private final byte[] mSecretId;
private final byte[] mEncryptedIdentity;
private final int mDeviceType;
private final String mDeviceImageUrl;
private final long mDiscoveryTimestampMillis;
private final List<DataElement> mExtendedProperties;
/**
* The id of the device.
*
* <p>This id is not a hardware id. It may rotate based on the remote device's broadcasts.
*/
@NonNull
public String getDeviceId() {
return mDeviceId;
}
/**
* Returns the salt used when presence device is discovered.
*/
@NonNull
public byte[] getSalt() {
return mSalt;
}
/**
* Returns the secret used when presence device is discovered.
*/
@NonNull
public byte[] getSecretId() {
return mSecretId;
}
/**
* Returns the encrypted identity used when presence device is discovered.
*/
@NonNull
public byte[] getEncryptedIdentity() {
return mEncryptedIdentity;
}
/** The type of the device. */
@DeviceType
public int getDeviceType() {
return mDeviceType;
}
/** An image URL representing the device. */
@Nullable
public String getDeviceImageUrl() {
return mDeviceImageUrl;
}
/** The timestamp (since boot) when the device is discovered. */
public long getDiscoveryTimestampMillis() {
return mDiscoveryTimestampMillis;
}
/**
* The extended properties of the device.
*/
@NonNull
public List<DataElement> getExtendedProperties() {
return mExtendedProperties;
}
private PresenceDevice(String deviceName, List<Integer> mMediums, int rssi, String deviceId,
byte[] salt, byte[] secretId, byte[] encryptedIdentity, int deviceType,
String deviceImageUrl, long discoveryTimestampMillis,
List<DataElement> extendedProperties) {
super(deviceName, mMediums, rssi);
mDeviceId = deviceId;
mSalt = salt;
mSecretId = secretId;
mEncryptedIdentity = encryptedIdentity;
mDeviceType = deviceType;
mDeviceImageUrl = deviceImageUrl;
mDiscoveryTimestampMillis = discoveryTimestampMillis;
mExtendedProperties = extendedProperties;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
String name = getName();
dest.writeInt(name == null ? 0 : 1);
if (name != null) {
dest.writeString(name);
}
List<Integer> mediums = getMediums();
dest.writeInt(mediums.size());
for (int medium : mediums) {
dest.writeInt(medium);
}
dest.writeInt(getRssi());
dest.writeInt(mSalt.length);
dest.writeByteArray(mSalt);
dest.writeInt(mSecretId.length);
dest.writeByteArray(mSecretId);
dest.writeInt(mEncryptedIdentity.length);
dest.writeByteArray(mEncryptedIdentity);
dest.writeString(mDeviceId);
dest.writeInt(mDeviceType);
dest.writeInt(mDeviceImageUrl == null ? 0 : 1);
if (mDeviceImageUrl != null) {
dest.writeString(mDeviceImageUrl);
}
dest.writeLong(mDiscoveryTimestampMillis);
dest.writeInt(mExtendedProperties.size());
for (DataElement dataElement : mExtendedProperties) {
dest.writeParcelable(dataElement, 0);
}
}
@Override
public int describeContents() {
return 0;
}
@NonNull
public static final Creator<PresenceDevice> CREATOR = new Creator<PresenceDevice>() {
@Override
public PresenceDevice createFromParcel(Parcel in) {
String name = null;
if (in.readInt() == 1) {
name = in.readString();
}
int size = in.readInt();
List<Integer> mediums = new ArrayList<>();
for (int i = 0; i < size; i++) {
mediums.add(in.readInt());
}
int rssi = in.readInt();
byte[] salt = new byte[in.readInt()];
in.readByteArray(salt);
byte[] secretId = new byte[in.readInt()];
in.readByteArray(secretId);
byte[] encryptedIdentity = new byte[in.readInt()];
in.readByteArray(encryptedIdentity);
String deviceId = in.readString();
int deviceType = in.readInt();
String deviceImageUrl = null;
if (in.readInt() == 1) {
deviceImageUrl = in.readString();
}
long discoveryTimeMillis = in.readLong();
int dataElementSize = in.readInt();
List<DataElement> dataElements = new ArrayList<>();
for (int i = 0; i < dataElementSize; i++) {
dataElements.add(
in.readParcelable(DataElement.class.getClassLoader(), DataElement.class));
}
Builder builder = new Builder(deviceId, salt, secretId, encryptedIdentity)
.setName(name)
.setRssi(rssi)
.setDeviceType(deviceType)
.setDeviceImageUrl(deviceImageUrl)
.setDiscoveryTimestampMillis(discoveryTimeMillis);
for (int i = 0; i < mediums.size(); i++) {
builder.addMedium(mediums.get(i));
}
for (int i = 0; i < dataElements.size(); i++) {
builder.addExtendedProperty(dataElements.get(i));
}
return builder.build();
}
@Override
public PresenceDevice[] newArray(int size) {
return new PresenceDevice[size];
}
};
/**
* Builder class for {@link PresenceDevice}.
*/
public static final class Builder {
private final List<DataElement> mExtendedProperties;
private final List<Integer> mMediums;
private final String mDeviceId;
private final byte[] mSalt;
private final byte[] mSecretId;
private final byte[] mEncryptedIdentity;
private String mName;
private int mRssi;
private int mDeviceType;
private String mDeviceImageUrl;
private long mDiscoveryTimestampMillis;
/**
* Constructs a {@link Builder}.
*
* @param deviceId the identifier on the discovered Presence device
* @param salt a random salt used in the beacon from the Presence device.
* @param secretId a secret identifier used in the beacon from the Presence device.
* @param encryptedIdentity the identity associated with the Presence device.
*/
public Builder(@NonNull String deviceId, @NonNull byte[] salt, @NonNull byte[] secretId,
@NonNull byte[] encryptedIdentity) {
mDeviceId = deviceId;
mSalt = salt;
mSecretId = secretId;
mEncryptedIdentity = encryptedIdentity;
mMediums = new ArrayList<>();
mExtendedProperties = new ArrayList<>();
mRssi = -127;
}
/**
* Sets the name of the Presence device.
*
* @param name Name of the Presence. Can be {@code null} if there is no name.
*/
@NonNull
public Builder setName(@Nullable String name) {
mName = name;
return this;
}
/**
* Adds the medium over which the Presence device is discovered.
*
* @param medium The {@link Medium} over which the device is discovered.
*/
@NonNull
public Builder addMedium(@Medium int medium) {
mMediums.add(medium);
return this;
}
/**
* Sets the RSSI on the discovered Presence device.
*
* @param rssi The received signal strength in dBm.
*/
@NonNull
public Builder setRssi(int rssi) {
mRssi = rssi;
return this;
}
/**
* Sets the type of discovered Presence device.
*
* @param deviceType Type of the Presence device.
*/
@NonNull
public Builder setDeviceType(@DeviceType int deviceType) {
mDeviceType = deviceType;
return this;
}
/**
* Sets the image url of the discovered Presence device.
*
* @param deviceImageUrl Url of the image for the Presence device.
*/
@NonNull
public Builder setDeviceImageUrl(@Nullable String deviceImageUrl) {
mDeviceImageUrl = deviceImageUrl;
return this;
}
/**
* Sets discovery timestamp, the clock is based on elapsed time.
*
* @param discoveryTimestampMillis Timestamp when the presence device is discovered.
*/
@NonNull
public Builder setDiscoveryTimestampMillis(long discoveryTimestampMillis) {
mDiscoveryTimestampMillis = discoveryTimestampMillis;
return this;
}
/**
* Adds an extended property of the discovered presence device.
*
* @param dataElement Data element of the extended property.
*/
@NonNull
public Builder addExtendedProperty(@NonNull DataElement dataElement) {
Objects.requireNonNull(dataElement);
mExtendedProperties.add(dataElement);
return this;
}
/**
* Builds a Presence device.
*/
@NonNull
public PresenceDevice build() {
return new PresenceDevice(mName, mMediums, mRssi, mDeviceId,
mSalt, mSecretId, mEncryptedIdentity,
mDeviceType,
mDeviceImageUrl,
mDiscoveryTimestampMillis, mExtendedProperties);
}
}
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* Filter for scanning a nearby presence device.
*
* @hide
*/
@SystemApi
public final class PresenceScanFilter extends ScanFilter implements Parcelable {
private final List<PublicCredential> mCredentials;
private final List<Integer> mPresenceActions;
private final List<DataElement> mExtendedProperties;
/**
* A list of credentials to filter on.
*/
@NonNull
public List<PublicCredential> getCredentials() {
return mCredentials;
}
/**
* A list of presence actions for matching.
*/
@NonNull
public List<Integer> getPresenceActions() {
return mPresenceActions;
}
/**
* A bundle of extended properties for matching.
*/
@NonNull
public List<DataElement> getExtendedProperties() {
return mExtendedProperties;
}
private PresenceScanFilter(int rssiThreshold, List<PublicCredential> credentials,
List<Integer> presenceActions, List<DataElement> extendedProperties) {
super(ScanRequest.SCAN_TYPE_NEARBY_PRESENCE, rssiThreshold);
mCredentials = new ArrayList<>(credentials);
mPresenceActions = new ArrayList<>(presenceActions);
mExtendedProperties = extendedProperties;
}
private PresenceScanFilter(Parcel in) {
super(ScanRequest.SCAN_TYPE_NEARBY_PRESENCE, in);
mCredentials = new ArrayList<>();
if (in.readInt() != 0) {
in.readParcelableList(mCredentials, PublicCredential.class.getClassLoader(),
PublicCredential.class);
}
mPresenceActions = new ArrayList<>();
if (in.readInt() != 0) {
in.readList(mPresenceActions, Integer.class.getClassLoader(), Integer.class);
}
mExtendedProperties = new ArrayList<>();
if (in.readInt() != 0) {
in.readParcelableList(mExtendedProperties, DataElement.class.getClassLoader(),
DataElement.class);
}
}
@NonNull
public static final Creator<PresenceScanFilter> CREATOR = new Creator<PresenceScanFilter>() {
@Override
public PresenceScanFilter createFromParcel(Parcel in) {
// Skip Scan Filter type as it's used for parent class.
in.readInt();
return createFromParcelBody(in);
}
@Override
public PresenceScanFilter[] newArray(int size) {
return new PresenceScanFilter[size];
}
};
/**
* Create a {@link PresenceScanFilter} from the parcel body. Scan Filter type is skipped.
*/
static PresenceScanFilter createFromParcelBody(Parcel in) {
return new PresenceScanFilter(in);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mCredentials.size());
if (!mCredentials.isEmpty()) {
dest.writeParcelableList(mCredentials, 0);
}
dest.writeInt(mPresenceActions.size());
if (!mPresenceActions.isEmpty()) {
dest.writeList(mPresenceActions);
}
dest.writeInt(mExtendedProperties.size());
if (!mExtendedProperties.isEmpty()) {
dest.writeList(mExtendedProperties);
}
}
/**
* Builder for {@link PresenceScanFilter}.
*/
public static final class Builder {
private int mMaxPathLoss;
private final Set<PublicCredential> mCredentials;
private final Set<Integer> mPresenceIdentities;
private final Set<Integer> mPresenceActions;
private final List<DataElement> mExtendedProperties;
public Builder() {
mMaxPathLoss = 127;
mCredentials = new ArraySet<>();
mPresenceIdentities = new ArraySet<>();
mPresenceActions = new ArraySet<>();
mExtendedProperties = new ArrayList<>();
}
/**
* Sets the max path loss (in dBm) for the scan request. The path loss is the attenuation
* of radio energy between sender and receiver. Path loss here is defined as (TxPower -
* Rssi).
*/
@NonNull
public Builder setMaxPathLoss(@IntRange(from = 0, to = 127) int maxPathLoss) {
mMaxPathLoss = maxPathLoss;
return this;
}
/**
* Adds a credential the scan filter is expected to match.
*/
@NonNull
public Builder addCredential(@NonNull PublicCredential credential) {
Objects.requireNonNull(credential);
mCredentials.add(credential);
return this;
}
/**
* Adds a presence action for filtering, which is an action the discoverer could take
* when it receives the broadcast of a presence device.
*/
@NonNull
public Builder addPresenceAction(@IntRange(from = 1, to = 255) int action) {
mPresenceActions.add(action);
return this;
}
/**
* Add an extended property for scan filtering.
*/
@NonNull
public Builder addExtendedProperty(@NonNull DataElement dataElement) {
Objects.requireNonNull(dataElement);
mExtendedProperties.add(dataElement);
return this;
}
/**
* Builds the scan filter.
*/
@NonNull
public PresenceScanFilter build() {
Preconditions.checkState(!mCredentials.isEmpty(), "credentials cannot be empty");
return new PresenceScanFilter(mMaxPathLoss,
new ArrayList<>(mCredentials),
new ArrayList<>(mPresenceActions),
mExtendedProperties);
}
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a private credential.
*
* @hide
*/
@SystemApi
public final class PrivateCredential extends PresenceCredential implements Parcelable {
@NonNull
public static final Creator<PrivateCredential> CREATOR = new Creator<PrivateCredential>() {
@Override
public PrivateCredential createFromParcel(Parcel in) {
in.readInt(); // Skip the type as it's used by parent class only.
return createFromParcelBody(in);
}
@Override
public PrivateCredential[] newArray(int size) {
return new PrivateCredential[size];
}
};
private byte[] mMetadataEncryptionKey;
private String mDeviceName;
private PrivateCredential(Parcel in) {
super(CREDENTIAL_TYPE_PRIVATE, in);
mMetadataEncryptionKey = new byte[in.readInt()];
in.readByteArray(mMetadataEncryptionKey);
mDeviceName = in.readString();
}
private PrivateCredential(int identityType, byte[] secretId,
String deviceName, byte[] authenticityKey, List<CredentialElement> credentialElements,
byte[] metadataEncryptionKey) {
super(CREDENTIAL_TYPE_PRIVATE, identityType, secretId, authenticityKey,
credentialElements);
mDeviceName = deviceName;
mMetadataEncryptionKey = metadataEncryptionKey;
}
static PrivateCredential createFromParcelBody(Parcel in) {
return new PrivateCredential(in);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mMetadataEncryptionKey.length);
dest.writeByteArray(mMetadataEncryptionKey);
dest.writeString(mDeviceName);
}
/**
* Returns the metadata encryption key associated with this credential.
*/
@NonNull
public byte[] getMetadataEncryptionKey() {
return mMetadataEncryptionKey;
}
/**
* Returns the device name associated with this credential.
*/
@NonNull
public String getDeviceName() {
return mDeviceName;
}
/**
* Builder class for {@link PresenceCredential}.
*/
public static final class Builder {
private final List<CredentialElement> mCredentialElements;
private @IdentityType int mIdentityType;
private final byte[] mSecretId;
private final byte[] mAuthenticityKey;
private final byte[] mMetadataEncryptionKey;
private final String mDeviceName;
public Builder(@NonNull byte[] secretId, @NonNull byte[] authenticityKey,
@NonNull byte[] metadataEncryptionKey, @NonNull String deviceName) {
Preconditions.checkState(secretId != null && secretId.length > 0,
"secret id cannot be empty");
Preconditions.checkState(authenticityKey != null && authenticityKey.length > 0,
"authenticity key cannot be empty");
Preconditions.checkState(
metadataEncryptionKey != null && metadataEncryptionKey.length > 0,
"metadataEncryptionKey cannot be empty");
Preconditions.checkState(deviceName != null && deviceName.length() > 0,
"deviceName cannot be empty");
mSecretId = secretId;
mAuthenticityKey = authenticityKey;
mMetadataEncryptionKey = metadataEncryptionKey;
mDeviceName = deviceName;
mCredentialElements = new ArrayList<>();
}
/**
* Sets the identity type for the presence credential.
*/
@NonNull
public Builder setIdentityType(@IdentityType int identityType) {
mIdentityType = identityType;
return this;
}
/**
* Adds an element to the credential.
*/
@NonNull
public Builder addCredentialElement(@NonNull CredentialElement credentialElement) {
mCredentialElements.add(credentialElement);
return this;
}
/**
* Builds the {@link PresenceCredential}.
*/
@NonNull
public PrivateCredential build() {
return new PrivateCredential(mIdentityType, mSecretId, mDeviceName,
mAuthenticityKey, mCredentialElements, mMetadataEncryptionKey);
}
}
}

View File

@@ -0,0 +1,184 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Represents a public credential.
*
* @hide
*/
@SystemApi
public final class PublicCredential extends PresenceCredential implements Parcelable {
@NonNull
public static final Creator<PublicCredential> CREATOR = new Creator<PublicCredential>() {
@Override
public PublicCredential createFromParcel(Parcel in) {
in.readInt(); // Skip the type as it's used by parent class only.
return createFromParcelBody(in);
}
@Override
public PublicCredential[] newArray(int size) {
return new PublicCredential[size];
}
};
private final byte[] mPublicKey;
private final byte[] mEncryptedMetadata;
private final byte[] mEncryptedMetadataKeyTag;
private PublicCredential(int identityType, byte[] secretId, byte[] authenticityKey,
List<CredentialElement> credentialElements, byte[] publicKey, byte[] encryptedMetadata,
byte[] metadataEncryptionKeyTag) {
super(CREDENTIAL_TYPE_PUBLIC, identityType, secretId, authenticityKey, credentialElements);
mPublicKey = publicKey;
mEncryptedMetadata = encryptedMetadata;
mEncryptedMetadataKeyTag = metadataEncryptionKeyTag;
}
private PublicCredential(Parcel in) {
super(CREDENTIAL_TYPE_PUBLIC, in);
mPublicKey = new byte[in.readInt()];
in.readByteArray(mPublicKey);
mEncryptedMetadata = new byte[in.readInt()];
in.readByteArray(mEncryptedMetadata);
mEncryptedMetadataKeyTag = new byte[in.readInt()];
in.readByteArray(mEncryptedMetadataKeyTag);
}
static PublicCredential createFromParcelBody(Parcel in) {
return new PublicCredential(in);
}
/**
* Returns the public key associated with this credential.
*/
@NonNull
public byte[] getPublicKey() {
return mPublicKey;
}
/**
* Returns the encrypted metadata associated with this credential.
*/
@NonNull
public byte[] getEncryptedMetadata() {
return mEncryptedMetadata;
}
/**
* Returns the metadata encryption key tag associated with this credential.
*/
@NonNull
public byte[] getEncryptedMetadataKeyTag() {
return mEncryptedMetadataKeyTag;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mPublicKey.length);
dest.writeByteArray(mPublicKey);
dest.writeInt(mEncryptedMetadata.length);
dest.writeByteArray(mEncryptedMetadata);
dest.writeInt(mEncryptedMetadataKeyTag.length);
dest.writeByteArray(mEncryptedMetadataKeyTag);
}
/**
* Builder class for {@link PresenceCredential}.
*/
public static final class Builder {
private final List<CredentialElement> mCredentialElements;
private @IdentityType int mIdentityType;
private final byte[] mSecretId;
private final byte[] mAuthenticityKey;
private final byte[] mPublicKey;
private final byte[] mEncryptedMetadata;
private final byte[] mEncryptedMetadataKeyTag;
public Builder(@NonNull byte[] secretId, @NonNull byte[] authenticityKey,
@NonNull byte[] publicKey, @NonNull byte[] encryptedMetadata,
@NonNull byte[] encryptedMetadataKeyTag) {
Preconditions.checkState(secretId != null && secretId.length > 0,
"secret id cannot be empty");
Preconditions.checkState(authenticityKey != null && authenticityKey.length > 0,
"authenticity key cannot be empty");
Preconditions.checkState(
publicKey != null && publicKey.length > 0,
"publicKey cannot be empty");
Preconditions.checkState(encryptedMetadata != null && encryptedMetadata.length > 0,
"encryptedMetadata cannot be empty");
Preconditions.checkState(
encryptedMetadataKeyTag != null && encryptedMetadataKeyTag.length > 0,
"encryptedMetadataKeyTag cannot be empty");
mSecretId = secretId;
mAuthenticityKey = authenticityKey;
mPublicKey = publicKey;
mEncryptedMetadata = encryptedMetadata;
mEncryptedMetadataKeyTag = encryptedMetadataKeyTag;
mCredentialElements = new ArrayList<>();
}
/**
* Sets the identity type for the presence credential.
*/
@NonNull
public Builder setIdentityType(@IdentityType int identityType) {
mIdentityType = identityType;
return this;
}
/**
* Adds an element to the credential.
*/
@NonNull
public Builder addCredentialElement(@NonNull CredentialElement credentialElement) {
Objects.requireNonNull(credentialElement);
mCredentialElements.add(credentialElement);
return this;
}
/**
* Builds the {@link PresenceCredential}.
*/
@NonNull
public PublicCredential build() {
return new PublicCredential(mIdentityType, mSecretId, mAuthenticityKey,
mCredentialElements, mPublicKey, mEncryptedMetadata, mEncryptedMetadataKeyTag);
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.NonNull;
import android.annotation.SystemApi;
/**
* Reports newly discovered devices.
* Note: The frequency of the callback is dependent on whether the caller
* is in the foreground or background. Foreground callbacks will occur
* as fast as the underlying medium supports, whereas background
* use cases will be rate limited to improve performance (ie, only on
* found/lost/significant changes).
*
* @hide
*/
@SystemApi
public interface ScanCallback {
/**
* Reports a {@link NearbyDevice} being discovered.
*
* @param device {@link NearbyDevice} that is found.
*/
void onDiscovered(@NonNull NearbyDevice device);
/**
* Reports a {@link NearbyDevice} information(distance, packet, and etc) changed.
*
* @param device {@link NearbyDevice} that has updates.
*/
void onUpdated(@NonNull NearbyDevice device);
/**
* Reports a {@link NearbyDevice} is no longer within range.
*
* @param device {@link NearbyDevice} that is lost.
*/
void onLost(@NonNull NearbyDevice device);
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
/**
* Filter for scanning a nearby device.
*
* @hide
*/
@SystemApi
public abstract class ScanFilter {
/**
* Creates a {@link ScanFilter} from the parcel.
*
* @hide
*/
public static ScanFilter createFromParcel(Parcel in) {
int type = in.readInt();
switch (type) {
// Currently, only Nearby Presence filtering is supported, in the future
// filtering other nearby specifications will be added.
case ScanRequest.SCAN_TYPE_NEARBY_PRESENCE:
return PresenceScanFilter.createFromParcelBody(in);
default:
throw new IllegalStateException(
"Unexpected scan type (value " + type + ") in parcel.");
}
}
private final @ScanRequest.ScanType int mType;
private final int mMaxPathLoss;
/**
* Constructs a Scan Filter.
*
* @hide
*/
ScanFilter(@ScanRequest.ScanType int type, @IntRange(from = 0, to = 127) int maxPathLoss) {
mType = type;
mMaxPathLoss = maxPathLoss;
}
/**
* Constructs a Scan Filter.
*
* @hide
*/
ScanFilter(@ScanRequest.ScanType int type, Parcel in) {
mType = type;
mMaxPathLoss = in.readInt();
}
/**
* Returns the type of this scan filter.
*/
public @ScanRequest.ScanType int getType() {
return mType;
}
/**
* Returns the maximum path loss (in dBm) of the received scan result. The path loss is the
* attenuation of radio energy between sender and receiver. Path loss here is defined as
* (TxPower - Rssi).
*/
@IntRange(from = 0, to = 127)
public int getMaxPathLoss() {
return mMaxPathLoss;
}
/**
*
* Writes the scan filter to the parcel.
*
* @hide
*/
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeInt(mMaxPathLoss);
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
parcelable ScanRequest;

View File

@@ -0,0 +1,366 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby;
import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.WorkSource;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* An encapsulation of various parameters for requesting nearby scans.
*
* @hide
*/
@SystemApi
public final class ScanRequest implements Parcelable {
/** Scan type for scanning devices using fast pair protocol. */
public static final int SCAN_TYPE_FAST_PAIR = 1;
/** Scan type for scanning devices using nearby share protocol. */
public static final int SCAN_TYPE_NEARBY_SHARE = 2;
/** Scan type for scanning devices using nearby presence protocol. */
public static final int SCAN_TYPE_NEARBY_PRESENCE = 3;
/** Scan type for scanning devices using exposure notification protocol. */
public static final int SCAN_TYPE_EXPOSURE_NOTIFICATION = 4;
/** Scan mode uses highest duty cycle. */
public static final int SCAN_MODE_LOW_LATENCY = 2;
/** Scan in balanced power mode.
* Scan results are returned at a rate that provides a good trade-off between scan
* frequency and power consumption.
*/
public static final int SCAN_MODE_BALANCED = 1;
/** Perform scan in low power mode. This is the default scan mode. */
public static final int SCAN_MODE_LOW_POWER = 0;
/**
* A special scan mode. Applications using this scan mode will passively listen for other scan
* results without starting BLE scans themselves.
*/
public static final int SCAN_MODE_NO_POWER = -1;
/**
* Used to read a ScanRequest from a Parcel.
*/
@NonNull
public static final Creator<ScanRequest> CREATOR = new Creator<ScanRequest>() {
@Override
public ScanRequest createFromParcel(Parcel in) {
ScanRequest.Builder builder = new ScanRequest.Builder()
.setScanType(in.readInt())
.setScanMode(in.readInt())
.setBleEnabled(in.readBoolean())
.setWorkSource(in.readTypedObject(WorkSource.CREATOR));
for (int i = 0; i < in.readInt(); i++) {
builder.addScanFilter(ScanFilter.createFromParcel(in));
}
return builder.build();
}
@Override
public ScanRequest[] newArray(int size) {
return new ScanRequest[size];
}
};
private final @ScanType int mScanType;
private final @ScanMode int mScanMode;
private final boolean mBleEnabled;
private final @NonNull WorkSource mWorkSource;
private final List<ScanFilter> mScanFilters;
private ScanRequest(@ScanType int scanType, @ScanMode int scanMode, boolean bleEnabled,
@NonNull WorkSource workSource, List<ScanFilter> scanFilters) {
mScanType = scanType;
mScanMode = scanMode;
mBleEnabled = bleEnabled;
mWorkSource = workSource;
mScanFilters = scanFilters;
}
/**
* Convert scan mode to readable string.
*
* @param scanMode Integer that may represent a{@link ScanMode}.
*/
@NonNull
public static String scanModeToString(@ScanMode int scanMode) {
switch (scanMode) {
case SCAN_MODE_LOW_LATENCY:
return "SCAN_MODE_LOW_LATENCY";
case SCAN_MODE_BALANCED:
return "SCAN_MODE_BALANCED";
case SCAN_MODE_LOW_POWER:
return "SCAN_MODE_LOW_POWER";
case SCAN_MODE_NO_POWER:
return "SCAN_MODE_NO_POWER";
default:
return "SCAN_MODE_INVALID";
}
}
/**
* Returns true if an integer is a defined scan type.
*/
public static boolean isValidScanType(@ScanType int scanType) {
return scanType == SCAN_TYPE_FAST_PAIR
|| scanType == SCAN_TYPE_NEARBY_SHARE
|| scanType == SCAN_TYPE_NEARBY_PRESENCE
|| scanType == SCAN_TYPE_EXPOSURE_NOTIFICATION;
}
/**
* Returns true if an integer is a defined scan mode.
*/
public static boolean isValidScanMode(@ScanMode int scanMode) {
return scanMode == SCAN_MODE_LOW_LATENCY
|| scanMode == SCAN_MODE_BALANCED
|| scanMode == SCAN_MODE_LOW_POWER
|| scanMode == SCAN_MODE_NO_POWER;
}
/**
* Returns the scan type for this request.
*/
public @ScanType int getScanType() {
return mScanType;
}
/**
* Returns the scan mode for this request.
*/
public @ScanMode int getScanMode() {
return mScanMode;
}
/**
* Returns if Bluetooth Low Energy enabled for scanning.
*/
public boolean isBleEnabled() {
return mBleEnabled;
}
/**
* Returns Scan Filters for this request.
*/
@NonNull
public List<ScanFilter> getScanFilters() {
return mScanFilters;
}
/**
* Returns the work source used for power attribution of this request.
*
* @hide
*/
@SystemApi
@NonNull
public WorkSource getWorkSource() {
return mWorkSource;
}
/**
* No special parcel contents.
*/
@Override
public int describeContents() {
return 0;
}
/**
* Returns a string representation of this ScanRequest.
*/
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Request[")
.append("scanType=").append(mScanType);
stringBuilder.append(", scanMode=").append(scanModeToString(mScanMode));
stringBuilder.append(", enableBle=").append(mBleEnabled);
stringBuilder.append(", workSource=").append(mWorkSource);
stringBuilder.append(", scanFilters=").append(mScanFilters);
stringBuilder.append("]");
return stringBuilder.toString();
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mScanType);
dest.writeInt(mScanMode);
dest.writeBoolean(mBleEnabled);
dest.writeTypedObject(mWorkSource, /* parcelableFlags= */0);
dest.writeInt(mScanFilters.size());
for (int i = 0; i < mScanFilters.size(); ++i) {
mScanFilters.get(i).writeToParcel(dest, flags);
}
}
@Override
public boolean equals(Object other) {
if (other instanceof ScanRequest) {
ScanRequest otherRequest = (ScanRequest) other;
return mScanType == otherRequest.mScanType
&& (mScanMode == otherRequest.mScanMode)
&& (mBleEnabled == otherRequest.mBleEnabled)
&& (Objects.equals(mWorkSource, otherRequest.mWorkSource));
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(mScanType, mScanMode, mBleEnabled, mWorkSource);
}
/** @hide **/
@Retention(RetentionPolicy.SOURCE)
@IntDef({SCAN_TYPE_FAST_PAIR, SCAN_TYPE_NEARBY_SHARE, SCAN_TYPE_NEARBY_PRESENCE,
SCAN_TYPE_EXPOSURE_NOTIFICATION})
public @interface ScanType {
}
/** @hide **/
@Retention(RetentionPolicy.SOURCE)
@IntDef({SCAN_MODE_LOW_LATENCY, SCAN_MODE_BALANCED,
SCAN_MODE_LOW_POWER,
SCAN_MODE_NO_POWER})
public @interface ScanMode {}
/** A builder class for {@link ScanRequest}. */
public static final class Builder {
private static final int INVALID_SCAN_TYPE = -1;
private @ScanType int mScanType;
private @ScanMode int mScanMode;
private boolean mBleEnabled;
private WorkSource mWorkSource;
private List<ScanFilter> mScanFilters;
/** Creates a new Builder with the given scan type. */
public Builder() {
mScanType = INVALID_SCAN_TYPE;
mBleEnabled = true;
mWorkSource = new WorkSource();
mScanFilters = new ArrayList<>();
}
/**
* Sets the scan type for the request. The scan type must be one of the SCAN_TYPE_ constants
* in {@link ScanRequest}.
*
* @param scanType The scan type for the request
*/
@NonNull
public Builder setScanType(@ScanType int scanType) {
mScanType = scanType;
return this;
}
/**
* Sets the scan mode for the request. The scan type must be one of the SCAN_MODE_ constants
* in {@link ScanRequest}.
*
* @param scanMode The scan mode for the request
*/
@NonNull
public Builder setScanMode(@ScanMode int scanMode) {
mScanMode = scanMode;
return this;
}
/**
* Sets if the ble is enabled for scanning.
*
* @param bleEnabled If the BluetoothLe is enabled in the device.
*/
@NonNull
public Builder setBleEnabled(boolean bleEnabled) {
mBleEnabled = bleEnabled;
return this;
}
/**
* Sets the work source to use for power attribution for this scan request. Defaults to
* empty work source, which implies the caller that sends the scan request will be used
* for power attribution.
*
* <p>Permission enforcement occurs when the resulting scan request is used, not when
* this method is invoked.
*
* @param workSource identifying the application(s) for which to blame for the scan.
* @hide
*/
@RequiresPermission(Manifest.permission.UPDATE_DEVICE_STATS)
@NonNull
@SystemApi
public Builder setWorkSource(@Nullable WorkSource workSource) {
if (workSource == null) {
mWorkSource = new WorkSource();
} else {
mWorkSource = workSource;
}
return this;
}
/**
* Adds a scan filter to the request. Client can call this method multiple times to add
* more than one scan filter. Scan results that match any of these scan filters will
* be returned.
*
* <p>On devices with hardware support, scan filters can significantly improve the battery
* usage of Nearby scans.
*
* @param scanFilter Filter for scanning the request.
*/
@NonNull
public Builder addScanFilter(@NonNull ScanFilter scanFilter) {
Objects.requireNonNull(scanFilter);
mScanFilters.add(scanFilter);
return this;
}
/**
* Builds a scan request from this builder.
*
* @return a new nearby scan request.
* @throws IllegalStateException if the scanType is not one of the SCAN_TYPE_ constants in
* {@link ScanRequest}.
*/
@NonNull
public ScanRequest build() {
Preconditions.checkState(isValidScanType(mScanType),
"invalid scan type : " + mScanType
+ ", scan type must be one of ScanRequest#SCAN_TYPE_");
Preconditions.checkState(isValidScanMode(mScanMode),
"invalid scan mode : " + mScanMode
+ ", scan mode must be one of ScanMode#SCAN_MODE_");
return new ScanRequest(mScanType, mScanMode, mBleEnabled, mWorkSource, mScanFilters);
}
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby.aidl;
/**
* This is to support 2D byte arrays.
* {@hide}
*/
parcelable ByteArrayParcel {
byte[] byteArray;
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby.aidl;
import android.accounts.Account;
import android.nearby.aidl.ByteArrayParcel;
/**
* Request details for Metadata of Fast Pair devices associated with an account.
* {@hide}
*/
parcelable FastPairAccountDevicesMetadataRequestParcel {
Account account;
ByteArrayParcel[] deviceAccountKeys;
}

View File

@@ -0,0 +1,35 @@
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package android.nearby.aidl;
import android.nearby.aidl.FastPairDeviceMetadataParcel;
import android.nearby.aidl.FastPairDiscoveryItemParcel;
/**
* Metadata of a Fast Pair device associated with an account.
* {@hide}
*/
// TODO(b/204780849): remove unnecessary fields and polish comments.
parcelable FastPairAccountKeyDeviceMetadataParcel {
// Key of the Fast Pair device associated with the account.
byte[] deviceAccountKey;
// Hash function of device account key and public bluetooth address.
byte[] sha256DeviceAccountKeyPublicAddress;
// Fast Pair device metadata for the Fast Pair device.
FastPairDeviceMetadataParcel metadata;
// Fast Pair discovery item tied to both the Fast Pair device and the
// account.
FastPairDiscoveryItemParcel discoveryItem;
}

View File

@@ -0,0 +1,31 @@
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package android.nearby.aidl;
import android.nearby.aidl.FastPairDeviceMetadataParcel;
/**
* Metadata of a Fast Pair device keyed by AntispoofKey,
* Used by initial pairing without account association.
*
* {@hide}
*/
parcelable FastPairAntispoofKeyDeviceMetadataParcel {
// Anti-spoof public key.
byte[] antispoofPublicKey;
// Fast Pair device metadata for the Fast Pair device.
FastPairDeviceMetadataParcel deviceMetadata;
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby.aidl;
/**
* Request details for metadata of a Fast Pair device keyed by either
* antispoofKey or modelId.
* {@hide}
*/
parcelable FastPairAntispoofKeyDeviceMetadataRequestParcel {
byte[] modelId;
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby.aidl;
/**
* Fast Pair Device Metadata for a given device model ID.
* @hide
*/
// TODO(b/204780849): remove unnecessary fields and polish comments.
parcelable FastPairDeviceMetadataParcel {
// The image to show on the notification.
String imageUrl;
// The intent that will be launched via the notification.
String intentUri;
// The transmit power of the device's BLE chip.
int bleTxPower;
// The distance that the device must be within to show a notification.
// If no distance is set, we default to 0.6 meters. Only Nearby admins can
// change this.
float triggerDistance;
// The image icon that shows in the notification.
byte[] image;
// The name of the device.
String name;
int deviceType;
// The image urls for device with device type "true wireless".
String trueWirelessImageUrlLeftBud;
String trueWirelessImageUrlRightBud;
String trueWirelessImageUrlCase;
// Stings to be displayed in notification surfaced for a device.
// The locale of all of the Strings.
String locale;
// The notification description for when the device is initially discovered.
String initialNotificationDescription;
// The notification description for when the device is initially discovered
// and no account is logged in.
String initialNotificationDescriptionNoAccount;
// The notification description for once we have finished pairing and the
// companion app has been opened. For Bisto devices, this String will point
// users to setting up the assistant.
String openCompanionAppDescription;
// The notification description for once we have finished pairing and the
// companion app needs to be updated before use.
String updateCompanionAppDescription;
// The notification description for once we have finished pairing and the
// companion app needs to be installed.
String downloadCompanionAppDescription;
// The notification title when a pairing fails.
String unableToConnectTitle;
// The notification summary when a pairing fails.
String unableToConnectDescription;
// The description that helps user initially paired with device.
String initialPairingDescription;
// The description that let user open the companion app.
String connectSuccessCompanionAppInstalled;
// The description that let user download the companion app.
String connectSuccessCompanionAppNotInstalled;
// The description that reminds user there is a paired device nearby.
String subsequentPairingDescription;
// The description that reminds users opt in their device.
String retroactivePairingDescription;
// The description that indicates companion app is about to launch.
String waitLaunchCompanionAppDescription;
// The description that indicates go to bluetooth settings when connection
// fail.
String failConnectGoToSettingsDescription;
// The title of the UI to ask the user to confirm the pin code.
String confirmPinTitle;
// The description of the UI to ask the user to confirm the pin code.
String confirmPinDescription;
// The title of the UI to ask the user to confirm to sync contacts.
String syncContactsTitle;
// The description of the UI to ask the user to confirm to sync contacts.
String syncContactsDescription;
// The title of the UI to ask the user to confirm to sync SMS.
String syncSmsTitle;
// The description of the UI to ask the user to confirm to sync SMS.
String syncSmsDescription;
// The description in half sheet to ask user setup google assistant
String assistantSetupHalfSheet;
// The description in notification to ask user setup google assistant
String assistantSetupNotification;
// Description of the connect device action on TV, when user is not logged in.
String fastPairTvConnectDeviceNoAccountDescription;
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby.aidl;
/**
* Fast Pair Discovery Item.
* @hide
*/
// TODO(b/204780849): remove unnecessary fields and polish comments.
parcelable FastPairDiscoveryItemParcel {
// Offline item: unique ID generated on client.
// Online item: unique ID generated on server.
String id;
int type;
// The most recent all upper case mac associated with this item.
// (Mac-to-DiscoveryItem is a many-to-many relationship)
String macAddress;
String actionUrl;
// The bluetooth device name from advertisement
String deviceName;
// Item's title
String title;
// Item's description.
String description;
// The URL for display
String displayUrl;
// Client timestamp when the beacon was last observed in BLE scan.
long lastObservationTimestampMillis;
// Client timestamp when the beacon was first observed in BLE scan.
long firstObservationTimestampMillis;
// Item's current state. e.g. if the item is blocked.
int state;
// The resolved url type for the action_url.
int actionUrlType;
// The timestamp when the user is redirected to Play Store after clicking on
// the item.
long pendingAppInstallTimestampMillis;
// Beacon's RSSI value
int rssi;
// Beacon's tx power
int txPower;
// Human readable name of the app designated to open the uri
// Used in the second line of the notification, "Open in {} app"
String appName;
// ID used for associating several DiscoveryItems. These items may be
// visually displayed together.
String groupId;
// Whether the attachment is created in debug namespace
int attachmentType;
// Package name of the App that owns this item.
String packageName;
// The "feature" graphic image url used for large sized list view entries.
String featureGraphicUrl;
// TriggerId identifies the trigger/beacon that is attached with a message.
// It's generated from server for online messages to synchronize formatting
// across client versions.
// Example:
// * BLE_UID: 3||deadbeef
// * BLE_URL: http://trigger.id
// See go/discovery-store-message-and-trigger-id for more details.
String triggerId;
// Bytes of item icon in PNG format displayed in Discovery item list.
byte[] iconPng;
// A FIFE URL of the item icon displayed in Discovery item list.
String iconFifeUrl;
// Message written to bugreport for 3P developers.(No sensitive info)
// null if the item is valid
String debugMessage;
// Weather the item is filtered out on server.
int debugCategory;
// Client timestamp when the trigger (e.g. beacon) was last lost (e.g. when
// Messages told us the beacon's no longer nearby).
long lostMillis;
// The kind of experience the user last had with this (e.g. if they dismissed
// the notification, that's bad; but if they tapped it, that's good).
int lastUserExperience;
// The most recent BLE advertisement related to this item.
byte[] bleRecordBytes;
// An ID generated on the server to uniquely identify content.
String entityId;
// Fast Pair antispoof key.
byte[] authenticationPublicKeySecp256r1;
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby.aidl;
import android.accounts.Account;
/**
* Fast Pair Eligible Account.
* {@hide}
*/
parcelable FastPairEligibleAccountParcel {
Account account;
// Whether the account opts in Fast Pair.
boolean optIn;
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby.aidl;
/**
* Request details for Fast Pair eligible accounts.
* Empty place holder for future expansion.
* {@hide}
*/
parcelable FastPairEligibleAccountsRequestParcel {
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby.aidl;
import android.accounts.Account;
import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
/**
* Request details for managing Fast Pair device-account mapping.
* {@hide}
*/
// TODO(b/204780849): remove unnecessary fields and polish comments.
parcelable FastPairManageAccountDeviceRequestParcel {
Account account;
// MANAGE_ACCOUNT_DEVICE_ADD: add Fast Pair device to the account.
// MANAGE_ACCOUNT_DEVICE_REMOVE: remove Fast Pair device from the account.
int requestType;
// Fast Pair account key-ed device metadata.
FastPairAccountKeyDeviceMetadataParcel accountKeyDeviceMetadata;
// BLE address of the device at the device add time.
String bleAddress;
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby.aidl;
import android.accounts.Account;
/**
* Request details for managing a Fast Pair account.
*
* {@hide}
*/
parcelable FastPairManageAccountRequestParcel {
Account account;
// MANAGE_ACCOUNT_OPT_IN: opt account into Fast Pair.
// MANAGE_ACCOUNT_OPT_OUT: opt account out of Fast Pair.
int requestType;
}

View File

@@ -0,0 +1,28 @@
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package android.nearby.aidl;
import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
/**
* Provides callback interface for OEMs to send back metadata of FastPair
* devices associated with an account.
*
* {@hide}
*/
interface IFastPairAccountDevicesMetadataCallback {
void onFastPairAccountDevicesMetadataReceived(in FastPairAccountKeyDeviceMetadataParcel[] accountDevicesMetadata);
void onError(int code, String message);
}

View File

@@ -0,0 +1,27 @@
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package android.nearby.aidl;
import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel;
/**
* Provides callback interface for OEMs to send FastPair AntispoofKey Device metadata back.
*
* {@hide}
*/
interface IFastPairAntispoofKeyDeviceMetadataCallback {
void onFastPairAntispoofKeyDeviceMetadataReceived(in FastPairAntispoofKeyDeviceMetadataParcel metadata);
void onError(int code, String message);
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby.aidl;
import android.nearby.aidl.IFastPairStatusCallback;
import android.nearby.FastPairDevice;
/**
* 0p API for controlling Fast Pair. Used to talk between foreground activities
* and background services.
*
* {@hide}
*/
interface IFastPairClient {
void registerHalfSheet(in IFastPairStatusCallback fastPairStatusCallback);
void unregisterHalfSheet(in IFastPairStatusCallback fastPairStatusCallback);
void connect(in FastPairDevice fastPairDevice);
}

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package android.nearby.aidl;
import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel;
import android.nearby.aidl.IFastPairAntispoofKeyDeviceMetadataCallback;
import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel;
import android.nearby.aidl.IFastPairAccountDevicesMetadataCallback;
import android.nearby.aidl.FastPairEligibleAccountsRequestParcel;
import android.nearby.aidl.IFastPairEligibleAccountsCallback;
import android.nearby.aidl.FastPairManageAccountRequestParcel;
import android.nearby.aidl.IFastPairManageAccountCallback;
import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel;
import android.nearby.aidl.IFastPairManageAccountDeviceCallback;
/**
* Interface for communicating with the fast pair providers.
*
* {@hide}
*/
oneway interface IFastPairDataProvider {
void loadFastPairAntispoofKeyDeviceMetadata(in FastPairAntispoofKeyDeviceMetadataRequestParcel request,
in IFastPairAntispoofKeyDeviceMetadataCallback callback);
void loadFastPairAccountDevicesMetadata(in FastPairAccountDevicesMetadataRequestParcel request,
in IFastPairAccountDevicesMetadataCallback callback);
void loadFastPairEligibleAccounts(in FastPairEligibleAccountsRequestParcel request,
in IFastPairEligibleAccountsCallback callback);
void manageFastPairAccount(in FastPairManageAccountRequestParcel request,
in IFastPairManageAccountCallback callback);
void manageFastPairAccountDevice(in FastPairManageAccountDeviceRequestParcel request,
in IFastPairManageAccountDeviceCallback callback);
}

View File

@@ -0,0 +1,28 @@
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package android.nearby.aidl;
import android.accounts.Account;
import android.nearby.aidl.FastPairEligibleAccountParcel;
/**
* Provides callback interface for OEMs to return FastPair Eligible accounts.
*
* {@hide}
*/
interface IFastPairEligibleAccountsCallback {
void onFastPairEligibleAccountsReceived(in FastPairEligibleAccountParcel[] accounts);
void onError(int code, String message);
}

View File

@@ -0,0 +1,25 @@
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package android.nearby.aidl;
/**
* Provides callback interface to send response for account management request.
*
* {@hide}
*/
interface IFastPairManageAccountCallback {
void onSuccess();
void onError(int code, String message);
}

View File

@@ -0,0 +1,26 @@
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package android.nearby.aidl;
/**
* Provides callback interface to send response for account-device mapping
* management request.
*
* {@hide}
*/
interface IFastPairManageAccountDeviceCallback {
void onSuccess();
void onError(int code, String message);
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2022, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.nearby.aidl;
import android.nearby.FastPairDevice;
import android.nearby.PairStatusMetadata;
/**
*
* Provides callbacks for Fast Pair foreground activity to learn about paring status from backend.
*
* {@hide}
*/
interface IFastPairStatusCallback {
/** Reports a pair status related metadata associated with a {@link FastPairDevice} */
void onPairUpdate(in FastPairDevice fastPairDevice, in PairStatusMetadata pairStatusMetadata);
}

View File

@@ -0,0 +1,57 @@
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
android_app {
name: "HalfSheetUX",
defaults: ["platform_app_defaults"],
srcs: ["src/**/*.java"],
sdk_version: "module_current",
// This is included in tethering apex, which uses min SDK 30
min_sdk_version: "30",
target_sdk_version: "current",
updatable: true,
certificate: ":com.android.nearby.halfsheetcertificate",
libs: [
"framework-bluetooth",
"framework-connectivity-t",
"nearby-service-string",
],
static_libs: [
"androidx.annotation_annotation",
"androidx.fragment_fragment",
"androidx-constraintlayout_constraintlayout",
"androidx.localbroadcastmanager_localbroadcastmanager",
"androidx.core_core",
"androidx.appcompat_appcompat",
"androidx.recyclerview_recyclerview",
"androidx.lifecycle_lifecycle-runtime",
"androidx.lifecycle_lifecycle-extensions",
"com.google.android.material_material",
"fast-pair-lite-protos",
],
plugins: ["java_api_finder"],
manifest: "AndroidManifest.xml",
jarjar_rules: ":nearby-jarjar-rules",
apex_available: ["com.android.tethering",],
lint: { strict_updatability_linting: true }
}
android_app_certificate {
name: "com.android.nearby.halfsheetcertificate",
certificate: "apk-certs/com.android.nearby.halfsheet"
}

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.nearby.halfsheet">
<application>
<activity
android:name="com.android.nearby.halfsheet.HalfSheetActivity"
android:exported="true"
android:launchMode="singleInstance"
android:theme="@style/HalfSheetStyle" >
<intent-filter>
<action android:name="android.nearby.SHOW_HALFSHEET"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,34 @@
-----BEGIN CERTIFICATE-----
MIIF6zCCA9OgAwIBAgIUU5ATKevcNA5ZSurwgwGenwrr4c4wDQYJKoZIhvcNAQEL
BQAwgYMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMQwwCgYDVQQH
DANNVFYxDzANBgNVBAoMBkdvb2dsZTEPMA0GA1UECwwGbmVhcmJ5MQswCQYDVQQD
DAJ3czEiMCAGCSqGSIb3DQEJARYTd2VpY2VzdW5AZ29vZ2xlLmNvbTAgFw0yMTEy
MDgwMTMxMzFaGA80NzU5MTEwNDAxMzEzMVowgYMxCzAJBgNVBAYTAlVTMRMwEQYD
VQQIDApDYWxpZm9ybmlhMQwwCgYDVQQHDANNVFYxDzANBgNVBAoMBkdvb2dsZTEP
MA0GA1UECwwGbmVhcmJ5MQswCQYDVQQDDAJ3czEiMCAGCSqGSIb3DQEJARYTd2Vp
Y2VzdW5AZ29vZ2xlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
AO0JW1YZ5bKHZG5B9eputz3kGREmXcWZ97dg/ODDs3+op4ulBmgaYeo5yeCy29GI
Sjgxo4G+9fNZ7Fejrk5/LLWovAoRvVxnkRxCkTfp15jZpKNnZjT2iTRLXzNz2O04
cC0jB81mu5vJ9a8pt+EQkuSwjDMiUi6q4Sf6IRxtTCd5a1yn9eHf1y2BbCmU+Eys
bs97HJl9PgMCp7hP+dYDxEtNTAESg5IpJ1i7uINgPNl8d0tvJ9rOEdy0IcdeGwt/
t0L9fIoRCePttH+idKIyDjcNyp9WtX2/wZKlsGap83rGzLdL2PI4DYJ2Ytmy8W3a
9qFJNrhl3Q3BYgPlcCg9qQOIKq6ZJgFFH3snVDKvtSFd8b9ofK7UzD5g2SllTqDA
4YvrdK4GETQunSjG7AC/2PpvN/FdhHm7pBi0fkgwykMh35gv0h8mmb6pBISYgr85
+GMBilNiNJ4G6j3cdOa72pvfDW5qn5dn5ks8cIgW2X1uF/GT8rR6Mb2rwhjY9eXk
TaP0RykyzheMY/7dWeA/PdN3uMCEJEt72ZakDIswgQVPCIw8KQPIf6pl0d5hcLSV
QzhqBaXudseVg0QlZ86iaobpZvCrW0KqQmMU5GVhEtDc2sPe5e+TCmUC/H+vo8F8
1UYu3MJaBcpePFlgIsLhW0niUTfCq2FiNrPykOJT7U9NAgMBAAGjUzBRMB0GA1Ud
DgQWBBQKSepRcKTv9hr8mmKjYCL7NeG2izAfBgNVHSMEGDAWgBQKSepRcKTv9hr8
mmKjYCL7NeG2izAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQC/
BoItafzvjYPzENY16BIkgRqJVU7IosWxGLczzg19NFu6HPa54alqkawp7RI1ZNVH
bJjQma5ap0L+Y06/peIU9rvEtfbCkkYJwvIaSRlTlzrNwNEcj3yJMmGTr/wfIzq8
PN1t0hihnqI8ZguOPC+sV6ARoC+ygkwaLU1oPbVvOGz9WplvSokE1mvtqKAyuDoL
LZfWwbhxRAgwgCIEz6cPfEcgg3Xzc+L4OzmNhTTc7GNOAtvvW7Zqc2Lohb8nQMNw
uY65yiHPNmjmc+xLHZk3jQg82tKv792JJRkVXPsIfQV087IzxFFjjvKy82rVfeaN
F9g2EpUvdjtm8zx7K5tiDv9Es/Up7oOnoB5baLgnMAEVMTZY+4k/6BfVM5CVUu+H
AO1yh2yeNWbzY8B+zxRef3C2Ax68lJHFyz8J1pfrGpWxML3rDmWiVDMtEk73t3g+
lcyLYo7OW+iBn6BODRcINO4R640oyMjFz2wPSPAsU0Zj/MbgC6iaS+goS3QnyPQS
O3hKWfwqQuA7BZ0la1n+plKH5PKxQESAbd37arzCsgQuktl33ONiwYOt6eUyHl/S
E3ZdldkmGm9z0mcBYG9NczDBSYmtuZOGjEzIRqI5GFD2WixE+dqTzVP/kyBd4BLc
OTmBynN/8D/qdUZNrT+tgs+mH/I2SsKYW9Zymwf7Qw==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDtCVtWGeWyh2Ru
QfXqbrc95BkRJl3Fmfe3YPzgw7N/qKeLpQZoGmHqOcngstvRiEo4MaOBvvXzWexX
o65Ofyy1qLwKEb1cZ5EcQpE36deY2aSjZ2Y09ok0S18zc9jtOHAtIwfNZrubyfWv
KbfhEJLksIwzIlIuquEn+iEcbUwneWtcp/Xh39ctgWwplPhMrG7PexyZfT4DAqe4
T/nWA8RLTUwBEoOSKSdYu7iDYDzZfHdLbyfazhHctCHHXhsLf7dC/XyKEQnj7bR/
onSiMg43DcqfVrV9v8GSpbBmqfN6xsy3S9jyOA2CdmLZsvFt2vahSTa4Zd0NwWID
5XAoPakDiCqumSYBRR97J1Qyr7UhXfG/aHyu1Mw+YNkpZU6gwOGL63SuBhE0Lp0o
xuwAv9j6bzfxXYR5u6QYtH5IMMpDId+YL9IfJpm+qQSEmIK/OfhjAYpTYjSeBuo9
3HTmu9qb3w1uap+XZ+ZLPHCIFtl9bhfxk/K0ejG9q8IY2PXl5E2j9EcpMs4XjGP+
3VngPz3Td7jAhCRLe9mWpAyLMIEFTwiMPCkDyH+qZdHeYXC0lUM4agWl7nbHlYNE
JWfOomqG6Wbwq1tCqkJjFORlYRLQ3NrD3uXvkwplAvx/r6PBfNVGLtzCWgXKXjxZ
YCLC4VtJ4lE3wqthYjaz8pDiU+1PTQIDAQABAoICAQCt4R5CM+8enlka1IIbvann
2cpVnUpOaNqhh6EZFBY5gDOfqafgd/H5yvh/P1UnCI5BWJBz3ew33nAT/fsglAPt
ImEGFetNvJ9jFqXGWWCRPJ6cS35bPbp6RQwKB2JK6grH4ZmYoFLhPi5elwDPNcQ7
xBKkc/nLSAiwtbjSTI7/qf8K0h752aTUOctpWWEnhZon00ywf4Ic3TbBatF/n/W/
s20coEMp1cyKN/JrVQ5uD/LGwDyBModB2lWpFSxLrB14I9DWyxbxP28X7ckXLhbl
ZdWMOyQZoa/S7n5PYT49g1Wq5BW54UpvuH5c6fpWtrgSqk1cyUR2EbTf3NAAhPLU
PgPK8wbFMcMB3TpQDXl7USA7QX5wSv22OfhivPsHQ9szGM0f84mK0PhXYPWBiNUY
Y8rrIjOijB4eFGDFnTIMTofAb07NxRThci710BYUqgBVTBG5N+avIesjwkikMjOI
PwYukKSQSw/Tqxy5Z9l22xksGynBZFjEFs/WT5pDczPAktA4xW3CGxjkMsIYaOBs
OCEujqc5+mHSywYvy8aN+nA+yPucJP5e5pLZ1qaU0tqyakCx8XeeOyP6Wfm3UAAV
AYelBRcWcJxM51w4o5UnUnpBD+Uxiz1sRVlqa9bLJjP4M+wJNL+WaIn9D6WhPOvl
+naDC+p29ou2JzyKFDsOQQKCAQEA+Jalm+xAAPc+t/gCdAqEDo0NMA2/NG8m9BRc
CVZRRaWVyGPeg5ziT/7caGwy2jpOZEjK0OOTCAqF+sJRDj6DDIw7nDrlxNyaXnCF
gguQHFIYaHcjKGTs5l0vgL3H7pMFHN2qVynf4xrTuBXyT1GJ4vdWKAJbooa02c8W
XI2fjwZ7Y8wSWrm1tn3oTTBR3N6o1GyPY6/TrL0mhpWwgx5eJeLl3GuUxOhXY5R9
y48ziS97Dqdq75MxUOHickofCNcm7p+jA8Hg+SxLMR/kUFsXOxawmvsBqdL1XzU5
LTS7xAEY9iMuBcO6yIxcxqBx96idjsPXx1lgARo1CpaZYCzgPQKCAQEA9BqKMN/Y
o+T+ac99St8x3TYkk5lkvLVqlPw+EQhEqrm9EEBPntxWM5FEIpPVmFm7taGTgPfN
KKaaNxX5XyK9B2v1QqN7XrX0nF4+6x7ao64fdpRUParIuBVctqzQWWthme66eHrf
L86T/tkt3o/7p+Hd4Z9UT3FaAew1ggWr00xz5PJ/4b3f3mRmtNmgeTYskWMxOpSj
bEenom4Row7sfLNeXNSWDGlzJ/lf6svvbVM2X5h2uFsxlt/Frq9ooTA3wwhnbd1i
cFifDQ6cxF5mBpz/V/hnlHVfuXlknEZa9EQXHNo/aC9y+bR+ai05FJyK/WgqleW8
5PBmoTReWA2MUQKCAQAnnnLkh+GnhcBEN83ESszDOO3KI9a+d5yguAH3Jv+q9voJ
Rwl2tnFHSJo+NkhgiXxm9UcFxc9wL6Us0v1yJLpkLJFvk9984Z/kv1A36rncGaV0
ONCspnEvQdjJTvXnax0cfaOhYrYhDuyBYVYOGDO+rabYl4+dNpTqRdwNgjDU7baK
sEKYnRJ99FEqxDG33vDPckHkJGi7FiZmusK4EwX0SdZSq/6450LORyNJZxhSm/Oj
4UDkz/PDLU0W5ANQOGInE+A6QBMoA0w0lx2fRPVN4I7jFHAubcXXl7b2InpugbJF
wFOcbZZ+UgiTS4z+aKw7zbC9P9xSMKgVeO0W6/ANAoIBABe0LA8q7YKczgfAWk5W
9iShCVQ75QheJYdqJyzIPMLHXpChbhnjE4vWY2NoL6mnrQ6qLgSsC4QTCY6n15th
aDG8Tgi2j1hXGvXEQR/b0ydp1SxSowuJ9gvKJ0Kl7WWBg+zKvdjNNbcSvFRXCpk+
KhXXXRB3xFwiibb+FQQXQOQ33FkzIy/snDygS0jsiSS8Gf/UPgeOP4BYRPME9Tl8
TYKeeF9TVW7HHqOXF7VZMFrRZcpKp9ynHl2kRTH9Xo+oewG5YzHL+a8nK+q8rIR1
Fjs2K6WDPauw6ia8nwR94H8vzX7Dwrx/Pw74c/4jfhN+UBDjeJ8tu/YPUif9SdwL
FMECggEALdCGKfQ4vPmqI6UdfVB5hdCPoM6tUsI2yrXFvlHjSGVanC/IG9x2mpRb
4odamLYx4G4NjP1IJSY08LFT9VhLZtRM1W3fGeboW12LTEVNrI3lRBU84rAQ1ced
l6/DvTKJjhfwTxb/W7sqmZY5hF3QuNxs67Z8x0pe4b58musa0qFCs4Sa8qTNZKRW
fIbxIKuvu1HSNOKkZLu6Gq8km+XIlVAaSVA03Tt+EK74MFL6+pcd7/VkS00MAYUC
gS4ic+QFzCl5P8zl/GoX8iUFsRZQCSJkZ75VwO13pEupVwCAW8WWJO83U4jBsnJs
ayrX7pbsnW6jsNYBUlck+RYVYkVkxA==
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/decelerate_quint">
<translate android:fromYDelta="100%"
android:toYDelta="0"
android:duration="900"/>
</set>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/decelerate_quint">
<translate android:fromYDelta="0"
android:toYDelta="100%"
android:duration="500"/>
</set>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:targetApi="23"
android:duration="@integer/half_sheet_slide_in_duration"
android:interpolator="@android:interpolator/fast_out_slow_in">
<translate
android:fromYDelta="100%p"
android:toYDelta="0%p"/>
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"/>
</set>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:duration="@integer/half_sheet_fade_out_duration"
android:interpolator="@android:interpolator/fast_out_slow_in">
<translate
android:fromYDelta="0%p"
android:toYDelta="100%p"/>
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"/>
</set>

View File

@@ -0,0 +1,25 @@
<!--
~ Copyright (C) 2022 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="@color/fast_pair_half_sheet_subtitle_color">
<path
android:fillColor="@color/fast_pair_half_sheet_subtitle_color"
android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
</vector>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="1dp"
android:color="@color/fast_pair_notification_image_outline"/>
</shape>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:targetApi="23">
<solid android:color="@color/fast_pair_half_sheet_background" />
<corners
android:topLeftRadius="16dp"
android:topRightRadius="16dp"
android:padding="8dp"/>
</shape>

View File

@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
tools:ignore="RtlCompat"
android:layout_width="match_parent" android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="340dp"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="12dp"
android:paddingBottom="12dp">
<TextView
android:id="@+id/header_subtitle"
android:textColor="@color/fast_pair_half_sheet_subtitle_color"
android:fontFamily="google-sans"
android:textSize="14sp"
android:maxLines="3"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/pairing_pic"
android:layout_width="@dimen/fast_pair_half_sheet_image_size"
android:layout_height="@dimen/fast_pair_half_sheet_image_size"
android:paddingTop="18dp"
android:paddingBottom="18dp"
android:importantForAccessibility="no"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/header_subtitle" />
<TextView
android:id="@+id/pin_code"
android:textColor="@color/fast_pair_half_sheet_subtitle_color"
android:layout_width="wrap_content"
android:layout_height="@dimen/fast_pair_half_sheet_image_size"
android:paddingTop="18dp"
android:paddingBottom="18dp"
android:visibility="invisible"
android:textSize="50sp"
android:letterSpacing="0.2"
android:fontFamily="google-sans-medium"
android:gravity="center"
android:importantForAccessibility="yes"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/header_subtitle" />
<ProgressBar
android:id="@+id/connect_progressbar"
android:layout_width="@dimen/fast_pair_half_sheet_image_size"
android:layout_height="2dp"
android:indeterminate="true"
android:indeterminateTint="@color/fast_pair_progress_color"
android:indeterminateTintMode="src_in"
style="?android:attr/progressBarStyleHorizontal"
android:layout_marginBottom="6dp"
app:layout_constraintTop_toBottomOf="@+id/pairing_pic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/connect_progressbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<ImageView
android:id="@+id/info_icon"
android:layout_width="24dp"
android:layout_height="24dp"
app:srcCompat="@drawable/fast_pair_ic_info"
android:layout_centerInParent="true"
android:contentDescription="@null"
android:layout_marginEnd="10dp"
android:layout_toStartOf="@id/connect_btn"
android:visibility="invisible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/connect_btn"
android:layout_width="@dimen/fast_pair_half_sheet_image_size"
android:layout_height="wrap_content"
android:text="@string/paring_action_connect"
android:layout_centerInParent="true"
style="@style/HalfSheetButton" />
</RelativeLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/settings_btn"
android:text="@string/paring_action_settings"
android:layout_height="wrap_content"
android:layout_width="@dimen/fast_pair_half_sheet_image_size"
app:layout_constraintTop_toBottomOf="@+id/connect_progressbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:visibility="invisible"
style="@style/HalfSheetButton" />
<com.google.android.material.button.MaterialButton
android:id="@+id/cancel_btn"
android:text="@string/paring_action_done"
android:visibility="invisible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:gravity="start|center_vertical"
android:layout_marginTop="6dp"
style="@style/HalfSheetButtonBorderless"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/setup_btn"
android:text="@string/paring_action_launch"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="6dp"
android:layout_marginBottom="16dp"
android:background="@color/fast_pair_half_sheet_button_color"
android:visibility="invisible"
android:layout_height="@dimen/fast_pair_half_sheet_bottom_button_height"
android:layout_width="wrap_content"
style="@style/HalfSheetButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="RtlCompat, UselessParent, MergeRootFrame"
android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/card"
android:orientation="vertical"
android:transitionName="card"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_gravity= "center|bottom"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:background="@drawable/half_sheet_bg"
android:accessibilityLiveRegion="polite"
android:gravity="bottom">
<RelativeLayout
android:id="@+id/toolbar_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="20dp"
android:paddingRight="20dp">
<ImageView
android:layout_marginTop="9dp"
android:layout_marginBottom="9dp"
android:id="@+id/toolbar_image"
android:layout_width="42dp"
android:layout_height="42dp"
android:contentDescription="@null"
android:layout_toStartOf="@id/toolbar_title"
android:layout_centerHorizontal="true"
android:visibility="invisible"/>
<TextView
android:layout_marginTop="18dp"
android:layout_marginBottom="18dp"
android:layout_centerHorizontal="true"
android:id="@+id/toolbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="google-sans-medium"
android:textSize="24sp"
android:textColor="@color/fast_pair_half_sheet_text_color"
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" />
</RelativeLayout>
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</FrameLayout>

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:baselineAligned="false"
android:background="@color/fast_pair_notification_background"
tools:ignore="ContentDescription,UnusedAttribute,RtlCompat,Overdraw">
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginTop="@dimen/fast_pair_notification_padding"
android:layout_marginStart="@dimen/fast_pair_notification_padding"
android:layout_marginEnd="@dimen/fast_pair_notification_padding">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:textSize="@dimen/fast_pair_notification_text_size"
android:textColor="@color/fast_pair_primary_text"
android:layout_marginBottom="2dp"
android:lines="1"/>
<TextView
android:id="@android:id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/fast_pair_notification_text_size_small"
android:textColor="@color/fast_pair_primary_text"
android:layout_marginBottom="2dp"
android:layout_marginStart="4dp"
android:lines="1"/>
</LinearLayout>
<TextView
android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/fast_pair_notification_text_size"
android:textColor="@color/fast_pair_primary_text"
android:maxLines="2"
android:ellipsize="end"
android:breakStrategy="simple" />
<FrameLayout
android:id="@android:id/secondaryProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="32dp"
android:orientation="horizontal"
android:visibility="gone">
<ProgressBar
android:id="@android:id/progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:indeterminateTint="@color/discovery_activity_accent"/>
</FrameLayout>
</LinearLayout>
<FrameLayout
android:id="@android:id/icon1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@@ -0,0 +1,7 @@
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/icon"
android:layout_width="@dimen/fast_pair_notification_large_image_size"
android:layout_height="@dimen/fast_pair_notification_large_image_size"
android:scaleType="fitStart"
tools:ignore="ContentDescription"/>

View File

@@ -0,0 +1,11 @@
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/icon"
android:layout_width="@dimen/fast_pair_notification_small_image_size"
android:layout_height="@dimen/fast_pair_notification_small_image_size"
android:layout_marginTop="@dimen/fast_pair_notification_padding"
android:layout_marginBottom="@dimen/fast_pair_notification_padding"
android:layout_marginStart="@dimen/fast_pair_notification_padding"
android:layout_marginEnd="@dimen/fast_pair_notification_padding"
android:scaleType="fitStart"
tools:ignore="ContentDescription,RtlCompat"/>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<resources
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Use original background color -->
<color name="fast_pair_notification_background">#00000000</color>
<!-- Ignores NewApi as below system colors are available since API 31, and HalfSheet is always
running on T+ even though it has min_sdk 30 to match its containing APEX -->
<color name="fast_pair_half_sheet_button_color" tools:ignore="NewApi">@android:color/system_accent1_100</color>
<color name="fast_pair_half_sheet_button_text" tools:ignore="NewApi">@android:color/system_neutral1_900</color>
<color name="fast_pair_half_sheet_button_accent_text" tools:ignore="NewApi">@android:color/system_neutral1_900</color>
<color name="fast_pair_progress_color" tools:ignore="NewApi">@android:color/system_accent1_600</color>
<color name="fast_pair_half_sheet_subtitle_color" tools:ignore="NewApi">@android:color/system_neutral2_700</color>
<color name="fast_pair_half_sheet_text_color" tools:ignore="NewApi">@android:color/system_neutral1_900</color>
<!-- Nearby Discoverer -->
<color name="discovery_activity_accent">#4285F4</color>
<!-- Fast Pair -->
<color name="fast_pair_primary_text">#DE000000</color>
<color name="fast_pair_notification_image_outline">#24000000</color>
<color name="fast_pair_battery_level_low">#D93025</color>
<color name="fast_pair_battery_level_normal">#80868B</color>
<color name="fast_pair_half_sheet_background">#FFFFFF</color>
<color name="fast_pair_half_sheet_color_accent">#1A73E8</color>
<color name="fast_pair_fail_progress_color">#F44336</color>
<color name="fast_pair_progress_back_ground">#24000000</color>
</resources>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Fast Pair notification values -->
<dimen name="fast_pair_halfsheet_mid_image_size">160dp</dimen>
<dimen name="fast_pair_notification_text_size">14sp</dimen>
<dimen name="fast_pair_notification_text_size_small">11sp</dimen>
<dimen name="fast_pair_battery_notification_empty_view_height">4dp</dimen>
<dimen name="fast_pair_battery_notification_margin_top">8dp</dimen>
<dimen name="fast_pair_battery_notification_margin_bottom">8dp</dimen>
<dimen name="fast_pair_battery_notification_content_height">40dp</dimen>
<dimen name="fast_pair_battery_notification_content_height_v2">64dp</dimen>
<dimen name="fast_pair_battery_notification_image_size">32dp</dimen>
<dimen name="fast_pair_battery_notification_image_padding">3dp</dimen>
<dimen name="fast_pair_half_sheet_min_height">350dp</dimen>
<dimen name="fast_pair_half_sheet_image_size">215dp</dimen>
<dimen name="fast_pair_half_sheet_land_image_size">136dp</dimen>
<dimen name="fast_pair_connect_button_height">36dp</dimen>
<dimen name="accessibility_required_min_touch_target_size">48dp</dimen>
<dimen name="fast_pair_half_sheet_battery_case_image_size">152dp</dimen>
<dimen name="fast_pair_half_sheet_battery_bud_image_size">100dp</dimen>
<integer name="half_sheet_battery_case_width_dp">156</integer>
<integer name="half_sheet_battery_case_height_dp">182</integer>
<!-- Maximum height for SliceView, override on slices/view/src/main/res/values/dimens.xml -->
<dimen name="abc_slice_large_height">360dp</dimen>
<dimen name="action_dialog_content_margin_left">16dp</dimen>
<dimen name="action_dialog_content_margin_top">70dp</dimen>
<dimen name="action_button_focused_elevation">4dp</dimen>
<!-- Subsequent Notification -->
<dimen name="fast_pair_notification_padding">4dp</dimen>
<dimen name="fast_pair_notification_large_image_size">32dp</dimen>
<dimen name="fast_pair_notification_small_image_size">32dp</dimen>
<!-- Battery Notification -->
<dimen name="fast_pair_battery_notification_main_view_padding">0dp</dimen>
<dimen name="fast_pair_battery_notification_title_image_margin_start">0dp</dimen>
<dimen name="fast_pair_battery_notification_title_text_margin_start">0dp</dimen>
<dimen name="fast_pair_battery_notification_title_text_margin_start_v2">0dp</dimen>
<dimen name="fast_pair_battery_notification_image_margin_start">0dp</dimen>
<dimen name="fast_pair_half_sheet_bottom_button_height">48dp</dimen>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="half_sheet_slide_in_duration">250</integer>
<integer name="half_sheet_fade_out_duration">250</integer>
</resources>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 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.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<overlayable name="NearbyHalfSheetResourcesConfig">
<policy type="product|system|vendor">
<item type="color" name="fast_pair_half_sheet_background"/>
<item type="color" name="fast_pair_half_sheet_button_color"/>
</policy>
</overlayable>
</resources>

View File

@@ -0,0 +1,72 @@
<!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<!--
============================================================
PAIRING FRAGMENT
============================================================
-->
<!--
A button shown to remind user setup is in progress. [CHAR LIMIT=30]
-->
<string name="fast_pair_setup_in_progress">Starting Setup&#x2026;</string>
<!--
Title text shown to remind user to setup a device through companion app. [CHAR LIMIT=40]
-->
<string name="fast_pair_title_setup">Set up device</string>
<!--
Title after we successfully pair with the audio device
[CHAR LIMIT=30]
-->
<string name="fast_pair_device_ready">Device connected</string>
<!-- Title text shown when peripheral device fail to connect to phone. [CHAR_LIMIT=30] -->
<string name="fast_pair_title_fail">Couldn\'t connect</string>
<!--
============================================================
MISCELLANEOUS
============================================================
-->
<!--
A button shown after paring process to dismiss the current activity.
[CHAR LIMIT=30]
-->
<string name="paring_action_done">Done</string>
<!--
A button shown for retroactive paring.
[CHAR LIMIT=30]
-->
<string name="paring_action_save">Save</string>
<!--
A button to start connecting process.
[CHAR LIMIT=30]
-->
<string name="paring_action_connect">Connect</string>
<!--
A button to launch a companion app.
[CHAR LIMIT=30]
-->
<string name="paring_action_launch">Set up</string>
<!--
A button to launch a bluetooth Settings page.
[CHAR LIMIT=20]
-->
<string name="paring_action_settings">Settings</string>
</resources>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="HalfSheetStyle" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:windowFrame">@null</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowEnterAnimation">@anim/fast_pair_half_sheet_slide_in</item>
<item name="android:windowExitAnimation">@anim/fast_pair_half_sheet_slide_out</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:fitsSystemWindows">true</item>
<item name="android:windowTranslucentNavigation">true</item>
</style>
<style name="HalfSheetButton" parent="@style/Widget.Material3.Button.TonalButton">
<item name="android:textColor">@color/fast_pair_half_sheet_button_accent_text</item>
<item name="android:backgroundTint">@color/fast_pair_half_sheet_button_color</item>
<item name="android:textSize">@dimen/fast_pair_notification_text_size</item>
<item name="android:fontFamily">google-sans-medium</item>
<item name="android:textAlignment">center</item>
<item name="android:textAllCaps">false</item>
</style>
<style name="HalfSheetButtonBorderless" parent="@style/Widget.Material3.Button.OutlinedButton">
<item name="android:textColor">@color/fast_pair_half_sheet_button_text</item>
<item name="android:strokeColor">@color/fast_pair_half_sheet_button_color</item>
<item name="android:textAllCaps">false</item>
<item name="android:textSize">@dimen/fast_pair_notification_text_size</item>
<item name="android:fontFamily">google-sans-medium</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textAlignment">center</item>
<item name="android:minHeight">@dimen/accessibility_required_min_touch_target_size</item>
</style>
</resources>

View File

@@ -0,0 +1,239 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.nearby.halfsheet;
import static com.android.nearby.halfsheet.fragment.DevicePairingFragment.APP_LAUNCH_FRAGMENT_TYPE;
import static com.android.server.nearby.common.bluetooth.fastpair.FastPairConstants.EXTRA_MODEL_ID;
import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_MAC_ADDRESS;
import static com.android.server.nearby.fastpair.Constant.ACTION_FAST_PAIR_HALF_SHEET_CANCEL;
import static com.android.server.nearby.fastpair.Constant.DEVICE_PAIRING_FRAGMENT_TYPE;
import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_TYPE;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import com.android.nearby.halfsheet.fragment.DevicePairingFragment;
import com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment;
import com.android.nearby.halfsheet.utils.BroadcastUtils;
import com.google.protobuf.InvalidProtocolBufferException;
import java.util.Locale;
import service.proto.Cache;
/**
* A class show Fast Pair related information in Half sheet format.
*/
public class HalfSheetActivity extends FragmentActivity {
public static final String TAG = "HalfSheetActivity";
public static final String EXTRA_HALF_SHEET_CONTENT =
"com.android.nearby.halfsheet.HALF_SHEET_CONTENT";
public static final String EXTRA_TITLE =
"com.android.nearby.halfsheet.HALF_SHEET_TITLE";
public static final String EXTRA_DESCRIPTION =
"com.android.nearby.halfsheet.HALF_SHEET_DESCRIPTION";
public static final String EXTRA_HALF_SHEET_ID =
"com.android.nearby.halfsheet.HALF_SHEET_ID";
public static final String EXTRA_HALF_SHEET_IS_RETROACTIVE =
"com.android.nearby.halfsheet.HALF_SHEET_IS_RETROACTIVE";
public static final String EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR =
"com.android.nearby.halfsheet.HALF_SHEET_IS_SUBSEQUENT_PAIR";
public static final String EXTRA_HALF_SHEET_PAIRING_RESURFACE =
"com.android.nearby.halfsheet.EXTRA_HALF_SHEET_PAIRING_RESURFACE";
public static final String ACTION_HALF_SHEET_FOREGROUND_STATE =
"com.android.nearby.halfsheet.ACTION_HALF_SHEET_FOREGROUND_STATE";
// Intent extra contains the user gmail name eg. testaccount@gmail.com.
public static final String EXTRA_HALF_SHEET_ACCOUNT_NAME =
"com.android.nearby.halfsheet.HALF_SHEET_ACCOUNT_NAME";
public static final String EXTRA_HALF_SHEET_FOREGROUND =
"com.android.nearby.halfsheet.EXTRA_HALF_SHEET_FOREGROUND";
public static final String ARG_FRAGMENT_STATE = "ARG_FRAGMENT_STATE";
@Nullable
private HalfSheetModuleFragment mHalfSheetModuleFragment;
@Nullable
private Cache.ScanFastPairStoreItem mScanFastPairStoreItem;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
byte[] infoArray = getIntent().getByteArrayExtra(EXTRA_HALF_SHEET_INFO);
String fragmentType = getIntent().getStringExtra(EXTRA_HALF_SHEET_TYPE);
if (infoArray == null || fragmentType == null) {
Log.d(
"HalfSheetActivity",
"exit flag off or do not have enough half sheet information.");
finish();
return;
}
switch (fragmentType) {
case DEVICE_PAIRING_FRAGMENT_TYPE:
mHalfSheetModuleFragment = DevicePairingFragment.newInstance(getIntent(),
savedInstanceState);
if (mHalfSheetModuleFragment == null) {
Log.d(TAG, "device pairing fragment has error.");
finish();
return;
}
break;
case APP_LAUNCH_FRAGMENT_TYPE:
// currentFragment = AppLaunchFragment.newInstance(getIntent());
if (mHalfSheetModuleFragment == null) {
Log.v(TAG, "app launch fragment has error.");
finish();
return;
}
break;
default:
Log.w(TAG, "there is no valid type for half sheet");
finish();
return;
}
if (mHalfSheetModuleFragment != null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, mHalfSheetModuleFragment)
.commit();
}
setContentView(R.layout.fast_pair_half_sheet);
// If the user taps on the background, then close the activity.
// Unless they tap on the card itself, then ignore the tap.
findViewById(R.id.background).setOnClickListener(v -> onCancelClicked());
findViewById(R.id.card)
.setOnClickListener(
v -> Log.v(TAG, "card view is clicked noop"));
try {
mScanFastPairStoreItem =
Cache.ScanFastPairStoreItem.parseFrom(infoArray);
} catch (InvalidProtocolBufferException e) {
Log.w(
TAG, "error happens when pass info to half sheet");
}
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
if (mHalfSheetModuleFragment != null) {
mHalfSheetModuleFragment.onSaveInstanceState(savedInstanceState);
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
sendHalfSheetCancelBroadcast();
}
@Override
protected void onUserLeaveHint() {
super.onUserLeaveHint();
sendHalfSheetCancelBroadcast();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String fragmentType = getIntent().getStringExtra(EXTRA_HALF_SHEET_TYPE);
if (fragmentType == null) {
return;
}
if (fragmentType.equals(DEVICE_PAIRING_FRAGMENT_TYPE)
&& intent.getExtras() != null
&& intent.getByteArrayExtra(EXTRA_HALF_SHEET_INFO) != null) {
try {
Cache.ScanFastPairStoreItem testScanFastPairStoreItem =
Cache.ScanFastPairStoreItem.parseFrom(
intent.getByteArrayExtra(EXTRA_HALF_SHEET_INFO));
if (mScanFastPairStoreItem != null
&& !testScanFastPairStoreItem.getAddress().equals(
mScanFastPairStoreItem.getAddress())
&& testScanFastPairStoreItem.getModelId().equals(
mScanFastPairStoreItem.getModelId())) {
Log.d(TAG, "possible factory reset happens");
halfSheetStateChange();
}
} catch (InvalidProtocolBufferException | NullPointerException e) {
Log.w(TAG, "error happens when pass info to half sheet");
}
}
}
/** This function should be called when user click empty area and cancel button. */
public void onCancelClicked() {
Log.d(TAG, "Cancels the half sheet and paring.");
sendHalfSheetCancelBroadcast();
finish();
}
/** Changes the half sheet foreground state to false. */
public void halfSheetStateChange() {
BroadcastUtils.sendBroadcast(
this,
new Intent(ACTION_HALF_SHEET_FOREGROUND_STATE)
.putExtra(EXTRA_HALF_SHEET_FOREGROUND, false));
finish();
}
private void sendHalfSheetCancelBroadcast() {
BroadcastUtils.sendBroadcast(
this,
new Intent(ACTION_HALF_SHEET_FOREGROUND_STATE)
.putExtra(EXTRA_HALF_SHEET_FOREGROUND, false));
if (mScanFastPairStoreItem != null) {
BroadcastUtils.sendBroadcast(
this,
new Intent(ACTION_FAST_PAIR_HALF_SHEET_CANCEL)
.putExtra(EXTRA_MODEL_ID,
mScanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT))
.putExtra(EXTRA_HALF_SHEET_TYPE,
getIntent().getStringExtra(EXTRA_HALF_SHEET_TYPE))
.putExtra(
EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR,
getIntent().getBooleanExtra(EXTRA_HALF_SHEET_IS_SUBSEQUENT_PAIR,
false))
.putExtra(
EXTRA_HALF_SHEET_IS_RETROACTIVE,
getIntent().getBooleanExtra(EXTRA_HALF_SHEET_IS_RETROACTIVE,
false))
.putExtra(EXTRA_MAC_ADDRESS, mScanFastPairStoreItem.getAddress()));
}
}
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
TextView toolbarTitle = findViewById(R.id.toolbar_title);
toolbarTitle.setText(title);
}
}

View File

@@ -0,0 +1,486 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.nearby.halfsheet.fragment;
import static android.text.TextUtils.isEmpty;
import static com.android.nearby.halfsheet.HalfSheetActivity.ARG_FRAGMENT_STATE;
import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_DESCRIPTION;
import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_ACCOUNT_NAME;
import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_CONTENT;
import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_HALF_SHEET_ID;
import static com.android.nearby.halfsheet.HalfSheetActivity.EXTRA_TITLE;
import static com.android.nearby.halfsheet.HalfSheetActivity.TAG;
import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.FAILED;
import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.FOUND_DEVICE;
import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.NOT_STARTED;
import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRED_LAUNCHABLE;
import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRED_UNLAUNCHABLE;
import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.PAIRING;
import static com.android.server.nearby.fastpair.Constant.EXTRA_BINDER;
import static com.android.server.nearby.fastpair.Constant.EXTRA_BUNDLE;
import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.nearby.FastPairClient;
import android.nearby.FastPairDevice;
import android.nearby.FastPairStatusCallback;
import android.nearby.NearbyDevice;
import android.nearby.PairStatusMetadata;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.nearby.halfsheet.HalfSheetActivity;
import com.android.nearby.halfsheet.R;
import com.android.nearby.halfsheet.utils.FastPairUtils;
import com.android.nearby.halfsheet.utils.IconUtils;
import com.google.protobuf.InvalidProtocolBufferException;
import java.util.Objects;
import service.proto.Cache.ScanFastPairStoreItem;
/**
* Modularize half sheet for fast pair this fragment will show when half sheet does device pairing.
*
* <p>This fragment will handle initial pairing subsequent pairing and retroactive pairing.
*/
@SuppressWarnings("nullness")
public class DevicePairingFragment extends HalfSheetModuleFragment implements
FastPairStatusCallback {
private TextView mTitleView;
private TextView mSubTitleView;
private ImageView mImage;
private Button mConnectButton;
private Button mSetupButton;
private Button mCancelButton;
// Opens Bluetooth Settings.
private Button mSettingsButton;
private ImageView mInfoIconButton;
private ProgressBar mConnectProgressBar;
private Bundle mBundle;
private ScanFastPairStoreItem mScanFastPairStoreItem;
private FastPairClient mFastPairClient;
private @PairStatusMetadata.Status int mPairStatus = PairStatusMetadata.Status.UNKNOWN;
// True when there is a companion app to open.
private boolean mIsLaunchable;
private boolean mIsConnecting;
// Indicates that the setup button is clicked before.
private boolean mSetupButtonClicked = false;
// Holds the new text while we transition between the two.
private static final int TAG_PENDING_TEXT = R.id.toolbar_title;
public static final String APP_LAUNCH_FRAGMENT_TYPE = "APP_LAUNCH";
private static final String ARG_SETUP_BUTTON_CLICKED = "SETUP_BUTTON_CLICKED";
private static final String ARG_PAIRING_RESULT = "PAIRING_RESULT";
/**
* Create certain fragment according to the intent.
*/
@Nullable
public static HalfSheetModuleFragment newInstance(
Intent intent, @Nullable Bundle saveInstanceStates) {
Bundle args = new Bundle();
byte[] infoArray = intent.getByteArrayExtra(EXTRA_HALF_SHEET_INFO);
Bundle bundle = intent.getBundleExtra(EXTRA_BUNDLE);
String title = intent.getStringExtra(EXTRA_TITLE);
String description = intent.getStringExtra(EXTRA_DESCRIPTION);
String accountName = intent.getStringExtra(EXTRA_HALF_SHEET_ACCOUNT_NAME);
String result = intent.getStringExtra(EXTRA_HALF_SHEET_CONTENT);
int halfSheetId = intent.getIntExtra(EXTRA_HALF_SHEET_ID, 0);
args.putByteArray(EXTRA_HALF_SHEET_INFO, infoArray);
args.putString(EXTRA_HALF_SHEET_ACCOUNT_NAME, accountName);
args.putString(EXTRA_TITLE, title);
args.putString(EXTRA_DESCRIPTION, description);
args.putInt(EXTRA_HALF_SHEET_ID, halfSheetId);
args.putString(EXTRA_HALF_SHEET_CONTENT, result == null ? "" : result);
args.putBundle(EXTRA_BUNDLE, bundle);
if (saveInstanceStates != null) {
if (saveInstanceStates.containsKey(ARG_FRAGMENT_STATE)) {
args.putSerializable(
ARG_FRAGMENT_STATE, saveInstanceStates.getSerializable(ARG_FRAGMENT_STATE));
}
if (saveInstanceStates.containsKey(BluetoothDevice.EXTRA_DEVICE)) {
args.putParcelable(
BluetoothDevice.EXTRA_DEVICE,
saveInstanceStates.getParcelable(BluetoothDevice.EXTRA_DEVICE));
}
if (saveInstanceStates.containsKey(BluetoothDevice.EXTRA_PAIRING_KEY)) {
args.putInt(
BluetoothDevice.EXTRA_PAIRING_KEY,
saveInstanceStates.getInt(BluetoothDevice.EXTRA_PAIRING_KEY));
}
if (saveInstanceStates.containsKey(ARG_SETUP_BUTTON_CLICKED)) {
args.putBoolean(
ARG_SETUP_BUTTON_CLICKED,
saveInstanceStates.getBoolean(ARG_SETUP_BUTTON_CLICKED));
}
if (saveInstanceStates.containsKey(ARG_PAIRING_RESULT)) {
args.putBoolean(ARG_PAIRING_RESULT,
saveInstanceStates.getBoolean(ARG_PAIRING_RESULT));
}
}
DevicePairingFragment fragment = new DevicePairingFragment();
fragment.setArguments(args);
return fragment;
}
@Nullable
@Override
public View onCreateView(
LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
/* attachToRoot= */
View rootView = inflater.inflate(
R.layout.fast_pair_device_pairing_fragment, container, /* attachToRoot= */
false);
if (getContext() == null) {
Log.d(TAG, "can't find the attached activity");
return rootView;
}
Bundle args = getArguments();
byte[] storeFastPairItemBytesArray = args.getByteArray(EXTRA_HALF_SHEET_INFO);
mBundle = args.getBundle(EXTRA_BUNDLE);
if (mBundle != null) {
mFastPairClient = new FastPairClient(getContext(), mBundle.getBinder(EXTRA_BINDER));
mFastPairClient.registerHalfSheet(this);
}
if (args.containsKey(ARG_FRAGMENT_STATE)) {
mFragmentState = (HalfSheetFragmentState) args.getSerializable(ARG_FRAGMENT_STATE);
}
if (args.containsKey(ARG_SETUP_BUTTON_CLICKED)) {
mSetupButtonClicked = args.getBoolean(ARG_SETUP_BUTTON_CLICKED);
}
if (args.containsKey(ARG_PAIRING_RESULT)) {
mPairStatus = args.getInt(ARG_PAIRING_RESULT);
}
// Initiate views.
mTitleView = Objects.requireNonNull(getActivity()).findViewById(R.id.toolbar_title);
mSubTitleView = rootView.findViewById(R.id.header_subtitle);
mImage = rootView.findViewById(R.id.pairing_pic);
mConnectProgressBar = rootView.findViewById(R.id.connect_progressbar);
mConnectButton = rootView.findViewById(R.id.connect_btn);
mCancelButton = rootView.findViewById(R.id.cancel_btn);
mSettingsButton = rootView.findViewById(R.id.settings_btn);
mSetupButton = rootView.findViewById(R.id.setup_btn);
mInfoIconButton = rootView.findViewById(R.id.info_icon);
mInfoIconButton.setImageResource(R.drawable.fast_pair_ic_info);
try {
setScanFastPairStoreItem(ScanFastPairStoreItem.parseFrom(storeFastPairItemBytesArray));
} catch (InvalidProtocolBufferException e) {
Log.w(TAG,
"DevicePairingFragment: error happens when pass info to half sheet");
return rootView;
}
// Config for landscape mode
DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
rootView.getLayoutParams().height = displayMetrics.heightPixels * 4 / 5;
rootView.getLayoutParams().width = displayMetrics.heightPixels * 4 / 5;
mImage.getLayoutParams().height = displayMetrics.heightPixels / 2;
mImage.getLayoutParams().width = displayMetrics.heightPixels / 2;
mConnectProgressBar.getLayoutParams().width = displayMetrics.heightPixels / 2;
mConnectButton.getLayoutParams().width = displayMetrics.heightPixels / 2;
//TODO(b/213373051): Add cancel button
}
Bitmap icon = IconUtils.getIcon(mScanFastPairStoreItem.getIconPng().toByteArray(),
mScanFastPairStoreItem.getIconPng().size());
if (icon != null) {
mImage.setImageBitmap(icon);
}
mConnectButton.setOnClickListener(v -> onConnectClick());
mCancelButton.setOnClickListener(v ->
((HalfSheetActivity) getActivity()).onCancelClicked());
mSettingsButton.setOnClickListener(v -> onSettingsClicked());
mSetupButton.setOnClickListener(v -> onSetupClick());
return rootView;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get access to the activity's menu
setHasOptionsMenu(true);
}
@Override
public void onStart() {
super.onStart();
Log.v(TAG, "onStart: invalidate states");
invalidateState();
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putSerializable(ARG_FRAGMENT_STATE, mFragmentState);
savedInstanceState.putBoolean(ARG_SETUP_BUTTON_CLICKED, mSetupButtonClicked);
savedInstanceState.putInt(ARG_PAIRING_RESULT, mPairStatus);
}
private void onSettingsClicked() {
startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
}
private void onSetupClick() {
String companionApp =
FastPairUtils.getCompanionAppFromActionUrl(mScanFastPairStoreItem.getActionUrl());
Intent intent =
FastPairUtils.createCompanionAppIntent(
Objects.requireNonNull(getContext()),
companionApp,
mScanFastPairStoreItem.getAddress());
mSetupButtonClicked = true;
if (mFragmentState == PAIRED_LAUNCHABLE) {
if (intent != null) {
startActivity(intent);
}
} else {
Log.d(TAG, "onSetupClick: State is " + mFragmentState);
}
}
private void onConnectClick() {
if (mScanFastPairStoreItem == null) {
Log.w(TAG, "No pairing related information in half sheet");
return;
}
if (getFragmentState() == PAIRING) {
return;
}
mIsConnecting = true;
invalidateState();
mFastPairClient.connect(
new FastPairDevice.Builder()
.addMedium(NearbyDevice.Medium.BLE)
.setBluetoothAddress(mScanFastPairStoreItem.getAddress())
.setData(FastPairUtils.convertFrom(mScanFastPairStoreItem)
.toByteArray())
.build());
}
// Receives callback from service.
@Override
public void onPairUpdate(FastPairDevice fastPairDevice, PairStatusMetadata pairStatusMetadata) {
@PairStatusMetadata.Status int status = pairStatusMetadata.getStatus();
if (status == PairStatusMetadata.Status.DISMISS && getActivity() != null) {
getActivity().finish();
}
mIsConnecting = false;
mPairStatus = status;
invalidateState();
}
@Override
public void invalidateState() {
HalfSheetFragmentState newState = NOT_STARTED;
if (mIsConnecting) {
newState = PAIRING;
} else {
switch (mPairStatus) {
case PairStatusMetadata.Status.SUCCESS:
newState = mIsLaunchable ? PAIRED_LAUNCHABLE : PAIRED_UNLAUNCHABLE;
break;
case PairStatusMetadata.Status.FAIL:
newState = FAILED;
break;
default:
if (mScanFastPairStoreItem != null) {
newState = FOUND_DEVICE;
}
}
}
if (newState == mFragmentState) {
return;
}
setState(newState);
}
@Override
public void setState(HalfSheetFragmentState state) {
super.setState(state);
invalidateTitles();
invalidateButtons();
}
private void setScanFastPairStoreItem(ScanFastPairStoreItem item) {
mScanFastPairStoreItem = item;
invalidateLaunchable();
}
private void invalidateLaunchable() {
String companionApp =
FastPairUtils.getCompanionAppFromActionUrl(mScanFastPairStoreItem.getActionUrl());
if (isEmpty(companionApp)) {
mIsLaunchable = false;
return;
}
mIsLaunchable =
FastPairUtils.isLaunchable(Objects.requireNonNull(getContext()), companionApp);
}
private void invalidateButtons() {
mConnectProgressBar.setVisibility(View.INVISIBLE);
mConnectButton.setVisibility(View.INVISIBLE);
mCancelButton.setVisibility(View.INVISIBLE);
mSetupButton.setVisibility(View.INVISIBLE);
mSettingsButton.setVisibility(View.INVISIBLE);
mInfoIconButton.setVisibility(View.INVISIBLE);
switch (mFragmentState) {
case FOUND_DEVICE:
mInfoIconButton.setVisibility(View.VISIBLE);
mConnectButton.setVisibility(View.VISIBLE);
break;
case PAIRING:
mConnectProgressBar.setVisibility(View.VISIBLE);
mCancelButton.setVisibility(View.VISIBLE);
setBackgroundClickable(false);
break;
case PAIRED_LAUNCHABLE:
mCancelButton.setVisibility(View.VISIBLE);
mSetupButton.setVisibility(View.VISIBLE);
setBackgroundClickable(true);
break;
case FAILED:
mSettingsButton.setVisibility(View.VISIBLE);
setBackgroundClickable(true);
break;
case NOT_STARTED:
case PAIRED_UNLAUNCHABLE:
default:
mCancelButton.setVisibility(View.VISIBLE);
setBackgroundClickable(true);
}
}
private void setBackgroundClickable(boolean isClickable) {
HalfSheetActivity activity = (HalfSheetActivity) getActivity();
if (activity == null) {
Log.w(TAG, "setBackgroundClickable: failed to set clickable to " + isClickable
+ " because cannot get HalfSheetActivity.");
return;
}
View background = activity.findViewById(R.id.background);
if (background == null) {
Log.w(TAG, "setBackgroundClickable: failed to set clickable to " + isClickable
+ " cannot find background at HalfSheetActivity.");
return;
}
Log.d(TAG, "setBackgroundClickable to " + isClickable);
background.setClickable(isClickable);
}
private void invalidateTitles() {
String newTitle = getTitle();
invalidateTextView(mTitleView, newTitle);
String newSubTitle = getSubTitle();
invalidateTextView(mSubTitleView, newSubTitle);
}
private void invalidateTextView(TextView textView, String newText) {
CharSequence oldText =
textView.getTag(TAG_PENDING_TEXT) != null
? (CharSequence) textView.getTag(TAG_PENDING_TEXT)
: textView.getText();
if (TextUtils.equals(oldText, newText)) {
return;
}
if (TextUtils.isEmpty(oldText)) {
// First time run. Don't animate since there's nothing to animate from.
textView.setText(newText);
} else {
textView.setTag(TAG_PENDING_TEXT, newText);
textView
.animate()
.alpha(0f)
.setDuration(TEXT_ANIMATION_DURATION_MILLISECONDS)
.withEndAction(
() -> {
textView.setText(newText);
textView
.animate()
.alpha(1f)
.setDuration(TEXT_ANIMATION_DURATION_MILLISECONDS);
});
}
}
private String getTitle() {
switch (mFragmentState) {
case PAIRED_LAUNCHABLE:
return getString(R.string.fast_pair_title_setup);
case FAILED:
return getString(R.string.fast_pair_title_fail);
case FOUND_DEVICE:
case NOT_STARTED:
case PAIRED_UNLAUNCHABLE:
default:
return mScanFastPairStoreItem.getDeviceName();
}
}
private String getSubTitle() {
switch (mFragmentState) {
case PAIRED_LAUNCHABLE:
return String.format(
mScanFastPairStoreItem
.getFastPairStrings()
.getPairingFinishedCompanionAppInstalled(),
mScanFastPairStoreItem.getDeviceName());
case FAILED:
return mScanFastPairStoreItem.getFastPairStrings().getPairingFailDescription();
case PAIRED_UNLAUNCHABLE:
getString(R.string.fast_pair_device_ready);
// fall through
case FOUND_DEVICE:
case NOT_STARTED:
return mScanFastPairStoreItem.getFastPairStrings().getInitialPairingDescription();
default:
return "";
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.nearby.halfsheet.fragment;
import static com.android.nearby.halfsheet.HalfSheetActivity.TAG;
import static com.android.nearby.halfsheet.fragment.HalfSheetModuleFragment.HalfSheetFragmentState.NOT_STARTED;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
/** Base class for all of the half sheet fragment. */
public abstract class HalfSheetModuleFragment extends Fragment {
static final int TEXT_ANIMATION_DURATION_MILLISECONDS = 200;
HalfSheetFragmentState mFragmentState = NOT_STARTED;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onDestroy() {
super.onDestroy();
}
/** UI states of the half-sheet fragment. */
public enum HalfSheetFragmentState {
NOT_STARTED, // Initial status
FOUND_DEVICE, // When a device is found found from Nearby scan service
PAIRING, // When user taps 'Connect' and Fast Pair stars pairing process
PAIRED_LAUNCHABLE, // When pair successfully
// and we found a launchable companion app installed
PAIRED_UNLAUNCHABLE, // When pair successfully
// but we cannot find a companion app to launch it
FAILED, // When paring was failed
FINISHED // When the activity is about to end finished.
}
/**
* Returns the {@link HalfSheetFragmentState} to the parent activity.
*
* <p>Overrides this method if the fragment's state needs to be preserved in the parent
* activity.
*/
public HalfSheetFragmentState getFragmentState() {
return mFragmentState;
}
void setState(HalfSheetFragmentState state) {
Log.v(TAG, "Settings state from " + mFragmentState + " to " + state);
mFragmentState = state;
}
/**
* Populate data to UI widgets according to the latest {@link HalfSheetFragmentState}.
*/
abstract void invalidateState();
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.nearby.halfsheet.utils;
import android.content.Context;
import android.content.Intent;
/**
* Broadcast util class
*/
public class BroadcastUtils {
/**
* Helps send broadcast.
*/
public static void sendBroadcast(Context context, Intent intent) {
context.sendBroadcast(intent);
}
private BroadcastUtils() {
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.nearby.halfsheet.utils;
import static com.android.server.nearby.common.fastpair.service.UserActionHandlerBase.EXTRA_COMPANION_APP;
import static com.android.server.nearby.fastpair.UserActionHandler.ACTION_FAST_PAIR;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.net.URISyntaxException;
import service.proto.Cache;
/**
* Util class in half sheet apk
*/
public class FastPairUtils {
/** FastPair util method check certain app is install on the device or not. */
public static boolean isAppInstalled(Context context, String packageName) {
try {
context.getPackageManager().getPackageInfo(packageName, 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
/** FastPair util method to properly format the action url extra. */
@Nullable
public static String getCompanionAppFromActionUrl(String actionUrl) {
try {
Intent intent = Intent.parseUri(actionUrl, Intent.URI_INTENT_SCHEME);
if (!intent.getAction().equals(ACTION_FAST_PAIR)) {
Log.e("FastPairUtils", "Companion app launch attempted from malformed action url");
return null;
}
return intent.getStringExtra(EXTRA_COMPANION_APP);
} catch (URISyntaxException e) {
Log.e("FastPairUtils", "FastPair: fail to get companion app info from discovery item");
return null;
}
}
/**
* Converts {@link service.proto.Cache.StoredDiscoveryItem} from
* {@link service.proto.Cache.ScanFastPairStoreItem}
*/
public static Cache.StoredDiscoveryItem convertFrom(Cache.ScanFastPairStoreItem item) {
return convertFrom(item, /* isSubsequentPair= */ false);
}
/**
* Converts a {@link service.proto.Cache.ScanFastPairStoreItem}
* to a {@link service.proto.Cache.StoredDiscoveryItem}.
*
* <p>This is needed to make the new Fast Pair scanning stack compatible with the rest of the
* legacy Fast Pair code.
*/
public static Cache.StoredDiscoveryItem convertFrom(
Cache.ScanFastPairStoreItem item, boolean isSubsequentPair) {
return Cache.StoredDiscoveryItem.newBuilder()
.setId(item.getModelId())
.setFirstObservationTimestampMillis(item.getFirstObservationTimestampMillis())
.setLastObservationTimestampMillis(item.getLastObservationTimestampMillis())
.setType(Cache.NearbyType.NEARBY_DEVICE)
.setActionUrl(item.getActionUrl())
.setActionUrlType(Cache.ResolvedUrlType.APP)
.setTitle(
isSubsequentPair
? item.getFastPairStrings().getTapToPairWithoutAccount()
: item.getDeviceName())
.setMacAddress(item.getAddress())
.setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED)
.setTriggerId(item.getModelId())
.setIconPng(item.getIconPng())
.setIconFifeUrl(item.getIconFifeUrl())
.setDescription(
isSubsequentPair
? item.getDeviceName()
: item.getFastPairStrings().getTapToPairWithoutAccount())
.setAuthenticationPublicKeySecp256R1(item.getAntiSpoofingPublicKey())
.setCompanionDetail(item.getCompanionDetail())
.setFastPairStrings(item.getFastPairStrings())
.setFastPairInformation(
Cache.FastPairInformation.newBuilder()
.setDataOnlyConnection(item.getDataOnlyConnection())
.setTrueWirelessImages(item.getTrueWirelessImages())
.setAssistantSupported(item.getAssistantSupported())
.setCompanyName(item.getCompanyName()))
.build();
}
/**
* Returns true the application is installed and can be opened on device.
*/
public static boolean isLaunchable(@NonNull Context context, String companionApp) {
return isAppInstalled(context, companionApp)
&& createCompanionAppIntent(context, companionApp, null) != null;
}
/**
* Returns an intent to launch given the package name and bluetooth address (if provided).
* Returns null if no such an intent can be found.
*/
@Nullable
public static Intent createCompanionAppIntent(@NonNull Context context, String packageName,
@Nullable String address) {
Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
if (intent == null) {
return null;
}
if (address != null) {
BluetoothAdapter adapter = getBluetoothAdapter(context);
if (adapter != null) {
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, adapter.getRemoteDevice(address));
}
}
return intent;
}
@Nullable
private static BluetoothAdapter getBluetoothAdapter(@NonNull Context context) {
BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class);
return bluetoothManager == null ? null : bluetoothManager.getAdapter();
}
private FastPairUtils() {}
}

View File

@@ -0,0 +1,133 @@
/*
* Copyright (C) 2022 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.nearby.halfsheet.utils;
import static com.android.nearby.halfsheet.HalfSheetActivity.TAG;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.graphics.ColorUtils;
/**
* Utility class for icon size verification.
*/
public class IconUtils {
private static final float NOTIFICATION_BACKGROUND_PADDING_PERCENT = 0.125f;
private static final float NOTIFICATION_BACKGROUND_ALPHA = 0.7f;
private static final int MIN_ICON_SIZE = 16;
private static final int DESIRED_ICON_SIZE = 32;
/**
* Verify that the icon is non null and falls in the small bucket. Just because an icon isn't
* small doesn't guarantee it is large or exists.
*/
public static boolean isIconSizedSmall(@Nullable Bitmap bitmap) {
if (bitmap == null) {
return false;
}
return bitmap.getWidth() >= MIN_ICON_SIZE
&& bitmap.getWidth() < DESIRED_ICON_SIZE
&& bitmap.getHeight() >= MIN_ICON_SIZE
&& bitmap.getHeight() < DESIRED_ICON_SIZE;
}
/**
* Verify that the icon is non null and falls in the regular / default size bucket. Doesn't
* guarantee if not regular then it is small.
*/
static boolean isIconSizedRegular(@Nullable Bitmap bitmap) {
if (bitmap == null) {
return false;
}
return bitmap.getWidth() >= DESIRED_ICON_SIZE && bitmap.getHeight() >= DESIRED_ICON_SIZE;
}
/**
* All icons that are sized correctly (larger than the MIN_ICON_SIZE icon size)
* are resize on the server to the DESIRED_ICON_SIZE icon size so that
* they appear correct.
*/
public static boolean isIconSizeCorrect(@Nullable Bitmap bitmap) {
if (bitmap == null) {
return false;
}
return isIconSizedSmall(bitmap) || isIconSizedRegular(bitmap);
}
/**
* Returns the bitmap from the byte array. Returns null if cannot decode or not in correct size.
*/
@Nullable
public static Bitmap getIcon(byte[] imageData, int size) {
try {
Bitmap icon =
BitmapFactory.decodeByteArray(imageData, /* offset= */ 0, size);
if (IconUtils.isIconSizeCorrect(icon)) {
// Do not add background for Half Sheet.
return IconUtils.addWhiteCircleBackground(icon);
}
} catch (OutOfMemoryError e) {
Log.w(TAG, "getIcon: Failed to decode icon, returning null.", e);
}
return null;
}
/** Adds a circular, white background to the bitmap. */
@Nullable
public static Bitmap addWhiteCircleBackground(Bitmap bitmap) {
if (bitmap == null) {
Log.w(TAG, "addWhiteCircleBackground: Bitmap is null, not adding background.");
return null;
}
if (bitmap.getWidth() != bitmap.getHeight()) {
Log.w(TAG, "addWhiteCircleBackground: Bitmap dimensions not square. Skipping"
+ "adding background.");
return bitmap;
}
int padding = (int) (bitmap.getWidth() * NOTIFICATION_BACKGROUND_PADDING_PERCENT);
Bitmap bitmapWithBackground =
Bitmap.createBitmap(
bitmap.getWidth() + (2 * padding),
bitmap.getHeight() + (2 * padding),
bitmap.getConfig());
Canvas canvas = new Canvas(bitmapWithBackground);
Paint paint = new Paint();
paint.setColor(
ColorUtils.setAlphaComponent(
Color.WHITE, (int) (255 * NOTIFICATION_BACKGROUND_ALPHA)));
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
canvas.drawCircle(
bitmapWithBackground.getWidth() / 2,
bitmapWithBackground.getHeight() / 2,
bitmapWithBackground.getWidth() / 2,
paint);
canvas.drawBitmap(bitmap, padding, padding, null);
return bitmapWithBackground;
}
}

98
nearby/service/Android.bp Normal file
View File

@@ -0,0 +1,98 @@
// Copyright (C) 2021 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
filegroup {
name: "nearby-service-srcs",
srcs: [
"java/**/*.java",
":statslog-nearby-java-gen",
],
}
filegroup {
name: "nearby-service-string-res",
srcs: [
"java/**/Constant.java",
"java/**/UserActionHandlerBase.java",
"java/**/UserActionHandler.java",
"java/**/FastPairConstants.java",
],
}
java_library {
name: "nearby-service-string",
srcs: [":nearby-service-string-res"],
libs: ["framework-bluetooth"],
sdk_version: "module_current",
}
// Main lib for nearby services.
java_library {
name: "service-nearby-pre-jarjar",
srcs: [":nearby-service-srcs"],
defaults: [
"framework-system-server-module-defaults"
],
libs: [
"framework-bluetooth.stubs.module_lib", // TODO(b/215722418): Change to framework-bluetooth once fixed
"error_prone_annotations",
"framework-connectivity-t.impl",
"framework-statsd.stubs.module_lib",
],
static_libs: [
"androidx.annotation_annotation",
"androidx.core_core",
"androidx.localbroadcastmanager_localbroadcastmanager",
"guava",
"libprotobuf-java-lite",
"fast-pair-lite-protos",
"modules-utils-build",
"modules-utils-handlerexecutor",
"modules-utils-preconditions",
"modules-utils-backgroundthread",
"presence-lite-protos",
],
sdk_version: "system_server_current",
// This is included in service-connectivity which is 30+
// TODO: allow APEXes to have service jars with higher min_sdk than the APEX
// (service-connectivity is only used on 31+) and use 31 here
min_sdk_version: "30",
installable: true,
dex_preopt: {
enabled: false,
app_image: false,
},
visibility: [
"//packages/modules/Nearby/apex",
],
apex_available: [
"com.android.tethering",
],
}
genrule {
name: "statslog-nearby-java-gen",
tools: ["stats-log-api-gen"],
cmd: "$(location stats-log-api-gen) --java $(out) --module nearby " +
" --javaPackage com.android.server.nearby.proto --javaClass NearbyStatsLog" +
" --minApiLevel 33",
out: ["com/android/server/nearby/proto/NearbyStatsLog.java"],
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2022 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.server.nearby;
import android.provider.DeviceConfig;
import androidx.annotation.VisibleForTesting;
/**
* A utility class for encapsulating Nearby feature flag configurations.
*/
public class NearbyConfiguration {
/**
* Flag use to enable presence legacy broadcast.
*/
public static final String NEARBY_ENABLE_PRESENCE_BROADCAST_LEGACY =
"nearby_enable_presence_broadcast_legacy";
private boolean mEnablePresenceBroadcastLegacy;
public NearbyConfiguration() {
mEnablePresenceBroadcastLegacy = getDeviceConfigBoolean(
NEARBY_ENABLE_PRESENCE_BROADCAST_LEGACY, false /* defaultValue */);
}
/**
* Returns whether broadcasting legacy presence spec is enabled.
*/
public boolean isPresenceBroadcastLegacyEnabled() {
return mEnablePresenceBroadcastLegacy;
}
private boolean getDeviceConfigBoolean(final String name, final boolean defaultValue) {
final String value = getDeviceConfigProperty(name);
return value != null ? Boolean.parseBoolean(value) : defaultValue;
}
@VisibleForTesting
protected String getDeviceConfigProperty(String name) {
return DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TETHERING, name);
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.nearby;
import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
import static com.android.server.SystemService.PHASE_THIRD_PARTY_APPS_CAN_START;
import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.location.ContextHubManager;
import android.nearby.BroadcastRequestParcelable;
import android.nearby.IBroadcastListener;
import android.nearby.INearbyManager;
import android.nearby.IScanListener;
import android.nearby.NearbyManager;
import android.nearby.ScanRequest;
import android.util.Log;
import com.android.server.nearby.common.locator.LocatorContextWrapper;
import com.android.server.nearby.fastpair.FastPairManager;
import com.android.server.nearby.injector.ContextHubManagerAdapter;
import com.android.server.nearby.injector.Injector;
import com.android.server.nearby.presence.ChreCommunication;
import com.android.server.nearby.presence.PresenceManager;
import com.android.server.nearby.provider.BroadcastProviderManager;
import com.android.server.nearby.provider.DiscoveryProviderManager;
import com.android.server.nearby.provider.FastPairDataProvider;
import java.util.concurrent.Executors;
import service.proto.Blefilter;
/** Service implementing nearby functionality. */
public class NearbyService extends INearbyManager.Stub {
public static final String TAG = "NearbyService";
private final Context mContext;
private final SystemInjector mSystemInjector;
private final FastPairManager mFastPairManager;
private final PresenceManager mPresenceManager;
private final BroadcastReceiver mBluetoothReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int state =
intent.getIntExtra(
BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
if (state == BluetoothAdapter.STATE_ON) {
if (mSystemInjector != null) {
// Have to do this logic in listener. Even during PHASE_BOOT_COMPLETED
// phase, BluetoothAdapter is not null, the BleScanner is null.
Log.v(TAG, "Initiating BluetoothAdapter when Bluetooth is turned on.");
mSystemInjector.initializeBluetoothAdapter();
}
}
}
};
private DiscoveryProviderManager mProviderManager;
private BroadcastProviderManager mBroadcastProviderManager;
public NearbyService(Context context) {
mContext = context;
mSystemInjector = new SystemInjector(context);
mProviderManager = new DiscoveryProviderManager(context, mSystemInjector);
mBroadcastProviderManager = new BroadcastProviderManager(context, mSystemInjector);
final LocatorContextWrapper lcw = new LocatorContextWrapper(context, null);
mFastPairManager = new FastPairManager(lcw);
mPresenceManager =
new PresenceManager(
mContext,
(results) -> {
// TODO(b/221082271): hooked with API codes.
for (Blefilter.BleFilterResult result : results.getResultList()) {
Log.i(
TAG,
String.format(
"received filter result with id: %d",
result.getId()));
}
});
}
@Override
@NearbyManager.ScanStatus
public int registerScanListener(ScanRequest scanRequest, IScanListener listener) {
if (mProviderManager.registerScanListener(scanRequest, listener)) {
return NearbyManager.ScanStatus.SUCCESS;
}
return NearbyManager.ScanStatus.ERROR;
}
@Override
public void unregisterScanListener(IScanListener listener) {
mProviderManager.unregisterScanListener(listener);
}
@Override
public void startBroadcast(BroadcastRequestParcelable broadcastRequestParcelable,
IBroadcastListener listener) {
mBroadcastProviderManager.startBroadcast(
broadcastRequestParcelable.getBroadcastRequest(),
listener);
}
@Override
public void stopBroadcast(IBroadcastListener listener) {
mBroadcastProviderManager.stopBroadcast(listener);
}
/**
* Called by the service initializer.
*
* <p>{@see com.android.server.SystemService#onBootPhase}.
*/
public void onBootPhase(int phase) {
switch (phase) {
case PHASE_THIRD_PARTY_APPS_CAN_START:
// Ensures that a fast pair data provider exists which will work in direct boot.
FastPairDataProvider.init(mContext);
break;
case PHASE_BOOT_COMPLETED:
// The nearby service must be functioning after this boot phase.
mSystemInjector.initializeBluetoothAdapter();
mContext.registerReceiver(
mBluetoothReceiver,
new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
mFastPairManager.initiate();
mSystemInjector.initializeContextHubManagerAdapter();
mPresenceManager.initiate(
new ChreCommunication(
mSystemInjector, Executors.newSingleThreadExecutor()));
break;
}
}
private static final class SystemInjector implements Injector {
private final Context mContext;
@Nullable private BluetoothAdapter mBluetoothAdapter;
@Nullable private ContextHubManagerAdapter mContextHubManagerAdapter;
SystemInjector(Context context) {
mContext = context;
}
@Override
@Nullable
public BluetoothAdapter getBluetoothAdapter() {
return mBluetoothAdapter;
}
@Override
public ContextHubManagerAdapter getContextHubManagerAdapter() {
return mContextHubManagerAdapter;
}
synchronized void initializeBluetoothAdapter() {
if (mBluetoothAdapter != null) {
return;
}
BluetoothManager manager = mContext.getSystemService(BluetoothManager.class);
if (manager == null) {
return;
}
mBluetoothAdapter = manager.getAdapter();
}
synchronized void initializeContextHubManagerAdapter() {
if (mContextHubManagerAdapter != null) {
return;
}
ContextHubManager manager = mContext.getSystemService(ContextHubManager.class);
if (manager == null) {
return;
}
mContextHubManagerAdapter = new ContextHubManagerAdapter(manager);
}
}
}

View File

@@ -0,0 +1,746 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.nearby.common.ble;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.ScanFilter;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/**
* Criteria for filtering BLE devices. A {@link BleFilter} allows clients to restrict BLE devices to
* only those that are of interest to them.
*
*
* <p>Current filtering on the following fields are supported:
* <li>Service UUIDs which identify the bluetooth gatt services running on the device.
* <li>Name of remote Bluetooth LE device.
* <li>Mac address of the remote device.
* <li>Service data which is the data associated with a service.
* <li>Manufacturer specific data which is the data associated with a particular manufacturer.
*
* @see BleSighting
*/
public final class BleFilter implements Parcelable {
@Nullable
private String mDeviceName;
@Nullable
private String mDeviceAddress;
@Nullable
private ParcelUuid mServiceUuid;
@Nullable
private ParcelUuid mServiceUuidMask;
@Nullable
private ParcelUuid mServiceDataUuid;
@Nullable
private byte[] mServiceData;
@Nullable
private byte[] mServiceDataMask;
private int mManufacturerId;
@Nullable
private byte[] mManufacturerData;
@Nullable
private byte[] mManufacturerDataMask;
@Override
public int describeContents() {
return 0;
}
BleFilter() {
}
BleFilter(
@Nullable String deviceName,
@Nullable String deviceAddress,
@Nullable ParcelUuid serviceUuid,
@Nullable ParcelUuid serviceUuidMask,
@Nullable ParcelUuid serviceDataUuid,
@Nullable byte[] serviceData,
@Nullable byte[] serviceDataMask,
int manufacturerId,
@Nullable byte[] manufacturerData,
@Nullable byte[] manufacturerDataMask) {
this.mDeviceName = deviceName;
this.mDeviceAddress = deviceAddress;
this.mServiceUuid = serviceUuid;
this.mServiceUuidMask = serviceUuidMask;
this.mServiceDataUuid = serviceDataUuid;
this.mServiceData = serviceData;
this.mServiceDataMask = serviceDataMask;
this.mManufacturerId = manufacturerId;
this.mManufacturerData = manufacturerData;
this.mManufacturerDataMask = manufacturerDataMask;
}
public static final Parcelable.Creator<BleFilter> CREATOR = new Creator<BleFilter>() {
@Override
public BleFilter createFromParcel(Parcel source) {
BleFilter nBleFilter = new BleFilter();
nBleFilter.mDeviceName = source.readString();
nBleFilter.mDeviceAddress = source.readString();
nBleFilter.mManufacturerId = source.readInt();
nBleFilter.mManufacturerData = source.marshall();
nBleFilter.mManufacturerDataMask = source.marshall();
nBleFilter.mServiceDataUuid = source.readParcelable(null);
nBleFilter.mServiceData = source.marshall();
nBleFilter.mServiceDataMask = source.marshall();
nBleFilter.mServiceUuid = source.readParcelable(null);
nBleFilter.mServiceUuidMask = source.readParcelable(null);
return nBleFilter;
}
@Override
public BleFilter[] newArray(int size) {
return new BleFilter[size];
}
};
/** Returns the filter set on the device name field of Bluetooth advertisement data. */
@Nullable
public String getDeviceName() {
return mDeviceName;
}
/** Returns the filter set on the service uuid. */
@Nullable
public ParcelUuid getServiceUuid() {
return mServiceUuid;
}
/** Returns the mask for the service uuid. */
@Nullable
public ParcelUuid getServiceUuidMask() {
return mServiceUuidMask;
}
/** Returns the filter set on the device address. */
@Nullable
public String getDeviceAddress() {
return mDeviceAddress;
}
/** Returns the filter set on the service data. */
@Nullable
public byte[] getServiceData() {
return mServiceData;
}
/** Returns the mask for the service data. */
@Nullable
public byte[] getServiceDataMask() {
return mServiceDataMask;
}
/** Returns the filter set on the service data uuid. */
@Nullable
public ParcelUuid getServiceDataUuid() {
return mServiceDataUuid;
}
/** Returns the manufacturer id. -1 if the manufacturer filter is not set. */
public int getManufacturerId() {
return mManufacturerId;
}
/** Returns the filter set on the manufacturer data. */
@Nullable
public byte[] getManufacturerData() {
return mManufacturerData;
}
/** Returns the mask for the manufacturer data. */
@Nullable
public byte[] getManufacturerDataMask() {
return mManufacturerDataMask;
}
/**
* Check if the filter matches a {@code BleSighting}. A BLE sighting is considered as a match if
* it matches all the field filters.
*/
public boolean matches(@Nullable BleSighting bleSighting) {
if (bleSighting == null) {
return false;
}
BluetoothDevice device = bleSighting.getDevice();
// Device match.
if (mDeviceAddress != null && (device == null || !mDeviceAddress.equals(
device.getAddress()))) {
return false;
}
BleRecord bleRecord = bleSighting.getBleRecord();
// Scan record is null but there exist filters on it.
if (bleRecord == null
&& (mDeviceName != null
|| mServiceUuid != null
|| mManufacturerData != null
|| mServiceData != null)) {
return false;
}
// Local name match.
if (mDeviceName != null && !mDeviceName.equals(bleRecord.getDeviceName())) {
return false;
}
// UUID match.
if (mServiceUuid != null
&& !matchesServiceUuids(mServiceUuid, mServiceUuidMask,
bleRecord.getServiceUuids())) {
return false;
}
// Service data match
if (mServiceDataUuid != null
&& !matchesPartialData(
mServiceData, mServiceDataMask, bleRecord.getServiceData(mServiceDataUuid))) {
return false;
}
// Manufacturer data match.
if (mManufacturerId >= 0
&& !matchesPartialData(
mManufacturerData,
mManufacturerDataMask,
bleRecord.getManufacturerSpecificData(mManufacturerId))) {
return false;
}
// All filters match.
return true;
}
/**
* Determines if the characteristics of this filter are a superset of the characteristics of the
* given filter.
*/
public boolean isSuperset(@Nullable BleFilter bleFilter) {
if (bleFilter == null) {
return false;
}
if (equals(bleFilter)) {
return true;
}
// Verify device address matches.
if (mDeviceAddress != null && !mDeviceAddress.equals(bleFilter.getDeviceAddress())) {
return false;
}
// Verify device name matches.
if (mDeviceName != null && !mDeviceName.equals(bleFilter.getDeviceName())) {
return false;
}
// Verify UUID is a superset.
if (mServiceUuid != null
&& !serviceUuidIsSuperset(
mServiceUuid,
mServiceUuidMask,
bleFilter.getServiceUuid(),
bleFilter.getServiceUuidMask())) {
return false;
}
// Verify service data is a superset.
if (mServiceDataUuid != null
&& (!mServiceDataUuid.equals(bleFilter.getServiceDataUuid())
|| !partialDataIsSuperset(
mServiceData,
mServiceDataMask,
bleFilter.getServiceData(),
bleFilter.getServiceDataMask()))) {
return false;
}
// Verify manufacturer data is a superset.
if (mManufacturerId >= 0
&& (mManufacturerId != bleFilter.getManufacturerId()
|| !partialDataIsSuperset(
mManufacturerData,
mManufacturerDataMask,
bleFilter.getManufacturerData(),
bleFilter.getManufacturerDataMask()))) {
return false;
}
return true;
}
/** Determines if the first uuid and mask are a superset of the second uuid and mask. */
private static boolean serviceUuidIsSuperset(
@Nullable ParcelUuid uuid1,
@Nullable ParcelUuid uuidMask1,
@Nullable ParcelUuid uuid2,
@Nullable ParcelUuid uuidMask2) {
// First uuid1 is null so it can match any service UUID.
if (uuid1 == null) {
return true;
}
// uuid2 is a superset of uuid1, but not the other way around.
if (uuid2 == null) {
return false;
}
// Without a mask, the uuids must match.
if (uuidMask1 == null) {
return uuid1.equals(uuid2);
}
// Mask2 should be at least as specific as mask1.
if (uuidMask2 != null) {
long uuid1MostSig = uuidMask1.getUuid().getMostSignificantBits();
long uuid1LeastSig = uuidMask1.getUuid().getLeastSignificantBits();
long uuid2MostSig = uuidMask2.getUuid().getMostSignificantBits();
long uuid2LeastSig = uuidMask2.getUuid().getLeastSignificantBits();
if (((uuid1MostSig & uuid2MostSig) != uuid1MostSig)
|| ((uuid1LeastSig & uuid2LeastSig) != uuid1LeastSig)) {
return false;
}
}
if (!matchesServiceUuids(uuid1, uuidMask1, Arrays.asList(uuid2))) {
return false;
}
return true;
}
/** Determines if the first data and mask are the superset of the second data and mask. */
private static boolean partialDataIsSuperset(
@Nullable byte[] data1,
@Nullable byte[] dataMask1,
@Nullable byte[] data2,
@Nullable byte[] dataMask2) {
if (Arrays.equals(data1, data2) && Arrays.equals(dataMask1, dataMask2)) {
return true;
}
if (data1 == null) {
return true;
}
if (data2 == null) {
return false;
}
// Mask2 should be at least as specific as mask1.
if (dataMask1 != null && dataMask2 != null) {
for (int i = 0, j = 0; i < dataMask1.length && j < dataMask2.length; i++, j++) {
if ((dataMask1[i] & dataMask2[j]) != dataMask1[i]) {
return false;
}
}
}
if (!matchesPartialData(data1, dataMask1, data2)) {
return false;
}
return true;
}
/** Check if the uuid pattern is contained in a list of parcel uuids. */
private static boolean matchesServiceUuids(
@Nullable ParcelUuid uuid, @Nullable ParcelUuid parcelUuidMask,
List<ParcelUuid> uuids) {
if (uuid == null) {
// No service uuid filter has been set, so there's a match.
return true;
}
UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid();
for (ParcelUuid parcelUuid : uuids) {
if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) {
return true;
}
}
return false;
}
/** Check if the uuid pattern matches the particular service uuid. */
private static boolean matchesServiceUuid(UUID uuid, @Nullable 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()));
}
/**
* Check whether the data pattern matches the parsed data. Assumes that {@code data} and {@code
* dataMask} have the same length.
*/
/* package */
static boolean matchesPartialData(
@Nullable byte[] data, @Nullable byte[] dataMask, @Nullable byte[] parsedData) {
if (data == null || parsedData == null || parsedData.length < data.length) {
return false;
}
if (dataMask == null) {
for (int i = 0; i < data.length; ++i) {
if (parsedData[i] != data[i]) {
return false;
}
}
return true;
}
for (int i = 0; i < data.length; ++i) {
if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) {
return false;
}
}
return true;
}
@Override
public String toString() {
return "BleFilter [deviceName="
+ mDeviceName
+ ", deviceAddress="
+ mDeviceAddress
+ ", uuid="
+ mServiceUuid
+ ", uuidMask="
+ mServiceUuidMask
+ ", serviceDataUuid="
+ mServiceDataUuid
+ ", serviceData="
+ Arrays.toString(mServiceData)
+ ", serviceDataMask="
+ Arrays.toString(mServiceDataMask)
+ ", manufacturerId="
+ mManufacturerId
+ ", manufacturerData="
+ Arrays.toString(mManufacturerData)
+ ", manufacturerDataMask="
+ Arrays.toString(mManufacturerDataMask)
+ "]";
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(mDeviceName);
out.writeString(mDeviceAddress);
out.writeInt(mManufacturerId);
out.writeByteArray(mManufacturerData);
out.writeByteArray(mManufacturerDataMask);
out.writeParcelable(mServiceDataUuid, flags);
out.writeByteArray(mServiceData);
out.writeByteArray(mServiceDataMask);
out.writeParcelable(mServiceUuid, flags);
out.writeParcelable(mServiceUuidMask, flags);
}
@Override
public int hashCode() {
return Objects.hash(
mDeviceName,
mDeviceAddress,
mManufacturerId,
Arrays.hashCode(mManufacturerData),
Arrays.hashCode(mManufacturerDataMask),
mServiceDataUuid,
Arrays.hashCode(mServiceData),
Arrays.hashCode(mServiceDataMask),
mServiceUuid,
mServiceUuidMask);
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
BleFilter other = (BleFilter) obj;
return mDeviceName.equals(other.mDeviceName)
&& mDeviceAddress.equals(other.mDeviceAddress)
&& mManufacturerId == other.mManufacturerId
&& Arrays.equals(mManufacturerData, other.mManufacturerData)
&& Arrays.equals(mManufacturerDataMask, other.mManufacturerDataMask)
&& mServiceDataUuid.equals(other.mServiceDataUuid)
&& Arrays.equals(mServiceData, other.mServiceData)
&& Arrays.equals(mServiceDataMask, other.mServiceDataMask)
&& mServiceUuid.equals(other.mServiceUuid)
&& mServiceUuidMask.equals(other.mServiceUuidMask);
}
/** Builder class for {@link BleFilter}. */
public static final class Builder {
private String mDeviceName;
private String mDeviceAddress;
@Nullable
private ParcelUuid mServiceUuid;
@Nullable
private ParcelUuid mUuidMask;
private ParcelUuid mServiceDataUuid;
@Nullable
private byte[] mServiceData;
@Nullable
private byte[] mServiceDataMask;
private int mManufacturerId = -1;
private byte[] mManufacturerData;
@Nullable
private byte[] mManufacturerDataMask;
/** Set filter on device name. */
public Builder setDeviceName(String deviceName) {
this.mDeviceName = deviceName;
return this;
}
/**
* Set filter on device address.
*
* @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
* format of "01:02:03:AB:CD:EF". The device address can be validated
* using {@link
* BluetoothAdapter#checkBluetoothAddress}.
* @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
*/
public Builder setDeviceAddress(String deviceAddress) {
if (!BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
throw new IllegalArgumentException("invalid device address " + deviceAddress);
}
this.mDeviceAddress = deviceAddress;
return this;
}
/** Set filter on service uuid. */
public Builder setServiceUuid(@Nullable ParcelUuid serviceUuid) {
this.mServiceUuid = serviceUuid;
mUuidMask = null; // clear uuid mask
return this;
}
/**
* Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the {@code
* serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the bit in
* {@code serviceUuid}, and 0 to ignore that bit.
*
* @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but {@code
* uuidMask}
* is not {@code null}.
*/
public Builder setServiceUuid(@Nullable ParcelUuid serviceUuid,
@Nullable ParcelUuid uuidMask) {
if (uuidMask != null && serviceUuid == null) {
throw new IllegalArgumentException("uuid is null while uuidMask is not null!");
}
this.mServiceUuid = serviceUuid;
this.mUuidMask = uuidMask;
return this;
}
/**
* Set filtering on service data.
*/
public Builder setServiceData(ParcelUuid serviceDataUuid, @Nullable byte[] serviceData) {
this.mServiceDataUuid = serviceDataUuid;
this.mServiceData = serviceData;
mServiceDataMask = null; // clear service data mask
return this;
}
/**
* Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to
* match
* the one in service data, otherwise set it to 0 to ignore that bit.
*
* <p>The {@code serviceDataMask} must have the same length of the {@code serviceData}.
*
* @throws IllegalArgumentException If {@code serviceDataMask} is {@code null} while {@code
* serviceData} is not or {@code serviceDataMask} and
* {@code serviceData} has different
* length.
*/
public Builder setServiceData(
ParcelUuid serviceDataUuid,
@Nullable byte[] serviceData,
@Nullable byte[] serviceDataMask) {
if (serviceDataMask != null) {
if (serviceData == null) {
throw new IllegalArgumentException(
"serviceData is null while serviceDataMask is not null");
}
// Since the serviceDataMask is a bit mask for serviceData, the lengths of the two
// byte array need to be the same.
if (serviceData.length != serviceDataMask.length) {
throw new IllegalArgumentException(
"size mismatch for service data and service data mask");
}
}
this.mServiceDataUuid = serviceDataUuid;
this.mServiceData = serviceData;
this.mServiceDataMask = serviceDataMask;
return this;
}
/**
* Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id.
*
* <p>Note the first two bytes of the {@code manufacturerData} is the manufacturerId.
*
* @throws IllegalArgumentException If the {@code manufacturerId} is invalid.
*/
public Builder setManufacturerData(int manufacturerId, @Nullable byte[] manufacturerData) {
return setManufacturerData(manufacturerId, manufacturerData, null /* mask */);
}
/**
* Set filter on partial manufacture data. For any bit in the mask, set it to 1 if it needs
* to
* match the one in manufacturer data, otherwise set it to 0.
*
* <p>The {@code manufacturerDataMask} must have the same length of {@code
* manufacturerData}.
*
* @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or {@code
* manufacturerData} is null while {@code
* manufacturerDataMask} is not, or {@code
* manufacturerData} and {@code manufacturerDataMask} have
* different length.
*/
public Builder setManufacturerData(
int manufacturerId,
@Nullable byte[] manufacturerData,
@Nullable byte[] manufacturerDataMask) {
if (manufacturerData != null && manufacturerId < 0) {
throw new IllegalArgumentException("invalid manufacture id");
}
if (manufacturerDataMask != null) {
if (manufacturerData == null) {
throw new IllegalArgumentException(
"manufacturerData is null while manufacturerDataMask is not null");
}
// Since the manufacturerDataMask is a bit mask for manufacturerData, the lengths
// of the two byte array need to be the same.
if (manufacturerData.length != manufacturerDataMask.length) {
throw new IllegalArgumentException(
"size mismatch for manufacturerData and manufacturerDataMask");
}
}
this.mManufacturerId = manufacturerId;
this.mManufacturerData = manufacturerData == null ? new byte[0] : manufacturerData;
this.mManufacturerDataMask = manufacturerDataMask;
return this;
}
/**
* Builds the filter.
*
* @throws IllegalArgumentException If the filter cannot be built.
*/
public BleFilter build() {
return new BleFilter(
mDeviceName,
mDeviceAddress,
mServiceUuid,
mUuidMask,
mServiceDataUuid,
mServiceData,
mServiceDataMask,
mManufacturerId,
mManufacturerData,
mManufacturerDataMask);
}
}
/**
* Changes ble filter to os filter
*/
public ScanFilter toOsFilter() {
ScanFilter.Builder osFilterBuilder = new ScanFilter.Builder();
if (!TextUtils.isEmpty(getDeviceAddress())) {
osFilterBuilder.setDeviceAddress(getDeviceAddress());
}
if (!TextUtils.isEmpty(getDeviceName())) {
osFilterBuilder.setDeviceName(getDeviceName());
}
byte[] manufacturerData = getManufacturerData();
if (getManufacturerId() != -1 && manufacturerData != null) {
byte[] manufacturerDataMask = getManufacturerDataMask();
if (manufacturerDataMask != null) {
osFilterBuilder.setManufacturerData(
getManufacturerId(), manufacturerData, manufacturerDataMask);
} else {
osFilterBuilder.setManufacturerData(getManufacturerId(), manufacturerData);
}
}
ParcelUuid serviceDataUuid = getServiceDataUuid();
byte[] serviceData = getServiceData();
if (serviceDataUuid != null && serviceData != null) {
byte[] serviceDataMask = getServiceDataMask();
if (serviceDataMask != null) {
osFilterBuilder.setServiceData(serviceDataUuid, serviceData, serviceDataMask);
} else {
osFilterBuilder.setServiceData(serviceDataUuid, serviceData);
}
}
ParcelUuid serviceUuid = getServiceUuid();
if (serviceUuid != null) {
ParcelUuid serviceUuidMask = getServiceUuidMask();
if (serviceUuidMask != null) {
osFilterBuilder.setServiceUuid(serviceUuid, serviceUuidMask);
} else {
osFilterBuilder.setServiceUuid(serviceUuid);
}
}
return osFilterBuilder.build();
}
}

View File

@@ -0,0 +1,395 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.nearby.common.ble;
import android.os.ParcelUuid;
import android.util.Log;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import com.android.server.nearby.common.ble.util.StringUtils;
import com.google.common.collect.ImmutableList;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Represents a BLE record from Bluetooth LE scan.
*/
public final class BleRecord {
// The following data type values are assigned by Bluetooth SIG.
// For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18.
private static final int DATA_TYPE_FLAGS = 0x01;
private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
private static final int DATA_TYPE_SERVICE_DATA = 0x16;
private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
/** The base 128-bit UUID representation of a 16-bit UUID. */
private static final ParcelUuid BASE_UUID =
ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB");
/** Length of bytes for 16 bit UUID. */
private static final int UUID_BYTES_16_BIT = 2;
/** Length of bytes for 32 bit UUID. */
private static final int UUID_BYTES_32_BIT = 4;
/** Length of bytes for 128 bit UUID. */
private static final int UUID_BYTES_128_BIT = 16;
// Flags of the advertising data.
// -1 when the scan record is not valid.
private final int mAdvertiseFlags;
private final ImmutableList<ParcelUuid> mServiceUuids;
// null when the scan record is not valid.
@Nullable
private final SparseArray<byte[]> mManufacturerSpecificData;
// null when the scan record is not valid.
@Nullable
private final Map<ParcelUuid, byte[]> mServiceData;
// Transmission power level(in dB).
// Integer.MIN_VALUE when the scan record is not valid.
private final int mTxPowerLevel;
// Local name of the Bluetooth LE device.
// null when the scan record is not valid.
@Nullable
private final String mDeviceName;
// Raw bytes of scan record.
// Never null, whether valid or not.
private final byte[] mBytes;
// If the raw scan record byte[] cannot be parsed, all non-primitive args here other than the
// raw scan record byte[] and serviceUudis will be null. See parsefromBytes().
private BleRecord(
List<ParcelUuid> serviceUuids,
@Nullable SparseArray<byte[]> manufacturerData,
@Nullable Map<ParcelUuid, byte[]> serviceData,
int advertiseFlags,
int txPowerLevel,
@Nullable String deviceName,
byte[] bytes) {
this.mServiceUuids = ImmutableList.copyOf(serviceUuids);
mManufacturerSpecificData = manufacturerData;
this.mServiceData = serviceData;
this.mDeviceName = deviceName;
this.mAdvertiseFlags = advertiseFlags;
this.mTxPowerLevel = txPowerLevel;
this.mBytes = bytes;
}
/**
* Returns a list of service UUIDs within the advertisement that are used to identify the
* bluetooth GATT services.
*/
public ImmutableList<ParcelUuid> getServiceUuids() {
return mServiceUuids;
}
/**
* Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific
* data.
*/
@Nullable
public SparseArray<byte[]> getManufacturerSpecificData() {
return mManufacturerSpecificData;
}
/**
* Returns the manufacturer specific data associated with the manufacturer id. Returns {@code
* null} if the {@code manufacturerId} is not found.
*/
@Nullable
public byte[] getManufacturerSpecificData(int manufacturerId) {
if (mManufacturerSpecificData == null) {
return null;
}
return mManufacturerSpecificData.get(manufacturerId);
}
/** Returns a map of service UUID and its corresponding service data. */
@Nullable
public Map<ParcelUuid, byte[]> getServiceData() {
return mServiceData;
}
/**
* Returns the service data byte array associated with the {@code serviceUuid}. Returns {@code
* null} if the {@code serviceDataUuid} is not found.
*/
@Nullable
public byte[] getServiceData(ParcelUuid serviceDataUuid) {
if (serviceDataUuid == null || mServiceData == null) {
return null;
}
return mServiceData.get(serviceDataUuid);
}
/**
* Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE}
* if
* the field is not set. This value can be used to calculate the path loss of a received packet
* using the following equation:
*
* <p><code>pathloss = txPowerLevel - rssi</code>
*/
public int getTxPowerLevel() {
return mTxPowerLevel;
}
/** Returns the local name of the BLE device. The is a UTF-8 encoded string. */
@Nullable
public String getDeviceName() {
return mDeviceName;
}
/** Returns raw bytes of scan record. */
public byte[] getBytes() {
return mBytes;
}
/**
* Parse scan record bytes to {@link BleRecord}.
*
* <p>The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
*
* <p>All numerical multi-byte entities and values shall use little-endian <strong>byte</strong>
* order.
*
* @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response.
*/
public static BleRecord parseFromBytes(byte[] scanRecord) {
int currentPos = 0;
int advertiseFlag = -1;
List<ParcelUuid> serviceUuids = new ArrayList<>();
String localName = null;
int txPowerLevel = Integer.MIN_VALUE;
SparseArray<byte[]> manufacturerData = new SparseArray<>();
Map<ParcelUuid, byte[]> serviceData = new HashMap<>();
try {
while (currentPos < scanRecord.length) {
// length is unsigned int.
int length = scanRecord[currentPos++] & 0xFF;
if (length == 0) {
break;
}
// Note the length includes the length of the field type itself.
int dataLength = length - 1;
// fieldType is unsigned int.
int fieldType = scanRecord[currentPos++] & 0xFF;
switch (fieldType) {
case DATA_TYPE_FLAGS:
advertiseFlag = scanRecord[currentPos] & 0xFF;
break;
case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
parseServiceUuid(scanRecord, currentPos, dataLength, UUID_BYTES_16_BIT,
serviceUuids);
break;
case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
parseServiceUuid(scanRecord, currentPos, dataLength, UUID_BYTES_32_BIT,
serviceUuids);
break;
case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
parseServiceUuid(scanRecord, currentPos, dataLength, UUID_BYTES_128_BIT,
serviceUuids);
break;
case DATA_TYPE_LOCAL_NAME_SHORT:
case DATA_TYPE_LOCAL_NAME_COMPLETE:
localName = new String(extractBytes(scanRecord, currentPos, dataLength));
break;
case DATA_TYPE_TX_POWER_LEVEL:
txPowerLevel = scanRecord[currentPos];
break;
case DATA_TYPE_SERVICE_DATA:
// The first two bytes of the service data are service data UUID in little
// endian. The rest bytes are service data.
int serviceUuidLength = UUID_BYTES_16_BIT;
byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
serviceUuidLength);
ParcelUuid serviceDataUuid = parseUuidFrom(serviceDataUuidBytes);
byte[] serviceDataArray =
extractBytes(
scanRecord, currentPos + serviceUuidLength,
dataLength - serviceUuidLength);
serviceData.put(serviceDataUuid, serviceDataArray);
break;
case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
// The first two bytes of the manufacturer specific data are
// manufacturer ids in little endian.
int manufacturerId =
((scanRecord[currentPos + 1] & 0xFF) << 8) + (scanRecord[currentPos]
& 0xFF);
byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2,
dataLength - 2);
manufacturerData.put(manufacturerId, manufacturerDataBytes);
break;
default:
// Just ignore, we don't handle such data type.
break;
}
currentPos += dataLength;
}
return new BleRecord(
serviceUuids,
manufacturerData,
serviceData,
advertiseFlag,
txPowerLevel,
localName,
scanRecord);
} catch (Exception e) {
Log.w("BleRecord", "Unable to parse scan record: " + Arrays.toString(scanRecord), e);
// As the record is invalid, ignore all the parsed results for this packet
// and return an empty record with raw scanRecord bytes in results
// check at the top of this method does? Maybe we expect callers to use the
// scanRecord part in
// some fallback. But if that's the reason, it would seem we still can return null.
// They still
// have the raw scanRecord in hand, 'cause they passed it to us. It seems too easy for a
// caller to misuse this "empty" BleRecord (as in b/22693067).
return new BleRecord(ImmutableList.of(), null, null, -1, Integer.MIN_VALUE, null,
scanRecord);
}
}
// Parse service UUIDs.
private static int parseServiceUuid(
byte[] scanRecord,
int currentPos,
int dataLength,
int uuidLength,
List<ParcelUuid> serviceUuids) {
while (dataLength > 0) {
byte[] uuidBytes = extractBytes(scanRecord, currentPos, uuidLength);
serviceUuids.add(parseUuidFrom(uuidBytes));
dataLength -= uuidLength;
currentPos += uuidLength;
}
return currentPos;
}
// Helper method to extract bytes from byte array.
private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
byte[] bytes = new byte[length];
System.arraycopy(scanRecord, start, bytes, 0, length);
return bytes;
}
@Override
public String toString() {
return "BleRecord [advertiseFlags="
+ mAdvertiseFlags
+ ", serviceUuids="
+ mServiceUuids
+ ", manufacturerSpecificData="
+ StringUtils.toString(mManufacturerSpecificData)
+ ", serviceData="
+ StringUtils.toString(mServiceData)
+ ", txPowerLevel="
+ mTxPowerLevel
+ ", deviceName="
+ mDeviceName
+ "]";
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof BleRecord)) {
return false;
}
BleRecord record = (BleRecord) obj;
// BleRecord objects are built from bytes, so we only need that field.
return Arrays.equals(mBytes, record.mBytes);
}
@Override
public int hashCode() {
// BleRecord objects are built from bytes, so we only need that field.
return Arrays.hashCode(mBytes);
}
/**
* Parse UUID from bytes. The {@code uuidBytes} can represent a 16-bit, 32-bit or 128-bit UUID,
* but the returned UUID is always in 128-bit format. Note UUID is little endian in Bluetooth.
*
* @param uuidBytes Byte representation of uuid.
* @return {@link ParcelUuid} parsed from bytes.
* @throws IllegalArgumentException If the {@code uuidBytes} cannot be parsed.
*/
private static ParcelUuid parseUuidFrom(byte[] uuidBytes) {
if (uuidBytes == null) {
throw new IllegalArgumentException("uuidBytes cannot be null");
}
int length = uuidBytes.length;
if (length != UUID_BYTES_16_BIT
&& length != UUID_BYTES_32_BIT
&& length != UUID_BYTES_128_BIT) {
throw new IllegalArgumentException("uuidBytes length invalid - " + length);
}
// Construct a 128 bit UUID.
if (length == UUID_BYTES_128_BIT) {
ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN);
long msb = buf.getLong(8);
long lsb = buf.getLong(0);
return new ParcelUuid(new UUID(msb, lsb));
}
// For 16 bit and 32 bit UUID we need to convert them to 128 bit value.
// 128_bit_value = uuid * 2^96 + BASE_UUID
long shortUuid;
if (length == UUID_BYTES_16_BIT) {
shortUuid = uuidBytes[0] & 0xFF;
shortUuid += (uuidBytes[1] & 0xFF) << 8;
} else {
shortUuid = uuidBytes[0] & 0xFF;
shortUuid += (uuidBytes[1] & 0xFF) << 8;
shortUuid += (uuidBytes[2] & 0xFF) << 16;
shortUuid += (uuidBytes[3] & 0xFF) << 24;
}
long msb = BASE_UUID.getUuid().getMostSignificantBits() + (shortUuid << 32);
long lsb = BASE_UUID.getUuid().getLeastSignificantBits();
return new ParcelUuid(new UUID(msb, lsb));
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.nearby.common.ble;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.os.Build.VERSION_CODES;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* A sighting of a BLE device found in a Bluetooth LE scan.
*/
public class BleSighting implements Parcelable {
public static final Parcelable.Creator<BleSighting> CREATOR = new Creator<BleSighting>() {
@Override
public BleSighting createFromParcel(Parcel source) {
BleSighting nBleSighting = new BleSighting(source.readParcelable(null),
source.marshall(), source.readInt(), source.readLong());
return null;
}
@Override
public BleSighting[] newArray(int size) {
return new BleSighting[size];
}
};
// Max and min rssi value which is from {@link android.bluetooth.le.ScanResult#getRssi()}.
@VisibleForTesting
public static final int MAX_RSSI_VALUE = 126;
@VisibleForTesting
public static final int MIN_RSSI_VALUE = -127;
/** Remote bluetooth device. */
private final BluetoothDevice mDevice;
/**
* BLE record, including advertising data and response data. BleRecord is not parcelable, so
* this
* is created from bleRecordBytes.
*/
private final BleRecord mBleRecord;
/** The bytes of a BLE record. */
private final byte[] mBleRecordBytes;
/** Received signal strength. */
private final int mRssi;
/** Nanos timestamp when the ble device was observed (epoch time). */
private final long mTimestampEpochNanos;
/**
* Constructor of a BLE sighting.
*
* @param device Remote bluetooth device that is found.
* @param bleRecordBytes The bytes that will create a BleRecord.
* @param rssi Received signal strength.
* @param timestampEpochNanos Nanos timestamp when the BLE device was observed (epoch time).
*/
public BleSighting(BluetoothDevice device, byte[] bleRecordBytes, int rssi,
long timestampEpochNanos) {
this.mDevice = device;
this.mBleRecordBytes = bleRecordBytes;
this.mRssi = rssi;
this.mTimestampEpochNanos = timestampEpochNanos;
mBleRecord = BleRecord.parseFromBytes(bleRecordBytes);
}
@Override
public int describeContents() {
return 0;
}
/** Returns the remote bluetooth device identified by the bluetooth device address. */
public BluetoothDevice getDevice() {
return mDevice;
}
/** Returns the BLE record, which is a combination of advertisement and scan response. */
public BleRecord getBleRecord() {
return mBleRecord;
}
/** Returns the bytes of the BLE record. */
public byte[] getBleRecordBytes() {
return mBleRecordBytes;
}
/** Returns the received signal strength in dBm. The valid range is [-127, 127]. */
public int getRssi() {
return mRssi;
}
/**
* Returns the received signal strength normalized with the offset specific to the given device.
* 3 is the rssi offset to calculate fast init distance.
* <p>This method utilized the rssi offset maintained by Nearby Sharing.
*
* @return normalized rssi which is between [-127, 126] according to {@link
* android.bluetooth.le.ScanResult#getRssi()}.
*/
public int getNormalizedRSSI() {
int adjustedRssi = mRssi + 3;
if (adjustedRssi < MIN_RSSI_VALUE) {
return MIN_RSSI_VALUE;
} else if (adjustedRssi > MAX_RSSI_VALUE) {
return MAX_RSSI_VALUE;
} else {
return adjustedRssi;
}
}
/** Returns timestamp in epoch time when the scan record was observed. */
public long getTimestampNanos() {
return mTimestampEpochNanos;
}
/** Returns timestamp in epoch time when the scan record was observed, in millis. */
public long getTimestampMillis() {
return TimeUnit.NANOSECONDS.toMillis(mTimestampEpochNanos);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(mDevice, flags);
dest.writeByteArray(mBleRecordBytes);
dest.writeInt(mRssi);
dest.writeLong(mTimestampEpochNanos);
}
@Override
public int hashCode() {
return Objects.hash(mDevice, mRssi, mTimestampEpochNanos, Arrays.hashCode(mBleRecordBytes));
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof BleSighting)) {
return false;
}
BleSighting other = (BleSighting) obj;
return Objects.equals(mDevice, other.mDevice)
&& mRssi == other.mRssi
&& Arrays.equals(mBleRecordBytes, other.mBleRecordBytes)
&& mTimestampEpochNanos == other.mTimestampEpochNanos;
}
@Override
public String toString() {
return "BleSighting{"
+ "device="
+ mDevice
+ ", bleRecord="
+ mBleRecord
+ ", rssi="
+ mRssi
+ ", timestampNanos="
+ mTimestampEpochNanos
+ "}";
}
/** Creates {@link BleSighting} using the {@link ScanResult}. */
@RequiresApi(api = VERSION_CODES.LOLLIPOP)
@Nullable
public static BleSighting createFromOsScanResult(ScanResult osResult) {
ScanRecord osScanRecord = osResult.getScanRecord();
if (osScanRecord == null) {
return null;
}
return new BleSighting(
osResult.getDevice(),
osScanRecord.getBytes(),
osResult.getRssi(),
// The timestamp from ScanResult is 'nanos since boot', Beacon lib will change it
// as 'nanos
// since epoch', but Nearby never reference this field, just pass it as 'nanos
// since boot'.
// ref to beacon/scan/impl/LBluetoothLeScannerCompat.fromOs for beacon design
// about how to
// convert nanos since boot to epoch.
osResult.getTimestampNanos());
}
}

Some files were not shown because too many files have changed in this diff Show More