/* BleDeviceDetector Copyright (c) 2015 NTT DOCOMO,INC. Released under the MIT license http://opensource.org/licenses/mit-license.php */ package org.deviceconnect.android.deviceplugin.heartrate.ble; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.os.Build; import android.os.Handler; import android.util.Log; import org.deviceconnect.android.deviceplugin.heartrate.BuildConfig; import org.deviceconnect.android.deviceplugin.heartrate.ble.adapter.NewBleDeviceAdapterImpl; import org.deviceconnect.android.deviceplugin.heartrate.ble.adapter.OldBleDeviceAdapterImpl; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** * This class to detect the BLE device. * * @author NTT DOCOMO, INC. */ public class BleDeviceDetector { /** * Tag for debugging. */ private static final String TAG = "Ble"; /** * Instance of ScheduledExecutorService. */ private ScheduledExecutorService mExecutor = Executors.newSingleThreadScheduledExecutor(); /** * ScheduledFuture of scan timer. */ private ScheduledFuture<?> mScanTimerFuture; /** * Defines a delay 1 second at first execution. */ private static final long SCAN_FIRST_WAIT_PERIOD = 1000; /** * Defines a period 10 seconds between successive executions. */ private static final long SCAN_WAIT_PERIOD = 10 * 1000; /** * Stops scanning after 1 second. */ private static final long SCAN_PERIOD = 2000; private Context mContext; private BleDeviceAdapter mBleAdapter; private Handler mHandler = new Handler(); private boolean mScanning; private BleDeviceDiscoveryListener mListener; private List<BluetoothDevice> mDevices = new CopyOnWriteArrayList<>(); private BleDeviceAdapterFactory mFactory; /** * Constructor. * * @param context context of this application */ public BleDeviceDetector(final Context context) { this(context, new BleDeviceAdapterFactory() { @Override public BleDeviceAdapter createAdapter(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return new NewBleDeviceAdapterImpl(context); } else { return new OldBleDeviceAdapterImpl(context); } } }); } /** * Constructor. * * @param context context of this application * @param factory factory class */ public BleDeviceDetector(final Context context, final BleDeviceAdapterFactory factory) { mContext = context; mFactory = factory; initialize(); } /** * Get a context. * * @return context */ public Context getContext() { return mContext; } /** * Initialize the BleDeviceDetector. */ public synchronized void initialize() { if (mBleAdapter == null) { mBleAdapter = mFactory.createAdapter(mContext); } } /** * Get a state of scan. * * @return true if scanning a BLE */ public boolean isScanning() { return mScanning; } /** * Return true if Bluetooth is currently enabled and ready for use. * * @return true if the local adapter is turned on */ public boolean isEnabled() { if (mBleAdapter == null) { return false; } if (!BleUtils.isBLEPermission(getContext())) { return false; } return mBleAdapter.isEnabled(); } /** * Sets a BluetoothDiscoveryListener. * * @param listener Notify that discovered the bluetooth device */ public void setListener(final BleDeviceDiscoveryListener listener) { mListener = listener; } /** * Start Bluetooth LE scan. */ public void startScan() { if (isEnabled()) { scanLeDevice(true); } } /** * Stop Bluetooth LE scan. */ public void stopScan() { if (isEnabled()) { scanLeDevice(false); } } /** * Gets a BluetoothDevice from address. * * @param address bluetooth address * @return instance of BluetoothDevice, null if not found device */ public BluetoothDevice getDevice(final String address) { if (mBleAdapter == null || !mBleAdapter.isEnabled()) { return null; } return mBleAdapter.getDevice(address); } /** * Gets the set of BluetoothDevice that are bonded (paired) to the local adapter. * * @return set of BluetoothDevice, or null on error */ public Set<BluetoothDevice> getBondedDevices() { if (mBleAdapter == null || !mBleAdapter.isEnabled()) { return null; } return mBleAdapter.getBondedDevices(); } public boolean checkBluetoothAddress(final String address) { return mBleAdapter.checkBluetoothAddress(address); } /** * Scan a BLE only once. * @param listener listener */ public synchronized void scanLeDeviceOnce(final BleDeviceDiscoveryListener listener) { final List<BluetoothDevice> devices = new ArrayList<>(); final BleDeviceAdapter.BleDeviceScanCallback callback = new BleDeviceAdapter.BleDeviceScanCallback() { @Override public void onLeScan(final BluetoothDevice device, final int rssi) { if (!devices.contains(device)) { devices.add(device); } } @Override public void onFail() { } }; mHandler.postDelayed(new Runnable() { @Override public void run() { if (mBleAdapter.isEnabled()) { mBleAdapter.stopScan(callback); listener.onDiscovery(devices); } } }, SCAN_PERIOD); mBleAdapter.startScan(callback); } /** * Sets the scan state of BLE. * * @param enable Start the scan if enable is true. Stop the scan if enable is false */ private synchronized void scanLeDevice(final boolean enable) { if (mBleAdapter == null || !mBleAdapter.isEnabled()) { return; } if (enable) { if (mScanning || mScanTimerFuture != null) { // scan have already started. return; } mScanning = true; mScanTimerFuture = mExecutor.scheduleAtFixedRate(new Runnable() { @Override public void run() { // Stops scanning after a pre-defined scan period. mHandler.postDelayed(new Runnable() { @Override public void run() { if (mBleAdapter.isEnabled()) { stopBleScan(); notifyBluetoothDevice(); } else { cancelScanTimer(); } } }, SCAN_PERIOD); if (mBleAdapter.isEnabled()) { mDevices.clear(); startBleScan(); } else { cancelScanTimer(); } } }, SCAN_FIRST_WAIT_PERIOD, SCAN_WAIT_PERIOD, TimeUnit.MILLISECONDS); } else { mScanning = false; stopBleScan(); cancelScanTimer(); } } /** * Stopped the scan timer. */ private synchronized void cancelScanTimer() { if (mScanTimerFuture != null) { mScanTimerFuture.cancel(true); mScanTimerFuture = null; } } /** * Starts a BLE scan. */ private void startBleScan() { try { mBleAdapter.startScan(mScanCallback); } catch (Exception e) { // Exception occurred when the BLE state is invalid. if (BuildConfig.DEBUG) { Log.e(TAG, "", e); } } } /** * Stops a BLE scan. */ private void stopBleScan() { try { mBleAdapter.stopScan(mScanCallback); } catch (Exception e) { // Exception occurred when the BLE state is invalid. if (BuildConfig.DEBUG) { Log.e(TAG, "", e); } } } /** * Notify that all bluetooth device was discovered. */ private void notifyBluetoothDevice() { if (mListener != null) { mListener.onDiscovery(mDevices); } } /** * Implement the BluetoothAdapter. */ private final BleDeviceAdapter.BleDeviceScanCallback mScanCallback = new BleDeviceAdapter.BleDeviceScanCallback() { @Override public void onLeScan(final BluetoothDevice device, final int rssi) { if (!mDevices.contains(device)) { mDevices.add(device); } } @Override public void onFail() { if (BuildConfig.DEBUG) { Log.w(TAG, "Failed to scan the ble."); } } }; /** * This listener to be notified when discovered the BLE device. */ public static interface BleDeviceDiscoveryListener { /** * Discovered the BLE device. * * @param devices BLE device list */ void onDiscovery(List<BluetoothDevice> devices); } }