mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-26 10:35:50 +01:00
Core: added first iteration of BLE intent API
Core: added BLE GATT Client Core: fixed string comparisons Core: unified intent APIs Core: fixed notification and publication bugs Core: extracted BLE Intent API logic Core: introduced finer BLE API permissions Core: use device name when adding test device through DiscoveryActivity Core: avoid reporting same device state multiple times Core: read firmware version on GATT Client connect connect Core: use onSendConfiguration instead of direct subscription Core: I18N for GATT API settings Core: I18N for GATT API settings Core: only show BLE API settings for BLE devices Core: refactored intent handler Core: extracted ble API to own class Core: fixed unitialized BLE Api BLE Intent API: I18N BLE Intent API: refactoring BLE Intent API: added back legacy API BLE Intent API: removed new DEVICE_CHANGED and CONNECT endpoints BLE Intent API: removed redundant ble api setting
This commit is contained in:
parent
0745a374a5
commit
aae1d40d54
@ -621,7 +621,7 @@ public class DebugActivity extends AbstractGBActivity {
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
createTestDevice(DebugActivity.this, selectedTestDeviceKey, selectedTestDeviceMAC);
|
||||
createTestDevice(DebugActivity.this, selectedTestDeviceKey, selectedTestDeviceMAC, null);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
|
||||
@ -1037,16 +1037,19 @@ public class DebugActivity extends AbstractGBActivity {
|
||||
spinner.setOnItemSelectedListener(new CustomOnDeviceSelectedListener());
|
||||
}
|
||||
|
||||
public static void createTestDevice(Context context, long deviceKey, String deviceMac) {
|
||||
public static void createTestDevice(Context context, long deviceKey, String deviceMac, String deviceName) {
|
||||
if (deviceKey == SELECT_DEVICE) {
|
||||
return;
|
||||
}
|
||||
DeviceType deviceType = DeviceType.values()[(int) deviceKey];
|
||||
String deviceName = deviceType.name();
|
||||
int deviceNameResource = deviceType.getDeviceCoordinator().getDeviceNameResource();
|
||||
if(deviceNameResource != 0){
|
||||
deviceName = context.getString(deviceNameResource);
|
||||
}
|
||||
if(deviceName == null) {
|
||||
int deviceNameResource = deviceType.getDeviceCoordinator().getDeviceNameResource();
|
||||
if(deviceNameResource == 0){
|
||||
deviceName = deviceType.name();
|
||||
}else {
|
||||
deviceName = context.getString(deviceNameResource);
|
||||
}
|
||||
};
|
||||
try (
|
||||
DBHandler db = GBApplication.acquireDB()) {
|
||||
DaoSession daoSession = db.getDaoSession();
|
||||
@ -1221,6 +1224,9 @@ public class DebugActivity extends AbstractGBActivity {
|
||||
TreeMap <String, Pair<Long, Integer>> sortedMap = new TreeMap<>(newMap);
|
||||
newMap = new LinkedHashMap<>(1);
|
||||
newMap.put(app.getString(R.string.widget_settings_select_device_title), new Pair(SELECT_DEVICE, R.drawable.ic_device_unknown));
|
||||
newMap.put(app.getString(R.string.devicetype_scannable), new Pair((long) DeviceType.SCANNABLE.ordinal(), R.drawable.ic_device_scannable));
|
||||
newMap.put(app.getString(R.string.devicetype_ble_gatt_client), new Pair((long) DeviceType.BLE_GATT_CLIENT.ordinal(), R.drawable.ic_device_scannable));
|
||||
|
||||
newMap.putAll(sortedMap);
|
||||
|
||||
return newMap;
|
||||
|
@ -526,4 +526,9 @@ public class DeviceSettingsPreferenceConst {
|
||||
|
||||
public static final String PREF_CYCLING_SENSOR_PERSISTENCE_INTERVAL = "pref_cycling_persistence_interval";
|
||||
public static final String PREF_CYCLING_SENSOR_WHEEL_DIAMETER = "pref_cycling_wheel_diameter";
|
||||
|
||||
public static final String PREFS_KEY_DEVICE_BLE_API_DEVICE_STATE = "prefs_device_ble_api_state";
|
||||
public static final String PREFS_KEY_DEVICE_BLE_API_DEVICE_READ_WRITE = "prefs_device_ble_api_characteristic_read_write";
|
||||
public static final String PREFS_KEY_DEVICE_BLE_API_DEVICE_NOTIFY = "prefs_device_ble_api_characteristic_notify";
|
||||
public static final String PREFS_KEY_DEVICE_BLE_API_PACKAGE = "prefs_device_ble_api_package";
|
||||
}
|
||||
|
@ -827,6 +827,11 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
|
||||
|
||||
addPreferenceHandlerFor(PREF_SPEAK_NOTIFICATIONS_FOCUS_EXCLUSIVE);
|
||||
|
||||
addPreferenceHandlerFor(PREFS_KEY_DEVICE_BLE_API_DEVICE_STATE);
|
||||
addPreferenceHandlerFor(PREFS_KEY_DEVICE_BLE_API_DEVICE_READ_WRITE);
|
||||
addPreferenceHandlerFor(PREFS_KEY_DEVICE_BLE_API_DEVICE_NOTIFY);
|
||||
addPreferenceHandlerFor(PREFS_KEY_DEVICE_BLE_API_PACKAGE);
|
||||
|
||||
addPreferenceHandlerFor("lock");
|
||||
|
||||
String sleepTimeState = prefs.getString(PREF_SLEEP_TIME, PREF_DO_NOT_DISTURB_OFF);
|
||||
@ -1307,6 +1312,12 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
|
||||
DeviceSpecificSettingsScreen.DEVELOPER,
|
||||
R.xml.devicesettings_settings_third_party_apps
|
||||
);
|
||||
if(coordinator.getConnectionType().usesBluetoothLE()) {
|
||||
deviceSpecificSettings.addRootScreen(
|
||||
DeviceSpecificSettingsScreen.DEVELOPER,
|
||||
R.xml.devicesettings_ble_api
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final DeviceSpecificSettingsCustomizer deviceSpecificSettingsCustomizer = coordinator.getDeviceSpecificSettingsCustomizer(device);
|
||||
|
@ -748,7 +748,12 @@ public class DiscoveryActivityV2 extends AbstractGBActivity implements AdapterVi
|
||||
.setView(linearLayout)
|
||||
.setPositiveButton(R.string.ok, (dialog, which) -> {
|
||||
if (selectedUnsupportedDeviceKey != DebugActivity.SELECT_DEVICE) {
|
||||
DebugActivity.createTestDevice(DiscoveryActivityV2.this, selectedUnsupportedDeviceKey, deviceCandidate.getMacAddress());
|
||||
DebugActivity.createTestDevice(
|
||||
DiscoveryActivityV2.this,
|
||||
selectedUnsupportedDeviceKey,
|
||||
deviceCandidate.getMacAddress(),
|
||||
deviceCandidate.getName()
|
||||
);
|
||||
finish();
|
||||
}
|
||||
})
|
||||
|
@ -257,6 +257,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.watchs1pro.XiaomiWatc
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.xiaomi.watchs3.XiaomiWatchS3Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.xwatch.XWatchCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimeCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.gatt_client.BleGattClientCoordinator;
|
||||
|
||||
/**
|
||||
* For every supported device, a device type constant must exist.
|
||||
@ -500,6 +501,7 @@ public enum DeviceType {
|
||||
COLMI_R06(ColmiR06Coordinator.class),
|
||||
SCANNABLE(ScannableDeviceCoordinator.class),
|
||||
CYCLING_SENSOR(CyclingSensorCoordinator.class),
|
||||
BLE_GATT_CLIENT(BleGattClientCoordinator.class),
|
||||
TEST(TestDeviceCoordinator.class);
|
||||
|
||||
private DeviceCoordinator coordinator;
|
||||
|
@ -101,7 +101,9 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLEScanService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BleIntentApi;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.receivers.AutoConnectIntervalReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBAutoFetchReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.EmojiConverter;
|
||||
@ -289,16 +291,17 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
"com.spotify.music.playbackstatechanged"
|
||||
};
|
||||
|
||||
private final String COMMAND_BLUETOOTH_CONNECT = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_CONNECT";
|
||||
private final String ACTION_DEVICE_CONNECTED = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_CONNECTED";
|
||||
private final String ACTION_DEVICE_SCANNED = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_SCANNED";
|
||||
private final int NOTIFICATIONS_CACHE_MAX = 10; // maximum amount of notifications to cache per device while disconnected
|
||||
private boolean allowBluetoothIntentApi = false;
|
||||
private boolean reconnectViaScan = GBPrefs.RECONNECT_SCAN_DEFAULT;
|
||||
|
||||
private final String API_LEGACY_COMMAND_BLUETOOTH_CONNECT = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_CONNECT";
|
||||
private final String API_LEGACY_ACTION_DEVICE_CONNECTED = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_CONNECTED";
|
||||
private final String API_LEGACY_ACTION_DEVICE_SCANNED = "nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_SCANNED";
|
||||
|
||||
private void sendDeviceAPIBroadcast(String address, String action){
|
||||
if(!allowBluetoothIntentApi){
|
||||
GB.log("not sending API event due to settings", GB.INFO, null);
|
||||
LOG.debug("not sending API event due to settings");
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(action);
|
||||
@ -308,14 +311,14 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
}
|
||||
|
||||
private void sendDeviceConnectedBroadcast(String address){
|
||||
sendDeviceAPIBroadcast(address, ACTION_DEVICE_CONNECTED);
|
||||
sendDeviceAPIBroadcast(address, API_LEGACY_ACTION_DEVICE_CONNECTED);
|
||||
}
|
||||
|
||||
BroadcastReceiver bluetoothCommandReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()){
|
||||
case COMMAND_BLUETOOTH_CONNECT:
|
||||
case API_LEGACY_COMMAND_BLUETOOTH_CONNECT:
|
||||
if(!allowBluetoothIntentApi){
|
||||
GB.log("Connection API not allowed in settings", GB.ERROR, null);
|
||||
return;
|
||||
@ -333,6 +336,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
if(isDeviceConnected(address)){
|
||||
GB.log(String.format("device %s already connected", address), GB.INFO, null);
|
||||
sendDeviceConnectedBroadcast(address);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -404,11 +408,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
GBDevice.DeviceUpdateSubject subject = (GBDevice.DeviceUpdateSubject) intent.getSerializableExtra(GBDevice.EXTRA_UPDATE_SUBJECT);
|
||||
|
||||
if(subject == GBDevice.DeviceUpdateSubject.DEVICE_STATE && device.isInitialized()){
|
||||
LOG.debug("device state update reason");
|
||||
sendDeviceConnectedBroadcast(device.getAddress());
|
||||
sendCachedNotifications(device);
|
||||
}else if(subject == GBDevice.DeviceUpdateSubject.CONNECTION_STATE && (device.getState() == GBDevice.State.SCANNED)){
|
||||
sendDeviceAPIBroadcast(device.getAddress(), ACTION_DEVICE_SCANNED);
|
||||
}else if(subject == GBDevice.DeviceUpdateSubject.DEVICE_STATE && (device.getState() == GBDevice.State.SCANNED)){
|
||||
sendDeviceAPIBroadcast(device.getAddress(), API_LEGACY_ACTION_DEVICE_SCANNED);
|
||||
}
|
||||
}else if(BLEScanService.EVENT_DEVICE_FOUND.equals(action)){
|
||||
String deviceAddress = intent.getStringExtra(BLEScanService.EXTRA_DEVICE_ADDRESS);
|
||||
@ -449,14 +452,14 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
}
|
||||
|
||||
target.setState(GBDevice.State.SCANNED);
|
||||
target.sendDeviceUpdateIntent(DeviceCommunicationService.this, GBDevice.DeviceUpdateSubject.CONNECTION_STATE);
|
||||
target.sendDeviceUpdateIntent(DeviceCommunicationService.this, GBDevice.DeviceUpdateSubject.DEVICE_STATE);
|
||||
new Handler().postDelayed(() -> {
|
||||
if(target.getState() != GBDevice.State.SCANNED){
|
||||
return;
|
||||
}
|
||||
deviceLastScannedTimestamps.put(target.getAddress(), System.currentTimeMillis());
|
||||
target.setState(GBDevice.State.WAITING_FOR_SCAN);
|
||||
target.sendDeviceUpdateIntent(DeviceCommunicationService.this, GBDevice.DeviceUpdateSubject.CONNECTION_STATE);
|
||||
target.sendDeviceUpdateIntent(DeviceCommunicationService.this, GBDevice.DeviceUpdateSubject.DEVICE_STATE);
|
||||
}, timeoutSeconds * 1000);
|
||||
return;
|
||||
}
|
||||
@ -508,7 +511,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
ContextCompat.registerReceiver(this, mAutoConnectInvervalReceiver, new IntentFilter("GB_RECONNECT"), ContextCompat.RECEIVER_EXPORTED);
|
||||
|
||||
IntentFilter bluetoothCommandFilter = new IntentFilter();
|
||||
bluetoothCommandFilter.addAction(COMMAND_BLUETOOTH_CONNECT);
|
||||
bluetoothCommandFilter.addAction(API_LEGACY_COMMAND_BLUETOOTH_CONNECT);
|
||||
ContextCompat.registerReceiver(this, bluetoothCommandReceiver, bluetoothCommandFilter, ContextCompat.RECEIVER_EXPORTED);
|
||||
|
||||
final IntentFilter deviceSettingsIntentFilter = new IntentFilter();
|
||||
@ -557,7 +560,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
}
|
||||
createDeviceStruct(device);
|
||||
device.setState(GBDevice.State.WAITING_FOR_SCAN);
|
||||
device.sendDeviceUpdateIntent(this);
|
||||
device.sendDeviceUpdateIntent(this, GBDevice.DeviceUpdateSubject.DEVICE_STATE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,11 +17,13 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.btle;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattDescriptor;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.content.Context;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -66,6 +68,8 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||
public static final String BASE_UUID = "0000%s-0000-1000-8000-00805f9b34fb"; //this is common for all BTLE devices. see http://stackoverflow.com/questions/18699251/finding-out-android-bluetooth-le-gatt-profiles
|
||||
private final Object characteristicsMonitor = new Object();
|
||||
|
||||
private BleIntentApi bleApi = null;
|
||||
|
||||
public AbstractBTLEDeviceSupport(Logger logger) {
|
||||
this.logger = logger;
|
||||
if (logger == null) {
|
||||
@ -77,11 +81,15 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||
public boolean connect() {
|
||||
if (mQueue == null) {
|
||||
mQueue = new BtLEQueue(getBluetoothAdapter(), getDevice(), this, this, getContext(), mSupportedServerServices);
|
||||
if(bleApi != null) {
|
||||
bleApi.setQueue(mQueue);
|
||||
}
|
||||
mQueue.setAutoReconnect(getAutoReconnect());
|
||||
mQueue.setScanReconnect(getScanReconnect());
|
||||
mQueue.setImplicitGattCallbackModify(getImplicitCallbackModify());
|
||||
mQueue.setSendWriteRequestResponse(getSendWriteRequestResponse());
|
||||
}
|
||||
|
||||
return mQueue.connect();
|
||||
}
|
||||
|
||||
@ -91,6 +99,29 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||
}
|
||||
}
|
||||
|
||||
public BleIntentApi getBleApi() {
|
||||
return bleApi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendConfiguration(String config) {
|
||||
if(bleApi != null) {
|
||||
bleApi.onSendConfiguration(config);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
|
||||
super.setContext(gbDevice, btAdapter, context);
|
||||
|
||||
if(BleIntentApi.isEnabled(gbDevice)) {
|
||||
bleApi = new BleIntentApi(context, gbDevice);
|
||||
bleApi.handleBLEApiPrefs();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the gatt callback should be implicitly set to the one on the transaction,
|
||||
* even if it was not set directly on the transaction. If true, the gatt callback will always
|
||||
@ -139,6 +170,10 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||
mQueue.dispose();
|
||||
mQueue = null;
|
||||
}
|
||||
|
||||
if(bleApi != null) {
|
||||
bleApi.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public TransactionBuilder createTransactionBuilder(String taskName) {
|
||||
@ -271,6 +306,10 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||
Set<UUID> supportedServices = getSupportedServices();
|
||||
Map<UUID, BluetoothGattCharacteristic> newCharacteristics = new HashMap<>();
|
||||
for (BluetoothGattService service : discoveredGattServices) {
|
||||
if(bleApi != null) {
|
||||
bleApi.addService(service);
|
||||
}
|
||||
|
||||
if (supportedServices.contains(service.getUuid())) {
|
||||
logger.debug("discovered supported service: {}: {}", BleNamesResolver.resolveServiceName(service.getUuid().toString()), service.getUuid());
|
||||
List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
|
||||
@ -322,12 +361,22 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||
logger.warn("Services discovered, but device state is already " + getDevice().getState() + " for device: " + getDevice() + ", so ignoring");
|
||||
return;
|
||||
}
|
||||
initializeDevice(createTransactionBuilder("Initializing device")).queue(getQueue());
|
||||
TransactionBuilder builder = createTransactionBuilder("Initializing device");
|
||||
|
||||
if(bleApi != null) {
|
||||
bleApi.initializeDevice(builder);
|
||||
}
|
||||
|
||||
initializeDevice(builder).queue(getQueue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicRead(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic, int status) {
|
||||
if(bleApi != null) {
|
||||
bleApi.onCharacteristicChanged(characteristic);
|
||||
}
|
||||
|
||||
for (AbstractBleProfile<?> profile : mSupportedProfiles) {
|
||||
if (profile.onCharacteristicRead(gatt, characteristic, status)) {
|
||||
return true;
|
||||
@ -370,6 +419,10 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||
@Override
|
||||
public boolean onCharacteristicChanged(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic) {
|
||||
if(bleApi != null) {
|
||||
bleApi.onCharacteristicChanged(characteristic);
|
||||
}
|
||||
|
||||
for (AbstractBleProfile<?> profile : mSupportedProfiles) {
|
||||
if (profile.onCharacteristicChanged(gatt, characteristic)) {
|
||||
return true;
|
||||
|
@ -0,0 +1,241 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.btle;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREFS_KEY_DEVICE_BLE_API_DEVICE_NOTIFY;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREFS_KEY_DEVICE_BLE_API_DEVICE_READ_WRITE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREFS_KEY_DEVICE_BLE_API_DEVICE_STATE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREFS_KEY_DEVICE_BLE_API_PACKAGE;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattServer;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
public class BleIntentApi {
|
||||
private Context context;
|
||||
GBDevice device;
|
||||
BtLEQueue queue;
|
||||
Logger logger;
|
||||
|
||||
private boolean intentApiEnabledDeviceState = false;
|
||||
private boolean intentApiEnabledReadWrite= false;
|
||||
private boolean intentApiEnabledNotifications= false;
|
||||
private String intentApiPackage = "";
|
||||
private boolean intentApiCharacteristicReceiverRegistered = false;
|
||||
private boolean intentApiDeviceStateReceiverRegistered = false;
|
||||
private String lastReportedState = null;
|
||||
|
||||
private final HashMap<String, BluetoothGattCharacteristic> characteristics = new HashMap<>();
|
||||
|
||||
public static final String BLE_API_COMMAND_READ = "nodomain.freeyourgadget.gadgetbridge.ble_api.commands.CHARACTERISTIC_READ";
|
||||
public static final String BLE_API_COMMAND_WRITE = "nodomain.freeyourgadget.gadgetbridge.ble_api.commands.CHARACTERISTIC_WRITE";
|
||||
public static final String BLE_API_EVENT_CHARACTERISTIC_CHANGED = "nodomain.freeyourgadget.gadgetbridge.ble_api.events.CHARACTERISTIC_CHANGED";
|
||||
|
||||
|
||||
BroadcastReceiver intentApiReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
|
||||
boolean isWrite = BLE_API_COMMAND_WRITE.equals(action);
|
||||
|
||||
boolean isRead = BLE_API_COMMAND_READ.equals(action);
|
||||
|
||||
if((!isWrite) && (!isRead)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!concernsThisDevice(intent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!getDevice().getState().equalsOrHigherThan(GBDevice.State.INITIALIZED)) {
|
||||
logger.error(String.format("BLE API: Device %s not initialized.", getDevice()));
|
||||
return;
|
||||
}
|
||||
|
||||
String uuid = intent.getStringExtra("EXTRA_CHARACTERISTIC_UUID");
|
||||
if (StringUtils.isNullOrEmpty(uuid)) {
|
||||
logger.error("BLE API: missing EXTRA_CHARACTERISTIC_UUID");
|
||||
return;
|
||||
}
|
||||
|
||||
String hexData = intent.getStringExtra("EXTRA_PAYLOAD");
|
||||
if (hexData == null) {
|
||||
logger.error("BLE API: missing EXTRA_PAYLOAD");
|
||||
return;
|
||||
}
|
||||
|
||||
BluetoothGattCharacteristic characteristic = characteristics.get(uuid);
|
||||
|
||||
if(characteristic == null) {
|
||||
logger.error("Characteristic {} not found", uuid);
|
||||
return;
|
||||
}
|
||||
|
||||
if(isWrite) {
|
||||
new TransactionBuilder("BLE API write")
|
||||
.write(characteristic, StringUtils.hexToBytes(hexData))
|
||||
.queue(getQueue());
|
||||
return;
|
||||
}
|
||||
|
||||
if(isRead) {
|
||||
new TransactionBuilder("BLE API read")
|
||||
.read(characteristic)
|
||||
.queue(getQueue());
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static boolean isEnabled(GBDevice device) {
|
||||
Prefs devicePrefs = GBApplication.getDevicePrefs(device.getAddress());
|
||||
|
||||
boolean intentApiEnabledReadWrite = devicePrefs.getBoolean(PREFS_KEY_DEVICE_BLE_API_DEVICE_READ_WRITE, false);
|
||||
boolean intentApiEnabledNotifications = devicePrefs.getBoolean(PREFS_KEY_DEVICE_BLE_API_DEVICE_NOTIFY, false);
|
||||
boolean intentApiEnabledDeviceState = devicePrefs.getBoolean(PREFS_KEY_DEVICE_BLE_API_DEVICE_STATE, false);
|
||||
|
||||
return intentApiEnabledReadWrite | intentApiEnabledNotifications | intentApiEnabledDeviceState;
|
||||
}
|
||||
|
||||
public void onCharacteristicChanged(BluetoothGattCharacteristic characteristic) {
|
||||
if(!intentApiEnabledNotifications) {
|
||||
return;
|
||||
}
|
||||
Intent intent = getBleApiIntent(BLE_API_EVENT_CHARACTERISTIC_CHANGED);
|
||||
if(!StringUtils.isNullOrEmpty(intentApiPackage)) {
|
||||
intent.setPackage(intentApiPackage);
|
||||
}
|
||||
intent.putExtra("EXTRA_CHARACTERISTIC", characteristic.getUuid().toString());
|
||||
intent.putExtra("EXTRA_PAYLOAD", StringUtils.bytesToHex(characteristic.getValue()));
|
||||
|
||||
getContext().sendBroadcast(intent);
|
||||
}
|
||||
|
||||
public void initializeDevice(TransactionBuilder builder) {
|
||||
if(intentApiEnabledNotifications) {
|
||||
for (BluetoothGattCharacteristic characteristic : characteristics.values()) {
|
||||
builder.notify(characteristic, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
registerBleApiCharacteristicReceivers(false);
|
||||
}
|
||||
|
||||
public void onSendConfiguration(String config) {
|
||||
if(StringUtils.isNullOrEmpty(config)) {
|
||||
return;
|
||||
}
|
||||
if(config.startsWith("prefs_device_ble_api_")) {
|
||||
// could subscribe here, but there is more setup to do than that...
|
||||
// handleBLEApiPrefs();
|
||||
GB.toast(
|
||||
getContext().getString(R.string.toast_setting_requires_reconnect),
|
||||
Toast.LENGTH_SHORT,
|
||||
GB.INFO
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public BtLEQueue getQueue() {
|
||||
return queue;
|
||||
}
|
||||
|
||||
public void setQueue(BtLEQueue queue) {
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
private void registerBleApiCharacteristicReceivers(boolean enable){
|
||||
if(enable == intentApiCharacteristicReceiverRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(enable){
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(BLE_API_COMMAND_READ);
|
||||
filter.addAction(BLE_API_COMMAND_WRITE);
|
||||
|
||||
ContextCompat.registerReceiver(
|
||||
getContext(),
|
||||
intentApiReceiver,
|
||||
filter,
|
||||
ContextCompat.RECEIVER_EXPORTED
|
||||
);
|
||||
}else{
|
||||
getContext().unregisterReceiver(intentApiReceiver);
|
||||
}
|
||||
intentApiCharacteristicReceiverRegistered = intentApiEnabledReadWrite;
|
||||
}
|
||||
|
||||
public void addService(BluetoothGattService service) {
|
||||
for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
|
||||
this.characteristics.put(characteristic.getUuid().toString(), characteristic);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleBLEApiPrefs(){
|
||||
Prefs devicePrefs = GBApplication.getDevicePrefs(getDevice().getAddress());
|
||||
this.intentApiEnabledReadWrite = devicePrefs.getBoolean(PREFS_KEY_DEVICE_BLE_API_DEVICE_READ_WRITE, false);
|
||||
this.intentApiEnabledNotifications = devicePrefs.getBoolean(PREFS_KEY_DEVICE_BLE_API_DEVICE_NOTIFY, false);
|
||||
this.intentApiEnabledDeviceState = devicePrefs.getBoolean(PREFS_KEY_DEVICE_BLE_API_DEVICE_STATE, false);
|
||||
this.intentApiPackage = devicePrefs.getString(PREFS_KEY_DEVICE_BLE_API_PACKAGE, "");
|
||||
|
||||
registerBleApiCharacteristicReceivers(this.intentApiEnabledReadWrite);
|
||||
}
|
||||
|
||||
public static Intent getBleApiIntent(String deviceAddress, String action) {
|
||||
Intent updateIntent = new Intent(action);
|
||||
updateIntent.putExtra("EXTRA_DEVICE_ADDRESS", deviceAddress);
|
||||
return updateIntent;
|
||||
}
|
||||
|
||||
private Intent getBleApiIntent(String action) {
|
||||
return getBleApiIntent(getDevice().getAddress(), action);
|
||||
}
|
||||
|
||||
public BleIntentApi(Context context, GBDevice device) {
|
||||
this.context = context;
|
||||
this.device = device;
|
||||
|
||||
this.logger = LoggerFactory.getLogger(BleIntentApi.class);
|
||||
}
|
||||
|
||||
public GBDevice getDevice() {
|
||||
return device;
|
||||
}
|
||||
|
||||
private boolean concernsThisDevice(Intent intent) {
|
||||
String deviceAddress = intent.getStringExtra("EXTRA_DEVICE_ADDRESS");
|
||||
if (StringUtils.isNullOrEmpty(deviceAddress)) {
|
||||
logger.error("BLE API: missing EXTRA_DEVICE_ADDRESS");
|
||||
return false;
|
||||
}
|
||||
return deviceAddress.equalsIgnoreCase(getDevice().getAddress());
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.gatt_client;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
|
||||
|
||||
public class BleGattClientCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Generic";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Class<? extends DeviceSupport> getDeviceSupportClass() {
|
||||
return BleGattClientSupport.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(GBDeviceCandidate candidate) {
|
||||
// can only add through debug settings
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeviceNameResource() {
|
||||
return R.string.devicetype_ble_gatt_client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefaultIconResource() {
|
||||
return R.drawable.ic_device_scannable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDisabledIconResource() {
|
||||
return R.drawable.ic_device_scannable_disabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.gatt_client;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.bluetooth.BluetoothGattService;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||
|
||||
public class BleGattClientSupport extends AbstractBTLEDeviceSupport {
|
||||
public static final Logger logger = LoggerFactory.getLogger(BleGattClientSupport.class);
|
||||
|
||||
public BleGattClientSupport() {
|
||||
super(logger);
|
||||
|
||||
addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
|
||||
addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useAutoConnect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
||||
if(characteristic.getUuid().equals(GattCharacteristic.UUID_CHARACTERISTIC_BATTERY_LEVEL)) {
|
||||
GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo();
|
||||
batteryInfo.level = characteristic.getValue()[0];
|
||||
handleGBDeviceEvent(batteryInfo);
|
||||
}else if(characteristic.getUuid().equals(GattCharacteristic.UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING)) {
|
||||
String firmwareVersion = characteristic.getStringValue(0);
|
||||
getDevice().setFirmwareVersion(firmwareVersion);
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
return super.onCharacteristicRead(gatt, characteristic, status);
|
||||
}
|
||||
|
||||
void readCharacteristicIfAvailable(UUID characteristicUUID, TransactionBuilder builder) {
|
||||
BluetoothGattCharacteristic characteristic = getCharacteristic(characteristicUUID);
|
||||
if(characteristic == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("found characteristic {}", characteristicUUID);
|
||||
builder.read(characteristic);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||
|
||||
readCharacteristicIfAvailable(GattCharacteristic.UUID_CHARACTERISTIC_BATTERY_LEVEL, builder);
|
||||
readCharacteristicIfAvailable(GattCharacteristic.UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING, builder);
|
||||
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
@ -190,6 +190,20 @@ public class StringUtils {
|
||||
return GB.hexdump(array, 0, -1);
|
||||
}
|
||||
|
||||
public static byte[] hexToBytes(String hexString) {
|
||||
if((hexString.length() % 2) == 1) {
|
||||
// pad with zero
|
||||
hexString = "0" + hexString;
|
||||
}
|
||||
byte[] bytes = new byte[hexString.length() / 2];
|
||||
for(int i = 0; i < bytes.length; i++) {
|
||||
String slice = hexString.substring(i * 2, i * 2 + 2);
|
||||
bytes[i] = (byte) Integer.parseInt(slice, 16);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a shortened version of an Android package name by using only the first
|
||||
* character of every non-last part of the package name.
|
||||
|
@ -3255,4 +3255,13 @@
|
||||
<string name="pref_header_deprecated_functionalities_warning">The following functionalities have been deprecated and will be removed soon from the software.\nIf you need to enable one of the following settings be sure to get in touch with the project team.</string>
|
||||
<string name="pref_deprecated_media_control_title">Deprecated media control</string>
|
||||
<string name="pref_deprecated_media_control_summary">Send media control commands as key events instead of the media controller.</string>
|
||||
<string name="devicetype_ble_gatt_client">Generic BLE GATT Client</string>
|
||||
<string name="prefs_title_gatt_client_notification_intents">Broadcast GATT notification Intents through BLE Intent API</string>
|
||||
<string name="prefs_summary_gatt_client_notification_intents">Receive BLE characteristic changes through Intents</string>
|
||||
<string name="prefs_title_gatt_client_allow_gatt_interactions">Allow GATT interaction through BLE Intent API</string>
|
||||
<string name="prefs_summary_gatt_client_allow_gatt_interactions">Allow to send BLE characteristic read/write and connect commands</string>
|
||||
<string name="prefs_summary_gatt_client_device_state_updates">Receive BLE connection state changes via Intents</string>
|
||||
<string name="prefs_title_gatt_client_api_package">BLE API package</string>
|
||||
<string name="prefs_summary_gatt_client_api_package">Restrict BLE Intent API communication to this package</string>
|
||||
<string name="prefs_title_ble_intent_api">BLE Intent API</string>
|
||||
</resources>
|
||||
|
27
app/src/main/res/xml/devicesettings_ble_api.xml
Normal file
27
app/src/main/res/xml/devicesettings_ble_api.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceCategory
|
||||
android:title="@string/prefs_title_ble_intent_api" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:icon="@drawable/ic_bluetooth"
|
||||
android:key="prefs_device_ble_api_characteristic_read_write"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:title="@string/prefs_title_gatt_client_allow_gatt_interactions"
|
||||
android:summary="@string/prefs_summary_gatt_client_allow_gatt_interactions"
|
||||
android:defaultValue="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:icon="@drawable/ic_bluetooth"
|
||||
android:key="prefs_device_ble_api_characteristic_notify"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:title="@string/prefs_title_gatt_client_notification_intents"
|
||||
android:summary="@string/prefs_summary_gatt_client_notification_intents"
|
||||
android:defaultValue="false" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="prefs_device_ble_api_package"
|
||||
android:title="@string/prefs_title_gatt_client_api_package"
|
||||
android:summary="@string/prefs_summary_gatt_client_api_package" />
|
||||
|
||||
</PreferenceScreen>
|
Loading…
Reference in New Issue
Block a user