Java tutorial
/******************************************************************************* * * This file is part of Beacon Transponder. * * Beacon Transponder is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Beacon Transponder is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Beacon Transponder. If not, see <http://www.gnu.org/licenses/>. * * Francesco Gabbrielli 2017 * ******************************************************************************/ package au.com.smarttrace.beacons; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.ScanCallback; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.os.SystemClock; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; /** * <p> * Bluetooth LE devices discovery service * * <p> * * @author Francesco Gabbrielli <francescogabbrielli at gmail> */ public class BluetoothService extends Service { private static final String TAG = BluetoothService.class.getSimpleName(); private static final long DELAY_REPEAT_SCAN = 15l; private BluetoothManager btManager; private BluetoothAdapter btAdapter; private BluetoothLeScanner btScanner; private ScanSettings btSettings; private List<ScanFilter> btFilters; public final static String ACTION_BLUETOOTH_STATUS_CHANGE = "action_bluetooth_status_change"; public final static String KEY_BLUETOOTH_STATUS = "key_bluetooth_status"; /** Start this service */ public final static String ACTION_SCAN = "action_start_service"; /** Re-start BLE scan */ public final static String ACTION_RESCAN = "action_rescan"; private LocalBinder binder; private boolean scanning; private ScheduledExecutorService executor; ScheduledFuture future; /** * Bluetooth callback to onReceive the scan results */ private ScanCallback scanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { Log.d(TAG, "ScanResult (" + callbackType + "): " + result.toString()); DeviceManager.getInstance().addDevice(getApplicationContext(), result); } @Override public void onBatchScanResults(List<ScanResult> results) { // for (ScanResult sr : results) { // Log.d("ScanResult - Results", sr.toString()); // DeviceManager.getInstance().addDevice(getApplicationContext(), sr); // } } @Override public void onScanFailed(int errorCode) { Log.w(TAG, "Scan error " + errorCode); } }; @Override public void onCreate() { super.onCreate(); initBluetooth(); binder = new LocalBinder(); scanning = false; executor = Executors.newSingleThreadScheduledExecutor(); } @Override public synchronized int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "Starting: " + intent); if (intent == null || ACTION_SCAN.equals(intent.getAction())) { connect(); } else if (ACTION_RESCAN.equals(intent.getAction())) { reconnect(); } DeviceManager.getInstance().onBluetoothOn(); return START_STICKY; } private void connect() { btScanner = btAdapter.getBluetoothLeScanner(); if (btScanner != null && btAdapter.isEnabled()) { Log.d(TAG, "START SCANNING!"); btScanner.startScan(btFilters, btSettings, scanCallback); scanning = true; sendChange(true); //XXX: workaround to avoid bluetooth scan halt AlarmManager alarm = (AlarmManager) getSystemService(ALARM_SERVICE); Intent intent = new Intent(BluetoothService.ACTION_RESCAN, null, this, BluetoothService.class); alarm.setExact(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 15000l, PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)); // future = executor.schedule(this, DELAY_REPEAT_SCAN, TimeUnit.SECONDS); } } private void reconnect() { if (btScanner != null) { disconnect(); connect(); } } private void disconnect() { if (btScanner != null && btAdapter.isEnabled()) { scanning = false; Log.d(TAG, "STOP SCANNING!"); btScanner.stopScan(scanCallback); } if (future != null && !future.isDone()) future.cancel(true); btScanner = null; sendChange(false); } public class LocalBinder extends Binder { public BluetoothService getService() { return BluetoothService.this; } } @Override public IBinder onBind(Intent intent) { Log.i(TAG, "Binding: " + intent); connect(); return binder; } @Override public boolean onUnbind(Intent intent) { Log.i(TAG, "Unbinding: " + intent); return super.onUnbind(intent); } /** Init bluetooth adapter */ private void initBluetooth() { if (btManager == null) btManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); btAdapter = btManager.getAdapter(); ScanSettings.Builder builder = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_BALANCED) .setReportDelay(0); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES); btSettings = builder.build(); btFilters = new ArrayList<>(); } @Override public synchronized void onDestroy() { Log.i(TAG, "Destroying"); disconnect(); DeviceManager.getInstance().onBluetoothOff(); scanning = false; executor.shutdownNow(); super.onDestroy(); } private void sendChange(final boolean status) { SharedPreferences prefs = getSharedPreferences(Utils.PREFS, MODE_PRIVATE); prefs.edit().putBoolean(Utils.PREF_KEY_BLUETOOTH_SERVICE_ENABLED, status).apply(); Intent intent = new Intent(ACTION_BLUETOOTH_STATUS_CHANGE); intent.putExtra(KEY_BLUETOOTH_STATUS, status); LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent); } public synchronized boolean isConnected() { return btScanner != null; } public void connect(Device device) { device.connect(); } public void disconnect(Device device) { device.disconnect(); } // public BluetoothAdapter getAdapter() { // return btAdapter; // } }