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