content: /*
   * Copyright (C) 2015 The Android Open Source Project
   *
   * 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.example.android.bluetoothadvertisements;
  
  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 android.os.Bundle;
  import android.os.Handler;
  import android.support.v4.app.ListFragment;
  import android.util.Log;
  import android.view.LayoutInflater;
  import android.view.Menu;
  import android.view.MenuInflater;
  import android.view.MenuItem;
  import android.view.View;
  import android.view.ViewGroup;
  import android.widget.Toast;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.concurrent.TimeUnit;
  
  
  /**
   * Scans for Bluetooth Low Energy Advertisements matching a filter and displays them to the user.
   */
  public class ScannerFragment extends ListFragment {
  
      private static final String TAG = ScannerFragment.class.getSimpleName();
  
      /**
       * Stops scanning after 5 seconds.
       */
      private static final long SCAN_PERIOD = 5000;
  
      private BluetoothAdapter mBluetoothAdapter;
  
      private BluetoothLeScanner mBluetoothLeScanner;
  
      private ScanCallback mScanCallback;
  
      private ScanResultAdapter mAdapter;
  
      private Handler mHandler;
  
      /**
       * Must be called after object creation by MainActivity.
       *
       * @param btAdapter the local BluetoothAdapter
       */
      public void setBluetoothAdapter(BluetoothAdapter btAdapter) {
          this.mBluetoothAdapter = btAdapter;
          mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
      }
  
      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setHasOptionsMenu(true);
          setRetainInstance(true);
  
          // Use getActivity().getApplicationContext() instead of just getActivity() because this
          // object lives in a fragment and needs to be kept separate from the Activity lifecycle.
          //
          // We could get a LayoutInflater from the ApplicationContext but it messes with the
          // default theme, so generate it from getActivity() and pass it in separately.
          mAdapter = new ScanResultAdapter(getActivity().getApplicationContext(),
                  LayoutInflater.from(getActivity()));
          mHandler = new Handler();
  
      }
  
      @Override
      public View onCreateView(LayoutInflater inflater, ViewGroup container,
                               Bundle savedInstanceState) {
  
          final View view = super.onCreateView(inflater, container, savedInstanceState);
  
          setListAdapter(mAdapter);
  
          return view;
      }
  
      @Override
      public void onViewCreated(View view, Bundle savedInstanceState) {
          super.onViewCreated(view, savedInstanceState);
  
          getListView().setDivider(null);
          getListView().setDividerHeight(0);
  
          setEmptyText(getString(R.string.empty_list));
  
          // Trigger refresh on app's 1st load
          startScanning();
  
      }
  
      @Override
      public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
          super.onCreateOptionsMenu(menu, inflater);
          inflater.inflate(R.menu.scanner_menu, menu);
      }
  
      @Override
      public boolean onOptionsItemSelected(MenuItem item) {
  
          switch (item.getItemId()) {
              case R.id.refresh:
                  startScanning();
                  return true;
              default:
                  return super.onOptionsItemSelected(item);
          }
      }
  
      /**
       * Start scanning for BLE Advertisements (& set it up to stop after a set period of time).
       */
      public void startScanning() {
          if (mScanCallback == null) {
              Log.d(TAG, "Starting Scanning");
  
              // Will stop the scanning after a set time.
              mHandler.postDelayed(new Runnable() {
                  @Override
                  public void run() {
                      stopScanning();
                  }
              }, SCAN_PERIOD);
  
              // Kick off a new scan.
              mScanCallback = new SampleScanCallback();
              mBluetoothLeScanner.startScan(buildScanFilters(), buildScanSettings(), mScanCallback);
  
              String toastText = getString(R.string.scan_start_toast) + " "
                      + TimeUnit.SECONDS.convert(SCAN_PERIOD, TimeUnit.MILLISECONDS) + " "
                      + getString(R.string.seconds);
              Toast.makeText(getActivity(), toastText, Toast.LENGTH_LONG).show();
          } else {
              Toast.makeText(getActivity(), R.string.already_scanning, Toast.LENGTH_SHORT).show();
          }
      }
  
      /**
       * Stop scanning for BLE Advertisements.
       */
      public void stopScanning() {
          Log.d(TAG, "Stopping Scanning");
  
          // Stop the scan, wipe the callback.
          mBluetoothLeScanner.stopScan(mScanCallback);
          mScanCallback = null;
  
          // Even if no new results, update 'last seen' times.
          mAdapter.notifyDataSetChanged();
      }
  
      /**
       * Return a List of {@link ScanFilter} objects to filter by Service UUID.
       */
      private List<ScanFilter> buildScanFilters() {
          List<ScanFilter> scanFilters = new ArrayList<>();
  
          ScanFilter.Builder builder = new ScanFilter.Builder();
          // Comment out the below line to see all BLE devices around you
          builder.setServiceUuid(Constants.Service_UUID);
          scanFilters.add(builder.build());
  
          return scanFilters;
      }
  
      /**
       * Return a {@link ScanSettings} object set to use low power (to preserve battery life).
       */
      private ScanSettings buildScanSettings() {
          ScanSettings.Builder builder = new ScanSettings.Builder();
          builder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
          return builder.build();
      }
  
      /**
       * Custom ScanCallback object - adds to adapter on success, displays error on failure.
       */
      private class SampleScanCallback extends ScanCallback {
  
          @Override
          public void onBatchScanResults(List<ScanResult> results) {
              super.onBatchScanResults(results);
  
              for (ScanResult result : results) {
                  mAdapter.add(result);
              }
              mAdapter.notifyDataSetChanged();
          }
  
          @Override
          public void onScanResult(int callbackType, ScanResult result) {
              super.onScanResult(callbackType, result);
  
              mAdapter.add(result);
              mAdapter.notifyDataSetChanged();
          }
  
          @Override
          public void onScanFailed(int errorCode) {
              super.onScanFailed(errorCode);
              Toast.makeText(getActivity(), "Scan failed with error: " + errorCode, Toast.LENGTH_LONG)
                      .show();
          }
      }
  }
  