| 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); |
| } |
| } |
| |
| /** |
| * 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(); |
| 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(); |
| } |
| } |
| } |