package io.trygvis.android.bt;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import io.trygvis.android.Function;
import io.trygvis.android.LocalBinder;
import io.trygvis.soilmoisture.R;
import static java.util.Collections.unmodifiableCollection;
public class DefaultBtService> extends Service implements BtService {
private final static String TAG = DefaultBtService.class.getSimpleName();
private final IBinder binder = new LocalBinder<>(this);
private Handler handler = new Handler();
// -----------------------------------------------------------------------
// State
// -----------------------------------------------------------------------
private Function, A> tagConstructor;
private BluetoothManager bluetoothManager;
private BluetoothAdapter bluetoothAdapter;
private final Set> devices = new HashSet<>();
private boolean scanning = false;
// -----------------------------------------------------------------------
// BtService Implementation
// -----------------------------------------------------------------------
@Override
public boolean initialize(Function, A> tagConstructor) {
if (bluetoothManager != null) {
Log.i(TAG, "Already initialized");
return false;
}
this.tagConstructor = tagConstructor;
// 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();
return false;
}
// Initializes a Bluetooth adapter. For API level 18 and above, get a reference to
// BluetoothAdapter through BluetoothManager.
bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager == null) {
Log.e(TAG, "Unable to initialize BluetoothManager.");
return false;
}
bluetoothAdapter = bluetoothManager.getAdapter();
if (bluetoothAdapter == null) {
Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();
bluetoothManager = null;
return false;
}
Log.i(TAG, "Bluetooth initialized");
return true;
}
@Override
public void clearCache() {
}
@Override
public boolean isScanning() {
return scanning;
}
@Override
public boolean startScanning(long timeoutMs) {
Log.d(TAG, "startScanning, timeoutMs=" + timeoutMs);
if (timeoutMs > 0) {
handler.postDelayed(this::stopScanning, timeoutMs);
}
if (bluetoothAdapter.startLeScan(leScanCallback)) {
scanning = true;
sendBroadcast(createScanStarted());
return true;
}
return false;
}
@Override
public void stopScanning() {
Log.d(TAG, "stopScanning");
// This doesn't mind being called twice.
bluetoothAdapter.stopLeScan(leScanCallback);
scanning = false;
sendBroadcast(createScanStopped());
}
public BtDevice getDevice(String mac) {
BtDevice device = findDevice(mac);
if (device != null) {
return device;
}
BluetoothDevice bluetoothDevice = bluetoothAdapter.getRemoteDevice(mac);
return register(bluetoothDevice, null, null);
}
@Override
public A getTag(String address) {
return getDevice(address).getTag();
}
@Override
public Collection> getDevices() {
return unmodifiableCollection(devices);
}
@Override
public Collection getTags() {
ArrayList tags = new ArrayList<>();
for (BtDevice device : devices) {
tags.add(device.getTag());
}
return tags;
}
// -----------------------------------------------------------------------
// Scanning
// -----------------------------------------------------------------------
private BluetoothAdapter.LeScanCallback leScanCallback = (device, rssi, scanRecord) -> {
BtScanResult scanResult = new BtScanResult(scanRecord);
register(device, rssi, scanResult);
};
// -----------------------------------------------------------------------
// Service Implementation
// -----------------------------------------------------------------------
@Override
public IBinder onBind(Intent intent) {
return binder;
}
// -----------------------------------------------------------------------
// Stuff
// -----------------------------------------------------------------------
private BtDevice register(BluetoothDevice bluetoothDevice, Integer rssi, BtScanResult scanResult) {
BtDevice btDevice = findDevice(bluetoothDevice.getAddress());
if (btDevice != null) {
return btDevice;
}
Log.i(TAG, "New device: " + bluetoothDevice.getAddress());
btDevice = new BtDevice<>(this, bluetoothDevice, tagConstructor, rssi, scanResult);
devices.add(btDevice);
sendBroadcast(createNewDevice(btDevice.getAddress()));
return btDevice;
}
private Intent createScanStarted() {
return new Intent(BtServiceListenerBroadcastReceiver.INTENT_NAME).
putExtra("event", "scanStarted");
}
private Intent createScanStopped() {
return new Intent(BtServiceListenerBroadcastReceiver.INTENT_NAME).
putExtra("event", "scanStopped");
}
private Intent createNewDevice(String address) {
return new Intent(BtServiceListenerBroadcastReceiver.INTENT_NAME).
putExtra("event", "newDevice").
putExtra("address", address);
}
public static void dispatchEvent(Intent intent, BtServiceListenerBroadcastReceiver listener) {
String event = intent.getStringExtra("event");
Log.i(TAG, "Dispatching event " + intent.getAction() + "/" + event);
switch (event) {
case "newDevice":
listener.onNewDevice(intent.getStringExtra("address"));
break;
case "scanStarted":
listener.onScanStarted();
break;
case "scanStopped":
listener.onScanStopped();
break;
default:
break;
}
}
private BtDevice findDevice(String mac) {
for (BtDevice d : devices) {
if (d.getAddress().equals(mac)) {
return d;
}
}
return null;
}
}