Home | History | Annotate | Download | only in bluetoothadvertisements
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.bluetoothadvertisements;
     18 
     19 import android.bluetooth.BluetoothAdapter;
     20 import android.bluetooth.le.BluetoothLeScanner;
     21 import android.bluetooth.le.ScanCallback;
     22 import android.bluetooth.le.ScanFilter;
     23 import android.bluetooth.le.ScanResult;
     24 import android.bluetooth.le.ScanSettings;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.support.v4.app.ListFragment;
     28 import android.util.Log;
     29 import android.view.LayoutInflater;
     30 import android.view.Menu;
     31 import android.view.MenuInflater;
     32 import android.view.MenuItem;
     33 import android.view.View;
     34 import android.view.ViewGroup;
     35 import android.widget.Toast;
     36 
     37 import java.util.ArrayList;
     38 import java.util.List;
     39 import java.util.concurrent.TimeUnit;
     40 
     41 
     42 /**
     43  * Scans for Bluetooth Low Energy Advertisements matching a filter and displays them to the user.
     44  */
     45 public class ScannerFragment extends ListFragment {
     46 
     47     private static final String TAG = ScannerFragment.class.getSimpleName();
     48 
     49     /**
     50      * Stops scanning after 5 seconds.
     51      */
     52     private static final long SCAN_PERIOD = 5000;
     53 
     54     private BluetoothAdapter mBluetoothAdapter;
     55 
     56     private BluetoothLeScanner mBluetoothLeScanner;
     57 
     58     private ScanCallback mScanCallback;
     59 
     60     private ScanResultAdapter mAdapter;
     61 
     62     private Handler mHandler;
     63 
     64     /**
     65      * Must be called after object creation by MainActivity.
     66      *
     67      * @param btAdapter the local BluetoothAdapter
     68      */
     69     public void setBluetoothAdapter(BluetoothAdapter btAdapter) {
     70         this.mBluetoothAdapter = btAdapter;
     71         mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
     72     }
     73 
     74     @Override
     75     public void onCreate(Bundle savedInstanceState) {
     76         super.onCreate(savedInstanceState);
     77         setHasOptionsMenu(true);
     78         setRetainInstance(true);
     79 
     80         // Use getActivity().getApplicationContext() instead of just getActivity() because this
     81         // object lives in a fragment and needs to be kept separate from the Activity lifecycle.
     82         //
     83         // We could get a LayoutInflater from the ApplicationContext but it messes with the
     84         // default theme, so generate it from getActivity() and pass it in separately.
     85         mAdapter = new ScanResultAdapter(getActivity().getApplicationContext(),
     86                 LayoutInflater.from(getActivity()));
     87         mHandler = new Handler();
     88 
     89     }
     90 
     91     @Override
     92     public View onCreateView(LayoutInflater inflater, ViewGroup container,
     93                              Bundle savedInstanceState) {
     94 
     95         final View view = super.onCreateView(inflater, container, savedInstanceState);
     96 
     97         setListAdapter(mAdapter);
     98 
     99         return view;
    100     }
    101 
    102     @Override
    103     public void onViewCreated(View view, Bundle savedInstanceState) {
    104         super.onViewCreated(view, savedInstanceState);
    105 
    106         getListView().setDivider(null);
    107         getListView().setDividerHeight(0);
    108 
    109         setEmptyText(getString(R.string.empty_list));
    110 
    111         // Trigger refresh on app's 1st load
    112         startScanning();
    113 
    114     }
    115 
    116     @Override
    117     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    118         super.onCreateOptionsMenu(menu, inflater);
    119         inflater.inflate(R.menu.scanner_menu, menu);
    120     }
    121 
    122     @Override
    123     public boolean onOptionsItemSelected(MenuItem item) {
    124 
    125         switch (item.getItemId()) {
    126             case R.id.refresh:
    127                 startScanning();
    128                 return true;
    129             default:
    130                 return super.onOptionsItemSelected(item);
    131         }
    132     }
    133 
    134     /**
    135      * Start scanning for BLE Advertisements (& set it up to stop after a set period of time).
    136      */
    137     public void startScanning() {
    138         if (mScanCallback == null) {
    139             Log.d(TAG, "Starting Scanning");
    140 
    141             // Will stop the scanning after a set time.
    142             mHandler.postDelayed(new Runnable() {
    143                 @Override
    144                 public void run() {
    145                     stopScanning();
    146                 }
    147             }, SCAN_PERIOD);
    148 
    149             // Kick off a new scan.
    150             mScanCallback = new SampleScanCallback();
    151             mBluetoothLeScanner.startScan(buildScanFilters(), buildScanSettings(), mScanCallback);
    152 
    153             String toastText = getString(R.string.scan_start_toast) + " "
    154                     + TimeUnit.SECONDS.convert(SCAN_PERIOD, TimeUnit.MILLISECONDS) + " "
    155                     + getString(R.string.seconds);
    156             Toast.makeText(getActivity(), toastText, Toast.LENGTH_LONG).show();
    157         } else {
    158             Toast.makeText(getActivity(), R.string.already_scanning, Toast.LENGTH_SHORT);
    159         }
    160     }
    161 
    162     /**
    163      * Stop scanning for BLE Advertisements.
    164      */
    165     public void stopScanning() {
    166         Log.d(TAG, "Stopping Scanning");
    167 
    168         // Stop the scan, wipe the callback.
    169         mBluetoothLeScanner.stopScan(mScanCallback);
    170         mScanCallback = null;
    171 
    172         // Even if no new results, update 'last seen' times.
    173         mAdapter.notifyDataSetChanged();
    174     }
    175 
    176     /**
    177      * Return a List of {@link ScanFilter} objects to filter by Service UUID.
    178      */
    179     private List<ScanFilter> buildScanFilters() {
    180         List<ScanFilter> scanFilters = new ArrayList<>();
    181 
    182         ScanFilter.Builder builder = new ScanFilter.Builder();
    183         // Comment out the below line to see all BLE devices around you
    184         builder.setServiceUuid(Constants.Service_UUID);
    185         scanFilters.add(builder.build());
    186 
    187         return scanFilters;
    188     }
    189 
    190     /**
    191      * Return a {@link ScanSettings} object set to use low power (to preserve battery life).
    192      */
    193     private ScanSettings buildScanSettings() {
    194         ScanSettings.Builder builder = new ScanSettings.Builder();
    195         builder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);
    196         return builder.build();
    197     }
    198 
    199     /**
    200      * Custom ScanCallback object - adds to adapter on success, displays error on failure.
    201      */
    202     private class SampleScanCallback extends ScanCallback {
    203 
    204         @Override
    205         public void onBatchScanResults(List<ScanResult> results) {
    206             super.onBatchScanResults(results);
    207 
    208             for (ScanResult result : results) {
    209                 mAdapter.add(result);
    210             }
    211             mAdapter.notifyDataSetChanged();
    212         }
    213 
    214         @Override
    215         public void onScanResult(int callbackType, ScanResult result) {
    216             super.onScanResult(callbackType, result);
    217 
    218             mAdapter.add(result);
    219             mAdapter.notifyDataSetChanged();
    220         }
    221 
    222         @Override
    223         public void onScanFailed(int errorCode) {
    224             super.onScanFailed(errorCode);
    225             Toast.makeText(getActivity(), "Scan failed with error: " + errorCode, Toast.LENGTH_LONG)
    226                     .show();
    227         }
    228     }
    229 }
    230