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 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 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 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 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 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 141 protected void onPause() { 142 super.onPause(); 143 scanLeDevice(false); 144 mLeDeviceListAdapter.clear(); 145 } 146 147 @Override 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 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 187 public LeDeviceListAdapter() { 188 super(); 189 mLeDevices = new ArrayList<BluetoothDevice>(); 190 mInflator = DeviceScanActivity.this.getLayoutInflater(); 191 } 192 193 public void addDevice(BluetoothDevice device) { 194 if(!mLeDevices.contains(device)) { 195 mLeDevices.add(device); 196 } 197 } 198 199 public BluetoothDevice getDevice(int position) { 200 return mLeDevices.get(position); 201 } 202 203 public void clear() { 204 mLeDevices.clear(); 205 } 206 207 @Override 208 public int getCount() { 209 return mLeDevices.size(); 210 } 211 212 @Override 213 public Object getItem(int i) { 214 return mLeDevices.get(i); 215 } 216 217 @Override 218 public long getItemId(int i) { 219 return i; 220 } 221 222 @Override 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 }