Java tutorial
/* * Copyright 2014 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.uribeacon.sample; import android.app.Activity; import android.app.ListActivity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Handler; import android.os.ParcelUuid; import android.os.Parcelable; import android.os.SystemClock; import android.support.v4.widget.SwipeRefreshLayout; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.Window; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import org.uribeacon.beacon.UriBeacon; import org.uribeacon.config.ProtocolV1; import org.uribeacon.config.ProtocolV2; import org.uribeacon.scan.compat.ScanRecord; import org.uribeacon.scan.compat.ScanResult; import org.uribeacon.scan.util.RangingUtils; import org.uribeacon.widget.ScanResultAdapter; import java.util.List; import java.util.concurrent.TimeUnit; import static android.preference.PreferenceManager.getDefaultSharedPreferences; /** * Activity for scanning and displaying available Bluetooth LE devices. */ public class UriBeaconScanActivity extends ListActivity implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "UriBeaconScan"; private static final int REQUEST_ENABLE_BT = 1; // Keep devices in adapter on screen forever. private static final int DEVICE_LIFETIME_SECONDS = Integer.MAX_VALUE; private static final Handler mHandler = new Handler(); private static final long SCAN_TIME_MILLIS = TimeUnit.SECONDS.toMillis(5); private final BluetoothAdapter.LeScanCallback mLeScanCallback = new LeScanCallback(); private DeviceListAdapter mLeDeviceListAdapter; private BluetoothAdapter mBluetoothAdapter; private boolean mIsScanRunning; private SwipeRefreshLayout mSwipeWidget; private boolean mIsConfig; private Parcelable[] mScanFilterUuids; // Run when the SCAN_TIME_MILLIS has elapsed. private Runnable mScanTimeout = new Runnable() { @Override public void run() { scanLeDevice(false); } }; private boolean leScanMatches(ScanRecord scanRecord) { if (mScanFilterUuids == null) { return true; } List services = scanRecord.getServiceUuids(); if (services != null) { for (Parcelable uuid : mScanFilterUuids) { if (services.contains(uuid)) { return true; } } } return false; } /** * Start or stop scanning. Only scan for a limited amount of time defined by SCAN_TIME_MILLIS. * * @param enable Set to true to enable scanning, false to stop. */ @SuppressWarnings("deprecation") private void scanLeDevice(final boolean enable) { if (mIsScanRunning != enable) { TextView view = (TextView) findViewById(android.R.id.empty); mIsScanRunning = enable; setProgressBarIndeterminateVisibility(enable); if (enable) { view.setText(mIsConfig ? R.string.empty_config_start : R.string.empty_scan_start); // Stops scanning after the predefined scan time has elapsed. mHandler.postDelayed(mScanTimeout, SCAN_TIME_MILLIS); mLeDeviceListAdapter.clear(); mBluetoothAdapter.startLeScan(mLeScanCallback); } else { // Cancel the scan timeout callback if still active or else it may fire later. mHandler.removeCallbacks(mScanTimeout); mBluetoothAdapter.stopLeScan(mLeScanCallback); view.setText(mIsConfig ? R.string.empty_config_end : R.string.empty_scan_end); } // update the refresh/stop refresh menu invalidateOptionsMenu(); } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.uribeacon_scan_layout); mSwipeWidget = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_widget); mSwipeWidget.setOnRefreshListener(this); // Initializes list view adapter. mLeDeviceListAdapter = new DeviceListAdapter(getLayoutInflater()); setListAdapter(mLeDeviceListAdapter); // Use this check to determine whether BLE is supported on the device. Then you can // selectively disable BLE-related features. if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); finish(); } // Initializes a Bluetooth adapter. For API version 18 and above, // get a reference to BluetoothAdapter through BluetoothManager. final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter(); // Checks if Bluetooth is supported on the device. if (mBluetoothAdapter == null) { Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show(); finish(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onPrepareOptionsMenu(Menu menu) { menu.findItem(R.id.menu_stop_refresh).setVisible(mIsScanRunning); menu.findItem(R.id.menu_refresh).setVisible(!mIsScanRunning); menu.findItem(R.id.menu_config).setVisible(!mIsConfig); return super.onPrepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { Intent intent; switch (item.getItemId()) { case R.id.menu_refresh: scanLeDevice(true); break; case R.id.menu_stop_refresh: scanLeDevice(false); break; case R.id.menu_settings: intent = new Intent(this, SettingsActivity.class); startActivity(intent); break; case R.id.menu_config: { // Start another copy of this activity with different filtering options. ParcelUuid services[] = { ProtocolV1.CONFIG_SERVICE_UUID, ProtocolV2.CONFIG_SERVICE_UUID }; intent = new Intent(this, UriBeaconScanActivity.class); intent.putExtra(BluetoothDevice.EXTRA_UUID, services); startActivity(intent); } break; } return true; } @Override protected void onListItemClick(ListView l, View v, int position, long id) { ScanResultAdapter.DeviceSighting sighting = mLeDeviceListAdapter.getItem(position); // Only open configuration activity if the selected devices advertises configuration. if (mIsConfig) { ConfigActivity.startConfigureActivity(this, sighting.scanResult); // On exit from configuration, return to the main scan screen. finish(); } } @Override protected void onResume() { super.onResume(); Parcelable configServices[] = getIntent().getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID); mIsConfig = configServices != null; // If we are invoked with EXTRA_UUID then we are configuration mode so set title. getActionBar().setTitle(mIsConfig ? R.string.title_config : R.string.title_devices); // Set the scan filter to be one of three filtering modes: all beacons, UriBeacons, // UriBeacons in config mode. final String keyUriBeacon = getString(R.string.pref_key_uribeacon); final SharedPreferences prefs = getDefaultSharedPreferences(this); boolean filterUriBeacon = prefs.getBoolean(keyUriBeacon, false); if (mIsConfig) { mScanFilterUuids = configServices; } else if (filterUriBeacon) { mScanFilterUuids = new ParcelUuid[] { UriBeacon.URI_SERVICE_UUID }; } else { mScanFilterUuids = null; } scanLeDevice(true); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // User chose not to enable Bluetooth. if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) { finish(); return; } super.onActivityResult(requestCode, resultCode, data); } @Override protected void onPause() { super.onPause(); // on Pause stop any scans that are running if not in background mode scanLeDevice(false); } private int getTxPowerLevel(ScanResult scanResult) { byte[] scanRecord = scanResult.getScanRecord().getBytes(); UriBeacon uriBeacon = UriBeacon.parseFromBytes(scanRecord); if (uriBeacon != null) { int txPowerLevel = uriBeacon.getTxPowerLevel(); return uriBeacon.getTxPowerLevel(); } return RangingUtils.DEFAULT_TX_POWER_LEVEL; } @Override public void onRefresh() { // This obscures the contents, so keep in the action bar mSwipeWidget.setRefreshing(false); scanLeDevice(true); } /** * Callback for LE scan results. */ private class LeScanCallback implements BluetoothAdapter.LeScanCallback { @Override public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanBytes) { ScanRecord scanRecord = ScanRecord.parseFromBytes(scanBytes); if (leScanMatches(scanRecord)) { final ScanResult scanResult = new ScanResult(device, scanRecord, rssi, SystemClock.elapsedRealtimeNanos()); final int txPower = getTxPowerLevel(scanResult); runOnUiThread(new Runnable() { @Override public void run() { mLeDeviceListAdapter.add(scanResult, txPower, DEVICE_LIFETIME_SECONDS); } }); } } } }