1 /*
2  * Copyright (C) 2013 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.bluetoothlegatt;
18 
19 import android.app.Activity;
20 import android.app.ListActivity;
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothManager;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.pm.PackageManager;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.view.LayoutInflater;
30 import android.view.Menu;
31 import android.view.MenuItem;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.widget.BaseAdapter;
35 import android.widget.ListView;
36 import android.widget.TextView;
37 import android.widget.Toast;
38 
39 import java.util.ArrayList;
40 
41 /**
42  * Activity for scanning and displaying available Bluetooth LE devices.
43  */
44 public class DeviceScanActivity extends ListActivity {
45     private LeDeviceListAdapter mLeDeviceListAdapter;
46     private BluetoothAdapter mBluetoothAdapter;
47     private boolean mScanning;
48     private Handler mHandler;
49 
50     private static final int REQUEST_ENABLE_BT = 1;
51     // Stops scanning after 10 seconds.
52     private static final long SCAN_PERIOD = 10000;
53 
54     @Override
onCreate(Bundle savedInstanceState)55     public void onCreate(Bundle savedInstanceState) {
56         super.onCreate(savedInstanceState);
57         getActionBar().setTitle(R.string.title_devices);
58         mHandler = new Handler();
59 
60         // Use this check to determine whether BLE is supported on the device.  Then you can
61         // selectively disable BLE-related features.
62         if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
63             Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
64             finish();
65         }
66 
67         // Initializes a Bluetooth adapter.  For API level 18 and above, get a reference to
68         // BluetoothAdapter through BluetoothManager.
69         final BluetoothManager bluetoothManager =
70                 (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
71         mBluetoothAdapter = bluetoothManager.getAdapter();
72 
73         // Checks if Bluetooth is supported on the device.
74         if (mBluetoothAdapter == null) {
75             Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();
76             finish();
77             return;
78         }
79     }
80 
81     @Override
onCreateOptionsMenu(Menu menu)82     public boolean onCreateOptionsMenu(Menu menu) {
83         getMenuInflater().inflate(R.menu.main, menu);
84         if (!mScanning) {
85             menu.findItem(R.id.menu_stop).setVisible(false);
86             menu.findItem(R.id.menu_scan).setVisible(true);
87             menu.findItem(R.id.menu_refresh).setActionView(null);
88         } else {
89             menu.findItem(R.id.menu_stop).setVisible(true);
90             menu.findItem(R.id.menu_scan).setVisible(false);
91             menu.findItem(R.id.menu_refresh).setActionView(
92                     R.layout.actionbar_indeterminate_progress);
93         }
94         return true;
95     }
96 
97     @Override
onOptionsItemSelected(MenuItem item)98     public boolean onOptionsItemSelected(MenuItem item) {
99         switch (item.getItemId()) {
100             case R.id.menu_scan:
101                 mLeDeviceListAdapter.clear();
102                 scanLeDevice(true);
103                 break;
104             case R.id.menu_stop:
105                 scanLeDevice(false);
106                 break;
107         }
108         return true;
109     }
110 
111     @Override
onResume()112     protected void onResume() {
113         super.onResume();
114 
115         // Ensures Bluetooth is enabled on the device.  If Bluetooth is not currently enabled,
116         // fire an intent to display a dialog asking the user to grant permission to enable it.
117         if (!mBluetoothAdapter.isEnabled()) {
118             if (!mBluetoothAdapter.isEnabled()) {
119                 Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
120                 startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
121             }
122         }
123 
124         // Initializes list view adapter.
125         mLeDeviceListAdapter = new LeDeviceListAdapter();
126         setListAdapter(mLeDeviceListAdapter);
127         scanLeDevice(true);
128     }
129 
130     @Override
onActivityResult(int requestCode, int resultCode, Intent data)131     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
132         // User chose not to enable Bluetooth.
133         if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) {
134             finish();
135             return;
136         }
137         super.onActivityResult(requestCode, resultCode, data);
138     }
139 
140     @Override
onPause()141     protected void onPause() {
142         super.onPause();
143         scanLeDevice(false);
144         mLeDeviceListAdapter.clear();
145     }
146 
147     @Override
onListItemClick(ListView l, View v, int position, long id)148     protected void onListItemClick(ListView l, View v, int position, long id) {
149         final BluetoothDevice device = mLeDeviceListAdapter.getDevice(position);
150         if (device == null) return;
151         final Intent intent = new Intent(this, DeviceControlActivity.class);
152         intent.putExtra(DeviceControlActivity.EXTRAS_DEVICE_NAME, device.getName());
153         intent.putExtra(DeviceControlActivity.EXTRAS_DEVICE_ADDRESS, device.getAddress());
154         if (mScanning) {
155             mBluetoothAdapter.stopLeScan(mLeScanCallback);
156             mScanning = false;
157         }
158         startActivity(intent);
159     }
160 
scanLeDevice(final boolean enable)161     private void scanLeDevice(final boolean enable) {
162         if (enable) {
163             // Stops scanning after a pre-defined scan period.
164             mHandler.postDelayed(new Runnable() {
165                 @Override
166                 public void run() {
167                     mScanning = false;
168                     mBluetoothAdapter.stopLeScan(mLeScanCallback);
169                     invalidateOptionsMenu();
170                 }
171             }, SCAN_PERIOD);
172 
173             mScanning = true;
174             mBluetoothAdapter.startLeScan(mLeScanCallback);
175         } else {
176             mScanning = false;
177             mBluetoothAdapter.stopLeScan(mLeScanCallback);
178         }
179         invalidateOptionsMenu();
180     }
181 
182     // Adapter for holding devices found through scanning.
183     private class LeDeviceListAdapter extends BaseAdapter {
184         private ArrayList<BluetoothDevice> mLeDevices;
185         private LayoutInflater mInflator;
186 
LeDeviceListAdapter()187         public LeDeviceListAdapter() {
188             super();
189             mLeDevices = new ArrayList<BluetoothDevice>();
190             mInflator = DeviceScanActivity.this.getLayoutInflater();
191         }
192 
addDevice(BluetoothDevice device)193         public void addDevice(BluetoothDevice device) {
194             if(!mLeDevices.contains(device)) {
195                 mLeDevices.add(device);
196             }
197         }
198 
getDevice(int position)199         public BluetoothDevice getDevice(int position) {
200             return mLeDevices.get(position);
201         }
202 
clear()203         public void clear() {
204             mLeDevices.clear();
205         }
206 
207         @Override
getCount()208         public int getCount() {
209             return mLeDevices.size();
210         }
211 
212         @Override
getItem(int i)213         public Object getItem(int i) {
214             return mLeDevices.get(i);
215         }
216 
217         @Override
getItemId(int i)218         public long getItemId(int i) {
219             return i;
220         }
221 
222         @Override
getView(int i, View view, ViewGroup viewGroup)223         public View getView(int i, View view, ViewGroup viewGroup) {
224             ViewHolder viewHolder;
225             // General ListView optimization code.
226             if (view == null) {
227                 view = mInflator.inflate(R.layout.listitem_device, null);
228                 viewHolder = new ViewHolder();
229                 viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address);
230                 viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name);
231                 view.setTag(viewHolder);
232             } else {
233                 viewHolder = (ViewHolder) view.getTag();
234             }
235 
236             BluetoothDevice device = mLeDevices.get(i);
237             final String deviceName = device.getName();
238             if (deviceName != null && deviceName.length() > 0)
239                 viewHolder.deviceName.setText(deviceName);
240             else
241                 viewHolder.deviceName.setText(R.string.unknown_device);
242             viewHolder.deviceAddress.setText(device.getAddress());
243 
244             return view;
245         }
246     }
247 
248     // Device scan callback.
249     private BluetoothAdapter.LeScanCallback mLeScanCallback =
250             new BluetoothAdapter.LeScanCallback() {
251 
252         @Override
253         public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
254             runOnUiThread(new Runnable() {
255                 @Override
256                 public void run() {
257                     mLeDeviceListAdapter.addDevice(device);
258                     mLeDeviceListAdapter.notifyDataSetChanged();
259                 }
260             });
261         }
262     };
263 
264     static class ViewHolder {
265         TextView deviceName;
266         TextView deviceAddress;
267     }
268 }