/* * Copyright 2019 Fitbit, Inc. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ package com.fitbit.bluetooth.fbgatt; import com.fitbit.bluetooth.fbgatt.util.Bytes; import com.fitbit.bluetooth.fbgatt.util.GattUtils; import android.bluetooth.BluetoothDevice; import android.bluetooth.le.ScanRecord; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.util.Locale; import java.util.concurrent.CopyOnWriteArrayList; import timber.log.Timber; /** * A device wrapper to allow for mocking, will hold the bt device and abstract some of the * functionality ultimately will be held by the fitbit Device object * * Created by iowens on 11/7/17. */ public class FitbitBluetoothDevice { public enum DeviceOrigin { UNKNOWN, SCANNED, CONNECTED, BONDED } public interface DevicePropertiesChangedCallback { /** * Will indicate that the properties of one of the bluetooth devices that are cached * have changed, could be rssi, device name, or the {@link ScanRecord} * @param device The newly updated device */ void onBluetoothPeripheralDevicePropertiesChanged(@NonNull FitbitBluetoothDevice device); } BluetoothDevice device; private String bluetoothAddress; private int rssi; @Nullable private ScanRecord scanRecord; private String name; DeviceOrigin origin = DeviceOrigin.UNKNOWN; private CopyOnWriteArrayList<DevicePropertiesChangedCallback> devicePropertiesChangedListeners = new CopyOnWriteArrayList<>(); public FitbitBluetoothDevice(@NonNull BluetoothDevice device) { this.device = device; this.bluetoothAddress = device.getAddress(); // this can throw a parcelable null pointer exception down in the stack try { this.name = new GattUtils().debugSafeGetBtDeviceName(device); } catch (NullPointerException ex) { this.name = "Unknown Device"; } } public FitbitBluetoothDevice(String bluetoothAddress, String name) { this.bluetoothAddress = bluetoothAddress; this.name = name; this.device = FitbitGatt.getInstance().getBluetoothDevice(bluetoothAddress); Timber.i("new fitbit bluetooth Device %s", device.toString()); } @VisibleForTesting FitbitBluetoothDevice(String bluetoothAddress, String name, BluetoothDevice device) { this.bluetoothAddress = bluetoothAddress; this.name = name; this.device = device; Timber.i("new fitbit bluetooth Device %s",device.toString()); } public String getName(){ return name; } void setName(String name) { this.name = name; notifyListenersOfPropertyChanged(); } public BluetoothDevice getBtDevice(){ return device; } public int getRssi() { return rssi; } /** * Register a listener for changes in this fitbit bluetooth device, will notify on change of name * or rssi or scanrecord * @param callback The callback to notify if the values change */ public void addDevicePropertiesChangedListener(DevicePropertiesChangedCallback callback) { this.devicePropertiesChangedListeners.add(callback); } /** * Unregister a listener for changes in this fitbit bluetooth device, will notify on change of name * or rssi or scanrecord * @param callback The callback to notify if the values change */ public void removeDevicePropertiesChangedListener(DevicePropertiesChangedCallback callback) { this.devicePropertiesChangedListeners.remove(callback); } /** * Unregister all devicePropertiesChangedListeners from this fitbit bluetooth device */ public void removeAllDevicePropertiesChangedListeners(){ this.devicePropertiesChangedListeners.clear(); } /** * Will indicate the last origin of the device, whether it was discovered in a scan, added * because it was an already connected device, or because it was a bonded device * @return The device origin of the device */ public DeviceOrigin getOrigin() { return origin; } /** * Scan Record which is useful for getting service data for further filtering * @return The scan record */ public @Nullable ScanRecord getScanRecord() { return scanRecord; } @Override public boolean equals(Object obj) { if(obj instanceof BluetoothDevice) { return ((BluetoothDevice) obj).getAddress().equals(this.bluetoothAddress); } else if (obj instanceof FitbitBluetoothDevice){ return ((FitbitBluetoothDevice)obj).getAddress().equals(this.bluetoothAddress); } else { throw new RuntimeException("Can't compare this kind of thing and FitbitBluetoothDevice"); } } /** * Overriding hashCode so that when stored in a map as a key, a new instance of the wrapper can * be used to retrieve a {@link GattConnection} that was stored with a different instance of * {@link FitbitBluetoothDevice} * @return an int representing this object */ @Override public int hashCode() { return bluetoothAddress.hashCode(); } public String getAddress() { return bluetoothAddress; } public void setRssi(int rssi) { this.rssi = rssi; notifyListenersOfPropertyChanged(); } void setScanRecord(ScanRecord scanRecord) { this.scanRecord = scanRecord; notifyListenersOfPropertyChanged(); } @Override public String toString() { return String.format(Locale.ENGLISH, "[FitbitBluetoothDevice Address: %s, Name: %s, Rssi: %s, Advertising Data: %s, Device Origin: %s]", getAddress(), getName(), getRssi(), getScanRecord() == null ? null : Bytes.byteArrayToHexString(getScanRecord().getBytes()), origin.name()); } private void notifyListenersOfPropertyChanged(){ for(DevicePropertiesChangedCallback callback : devicePropertiesChangedListeners) { callback.onBluetoothPeripheralDevicePropertiesChanged(this); } } }