content: package com.adafruit.bluefruit.le.connect.ble;
  
  
  import android.bluetooth.BluetoothAdapter;
  import android.bluetooth.BluetoothDevice;
  import android.os.Handler;
  import android.os.Looper;
  import android.util.Log;
  
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.List;
  import java.util.UUID;
  
  public class BleDevicesScanner {
      private static final String TAG = BleDevicesScanner.class.getSimpleName();
      private static final long kScanPeriod = 20 * 1000; // scan period in milliseconds
  
      // Data
      private final BluetoothAdapter mBluetoothAdapter;
      private volatile boolean mIsScanning = false;
      private Handler mHandler;
      private List<UUID> mServicesToDiscover;
      private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
      private final LeScansPoster mLeScansPoster;
  
      //
      private BluetoothAdapter.LeScanCallback mLeScanCallback =
              new BluetoothAdapter.LeScanCallback() {
  
                  @Override
                  public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
                      synchronized (mLeScansPoster) {
                          if (mServicesToDiscover == null || !Collections.disjoint(parseUuids(scanRecord), mServicesToDiscover)) {       // only process the devices with uuids in mServicesToDiscover
                              mLeScansPoster.set(device, rssi, scanRecord);
                              mMainThreadHandler.post(mLeScansPoster);
                          }
                      }
                  }
              };
  
      public BleDevicesScanner(BluetoothAdapter adapter, UUID[] servicesToDiscover, BluetoothAdapter.LeScanCallback callback) {
          mBluetoothAdapter = adapter;
          mServicesToDiscover = servicesToDiscover == null ? null : Arrays.asList(servicesToDiscover);
          mLeScansPoster = new LeScansPoster(callback);
  
          mHandler = new Handler();
      }
  
      public void start() {
          if (kScanPeriod > 0) {
              // Stops scanning after a pre-defined scan period.
              mHandler.postDelayed(new Runnable() {
                  @Override
                  public void run() {
                      if (mIsScanning) {
                          Log.d(TAG, "Scan timer expired. Restart scan");
                          stop();
                          start();
                      }
                  }
              }, kScanPeriod);
          }
  
          mIsScanning = true;
          Log.d(TAG, "start scanning");
          mBluetoothAdapter.startLeScan(mLeScanCallback);
  
      }
  
      public void stop() {
          if (mIsScanning) {
              mHandler.removeCallbacksAndMessages(null);      // cancel pending calls to stop
              mIsScanning = false;
              mBluetoothAdapter.stopLeScan(mLeScanCallback);
              Log.d(TAG, "stop scanning");
          }
      }
  
      public boolean isScanning() {
          return mIsScanning;
      }
  
      private static class LeScansPoster implements Runnable {
          private final BluetoothAdapter.LeScanCallback leScanCallback;
  
          private BluetoothDevice device;
          private int rssi;
          private byte[] scanRecord;
  
          private LeScansPoster(BluetoothAdapter.LeScanCallback leScanCallback) {
              this.leScanCallback = leScanCallback;
          }
  
          public void set(BluetoothDevice device, int rssi, byte[] scanRecord) {
              this.device = device;
              this.rssi = rssi;
              this.scanRecord = scanRecord;
          }
  
          @Override
          public void run() {
              leScanCallback.onLeScan(device, rssi, scanRecord);
          }
      }
  
      // Filtering by custom UUID is broken in Android 4.3 and 4.4, see:
      //   http://stackoverflow.com/questions/18019161/startlescan-with-128-bit-uuids-doesnt-work-on-native-android-ble-implementation?noredirect=1#comment27879874_18019161
      // This is a workaround function from the SO thread to manually parse advertisement data.
      private List<UUID> parseUuids(byte[] advertisedData) {
          List<UUID> uuids = new ArrayList<>();
  
          ByteBuffer buffer = ByteBuffer.wrap(advertisedData).order(ByteOrder.LITTLE_ENDIAN);
          while (buffer.remaining() > 2) {
              byte length = buffer.get();
              if (length == 0) break;
  
              byte type = buffer.get();
              switch (type) {
                  case 0x02: // Partial list of 16-bit UUIDs
                  case 0x03: // Complete list of 16-bit UUIDs
                      while (length >= 2) {
                          uuids.add(UUID.fromString(String.format("%08x-0000-1000-8000-00805f9b34fb", buffer.getShort())));
                          length -= 2;
                      }
                      break;
  
                  case 0x06: // Partial list of 128-bit UUIDs
                  case 0x07: // Complete list of 128-bit UUIDs
                      while (length >= 16) {
                          long lsb = buffer.getLong();
                          long msb = buffer.getLong();
                          uuids.add(new UUID(msb, lsb));
                          length -= 16;
                      }
                      break;
  
                  default:
                      buffer.position(buffer.position() + length - 1);
                      break;
              }
          }
  
          return uuids;
      }
  }