/* * Copyright (C) 2015 Joe Rogers * * 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.blakequ.bluetooth_manager_lib.scan.bluetoothcompat; import android.annotation.TargetApi; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RequiresPermission; import android.support.v4.util.SimpleArrayMap; import com.blakequ.bluetooth_manager_lib.scan.BluetoothScanManager; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; public class BluetoothLeScannerCompat { private static final String TAG = "BLeScannerCompat"; @RequiresPermission("android.permission.BLUETOOTH_ADMIN") public static void flushPendingScanResults(@NonNull BluetoothAdapter adapter, @NonNull ScanCallbackCompat callbackCompat) { IMPL.flushPendingScanResults(adapter, callbackCompat); } @RequiresPermission("android.permission.BLUETOOTH_ADMIN") public static void startScan(@NonNull BluetoothAdapter adapter, @Nullable List<ScanFilterCompat> filters, @NonNull ScanSettingsCompat settings, @NonNull ScanCallbackCompat callbackCompat) { IMPL.startScan(adapter, filters, settings, callbackCompat); } @RequiresPermission("android.permission.BLUETOOTH_ADMIN") public static void startScan(@NonNull BluetoothAdapter adapter, @NonNull ScanCallbackCompat callbackCompat) { IMPL.startScan(adapter, callbackCompat); } @RequiresPermission("android.permission.BLUETOOTH_ADMIN") public static void stopScan(@NonNull BluetoothAdapter adapter, @NonNull ScanCallbackCompat callbackCompat) { IMPL.stopScan(adapter, callbackCompat); } static final BluetoothLeScannerCompatImpl IMPL; static { final int version = Build.VERSION.SDK_INT; if (BluetoothScanManager.isAPI21ScanningDisabled()){ IMPL = new API18BluetoothLeScannerCompatImpl(); }else{ if (version >= Build.VERSION_CODES.LOLLIPOP) { IMPL = new API21BluetoothLeScannerCompatImpl(); }else { IMPL = new API18BluetoothLeScannerCompatImpl(); } } } interface BluetoothLeScannerCompatImpl { void flushPendingScanResults(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat); void startScan(BluetoothAdapter adapter, List<ScanFilterCompat> filters, ScanSettingsCompat settings, ScanCallbackCompat callbackCompat); void startScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat); void stopScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) static class API21BluetoothLeScannerCompatImpl implements BluetoothLeScannerCompatImpl { static final SimpleArrayMap<ScanCallbackCompat, API21ScanCallback> callbackMap = new SimpleArrayMap<>(); @Override public void flushPendingScanResults(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) { API21ScanCallback result = callbackMap.get(callbackCompat); if (result == null) { return; } adapter.getBluetoothLeScanner().flushPendingScanResults(result); } @Override public void startScan(BluetoothAdapter adapter, List<ScanFilterCompat> filters, ScanSettingsCompat settings, ScanCallbackCompat callbackCompat) { List<ScanFilter> scanFilters = null; if (filters != null) { scanFilters = new ArrayList<>(filters.size()); for (ScanFilterCompat filter : filters) { scanFilters.add(filter.toApi21()); } } if (settings == null) { throw new IllegalStateException("Scan settings are null"); } ScanSettings scanSettings = settings.toApi21(); adapter.getBluetoothLeScanner().startScan(scanFilters, scanSettings, registerCallback(callbackCompat)); } @Override public void startScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) { adapter.getBluetoothLeScanner().startScan(registerCallback(callbackCompat)); } @Override public void stopScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) { API21ScanCallback result = callbackMap.remove(callbackCompat); if (result == null) { return; } adapter.getBluetoothLeScanner().stopScan(result); } private API21ScanCallback registerCallback(ScanCallbackCompat callbackCompat) { API21ScanCallback result = callbackMap.get(callbackCompat); // Attempting to rescan, just let it fail deeper down. if (result != null) { return result; } result = new API21ScanCallback(callbackCompat); callbackMap.put(callbackCompat, result); return result; } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) static class API18BluetoothLeScannerCompatImpl implements BluetoothLeScannerCompatImpl { static final SimpleArrayMap<ScanCallbackCompat, API18ScanCallback> callbackMap = new SimpleArrayMap<>(); @Override public void flushPendingScanResults(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) { // no matching api new IllegalArgumentException("this method can not found"); } @SuppressWarnings("deprecation") @Override public void startScan(BluetoothAdapter adapter, List<ScanFilterCompat> filters, ScanSettingsCompat settings, ScanCallbackCompat callbackCompat) { adapter.startLeScan(registerCallback(filters, callbackCompat)); } @SuppressWarnings("deprecation") @Override public void startScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) { adapter.startLeScan(registerCallback(null, callbackCompat)); } @SuppressWarnings("deprecation") @Override public void stopScan(BluetoothAdapter adapter, ScanCallbackCompat callbackCompat) { API18ScanCallback callback = callbackMap.remove(callbackCompat); if (callback == null) { return; } adapter.stopLeScan(callback); } private API18ScanCallback registerCallback(List<ScanFilterCompat> filters, ScanCallbackCompat callbackCompat) { API18ScanCallback result = callbackMap.get(callbackCompat); // Attempting to rescan, just let it fail deeper down. if (result != null) { return result; } result = new API18ScanCallback(filters, callbackCompat); callbackMap.put(callbackCompat, result); return result; } } static class API18ScanCallback implements BluetoothAdapter.LeScanCallback { private final List<ScanFilterCompat> filters; private final WeakReference<ScanCallbackCompat> callbackCompatRef; API18ScanCallback(List<ScanFilterCompat> filters, ScanCallbackCompat callbackCompat) { this.filters = filters; this.callbackCompatRef = new WeakReference<>(callbackCompat); } @Override public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { ScanCallbackCompat callbackCompat = callbackCompatRef.get(); if (callbackCompat == null) return; ScanResultCompat result = new ScanResultCompat( device, ScanRecordCompat.parseFromBytes(scanRecord), rssi, System.currentTimeMillis()); // No filters so return any result if (filters == null) { callbackCompat.onScanResult(ScanSettingsCompat.CALLBACK_TYPE_ALL_MATCHES, result); return; } // Filters specified, so see if there is a match. for (ScanFilterCompat filter : filters) { if (filter.matches(result)) { callbackCompat.onScanResult(ScanSettingsCompat.CALLBACK_TYPE_ALL_MATCHES, result); return; } } } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) static class API21ScanCallback extends ScanCallback { private final WeakReference<ScanCallbackCompat> callbackCompatRef; API21ScanCallback(ScanCallbackCompat callbackCompat) { this.callbackCompatRef = new WeakReference<>(callbackCompat); } @Override public void onScanResult(int callbackType, ScanResult result) { ScanCallbackCompat callbackCompat = callbackCompatRef.get(); if (callbackCompat != null) { callbackCompat.onScanResult(callbackType, new ScanResultCompat(result)); } } @Override public void onBatchScanResults(List<ScanResult> results) { ScanCallbackCompat callbackCompat = callbackCompatRef.get(); if (callbackCompat == null) return; List<ScanResultCompat> compatResults = new ArrayList<>(results.size()); for (ScanResult result : results) { compatResults.add(new ScanResultCompat(result)); } callbackCompat.onBatchScanResults(compatResults); } @Override public void onScanFailed(int errorCode) { ScanCallbackCompat callbackCompat = callbackCompatRef.get(); if (callbackCompat != null) { callbackCompat.onScanFailed(errorCode); } } } }