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