package com.blakequ.bluetooth_manager_lib.device;
|
|
import android.bluetooth.BluetoothDevice;
|
import android.os.Bundle;
|
import android.os.Parcel;
|
import android.os.Parcelable;
|
|
import com.blakequ.bluetooth_manager_lib.device.adrecord.AdRecordStore;
|
import com.blakequ.bluetooth_manager_lib.device.adrecord.AdRecordUtils;
|
import com.blakequ.bluetooth_manager_lib.device.resolvers.BluetoothClassResolver;
|
import com.blakequ.bluetooth_manager_lib.device.ibeacon.IBeaconDevice;
|
import com.blakequ.bluetooth_manager_lib.util.ByteUtils;
|
import com.blakequ.bluetooth_manager_lib.util.LimitedLinkHashMap;
|
|
import java.io.Serializable;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.HashSet;
|
import java.util.Map;
|
import java.util.Set;
|
|
|
// TODO: Auto-generated Javadoc
|
|
/**
|
* This is a wrapper around the default BluetoothDevice object
|
* As BluetoothDevice is final it cannot be extended, so to get it you
|
* need to call {@link #getDevice()} method.
|
*
|
* @author Alexandros Schillings
|
*/
|
public class BluetoothLeDevice implements Parcelable ,BeaconDevice{
|
/**
|
* The Constant CREATOR.
|
*/
|
public static final Creator<BluetoothLeDevice> CREATOR = new Creator<BluetoothLeDevice>() {
|
public BluetoothLeDevice createFromParcel(final Parcel in) {
|
return new BluetoothLeDevice(in);
|
}
|
|
public BluetoothLeDevice[] newArray(final int size) {
|
return new BluetoothLeDevice[size];
|
}
|
};
|
protected static final int MAX_RSSI_LOG_SIZE = 10;
|
private static final String PARCEL_EXTRA_BLUETOOTH_DEVICE = "bluetooth_device";
|
private static final String PARCEL_EXTRA_CURRENT_RSSI = "current_rssi";
|
private static final String PARCEL_EXTRA_CURRENT_TIMESTAMP = "current_timestamp";
|
private static final String PARCEL_EXTRA_DEVICE_RSSI_LOG = "device_rssi_log";
|
private static final String PARCEL_EXTRA_DEVICE_SCANRECORD = "device_scanrecord";
|
private static final String PARCEL_EXTRA_DEVICE_SCANRECORD_STORE = "device_scanrecord_store";
|
private static final String PARCEL_EXTRA_FIRST_RSSI = "device_first_rssi";
|
private static final String PARCEL_EXTRA_FIRST_TIMESTAMP = "first_timestamp";
|
private static final long LOG_INVALIDATION_THRESHOLD = 10 * 1000;
|
private final AdRecordStore mRecordStore;
|
private final BluetoothDevice mDevice;
|
private final Map<Long, Integer> mRssiLog;
|
private final byte[] mScanRecord;
|
private final int mFirstRssi;
|
private final long mFirstTimestamp;
|
private int mCurrentRssi;
|
private long mCurrentTimestamp;
|
private transient Set<BluetoothService> mServiceSet;
|
|
/**
|
* Instantiates a new Bluetooth LE device.
|
*
|
* @param device a standard android Bluetooth device
|
* @param rssi the RSSI value of the Bluetooth device
|
* @param scanRecord the scan record of the device
|
* @param timestamp the timestamp of the RSSI reading
|
*/
|
public BluetoothLeDevice(final BluetoothDevice device, final int rssi, final byte[] scanRecord, final long timestamp) {
|
mDevice = device;
|
mFirstRssi = rssi;
|
mFirstTimestamp = timestamp;
|
mRecordStore = new AdRecordStore(AdRecordUtils.parseScanRecordAsSparseArray(scanRecord));
|
mScanRecord = scanRecord;
|
mRssiLog = new LimitedLinkHashMap<>(MAX_RSSI_LOG_SIZE);
|
updateRssiReading(timestamp, rssi);
|
}
|
|
/**
|
* Instantiates a new Bluetooth LE device.
|
*
|
* @param device the device
|
*/
|
public BluetoothLeDevice(final BluetoothLeDevice device) {
|
mCurrentRssi = device.getRssi();
|
mCurrentTimestamp = device.getTimestamp();
|
mDevice = device.getDevice();
|
mFirstRssi = device.getFirstRssi();
|
mFirstTimestamp = device.getFirstTimestamp();
|
mRecordStore = new AdRecordStore(
|
AdRecordUtils.parseScanRecordAsSparseArray(device.getScanRecord()));
|
mRssiLog = device.getRssiLog();
|
mScanRecord = device.getScanRecord();
|
}
|
|
/**
|
* Instantiates a new bluetooth le device.
|
*
|
* @param in the in
|
*/
|
@SuppressWarnings("unchecked")
|
protected BluetoothLeDevice(final Parcel in) {
|
final Bundle b = in.readBundle(getClass().getClassLoader());
|
|
mCurrentRssi = b.getInt(PARCEL_EXTRA_CURRENT_RSSI, 0);
|
mCurrentTimestamp = b.getLong(PARCEL_EXTRA_CURRENT_TIMESTAMP, 0);
|
mDevice = b.getParcelable(PARCEL_EXTRA_BLUETOOTH_DEVICE);
|
mFirstRssi = b.getInt(PARCEL_EXTRA_FIRST_RSSI, 0);
|
mFirstTimestamp = b.getLong(PARCEL_EXTRA_FIRST_TIMESTAMP, 0);
|
mRecordStore = b.getParcelable(PARCEL_EXTRA_DEVICE_SCANRECORD_STORE);
|
mRssiLog = (Map<Long, Integer>) b.getSerializable(PARCEL_EXTRA_DEVICE_RSSI_LOG);
|
mScanRecord = b.getByteArray(PARCEL_EXTRA_DEVICE_SCANRECORD);
|
}
|
|
@Override
|
public BeaconType getBeaconType() {
|
return BeaconUtils.getBeaconType(this);
|
}
|
|
/**
|
* if ble device is ibeacon will return Ibeacon device, else return null
|
* @return
|
*/
|
public IBeaconDevice getIBeaconDevice(){
|
if (getBeaconType() == BeaconType.IBEACON){
|
return new IBeaconDevice(this);
|
}
|
return null;
|
}
|
|
/**
|
* Adds the to rssi log.
|
*
|
* @param timestamp the timestamp
|
* @param rssiReading the rssi reading
|
*/
|
private void addToRssiLog(final long timestamp, final int rssiReading) {
|
synchronized (mRssiLog) {
|
if (timestamp - mCurrentTimestamp > LOG_INVALIDATION_THRESHOLD) {
|
mRssiLog.clear();
|
}
|
|
mCurrentRssi = rssiReading;
|
mCurrentTimestamp = timestamp;
|
mRssiLog.put(timestamp, rssiReading);
|
}
|
}
|
|
/* (non-Javadoc)
|
* @see android.os.Parcelable#describeContents()
|
*/
|
@Override
|
public int describeContents() {
|
return 0;
|
}
|
|
/* (non-Javadoc)
|
* @see java.lang.Object#equals(java.lang.Object)
|
*/
|
@Override
|
public boolean equals(final Object obj) {
|
if (this == obj)
|
return true;
|
if (obj == null)
|
return false;
|
if (getClass() != obj.getClass())
|
return false;
|
final BluetoothLeDevice other = (BluetoothLeDevice) obj;
|
if (mCurrentRssi != other.mCurrentRssi)
|
return false;
|
if (mCurrentTimestamp != other.mCurrentTimestamp)
|
return false;
|
if (mDevice == null) {
|
if (other.mDevice != null)
|
return false;
|
} else if (!mDevice.equals(other.mDevice))
|
return false;
|
if (mFirstRssi != other.mFirstRssi)
|
return false;
|
if (mFirstTimestamp != other.mFirstTimestamp)
|
return false;
|
if (mRecordStore == null) {
|
if (other.mRecordStore != null)
|
return false;
|
} else if (!mRecordStore.equals(other.mRecordStore))
|
return false;
|
if (mRssiLog == null) {
|
if (other.mRssiLog != null)
|
return false;
|
} else if (!mRssiLog.equals(other.mRssiLog))
|
return false;
|
if (!Arrays.equals(mScanRecord, other.mScanRecord))
|
return false;
|
return true;
|
}
|
|
/**
|
* Gets the ad record store.
|
*
|
* @return the ad record store
|
*/
|
public AdRecordStore getAdRecordStore() {
|
return mRecordStore;
|
}
|
|
/**
|
* Gets the address.
|
*
|
* @return the address
|
*/
|
public String getAddress() {
|
return mDevice.getAddress();
|
}
|
|
/**
|
* Gets the bluetooth device bond state.
|
*
|
* @return the bluetooth device bond state
|
*/
|
public String getBluetoothDeviceBondState() {
|
return resolveBondingState(mDevice.getBondState());
|
}
|
|
/**
|
* Gets the bluetooth device class name.
|
*
|
* @return the bluetooth device class name
|
*/
|
public String getBluetoothDeviceClassName() {
|
return BluetoothClassResolver.resolveDeviceClass(mDevice.getBluetoothClass().getDeviceClass());
|
}
|
|
public Set<BluetoothService> getBluetoothDeviceKnownSupportedServices() {
|
if (mServiceSet == null) {
|
synchronized (this) {
|
if (mServiceSet == null) {
|
final Set<BluetoothService> serviceSet = new HashSet<>();
|
for (final BluetoothService service : BluetoothService.values()) {
|
|
if (mDevice.getBluetoothClass().hasService(service.getAndroidConstant())) {
|
serviceSet.add(service);
|
}
|
}
|
mServiceSet = Collections.unmodifiableSet(serviceSet);
|
}
|
}
|
}
|
|
return mServiceSet;
|
}
|
|
/**
|
* Gets the bluetooth device major class name.
|
*
|
* @return the bluetooth device major class name
|
*/
|
public String getBluetoothDeviceMajorClassName() {
|
return BluetoothClassResolver.resolveMajorDeviceClass(mDevice.getBluetoothClass().getMajorDeviceClass());
|
}
|
|
/**
|
* Gets the device.
|
*
|
* @return the device
|
*/
|
public BluetoothDevice getDevice() {
|
return mDevice;
|
}
|
|
/**
|
* Gets the first rssi.
|
*
|
* @return the first rssi
|
*/
|
public int getFirstRssi() {
|
return mFirstRssi;
|
}
|
|
/**
|
* Gets the first timestamp.
|
*
|
* @return the first timestamp
|
*/
|
public long getFirstTimestamp() {
|
return mFirstTimestamp;
|
}
|
|
/**
|
* Gets the name.
|
*
|
* @return the name
|
*/
|
public String getName() {
|
return mDevice.getName();
|
}
|
|
/**
|
* Gets the rssi.
|
*
|
* @return the rssi
|
*/
|
public int getRssi() {
|
return mCurrentRssi;
|
}
|
|
/**
|
* Gets the rssi log.
|
*
|
* @return the rssi log
|
*/
|
protected Map<Long, Integer> getRssiLog() {
|
synchronized (mRssiLog) {
|
return mRssiLog;
|
}
|
}
|
|
/**
|
* Gets the running average rssi.
|
*
|
* @return the running average rssi
|
*/
|
public double getRunningAverageRssi() {
|
int sum = 0;
|
int count = 0;
|
|
synchronized (mRssiLog) {
|
|
for (final Long aLong : mRssiLog.keySet()) {
|
count++;
|
sum += mRssiLog.get(aLong);
|
}
|
}
|
|
if (count > 0) {
|
return sum / count;
|
} else {
|
return 0;
|
}
|
|
}
|
|
/**
|
* Gets the scan record.
|
*
|
* @return the scan record
|
*/
|
public byte[] getScanRecord() {
|
return mScanRecord;
|
}
|
|
/**
|
* Gets the timestamp.
|
*
|
* @return the timestamp
|
*/
|
public long getTimestamp() {
|
return mCurrentTimestamp;
|
}
|
|
/* (non-Javadoc)
|
* @see java.lang.Object#hashCode()
|
*/
|
@Override
|
public int hashCode() {
|
final int prime = 31;
|
int result = 1;
|
result = prime * result + mCurrentRssi;
|
result = prime * result + (int) (mCurrentTimestamp ^ (mCurrentTimestamp >>> 32));
|
result = prime * result + ((mDevice == null) ? 0 : mDevice.hashCode());
|
result = prime * result + mFirstRssi;
|
result = prime * result + (int) (mFirstTimestamp ^ (mFirstTimestamp >>> 32));
|
result = prime * result + ((mRecordStore == null) ? 0 : mRecordStore.hashCode());
|
result = prime * result + ((mRssiLog == null) ? 0 : mRssiLog.hashCode());
|
result = prime * result + Arrays.hashCode(mScanRecord);
|
return result;
|
}
|
|
/* (non-Javadoc)
|
* @see java.lang.Object#toString()
|
*/
|
@Override
|
public String toString() {
|
return "BluetoothLeDevice [mDevice=" + mDevice + ", mRssi=" + mFirstRssi + ", mScanRecord=" + ByteUtils.byteArrayToHexString(mScanRecord) + ", mRecordStore=" + mRecordStore + ", getBluetoothDeviceBondState()=" + getBluetoothDeviceBondState() + ", getBluetoothDeviceClassName()=" + getBluetoothDeviceClassName() + "]";
|
}
|
|
/**
|
* Update rssi reading.
|
*
|
* @param timestamp the timestamp
|
* @param rssiReading the rssi reading
|
*/
|
public void updateRssiReading(final long timestamp, final int rssiReading) {
|
addToRssiLog(timestamp, rssiReading);
|
}
|
|
/* (non-Javadoc)
|
* @see android.os.Parcelable#writeToParcel(android.os.Parcel, int)
|
*/
|
@Override
|
public void writeToParcel(final Parcel parcel, final int arg1) {
|
final Bundle b = new Bundle(getClass().getClassLoader());
|
|
b.putByteArray(PARCEL_EXTRA_DEVICE_SCANRECORD, mScanRecord);
|
|
b.putInt(PARCEL_EXTRA_FIRST_RSSI, mFirstRssi);
|
b.putInt(PARCEL_EXTRA_CURRENT_RSSI, mCurrentRssi);
|
|
b.putLong(PARCEL_EXTRA_FIRST_TIMESTAMP, mFirstTimestamp);
|
b.putLong(PARCEL_EXTRA_CURRENT_TIMESTAMP, mCurrentTimestamp);
|
|
b.putParcelable(PARCEL_EXTRA_BLUETOOTH_DEVICE, mDevice);
|
b.putParcelable(PARCEL_EXTRA_DEVICE_SCANRECORD_STORE, mRecordStore);
|
b.putSerializable(PARCEL_EXTRA_DEVICE_RSSI_LOG, (Serializable) mRssiLog);
|
|
parcel.writeBundle(b);
|
}
|
|
/**
|
* Resolve bonding state.
|
*
|
* @param bondState the bond state
|
* @return the string
|
*/
|
private static String resolveBondingState(final int bondState) {
|
switch (bondState) {
|
case BluetoothDevice.BOND_BONDED:
|
return "Paired";
|
case BluetoothDevice.BOND_BONDING:
|
return "Pairing";
|
case BluetoothDevice.BOND_NONE:
|
return "Unbonded";
|
default:
|
return "Unknown";
|
}
|
}
|
}
|