Device FlipperZero: added basic support for the Flipper Zero (#2840)

This PR adds support for the flipper zero device.

It's main purpose currently is to provide an Intent-based API to Tasker and similar apps to play sub-GHz files.

In the future, file management and other features might be useful.

Co-authored-by: Daniel Dakhno <dakhnod@gmail.com>
Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2840
Co-authored-by: dakhnod <dakhnod@noreply.codeberg.org>
Co-committed-by: dakhnod <dakhnod@noreply.codeberg.org>
This commit is contained in:
dakhnod 2022-09-04 23:05:57 +02:00 committed by vanous
parent 42853df591
commit ee207c978f
13 changed files with 627 additions and 4 deletions

View File

@ -175,6 +175,15 @@ public class DeviceManager {
return Collections.unmodifiableList(deviceList);
}
public GBDevice getDeviceByAddress(String address){
for(GBDevice device : deviceList){
if(device.getAddress().compareToIgnoreCase(address) == 0){
return device;
}
}
return null;
}
public List<GBDevice> getSelectedDevices() {
return selectedDevices;
}

View File

@ -0,0 +1,118 @@
package nodomain.freeyourgadget.gadgetbridge.devices.flipper.zero;
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
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.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
public class FlipperZeroCoordinator extends AbstractBLEDeviceCoordinator {
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
}
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
if(candidate.supportsService(UUID.fromString("00003082-0000-1000-8000-00805f9b34fb"))){
return DeviceType.FLIPPER_ZERO; // need to filter for flipper here
}
return DeviceType.UNKNOWN;
}
@Override
public DeviceType getDeviceType() {
return DeviceType.FLIPPER_ZERO;
}
@Nullable
@Override
public Class<? extends Activity> getPairingActivity() {
return null;
}
@Override
public boolean supportsActivityDataFetching() {
return false;
}
@Override
public boolean supportsActivityTracking() {
return false;
}
@Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return null;
}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return null;
}
@Override
public boolean supportsScreenshots() {
return false;
}
@Override
public int getAlarmSlotCount() {
return 0;
}
@Override
public boolean supportsSmartWakeup(GBDevice device) {
return false;
}
@Override
public String getManufacturer() {
return "Flipper devices";
}
@Override
public boolean supportsAppsManagement() {
return false;
}
@Override
public Class<? extends Activity> getAppsManagementActivity() {
return null;
}
@Override
public boolean supportsCalendarEvents() {
return false;
}
@Override
public boolean supportsRealtimeData() {
return false;
}
@Override
public boolean supportsFindDevice() {
return false;
}
@Override
public int getBondingStyle() {
return BONDING_STYLE_NONE;
}
}

View File

@ -115,6 +115,7 @@ public enum DeviceType {
VESC_NRF(500, R.drawable.ic_device_vesc, R.drawable.ic_device_vesc_disabled, R.string.devicetype_vesc),
VESC_HM10(501, R.drawable.ic_device_vesc, R.drawable.ic_device_vesc_disabled, R.string.devicetype_vesc),
BINARY_SENSOR(510, R.drawable.ic_device_unknown, R.drawable.ic_device_unknown_disabled, R.string.devicetype_binary_sensor),
FLIPPER_ZERO(520, R.drawable.ic_device_unknown, R.drawable.ic_device_unknown_disabled, R.string.devicetype_flipper_zero),
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
private final int key;

View File

@ -32,6 +32,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.widget.Toast;
@ -53,6 +54,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmClockReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver;
@ -337,6 +339,68 @@ 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 boolean allowBluetoothIntentApi = false;
private void sendDeviceConnectedBroadcast(String address){
if(!allowBluetoothIntentApi){
GB.log("not sending API event due to settings", GB.INFO, null);
return;
}
Intent intent = new Intent(ACTION_DEVICE_CONNECTED);
intent.putExtra("EXTRA_DEVICE_ADDRESS", address);
sendBroadcast(intent);
}
BroadcastReceiver bluetoothCommandReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()){
case COMMAND_BLUETOOTH_CONNECT:
if(!allowBluetoothIntentApi){
GB.log("Connection API not allowed in settings", GB.ERROR, null);
return;
}
Bundle extras = intent.getExtras();
if(extras == null){
GB.log("no extras provided in Intent", GB.ERROR, null);
return;
}
String address = extras.getString("EXTRA_DEVICE_ADDRESS", "");
if(address.isEmpty()){
GB.log("no bluetooth address provided in Intent", GB.ERROR, null);
return;
}
if(isDeviceConnected(address)){
GB.log(String.format("device %s already connected", address), GB.INFO, null);
sendDeviceConnectedBroadcast(address);
return;
}
List<GBDevice> devices = GBApplication.app().getDeviceManager().getDevices();
GBDevice targetDevice = GBApplication
.app()
.getDeviceManager()
.getDeviceByAddress(address);
if(targetDevice == null){
GB.log(String.format("device %s not registered", address), GB.ERROR, null);
return;
}
GB.log(String.format("connecting to %s", address), GB.INFO, null);
GBApplication
.deviceService(targetDevice)
.connect();
break;
}
}
};
/**
* For testing!
*
@ -366,6 +430,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
cachedStruct.setCoordinator(newCoordinator);
}
updateReceiversState();
if(device.isInitialized()){
sendDeviceConnectedBroadcast(device.getAddress());
}
}
}
};
@ -412,7 +480,12 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
if (hasPrefs()) {
getPrefs().getPreferences().registerOnSharedPreferenceChangeListener(this);
allowBluetoothIntentApi = getPrefs().getBoolean(GBPrefs.PREF_ALLOW_INTENT_API, false);
}
IntentFilter bluetoothCommandFilter = new IntentFilter();
bluetoothCommandFilter.addAction(COMMAND_BLUETOOTH_CONNECT);
registerReceiver(bluetoothCommandReceiver, bluetoothCommandFilter);
}
private DeviceSupportFactory getDeviceSupportFactory() {
@ -957,8 +1030,12 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
}
private boolean isDeviceConnected(GBDevice device) {
return isDeviceConnected(device.getAddress());
}
private boolean isDeviceConnected(String deviceAddress) {
for(DeviceStruct struct : deviceStructs){
if(struct.getDevice().equals(device) ){
if(struct.getDevice().getAddress().compareToIgnoreCase(deviceAddress) == 0){
return struct.getDevice().isConnected();
}
}
@ -966,8 +1043,12 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
}
private boolean isDeviceConnecting(GBDevice device) {
return isDeviceConnecting(device.getAddress());
}
private boolean isDeviceConnecting(String deviceAddress) {
for(DeviceStruct struct : deviceStructs){
if(struct.getDevice().equals(device) ){
if(struct.getDevice().getAddress().compareToIgnoreCase(deviceAddress) == 0){
return struct.getDevice().isConnecting();
}
}
@ -975,8 +1056,12 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
}
private boolean isDeviceInitialized(GBDevice device) {
return isDeviceInitialized(device.getAddress());
}
private boolean isDeviceInitialized(String deviceAddress) {
for(DeviceStruct struct : deviceStructs){
if(struct.getDevice().equals(device) ){
if(struct.getDevice().getAddress().compareToIgnoreCase(deviceAddress) == 0){
return struct.getDevice().isInitialized();
}
}
@ -1188,6 +1273,8 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
}
}
GB.removeNotification(GB.NOTIFICATION_ID, this); // need to do this because the updated notification won't be cancelled when service stops
unregisterReceiver(bluetoothCommandReceiver);
}
@Override
@ -1206,6 +1293,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
if (GBPrefs.CHART_MAX_HEART_RATE.equals(key) || GBPrefs.CHART_MIN_HEART_RATE.equals(key)) {
HeartRateUtils.getInstance().updateCachedHeartRatePreferences();
}
if (GBPrefs.PREF_ALLOW_INTENT_API.equals(key)){
allowBluetoothIntentApi = sharedPreferences.getBoolean(GBPrefs.PREF_ALLOW_INTENT_API, false);
GB.log("allowBluetoothIntentApi changed to " + allowBluetoothIntentApi, GB.INFO, null);
}
}
protected boolean hasPrefs() {

View File

@ -39,6 +39,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.banglejs.BangleJSDev
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.CasioGB6900DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.casio.CasioGBX100DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.domyos.DomyosT540Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.flipper.zero.support.FlipperZeroSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.galaxy_buds.GalaxyBudsDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.hplus.HPlusSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
@ -324,6 +325,8 @@ public class DeviceSupportFactory {
return new ServiceDeviceSupport(new QC35BaseSupport());
case BINARY_SENSOR:
return new ServiceDeviceSupport(new BinarySensorSupport());
case FLIPPER_ZERO:
return new ServiceDeviceSupport(new FlipperZeroSupport());
}
return null;
}

View File

@ -550,6 +550,8 @@ public final class BtLEQueue {
if(getCallbackToUse() != null){
getCallbackToUse().onMtuChanged(gatt, mtu, status);
}
mWaitForActionResultLatch.countDown();
}

View File

@ -36,7 +36,7 @@ public class RequestMtuAction extends BtLEAction {
@Override
public boolean expectsResult() {
return false;
return true;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)

View File

@ -0,0 +1,179 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.flipper.zero.support;
import android.net.Uri;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
public class FlipperZeroBaseSupport extends AbstractBTLEDeviceSupport {
public FlipperZeroBaseSupport() {
super(LoggerFactory.getLogger(FlipperZeroBaseSupport.class));
}
@Override
public void onNotification(NotificationSpec notificationSpec) {
}
@Override
public void onDeleteNotification(int id) {
}
@Override
public void onSetTime() {
}
@Override
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
}
@Override
public void onSetCallState(CallSpec callSpec) {
}
@Override
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
}
@Override
public void onSetMusicState(MusicStateSpec stateSpec) {
}
@Override
public void onSetMusicInfo(MusicSpec musicSpec) {
}
@Override
public void onEnableRealtimeSteps(boolean enable) {
}
@Override
public void onInstallApp(Uri uri) {
}
@Override
public void onAppInfoReq() {
}
@Override
public void onAppStart(UUID uuid, boolean start) {
}
@Override
public void onAppDelete(UUID uuid) {
}
@Override
public void onAppConfiguration(UUID appUuid, String config, Integer id) {
}
@Override
public void onAppReorder(UUID[] uuids) {
}
@Override
public void onFetchRecordedData(int dataTypes) {
}
@Override
public void onReset(int flags) {
}
@Override
public void onHeartRateTest() {
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
}
@Override
public void onFindDevice(boolean start) {
}
@Override
public void onSetConstantVibration(int integer) {
}
@Override
public void onScreenshotReq() {
}
@Override
public void onEnableHeartRateSleepSupport(boolean enable) {
}
@Override
public void onSetHeartRateMeasurementInterval(int seconds) {
}
@Override
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
}
@Override
public void onDeleteCalendarEvent(byte type, long id) {
}
@Override
public void onSendConfiguration(String config) {
}
@Override
public void onReadConfiguration(String config) {
}
@Override
public void onTestNewFunction() {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
}
@Override
public boolean useAutoConnect() {
return false;
}
}

View File

@ -0,0 +1,209 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.flipper.zero.support;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.widget.Toast;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class FlipperZeroSupport extends FlipperZeroBaseSupport{
private BatteryInfoProfile batteryInfoProfile = new BatteryInfoProfile(this);
private final String UUID_SERIAL_SERVICE = "8fe5b3d5-2e7f-4a98-2a48-7acc60fe0000";
private final String UUID_SERIAL_CHARACTERISTIC_WRITE = "19ed82ae-ed21-4c9d-4145-228e62fe0000";
private final String UUID_SERIAL_CHARACTERISTIC_RESPONSE = "19ed82ae-ed21-4c9d-4145-228e61fe0000";
private final String COMMAND_PLAY_FILE = "nodomain.freeyourgadget.gadgetbridge.flipper.zero.PLAY_FILE";
private final String ACTION_PLAY_DONE = "nodomain.freeyourgadget.gadgetbridge.flipper.zero.PLAY_DONE";
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, final Intent intent) {
new Thread(new Runnable() {
@Override
public void run() {
if(COMMAND_PLAY_FILE.equals(intent.getAction())){
handlePlaySubGHZ(intent);
}
}
}).start();
}
};
private void handlePlaySubGHZ(Intent intent) {
String appName = intent.getExtras().getString("EXTRA_APP_NAME", "Sub-GHz");
String filePath = intent.getStringExtra("EXTRA_FILE_PATH");
if(filePath == null){
GB.log("missing EXTRA_FILE_PATH in intent", GB.ERROR, null);
return;
}
if(filePath.isEmpty()){
GB.log("empty EXTRA_FILE_PATH in intent", GB.ERROR, null);
return;
}
GB.toast(String.format("playing %s file", appName), Toast.LENGTH_SHORT, GB.INFO);
playFile(appName, filePath);
Intent response = new Intent(ACTION_PLAY_DONE);
getContext().sendBroadcast(response);
}
public FlipperZeroSupport() {
super();
batteryInfoProfile.addListener(new IntentListener() {
@Override
public void notify(Intent intent) {
BatteryInfo info = intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO);
GBDeviceEventBatteryInfo batteryEvent = new GBDeviceEventBatteryInfo();
batteryEvent.state = BatteryState.BATTERY_NORMAL;
batteryEvent.level = info.getPercentCharged();
evaluateGBDeviceEvent(batteryEvent);
}
});
addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE);
addSupportedProfile(batteryInfoProfile);
addSupportedService(UUID.fromString(UUID_SERIAL_SERVICE));
}
@Override
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
getContext().registerReceiver(receiver, new IntentFilter(COMMAND_PLAY_FILE));
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
batteryInfoProfile.requestBatteryInfo(builder);
batteryInfoProfile.enableNotify(builder, true);
return builder
.notify(getCharacteristic(UUID.fromString(UUID_SERIAL_CHARACTERISTIC_RESPONSE)), true)
.requestMtu(512)
.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
}
@Override
public void dispose() {
super.dispose();
getContext().unregisterReceiver(receiver);
}
private void sendSerialData(byte[] data){
new TransactionBuilder("send serial data")
.write(getCharacteristic(UUID.fromString(UUID_SERIAL_CHARACTERISTIC_WRITE)), data)
.queue(getQueue());
}
private void sendProtobufPacket(byte[] packet){
byte[] fullPacket = new byte[packet.length + 1];
fullPacket[0] = (byte) packet.length;
System.arraycopy(packet, 0, fullPacket, 1, packet.length);
sendSerialData(fullPacket);
}
private void openApp(String appName){
// sub ghz payload: 13-08-15-82-01-0E-0A-07- 53-75-62-2D-47-48-7A -12-03-52-50-43
ByteBuffer buffer = ByteBuffer.allocate(12 + appName.length());
buffer.put((byte)0x08);
buffer.put((byte)0x15);
// buffer.put((byte)(Math.random() * 256));
buffer.put((byte)0x82);
buffer.put((byte)0x01);
buffer.put((byte) (appName.length() + 7));
buffer.put((byte)0x0A);
buffer.put((byte) appName.length());
buffer.put(appName.getBytes());
buffer.put((byte)0x12);
buffer.put((byte)0x03);
buffer.put((byte)0x52);
buffer.put((byte)0x50);
buffer.put((byte)0x43);
sendProtobufPacket(buffer.array());
}
private void openSubGhzApp(){
openApp("Sub-GHz");
}
private void appLoadFile(String filePath){
// example payload 1C-08-16-82-03-17-0A-15- 2F-61-6E-79-2F-73-75-62-67-68-7A-2F-74-65-73-6C-61-2E-73-75-62
ByteBuffer buffer = ByteBuffer.allocate(7 + filePath.length());
buffer.put((byte) 0x08);
buffer.put((byte) 0x16);
buffer.put((byte) 0x82);
buffer.put((byte) 0x03);
buffer.put((byte) (filePath.length() + 2));
buffer.put((byte) 0x0A);
buffer.put((byte) filePath.length());
buffer.put(filePath.getBytes());
sendProtobufPacket(buffer.array());
}
private void appButtonPress(){
sendProtobufPacket(new byte[]{
(byte) 0x08, (byte) 0x17, (byte) 0x8A, (byte) 0x03, (byte) 0x00}
);
}
private void appButtonRelease(){
sendProtobufPacket(new byte[]{
(byte) 0x08, (byte) 0x18, (byte) 0x92, (byte) 0x03, (byte) 0x00}
);
}
private void appExitRequest(){
sendProtobufPacket(new byte[]{
(byte) 0x08, (byte) 0x19, (byte) 0xFA, (byte) 0x02, (byte) 0x00
});
}
@Override
public void onTestNewFunction() {
openApp("Infrared");
}
private void playFile(String appName, String filePath){
openApp(appName);
try {
Thread.sleep(500);
appLoadFile(filePath);
Thread.sleep(500);
appButtonPress();
Thread.sleep(500);
appButtonRelease();
Thread.sleep(1000);
appExitRequest();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onFetchRecordedData(int dataTypes) {
super.onFetchRecordedData(dataTypes);
onTestNewFunction();
}
}

View File

@ -49,6 +49,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSCoordinator
import nodomain.freeyourgadget.gadgetbridge.devices.casio.gb6900.CasioGB6900DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.casio.gbx100.CasioGBX100DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.domyos.DomyosT540Cooridnator;
import nodomain.freeyourgadget.gadgetbridge.devices.flipper.zero.FlipperZeroCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBudsDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBudsLiveDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.galaxy_buds.GalaxyBudsProDeviceCoordinator;
@ -335,6 +336,7 @@ public class DeviceHelper {
result.add(new SonyWF1000XM3Coordinator());
result.add(new QC35Coordinator());
result.add(new BinarySensorCoordinator());
result.add(new FlipperZeroCoordinator());
return result;
}

View File

@ -56,6 +56,7 @@ public class GBPrefs {
public static final String RTL_SUPPORT = "rtl";
public static final String RTL_CONTEXTUAL_ARABIC = "contextualArabic";
public static boolean AUTO_RECONNECT_DEFAULT = true;
public static final String PREF_ALLOW_INTENT_API = "prefs_key_allow_bluetooth_intent_api";
public static final String USER_NAME = "mi_user_alias";
public static final String USER_NAME_DEFAULT = "gadgetbridge-user";

View File

@ -1813,4 +1813,7 @@
<string name="step_streaks_achievements_sharing_title">Steps Achievements</string>
<string name="prefs_hourly_chime">Hourly chime</string>
<string name="prefs_hourly_chime_summary">The watch will beep once an hour</string>
<string name="devicetype_flipper_zero">Flipper zero</string>
<string name="activity_prefs_allow_bluetooth_intent_api">Bluetooth Intent API</string>
<string name="activity_prefs_summary_allow_bluetooth_intent_api">Allow controlling Bluetooth connection via Intent API</string>
</resources>

View File

@ -386,5 +386,10 @@
android:key="pref_discovery_pairing"
android:title="@string/activity_prefs_discovery_pairing" />
<CheckBoxPreference
android:key="prefs_key_allow_bluetooth_intent_api"
android:title="@string/activity_prefs_allow_bluetooth_intent_api"
android:summary="@string/activity_prefs_summary_allow_bluetooth_intent_api" />
</PreferenceCategory>
</PreferenceScreen>