下面列出了io.reactivex.functions.Cancellable#com.polidea.rxandroidble2.internal.RxBleLog 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
/**
* Return the float value interpreted from the passed byte array.
*
* @param value The byte array from which to interpret value.
* @param formatType The format type used to interpret the value.
* @param offset Offset at which the float value can be found.
* @return The value at a given offset or null if the requested offset exceeds the value size.
*/
public static Float getFloatValue(@NonNull byte[] value, @FloatFormatType int formatType, @IntRange(from = 0) int offset) {
if ((offset + getTypeLen(formatType)) > value.length) {
RxBleLog.w(
"Float formatType (0x%x) is longer than remaining bytes (%d) - returning null", formatType, value.length - offset
);
return null;
}
switch (formatType) {
case FORMAT_SFLOAT:
return bytesToFloat(value[offset], value[offset + 1]);
case FORMAT_FLOAT:
return bytesToFloat(value[offset], value[offset + 1],
value[offset + 2], value[offset + 3]);
default:
RxBleLog.w("Passed an invalid float formatType (0x%x) - returning null", formatType);
return null;
}
}
ObservableTransformer<RxBleInternalScanResult, RxBleInternalScanResult> emulateScanMode(@ScanSettings.ScanMode int scanMode) {
switch (scanMode) {
case ScanSettings.SCAN_MODE_BALANCED:
return scanModeBalancedTransformer();
case ScanSettings.SCAN_MODE_OPPORTUNISTIC:
RxBleLog.w("Cannot emulate opportunistic scan mode since it is OS dependent - fallthrough to low power");
// fallthrough
case ScanSettings.SCAN_MODE_LOW_POWER:
return scanModeLowPowerTransformer();
case ScanSettings.SCAN_MODE_LOW_LATENCY:
// return the original observable - fallthrough
default: // checkstyle always needs default
return identityTransformer();
}
}
@RequiresApi(26 /* Build.VERSION_CODES.O */)
@Override
public void scanBleDeviceInBackground(@NonNull PendingIntent callbackIntent, ScanSettings scanSettings, ScanFilter... scanFilters) {
if (Build.VERSION.SDK_INT < 26 /* Build.VERSION_CODES.O */) {
RxBleLog.w("PendingIntent based scanning is available for Android O and higher only.");
return;
}
if (!rxBleAdapterWrapper.isBluetoothEnabled()) {
RxBleLog.w("PendingIntent based scanning is available only when Bluetooth is ON.");
throw new BleScanException(BleScanException.BLUETOOTH_DISABLED);
}
RxBleLog.i("Requesting pending intent based scan.");
final List<android.bluetooth.le.ScanFilter> nativeScanFilters = scanObjectsConverter.toNativeFilters(scanFilters);
final android.bluetooth.le.ScanSettings nativeScanSettings = scanObjectsConverter.toNativeSettings(scanSettings);
final int scanStartResult = rxBleAdapterWrapper.startLeScan(nativeScanFilters, nativeScanSettings, callbackIntent);
if (scanStartResult != NO_ERROR) {
final BleScanException bleScanException = new BleScanException(scanStartResult);
RxBleLog.w(bleScanException, "Failed to start scan"); // TODO?
throw bleScanException;
}
}
@Override
BluetoothAdapter.LeScanCallback createScanCallback(final ObservableEmitter<RxBleInternalScanResultLegacy> emitter) {
return new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
if (filterUuids != null && RxBleLog.isAtLeast(LogConstants.DEBUG)) {
RxBleLog.d("%s, name=%s, rssi=%d, data=%s",
LoggerUtil.commonMacMessage(device.getAddress()),
device.getName(),
rssi,
LoggerUtil.bytesToHex(scanRecord)
);
}
if (filterUuids == null || uuidUtil.extractUUIDs(scanRecord).containsAll(filterUuids)) {
emitter.onNext(new RxBleInternalScanResultLegacy(device, rssi, scanRecord));
}
}
};
}
@Override
BluetoothAdapter.LeScanCallback createScanCallback(final ObservableEmitter<RxBleInternalScanResult> emitter) {
return new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
if (!scanFilterMatcher.isEmpty() && RxBleLog.isAtLeast(LogConstants.DEBUG) && RxBleLog.getShouldLogScannedPeripherals()) {
RxBleLog.d("%s, name=%s, rssi=%d, data=%s",
LoggerUtil.commonMacMessage(device.getAddress()),
device.getName(),
rssi,
LoggerUtil.bytesToHex(scanRecord)
);
}
final RxBleInternalScanResult internalScanResult = scanResultCreator.create(device, rssi, scanRecord);
if (scanFilterMatcher.matches(internalScanResult)) {
emitter.onNext(internalScanResult);
}
}
};
}
@BleScanException.Reason
static int errorCodeToBleErrorCode(int errorCode) {
switch (errorCode) {
case ScanCallback.SCAN_FAILED_ALREADY_STARTED:
return BleScanException.SCAN_FAILED_ALREADY_STARTED;
case ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
return BleScanException.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED;
case ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED:
return BleScanException.SCAN_FAILED_FEATURE_UNSUPPORTED;
case ScanCallback.SCAN_FAILED_INTERNAL_ERROR:
return BleScanException.SCAN_FAILED_INTERNAL_ERROR;
case 5: // ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES
return BleScanException.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES;
default:
RxBleLog.w("Encountered unknown scanning error code: %d -> check android.bluetooth.le.ScanCallback");
return BleScanException.UNKNOWN_ERROR_CODE;
}
}
@Override
final protected void protectedRun(final ObservableEmitter<SCAN_RESULT_TYPE> emitter, QueueReleaseInterface queueReleaseInterface) {
final SCAN_CALLBACK_TYPE scanCallback = createScanCallback(emitter);
try {
emitter.setCancellable(new Cancellable() {
@Override
public void cancel() {
RxBleLog.i("Scan operation is requested to stop.");
stopScan(rxBleAdapterWrapper, scanCallback);
}
});
RxBleLog.i("Scan operation is requested to start.");
boolean startLeScanStatus = startScan(rxBleAdapterWrapper, scanCallback);
if (!startLeScanStatus) {
emitter.tryOnError(new BleScanException(BleScanException.BLUETOOTH_CANNOT_START));
}
} catch (Throwable throwable) {
RxBleLog.w(throwable, "Error while calling the start scan function");
emitter.tryOnError(new BleScanException(BleScanException.BLUETOOTH_CANNOT_START, throwable));
} finally {
queueReleaseInterface.release();
}
}
@TargetApi(21 /* Build.VERSION_CODES.LOLLIPOP */)
public void stopLeScan(ScanCallback scanCallback) {
if (!bluetoothAdapter.isEnabled()) {
// this situation seems to be a problem since API 29
RxBleLog.v(
"BluetoothAdapter is disabled, calling BluetoothLeScanner.stopScan(ScanCallback) may cause IllegalStateException"
);
// if stopping the scan is not possible due to BluetoothAdapter turned off then it is probably stopped anyway
return;
}
final BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
if (bluetoothLeScanner == null) {
RxBleLog.w(
"Cannot call BluetoothLeScanner.stopScan(ScanCallback) on 'null' reference; BluetoothAdapter.isEnabled() == %b",
bluetoothAdapter.isEnabled()
);
// if stopping the scan is not possible due to BluetoothLeScanner not accessible then it is probably stopped anyway
// this should not happen since the check for BluetoothAdapter.isEnabled() has been added above. This situation was only
// observed when the adapter was disabled
return;
}
bluetoothLeScanner.stopScan(scanCallback);
}
@NonNull
private String propertyToString(@BluetoothGattCharacteristicProperty int property) {
if (property == propertyRead) {
return "READ";
} else if (property == propertyWrite) {
return "WRITE";
} else if (property == propertyWriteNoResponse) {
return "WRITE_NO_RESPONSE";
} else if (property == propertySignedWrite) {
return "SIGNED_WRITE";
} else if (property == propertyIndicate) {
return "INDICATE";
} else if (property == propertyBroadcast) {
return "BROADCAST";
} else if (property == propertyNotify) {
return "NOTIFY";
} else if (property == 0) {
return "";
} else {
// This case is unicorny and only left for my peace of mind. The property is matched against known dictionary before
// being passed here, so it MUST match one of the values [MK]
RxBleLog.e("Unknown property specified (%d)", property);
return "UNKNOWN (" + property + " -> check android.bluetooth.BluetoothGattCharacteristic)";
}
}
@Override
public Observable<ScanResult> scanBleDevices(final ScanSettings scanSettings, final ScanFilter... scanFilters) {
return Observable.defer(new Callable<ObservableSource<? extends ScanResult>>() {
@Override
public Observable<ScanResult> call() {
scanPreconditionVerifier.verify(scanSettings.shouldCheckLocationProviderState());
final ScanSetup scanSetup = scanSetupBuilder.build(scanSettings, scanFilters);
final Operation<RxBleInternalScanResult> scanOperation = scanSetup.scanOperation;
return operationQueue.queue(scanOperation)
.unsubscribeOn(bluetoothInteractionScheduler)
.compose(scanSetup.scanOperationBehaviourEmulatorTransformer)
.map(internalToExternalScanResultMapFunction)
.doOnNext(new Consumer<ScanResult>() {
@Override
public void accept(ScanResult scanResult) {
if (RxBleLog.getShouldLogScannedPeripherals()) RxBleLog.i("%s", scanResult);
}
})
.mergeWith(RxBleClientImpl.this.<ScanResult>bluetoothAdapterOffExceptionObservable());
}
});
}
/**
* Return the integer value interpreted from the passed byte array.
*
* <p>The formatType parameter determines how the value
* is to be interpreted. For example, setting formatType to
* {@link #FORMAT_UINT16} specifies that the first two bytes of the
* characteristic value at the given offset are interpreted to generate the
* return value.
*
* @param value The byte array from which to interpret value.
* @param formatType The format type used to interpret the value.
* @param offset Offset at which the integer value can be found.
* @return The value at a given offset or null if offset exceeds value size.
*/
public static Integer getIntValue(@NonNull byte[] value, @IntFormatType int formatType, @IntRange(from = 0) int offset) {
if ((offset + getTypeLen(formatType)) > value.length) {
RxBleLog.w(
"Int formatType (0x%x) is longer than remaining bytes (%d) - returning null", formatType, value.length - offset
);
return null;
}
switch (formatType) {
case FORMAT_UINT8:
return unsignedByteToInt(value[offset]);
case FORMAT_UINT16:
return unsignedBytesToInt(value[offset], value[offset + 1]);
case FORMAT_UINT32:
return unsignedBytesToInt(value[offset], value[offset + 1],
value[offset + 2], value[offset + 3]);
case FORMAT_SINT8:
return unsignedToSigned(unsignedByteToInt(value[offset]), 8);
case FORMAT_SINT16:
return unsignedToSigned(unsignedBytesToInt(value[offset],
value[offset + 1]), 16);
case FORMAT_SINT32:
return unsignedToSigned(unsignedBytesToInt(value[offset],
value[offset + 1], value[offset + 2], value[offset + 3]), 32);
default:
RxBleLog.w("Passed an invalid integer formatType (0x%x) - returning null", formatType);
return null;
}
}
/**
* Return the string value interpreted from the passed byte array.
*
* @param offset Offset at which the string value can be found.
* @return The value at a given offset
*/
public static String getStringValue(@NonNull byte[] value, @IntRange(from = 0) int offset) {
if (offset > value.length) {
RxBleLog.w("Passed offset that exceeds the length of the byte array - returning null");
return null;
}
byte[] strBytes = new byte[value.length - offset];
for (int i = 0; i != (value.length - offset); ++i) {
strBytes[i] = value[offset + i];
}
return new String(strBytes);
}
@Inject
public ClientOperationQueueImpl(@Named(ClientComponent.NamedSchedulers.BLUETOOTH_INTERACTION) final Scheduler callbackScheduler) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
final FIFORunnableEntry<?> entry = queue.take();
final Operation<?> operation = entry.operation;
final long startedAtTime = System.currentTimeMillis();
logOperationStarted(operation);
logOperationRunning(operation);
/*
* Calling bluetooth calls before the previous one returns in a callback usually finishes with a failure
* status. Below a QueueSemaphore is passed to the RxBleCustomOperation and is meant to be released
* at appropriate time when the next operation should be able to start successfully.
*/
final QueueSemaphore clientOperationSemaphore = new QueueSemaphore();
entry.run(clientOperationSemaphore, callbackScheduler);
clientOperationSemaphore.awaitRelease();
logOperationFinished(operation, startedAtTime, System.currentTimeMillis());
} catch (InterruptedException e) {
RxBleLog.e(e, "Error while processing client operation queue");
}
}
}
}).start();
}
@Override
public synchronized void awaitRelease() throws InterruptedException {
while (!isReleased.get()) {
try {
wait();
} catch (InterruptedException e) {
if (!isReleased.get()) {
RxBleLog.w(e, "Queue's awaitRelease() has been interrupted abruptly "
+ "while it wasn't released by the release() method.");
}
}
}
}
@Override
public synchronized void terminate(BleException disconnectException) {
if (this.disconnectionException != null) {
// already terminated
return;
}
RxBleLog.d(disconnectException, "Connection operations queue to be terminated (%s)", commonMacMessage(deviceMacAddress));
shouldRun = false;
disconnectionException = disconnectException;
runnableFuture.cancel(true);
}
@RequiresApi(26 /* Build.VERSION_CODES.O */)
@Override
public void stopBackgroundBleScan(@NonNull PendingIntent callbackIntent) {
if (Build.VERSION.SDK_INT < 26 /* Build.VERSION_CODES.O */) {
RxBleLog.w("PendingIntent based scanning is available for Android O and higher only.");
return;
}
if (!rxBleAdapterWrapper.isBluetoothEnabled()) {
RxBleLog.w("PendingIntent based scanning is available only when Bluetooth is ON.");
return;
}
RxBleLog.i("Stopping pending intent based scan.");
rxBleAdapterWrapper.stopLeScan(callbackIntent);
}
@RequiresApi(21 /* Build.VERSION_CODES.LOLLIPOP */)
private static ScanCallbackType toScanCallbackType(int callbackType) {
switch (callbackType) {
case ScanSettings.CALLBACK_TYPE_ALL_MATCHES:
return CALLBACK_TYPE_ALL_MATCHES;
case ScanSettings.CALLBACK_TYPE_FIRST_MATCH:
return CALLBACK_TYPE_FIRST_MATCH;
case ScanSettings.CALLBACK_TYPE_MATCH_LOST:
return CALLBACK_TYPE_MATCH_LOST;
default:
RxBleLog.w("Unknown callback type %d -> check android.bluetooth.le.ScanSettings", callbackType);
return CALLBACK_TYPE_UNKNOWN;
}
}
@RequiresApi(21 /* Build.VERSION_CODES.LOLLIPOP */)
@Override
public ScanSetup build(ScanSettings scanSettings, ScanFilter... scanFilters) {
boolean areFiltersSpecified = areFiltersSpecified(scanFilters);
boolean isFilteringCallbackType = scanSettings.getCallbackType() != ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
ObservableTransformer<RxBleInternalScanResult, RxBleInternalScanResult> resultTransformer = ObservableUtil.identityTransformer();
ScanSettings resultScanSettings = scanSettings;
// native matching (when a device is first seen or no longer seen) does not work with no filters specified —
// see https://issuetracker.google.com/issues/37127640
// so we will use a callback type that will work and emulate the desired behaviour
boolean shouldEmulateCallbackType = isFilteringCallbackType && !areFiltersSpecified;
if (shouldEmulateCallbackType) {
RxBleLog.d("ScanSettings.callbackType != CALLBACK_TYPE_ALL_MATCHES but no (or only empty) filters are specified. "
+ "Falling back to callbackType emulation.");
resultTransformer = scanSettingsEmulator.emulateCallbackType(scanSettings.getCallbackType());
resultScanSettings = scanSettings.copyWithCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES);
}
return new ScanSetup(
new ScanOperationApi21(
rxBleAdapterWrapper,
internalScanResultCreator,
androidScanObjectsConverter,
resultScanSettings,
new EmulatedScanFilterMatcher(),
scanFilters),
resultTransformer
);
}
@Override
boolean startScan(RxBleAdapterWrapper rxBleAdapterWrapper, BluetoothAdapter.LeScanCallback scanCallback) {
if (this.filterUuids == null) {
RxBleLog.d("No library side filtering —> debug logs of scanned devices disabled");
}
return rxBleAdapterWrapper.startLegacyLeScan(scanCallback);
}
@Override
boolean startScan(RxBleAdapterWrapper rxBleAdapterWrapper, BluetoothAdapter.LeScanCallback scanCallback) {
if (this.scanFilterMatcher.isEmpty()) {
RxBleLog.d("No library side filtering —> debug logs of scanned devices disabled");
}
return rxBleAdapterWrapper.startLegacyLeScan(scanCallback);
}
@Override
boolean startScan(RxBleAdapterWrapper rxBleAdapterWrapper, ScanCallback scanCallback) {
if (this.emulatedScanFilterMatcher.isEmpty()) {
RxBleLog.d("No library side filtering —> debug logs of scanned devices disabled");
}
rxBleAdapterWrapper.startLeScan(
androidScanObjectsConverter.toNativeFilters(scanFilters),
androidScanObjectsConverter.toNativeSettings(scanSettings),
scanCallback
);
return true;
}
void writeData(byte[] bytesBatch, IntSupplier batchIndexGetter) {
if (RxBleLog.isAtLeast(LogConstants.DEBUG)) {
RxBleLog.d("Writing batch #%04d: %s", batchIndexGetter.get(), LoggerUtil.bytesToHex(bytesBatch));
}
bluetoothGattCharacteristic.setValue(bytesBatch);
final boolean success = bluetoothGatt.writeCharacteristic(bluetoothGattCharacteristic);
if (!success) {
throw new BleGattCannotStartException(bluetoothGatt, BleGattOperationType.CHARACTERISTIC_LONG_WRITE);
}
}
public static void logCallback(String callbackName, BluetoothGatt gatt, int status, BluetoothGattCharacteristic characteristic,
boolean valueMatters) {
if (!RxBleLog.isAtLeast(LogConstants.INFO)) {
return;
}
AttributeLogWrapper value = new AttributeLogWrapper(characteristic.getUuid(), characteristic.getValue(), valueMatters);
RxBleLog.i(commonMacMessage(gatt) + commonCallbackMessage() + commonStatusMessage() + commonValueMessage(),
callbackName, status, value);
}
public static void logCallback(String callbackName, BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
boolean valueMatters) {
if (!RxBleLog.isAtLeast(LogConstants.INFO)) {
return;
}
AttributeLogWrapper value = new AttributeLogWrapper(characteristic.getUuid(), characteristic.getValue(), valueMatters);
RxBleLog.i(commonMacMessage(gatt) + commonCallbackMessage() + commonValueMessage(), callbackName, value);
}
public static void logCallback(String callbackName, BluetoothGatt gatt, int status, BluetoothGattDescriptor descriptor,
boolean valueMatters) {
if (!RxBleLog.isAtLeast(LogConstants.INFO)) {
return;
}
AttributeLogWrapper value = new AttributeLogWrapper(descriptor.getUuid(), descriptor.getValue(), valueMatters);
RxBleLog.i(commonMacMessage(gatt) + commonCallbackMessage() + commonStatusMessage() + commonValueMessage(),
callbackName, status, value);
}
public static void logCallback(String callbackName, BluetoothGatt gatt, int status, int value) {
if (!RxBleLog.isAtLeast(LogConstants.INFO)) {
return;
}
RxBleLog.i(commonMacMessage(gatt) + commonCallbackMessage() + commonStatusMessage() + commonValueMessage(),
callbackName, status, value);
}
public static void logConnectionUpdateCallback(String callbackName, BluetoothGatt gatt,
int status, int interval, int latency, int timeout) {
if (!RxBleLog.isAtLeast(LogConstants.INFO)) {
return;
}
String customValueMessage = ", interval=%d (%.2f ms), latency=%d, timeout=%d (%.0f ms)";
RxBleLog.i(commonMacMessage(gatt) + commonCallbackMessage() + commonStatusMessage() + customValueMessage,
callbackName, status, interval, interval * 1.25f, latency, timeout, timeout * 10f);
}
public static String commonMacMessage(String macAddress) {
if (macAddress == null) return "MAC=null";
int logSetting = RxBleLog.getMacAddressLogSetting();
switch (logSetting) {
case LogConstants.MAC_ADDRESS_TRUNCATED:
macAddress = macAddress.substring(0, 15) + "XX";
break;
case LogConstants.NONE:
macAddress = "XX:XX:XX:XX:XX:XX";
case LogConstants.MAC_ADDRESS_FULL:
default:
}
return String.format("MAC='%s'", macAddress);
}
public static String getUuidToLog(UUID uuid) {
int uuidLogSetting = RxBleLog.getUuidLogSetting();
if (uuidLogSetting == LogConstants.UUIDS_FULL) {
return uuid.toString();
}
return "...";
}
private BluetoothGatt connectGattCompat(BluetoothGattCallback bluetoothGattCallback, BluetoothDevice device, boolean autoConnect) {
RxBleLog.v("Connecting without reflection");
if (Build.VERSION.SDK_INT >= 23 /* Build.VERSION_CODES.M */) {
return device.connectGatt(context, autoConnect, bluetoothGattCallback, TRANSPORT_LE);
} else {
return device.connectGatt(context, autoConnect, bluetoothGattCallback);
}
}