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