diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 578f7e773..24cc61e37 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -689,6 +689,10 @@ android:name=".devices.um25.Activity.DataActivity" android:exported="true" /> + + diff --git a/app/src/main/assets/ic_device_supercars.svg b/app/src/main/assets/ic_device_supercars.svg new file mode 100644 index 000000000..596f4e157 --- /dev/null +++ b/app/src/main/assets/ic_device_supercars.svg @@ -0,0 +1,134 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/ic_device_supercars_disabled.svg b/app/src/main/assets/ic_device_supercars_disabled.svg new file mode 100644 index 000000000..234fc3f96 --- /dev/null +++ b/app/src/main/assets/ic_device_supercars_disabled.svg @@ -0,0 +1,179 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/ControlActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/ControlActivity.java new file mode 100644 index 000000000..b053d97ae --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/ControlActivity.java @@ -0,0 +1,62 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.supercars; + +import android.content.Intent; +import android.os.Bundle; +import android.view.MotionEvent; +import android.view.View; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; +import nodomain.freeyourgadget.gadgetbridge.service.devices.supercars.SuperCarsSupport; + +public class ControlActivity extends AbstractGBActivity { + LocalBroadcastManager localBroadcastManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_supercars_control); + localBroadcastManager = LocalBroadcastManager.getInstance(this); + + View.OnTouchListener controlTouchListener = new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + String command = "idle"; + if (v.getId() == R.id.supercars_control_button_left_up) { + command = "left_up"; + } else if (v.getId() == R.id.supercars_control_button_center_up) { + command = "center_up"; + } else if (v.getId() == R.id.supercars_control_button_right_up) { + command = "right_up"; + } + if (v.getId() == R.id.supercars_control_button_left_down) { + command = "left_down"; + } else if (v.getId() == R.id.supercars_control_button_center_down) { + command = "center_down"; + } else if (v.getId() == R.id.supercars_control_button_right_down) { + command = "right_down"; + } + Intent intent = new Intent(SuperCarsSupport.COMMAND_DRIVE_CONTROL); + intent.putExtra(SuperCarsSupport.EXTRA_DIRECTION, command); + sendLocalBroadcast(intent); + return true; + } + }; + + findViewById(R.id.supercars_control_button_left_up).setOnTouchListener(controlTouchListener); + findViewById(R.id.supercars_control_button_center_up).setOnTouchListener(controlTouchListener); + findViewById(R.id.supercars_control_button_right_up).setOnTouchListener(controlTouchListener); + findViewById(R.id.supercars_control_button_left_down).setOnTouchListener(controlTouchListener); + findViewById(R.id.supercars_control_button_center_down).setOnTouchListener(controlTouchListener); + findViewById(R.id.supercars_control_button_right_down).setOnTouchListener(controlTouchListener); + + + } + + private void sendLocalBroadcast(Intent intent) { + localBroadcastManager.sendBroadcast(intent); + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/SuperCarsConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/SuperCarsConstants.java new file mode 100644 index 000000000..8a621c95e --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/SuperCarsConstants.java @@ -0,0 +1,28 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.supercars; + +import java.util.UUID; + +public class SuperCarsConstants { + + public static final UUID SERVICE_UUID_FFF = UUID.fromString("0000fff0-0000-1000-8000-00805f9b34fb"); + public static final UUID CHARACTERISTIC_UUID_FFF1 = UUID.fromString("d44bc439-abfd-45a2-b575-925416129600"); + public static final UUID CHARACTERISTIC_UUID_FFF2 = UUID.fromString("d44bc439-abfd-45a2-b575-92541612960a"); + public static final UUID CHARACTERISTIC_UUID_FFF3 = UUID.fromString("d44bc439-abfd-45a2-b575-92541612960b"); + public static final UUID CHARACTERISTIC_UUID_FFF4 = UUID.fromString("d44bc439-abfd-45a2-b575-925416129601"); + + public static final UUID SERVICE_UUID_FD = UUID.fromString("0000fd00-0000-1000-8000-00805f9b34fb"); + public static final UUID CHARACTERISTIC_UUID_FD1 = UUID.fromString("0000fd01-0000-1000-8000-00805f9b34fb"); + public static final UUID CHARACTERISTIC_UUID_FD2 = UUID.fromString("0000fd02-0000-1000-8000-00805f9b34fb"); + + public static final byte[] idle_data = new byte[]{0x02, 0x5e, 0x69, 0x5a, 0x48, (byte) 0xff, 0x2a, 0x43, (byte) 0x8c, (byte) 0xa6, (byte) 0x80, (byte) 0xf8, 0x3e, 0x04, (byte) 0xe4, 0x5d}; + public static final byte[] up_data = new byte[]{0x29, 0x60, (byte) 0x9c, 0x66, 0x48, 0x52, (byte) 0xcf, (byte) 0xf1, (byte) 0xb0, (byte) 0xf0, (byte) 0xcb, (byte) 0xb9, (byte) 0x80, 0x14, (byte) 0xbd, 0x2c}; + public static final byte[] down_data = new byte[]{0x03, 0x20, (byte) 0x99, 0x09, (byte) 0xba, (byte) 0x9d, (byte) 0xa1, (byte) 0xc8, (byte) 0xb9, (byte) 0x86, 0x16, 0x3c, 0x6d, 0x48, 0x46, 0x55}; + public static final byte[] left_data = new byte[]{0x51, 0x38, 0x21, 0x12, 0x13, 0x5c, (byte) 0xcc, (byte) 0xdb, (byte) 0x46, (byte) 0xcf, (byte) 0x89, 0x21, (byte) 0xb7, 0x05, 0x49, (byte) 0x9a}; + public static final byte[] right_data = new byte[]{0x1b, 0x57, 0x69, (byte) 0xcd, (byte) 0xf1, 0x3e, (byte) 0x8a, (byte) 0xb6, 0x27, 0x08, 0x0f, (byte) 0xf3, (byte) 0xce, (byte) 0xfc, 0x3b, (byte) 0xc0}; + + public static final byte[] up_left_data = new byte[]{(byte) 0x99, 0x28, (byte) 0xe5, (byte) 0x90, (byte) 0xdf, (byte) 0xe8, 0x21, 0x48, 0x5f, 0x41, 0x4f, (byte) 0xbb, 0x63, 0x3d, 0x5c, 0x4e}; + public static final byte[] up_right_data = new byte[]{0x0f, 0x2c, (byte) 0xe5, 0x66, 0x62, (byte) 0xd4, (byte) 0xfd, (byte) 0x9d, 0x32, (byte) 0xa4, 0x4f, 0x10, 0x2b, (byte) 0xf2, 0x0a, (byte) 0xa7}; + public static final byte[] down_left_data = new byte[]{(byte) 0x98, (byte) 0xce, (byte) 0x98, 0x1d, 0x58, (byte) 0xd1, 0x15, (byte) 0xaf, (byte) 0xe1, 0x19, 0x60, (byte) 0xbf, 0x46, 0x13, (byte) 0x92, 0x5c}; + public static final byte[] down_right_data = new byte[]{(byte) 0xf2, 0x52, 0x0f, (byte) 0xba, 0x31, 0x44, (byte) 0xfb, 0x11, 0x46, (byte) 0x8f, (byte) 0xe0, (byte) 0x80, (byte) 0xc6, (byte) 0xc2, (byte) 0xc2, 0x3c}; + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/SuperCarsCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/SuperCarsCoordinator.java new file mode 100644 index 000000000..9563931b3 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/supercars/SuperCarsCoordinator.java @@ -0,0 +1,136 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.supercars; + +import android.app.Activity; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.net.Uri; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.GBException; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; +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 SuperCarsCoordinator extends AbstractDeviceCoordinator { + private static final Logger LOG = LoggerFactory.getLogger(SuperCarsCoordinator.class); + + @Override + protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { + + } + + @NonNull + @Override + public DeviceType getSupportedType(GBDeviceCandidate candidate) { + try { + BluetoothDevice device = candidate.getDevice(); + String name = device.getName(); + + if (name != null && name.startsWith("QCAR-")) { + return DeviceType.SUPER_CARS; + } + + } catch (Exception ex) { + LOG.error("unable to check device support", ex); + } + + return DeviceType.UNKNOWN; + } + + @Override + public DeviceType getDeviceType() { + return DeviceType.SUPER_CARS; + } + + @Override + public int getBondingStyle() { + return BONDING_STYLE_NONE; + } + + @Override + public int getBatteryCount() { + return 0; + } + + @Override + public Class getAppsManagementActivity() { + return ControlActivity.class; + } + + @Nullable + @Override + public Class getPairingActivity() { + return null; + } + + @Override + public boolean supportsActivityDataFetching() { + return false; + } + + @Override + public boolean supportsActivityTracking() { + return false; + } + + @Override + public SampleProvider 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 "Brand Base"; + } + + @Override + public boolean supportsAppsManagement() { + return true; + } + + @Override + public boolean supportsCalendarEvents() { + return false; + } + + @Override + public boolean supportsRealtimeData() { + return false; + } + + @Override + public boolean supportsFindDevice() { + return false; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java index 743b8c6a9..536c05aac 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -118,6 +118,7 @@ public enum DeviceType { 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_flipper, R.drawable.ic_device_flipper_disabled, R.string.devicetype_flipper_zero), + SUPER_CARS(530, R.drawable.ic_device_supercars, R.drawable.ic_device_supercars_disabled, R.string.devicetype_super_cars), TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test); private final int key; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java index 39434b1a9..9f1acd4ba 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java @@ -99,6 +99,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi.RoidmiSupport import nodomain.freeyourgadget.gadgetbridge.service.devices.smaq2oss.SMAQ2OSSSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.SonyHeadphonesSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12DeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.supercars.SuperCarsSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.tlw64.TLW64Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Support.UM25Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.vesc.VescDeviceSupport; @@ -333,6 +334,8 @@ public class DeviceSupportFactory { return new ServiceDeviceSupport(new BinarySensorSupport()); case FLIPPER_ZERO: return new ServiceDeviceSupport(new FlipperZeroSupport()); + case SUPER_CARS: + return new ServiceDeviceSupport(new SuperCarsSupport()); } return null; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/supercars/SuperCarsSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/supercars/SuperCarsSupport.java new file mode 100644 index 000000000..ae25a0573 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/supercars/SuperCarsSupport.java @@ -0,0 +1,261 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.supercars; + +import android.bluetooth.BluetoothGattCharacteristic; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.devices.supercars.SuperCarsConstants; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +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; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; +import nodomain.freeyourgadget.gadgetbridge.service.devices.fitpro.FitProDeviceSupport; + +public class SuperCarsSupport extends AbstractBTLEDeviceSupport { + private static final Logger LOG = LoggerFactory.getLogger(SuperCarsSupport.class); + public static final String COMMAND_DRIVE_CONTROL = "nodomain.freeyourgadget.gadgetbridge.supercars.command.DRIVE_CONTROL"; + public static final String EXTRA_DIRECTION = "EXTRA_DIRECTION"; + + public SuperCarsSupport() { + super(LOG); + addSupportedService(SuperCarsConstants.SERVICE_UUID_FFF); + } + + @Override + protected TransactionBuilder initializeDevice(TransactionBuilder builder) { + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); + LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext()); + + IntentFilter filter = new IntentFilter(); + filter.addAction(COMMAND_DRIVE_CONTROL); + broadcastManager.registerReceiver(commandReceiver, filter); + + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); + LOG.debug("name " + gbDevice.getName()); + return builder; + } + + BroadcastReceiver commandReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(COMMAND_DRIVE_CONTROL)) { + SuperCarsSupport.this.setDirection( + intent.getStringExtra(EXTRA_DIRECTION) + ); + } + } + }; + + @Override + public void onNotification(NotificationSpec notificationSpec) { + + } + + @Override + public void onDeleteNotification(int id) { + + } + + @Override + public void onSetTime() { + + } + + @Override + public void onSetAlarms(ArrayList 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; + } + + private void setDirection(String direction) { + byte[] command = SuperCarsConstants.idle_data; + + switch (direction) { + case "left_up": + command = SuperCarsConstants.up_left_data; + break; + case "center_up": + command = SuperCarsConstants.up_data; + break; + case "right_up": + command = SuperCarsConstants.up_right_data; + break; + case "left_down": + command = SuperCarsConstants.down_left_data; + break; + case "center_down": + command = SuperCarsConstants.down_data; + break; + case "right_down": + command = SuperCarsConstants.down_right_data; + break; + default: + command = SuperCarsConstants.idle_data; + } + TransactionBuilder builder = new TransactionBuilder("test"); + BluetoothGattCharacteristic writeCharacteristic = getCharacteristic(SuperCarsConstants.CHARACTERISTIC_UUID_FFF1); + builder.write(writeCharacteristic, command); + builder.queue(getQueue()); + + } + + @Override + public void dispose() { + super.dispose(); + LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(commandReceiver); + } + +} + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java index 524345009..e94abf324 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -43,6 +43,7 @@ import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.supercars.SuperCarsCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.fitpro.FitProDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSCoordinator; @@ -341,6 +342,7 @@ public class DeviceHelper { result.add(new QC35Coordinator()); result.add(new BinarySensorCoordinator()); result.add(new FlipperZeroCoordinator()); + result.add(new SuperCarsCoordinator()); return result; } diff --git a/app/src/main/res/drawable/ic_device_supercars.xml b/app/src/main/res/drawable/ic_device_supercars.xml new file mode 100644 index 000000000..0a2ac0997 --- /dev/null +++ b/app/src/main/res/drawable/ic_device_supercars.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_device_supercars_disabled.xml b/app/src/main/res/drawable/ic_device_supercars_disabled.xml new file mode 100644 index 000000000..f40c2fe0d --- /dev/null +++ b/app/src/main/res/drawable/ic_device_supercars_disabled.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/app/src/main/res/layout/activity_supercars_control.xml b/app/src/main/res/layout/activity_supercars_control.xml new file mode 100644 index 000000000..f9e10c054 --- /dev/null +++ b/app/src/main/res/layout/activity_supercars_control.xml @@ -0,0 +1,74 @@ + + + + + + + +