1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.nearby.common.ble; 18 19 import android.bluetooth.BluetoothDevice; 20 import android.bluetooth.le.ScanRecord; 21 import android.bluetooth.le.ScanResult; 22 import android.os.Build.VERSION_CODES; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 26 import androidx.annotation.Nullable; 27 import androidx.annotation.RequiresApi; 28 import androidx.annotation.VisibleForTesting; 29 30 import java.util.Arrays; 31 import java.util.Objects; 32 import java.util.concurrent.TimeUnit; 33 34 /** 35 * A sighting of a BLE device found in a Bluetooth LE scan. 36 */ 37 38 public class BleSighting implements Parcelable { 39 40 public static final Parcelable.Creator<BleSighting> CREATOR = new Creator<BleSighting>() { 41 @Override 42 public BleSighting createFromParcel(Parcel source) { 43 BleSighting nBleSighting = new BleSighting(source.readParcelable(null), 44 source.marshall(), source.readInt(), source.readLong()); 45 return null; 46 } 47 48 @Override 49 public BleSighting[] newArray(int size) { 50 return new BleSighting[size]; 51 } 52 }; 53 54 // Max and min rssi value which is from {@link android.bluetooth.le.ScanResult#getRssi()}. 55 @VisibleForTesting 56 public static final int MAX_RSSI_VALUE = 126; 57 @VisibleForTesting 58 public static final int MIN_RSSI_VALUE = -127; 59 60 /** Remote bluetooth device. */ 61 private final BluetoothDevice mDevice; 62 63 /** 64 * BLE record, including advertising data and response data. BleRecord is not parcelable, so 65 * this 66 * is created from bleRecordBytes. 67 */ 68 private final BleRecord mBleRecord; 69 70 /** The bytes of a BLE record. */ 71 private final byte[] mBleRecordBytes; 72 73 /** Received signal strength. */ 74 private final int mRssi; 75 76 /** Nanos timestamp when the ble device was observed (epoch time). */ 77 private final long mTimestampEpochNanos; 78 79 /** 80 * Constructor of a BLE sighting. 81 * 82 * @param device Remote bluetooth device that is found. 83 * @param bleRecordBytes The bytes that will create a BleRecord. 84 * @param rssi Received signal strength. 85 * @param timestampEpochNanos Nanos timestamp when the BLE device was observed (epoch time). 86 */ BleSighting(BluetoothDevice device, byte[] bleRecordBytes, int rssi, long timestampEpochNanos)87 public BleSighting(BluetoothDevice device, byte[] bleRecordBytes, int rssi, 88 long timestampEpochNanos) { 89 this.mDevice = device; 90 this.mBleRecordBytes = bleRecordBytes; 91 this.mRssi = rssi; 92 this.mTimestampEpochNanos = timestampEpochNanos; 93 mBleRecord = BleRecord.parseFromBytes(bleRecordBytes); 94 } 95 96 @Override describeContents()97 public int describeContents() { 98 return 0; 99 } 100 101 /** Returns the remote bluetooth device identified by the bluetooth device address. */ getDevice()102 public BluetoothDevice getDevice() { 103 return mDevice; 104 } 105 106 /** Returns the BLE record, which is a combination of advertisement and scan response. */ getBleRecord()107 public BleRecord getBleRecord() { 108 return mBleRecord; 109 } 110 111 /** Returns the bytes of the BLE record. */ getBleRecordBytes()112 public byte[] getBleRecordBytes() { 113 return mBleRecordBytes; 114 } 115 116 /** Returns the received signal strength in dBm. The valid range is [-127, 127]. */ getRssi()117 public int getRssi() { 118 return mRssi; 119 } 120 121 /** 122 * Returns the received signal strength normalized with the offset specific to the given device. 123 * 3 is the rssi offset to calculate fast init distance. 124 * <p>This method utilized the rssi offset maintained by Nearby Sharing. 125 * 126 * @return normalized rssi which is between [-127, 126] according to {@link 127 * android.bluetooth.le.ScanResult#getRssi()}. 128 */ getNormalizedRSSI()129 public int getNormalizedRSSI() { 130 int adjustedRssi = mRssi + 3; 131 if (adjustedRssi < MIN_RSSI_VALUE) { 132 return MIN_RSSI_VALUE; 133 } else if (adjustedRssi > MAX_RSSI_VALUE) { 134 return MAX_RSSI_VALUE; 135 } else { 136 return adjustedRssi; 137 } 138 } 139 140 /** Returns timestamp in epoch time when the scan record was observed. */ getTimestampNanos()141 public long getTimestampNanos() { 142 return mTimestampEpochNanos; 143 } 144 145 /** Returns timestamp in epoch time when the scan record was observed, in millis. */ getTimestampMillis()146 public long getTimestampMillis() { 147 return TimeUnit.NANOSECONDS.toMillis(mTimestampEpochNanos); 148 } 149 150 @Override writeToParcel(Parcel dest, int flags)151 public void writeToParcel(Parcel dest, int flags) { 152 dest.writeParcelable(mDevice, flags); 153 dest.writeByteArray(mBleRecordBytes); 154 dest.writeInt(mRssi); 155 dest.writeLong(mTimestampEpochNanos); 156 } 157 158 @Override hashCode()159 public int hashCode() { 160 return Objects.hash(mDevice, mRssi, mTimestampEpochNanos, Arrays.hashCode(mBleRecordBytes)); 161 } 162 163 @Override equals(@ullable Object obj)164 public boolean equals(@Nullable Object obj) { 165 if (this == obj) { 166 return true; 167 } 168 if (!(obj instanceof BleSighting)) { 169 return false; 170 } 171 BleSighting other = (BleSighting) obj; 172 return Objects.equals(mDevice, other.mDevice) 173 && mRssi == other.mRssi 174 && Arrays.equals(mBleRecordBytes, other.mBleRecordBytes) 175 && mTimestampEpochNanos == other.mTimestampEpochNanos; 176 } 177 178 @Override toString()179 public String toString() { 180 return "BleSighting{" 181 + "device=" 182 + mDevice 183 + ", bleRecord=" 184 + mBleRecord 185 + ", rssi=" 186 + mRssi 187 + ", timestampNanos=" 188 + mTimestampEpochNanos 189 + "}"; 190 } 191 192 /** Creates {@link BleSighting} using the {@link ScanResult}. */ 193 @RequiresApi(api = VERSION_CODES.LOLLIPOP) 194 @Nullable createFromOsScanResult(ScanResult osResult)195 public static BleSighting createFromOsScanResult(ScanResult osResult) { 196 ScanRecord osScanRecord = osResult.getScanRecord(); 197 if (osScanRecord == null) { 198 return null; 199 } 200 201 return new BleSighting( 202 osResult.getDevice(), 203 osScanRecord.getBytes(), 204 osResult.getRssi(), 205 // The timestamp from ScanResult is 'nanos since boot', Beacon lib will change it 206 // as 'nanos 207 // since epoch', but Nearby never reference this field, just pass it as 'nanos 208 // since boot'. 209 // ref to beacon/scan/impl/LBluetoothLeScannerCompat.fromOs for beacon design 210 // about how to 211 // convert nanos since boot to epoch. 212 osResult.getTimestampNanos()); 213 } 214 } 215 216