code: /*
   * Copyright 2015 Google Inc. All rights reserved.
   *
   * 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 org.physical_web.physicalweb;
  
  import org.physical_web.collection.EddystoneBeacon;
  import org.physical_web.collection.UrlDevice;
  import org.physical_web.physicalweb.ble.ScanRecord;
  
  import android.bluetooth.BluetoothAdapter;
  import android.bluetooth.BluetoothDevice;
  import android.bluetooth.BluetoothManager;
  import android.content.Context;
  import android.os.ParcelUuid;
  import android.os.Parcelable;
  import android.webkit.URLUtil;
  
  import java.util.List;
  
  class BleUrlDeviceDiscoverer extends UrlDeviceDiscoverer
                               implements BluetoothAdapter.LeScanCallback {
    private static final String TAG = BleUrlDeviceDiscoverer.class.getSimpleName();
    private static final ParcelUuid URIBEACON_SERVICE_UUID =
        ParcelUuid.fromString("0000FED8-0000-1000-8000-00805F9B34FB");
    private static final ParcelUuid EDDYSTONE_URL_SERVICE_UUID =
        ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB");
    private BluetoothAdapter mBluetoothAdapter;
    private Parcelable[] mScanFilterUuids;
    private Context mContext;
  
    public BleUrlDeviceDiscoverer(Context context) {
      mContext = context;
      final BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService(
          Context.BLUETOOTH_SERVICE);
      mBluetoothAdapter = bluetoothManager.getAdapter();
      mScanFilterUuids = new ParcelUuid[]{URIBEACON_SERVICE_UUID, EDDYSTONE_URL_SERVICE_UUID};
    }
  
    private boolean leScanMatches(ScanRecord scanRecord) {
      if (mScanFilterUuids == null) {
        return true;
      }
      List services = scanRecord.getServiceUuids();
      if (services != null) {
        for (Parcelable uuid : mScanFilterUuids) {
          if (services.contains(uuid)) {
            return true;
          }
        }
      }
      return false;
    }
  
    @Override
    public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanBytes) {
      ScanRecord scanRecord = ScanRecord.parseFromBytes(scanBytes);
      if (!leScanMatches(scanRecord)) {
        return;
      }
  
      byte[] urlServiceData = scanRecord.getServiceData(EDDYSTONE_URL_SERVICE_UUID);
      byte[] uriServiceData = scanRecord.getServiceData(URIBEACON_SERVICE_UUID);
      if (Utils.isFatBeaconEnabled(mContext) && EddystoneBeacon.isFatBeacon(urlServiceData)) {
        String title = EddystoneBeacon.getFatBeaconTitle(urlServiceData);
        if (title.isEmpty()) {
          return;
        }
        UrlDevice urlDevice = createUrlDeviceBuilder(TAG + device.getAddress(), device.getAddress())
            .setTitle(title)
            .setDescription(mContext.getString(R.string.fatbeacon_description))
            .setDeviceType(Utils.FAT_BEACON_DEVICE_TYPE)
            .build();
        reportUrlDevice(urlDevice);
        return;
      }
  
      EddystoneBeacon beacon = EddystoneBeacon.parseFromServiceData(urlServiceData, uriServiceData);
      if (beacon == null || !URLUtil.isNetworkUrl(beacon.getUrl())) {
        return;
      }
      UrlDevice urlDevice = createUrlDeviceBuilder(TAG + device.getAddress() + beacon.getUrl(),
                                                   beacon.getUrl())
          .setRssi(rssi)
          .setTxPower(beacon.getTxPowerLevel())
          .setDeviceType(Utils.BLE_DEVICE_TYPE)
          .build();
      Utils.updateRegion(urlDevice);
      reportUrlDevice(urlDevice);
    }
  
    @Override
    @SuppressWarnings("deprecation")
    public synchronized void startScanImpl() {
      mBluetoothAdapter.startLeScan(this);
    }
  
    @Override
    @SuppressWarnings("deprecation")
    public synchronized void stopScanImpl() {
      mBluetoothAdapter.stopLeScan(this);
    }
  }
  