/** * Copyright (c) 2018 Uber Technologies, Inc. * * 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.uber.rxcentralble.core.scanners; import android.annotation.TargetApi; import android.bluetooth.BluetoothAdapter; import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import com.uber.rxcentralble.ParsedAdvertisement; import com.uber.rxcentralble.RxCentralLogger; import com.uber.rxcentralble.ScanData; import com.uber.rxcentralble.ConnectionError; import com.uber.rxcentralble.Scanner; import com.uber.rxcentralble.core.CoreParsedAdvertisement; import java.util.ArrayList; import java.util.List; import android.support.annotation.Nullable; import io.reactivex.Observable; import io.reactivex.subjects.PublishSubject; import static com.uber.rxcentralble.ConnectionError.Code.SCAN_FAILED; import static com.uber.rxcentralble.ConnectionError.Code.SCAN_IN_PROGRESS; /** * Lollipop Scanner has been deprecated and should not be used. Please migrate to * {@link ThrottledLollipopScanner}. */ @TargetApi(21) @Deprecated public class LollipopScanner implements Scanner { private final ParsedAdvertisement.Factory parsedAdDataFactory; private final ScanCallback scanCallback; @Nullable private PublishSubject<ScanData> scanDataSubject; @Deprecated public LollipopScanner() { this(new CoreParsedAdvertisement.Factory()); } @Deprecated public LollipopScanner(ParsedAdvertisement.Factory parsedAdDataFactory) { this.parsedAdDataFactory = parsedAdDataFactory; this.scanCallback = getScanCallback(); } /** * Lollipop Scanner has been deprecated and should not be used. Please migrate to * {@link ThrottledLollipopScanner}. */ @Override @Deprecated public Observable<ScanData> scan() { if (scanDataSubject != null) { return Observable.error(new ConnectionError(SCAN_IN_PROGRESS)); } this.scanDataSubject = PublishSubject.create(); return scanDataSubject .doOnSubscribe( d -> { if (scanDataSubject != null) { startScan(scanDataSubject); } }) .doFinally(this::stopScan) .share(); } /** * Lollipop Scanner has been deprecated and should not be used. Please migrate to * {@link ThrottledLollipopScanner}. */ @Override @Deprecated public Observable<ScanData> scan(int scanLatency) { return scan(); } private void startScan(PublishSubject scanDataSubject) { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter != null && adapter.isEnabled()) { // Setting service uuid scan filter failing on Galaxy S6 on Android 7. // Other devices and versions of Android have additional issues with Scan Filters. // Manually filter on scan operation. Add a dummy filter to avoid Android 8.1+ enforcement // of filters during background scanning. List<ScanFilter> filters = new ArrayList<>(); ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder(); filters.add(scanFilterBuilder.build()); ScanSettings.Builder settingsBuilder = new ScanSettings.Builder(); settingsBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY); BluetoothLeScanner bleScanner = adapter.getBluetoothLeScanner(); if (bleScanner != null) { bleScanner.startScan(filters, settingsBuilder.build(), scanCallback); } else { if (RxCentralLogger.isError()) { RxCentralLogger.error("startScan - BluetoothLeScanner is null!"); } scanDataSubject.onError(new ConnectionError(SCAN_FAILED)); } } else { if (RxCentralLogger.isError()) { if (adapter == null) { RxCentralLogger.error("startScan - Default Bluetooth Adapter is null!"); } else { RxCentralLogger.error("startScan - Bluetooth Adapter is disabled."); } } scanDataSubject.onError(new ConnectionError(SCAN_FAILED)); } } private void stopScan() { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter != null && adapter.isEnabled()) { BluetoothLeScanner bleScanner = adapter.getBluetoothLeScanner(); if (bleScanner != null) { bleScanner.stopScan(scanCallback); } else if (RxCentralLogger.isError()) { RxCentralLogger.error("stopScan - BluetoothLeScanner is null!"); } } else if (RxCentralLogger.isError()) { if (adapter == null) { RxCentralLogger.error("stopScan - Default Bluetooth Adapter is null!"); } else { RxCentralLogger.error("stopScan - Bluetooth Adapter is disabled."); } } scanDataSubject = null; } private ScanCallback getScanCallback() { return new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult scanResult) { if (RxCentralLogger.isDebug()) { RxCentralLogger.debug("onScanResult - BD_ADDR: " + scanResult.getDevice().getAddress() + " | RSSI: " + scanResult.getRssi()); } handleScanData(scanResult); } @Override public void onScanFailed(int errorCode) { if (RxCentralLogger.isError()) { RxCentralLogger.error("onScanFailed - Error Code: " + errorCode); } if (scanDataSubject != null) { scanDataSubject.onError(new ConnectionError(SCAN_FAILED)); } } @Override public void onBatchScanResults(List<ScanResult> results) { for (ScanResult scanResult : results) { if (RxCentralLogger.isDebug()) { RxCentralLogger.debug("onBatchScanResults - BD_ADDR: " + scanResult.getDevice().getAddress() + " | RSSI: " + scanResult.getRssi()); } handleScanData(scanResult); } } private void handleScanData(ScanResult scanResult) { if (scanDataSubject != null) { ParsedAdvertisement parsedAdvertisement = null; if (scanResult.getScanRecord() != null) { parsedAdvertisement = parsedAdDataFactory.produce(scanResult.getScanRecord().getBytes()); } ScanData scanData = new LollipopScanData(scanResult, parsedAdvertisement); scanDataSubject.onNext(scanData); } } }; } }