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