diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/itag/ITagConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/itag/ITagConstants.java new file mode 100644 index 000000000..f464cc90f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/itag/ITagConstants.java @@ -0,0 +1,23 @@ +/* Copyright (C) 2018 Taavi Eomäe + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.itag; + +import java.util.UUID; + +public final class ITagConstants { + public static final UUID UUID_SERVICE_BUTTON = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"); // Contains information about the button state +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/itag/ITagCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/itag/ITagCoordinator.java new file mode 100644 index 000000000..5a65c8ce9 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/itag/ITagCoordinator.java @@ -0,0 +1,137 @@ +/* Copyright (C) 2016-2019 Andreas Shimokawa, Carsten Pfeiffer, Daniele Gobbetti, Taavi Eomäe + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +package nodomain.freeyourgadget.gadgetbridge.devices.itag; + +import android.app.Activity; +import android.content.Context; +import android.net.Uri; + +import androidx.annotation.NonNull; + +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.devices.AbstractDeviceCoordinator; +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 ITagCoordinator extends AbstractDeviceCoordinator { + @Override + @NonNull + public DeviceType getSupportedType(GBDeviceCandidate candidate) { + String name = candidate.getDevice().getName(); + if (name != null && name.toLowerCase().startsWith("itag")) { // All four: iTAG, iTag, ITAG and ITag exist + return DeviceType.ITAG; + } + return DeviceType.UNKNOWN; + } + + @Override + public DeviceType getDeviceType() { + return DeviceType.ITAG; + } + + @Override + public Class getPairingActivity() { + return null; + } + + @Override + public InstallHandler findInstallHandler(Uri uri, Context context) { + 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 boolean supportsScreenshots() { + return false; + } + + @Override + public int getAlarmSlotCount() { + return 0; + } + + @Override + public boolean supportsSmartWakeup(GBDevice device) { + return false; + } + + @Override + public boolean supportsHeartRateMeasurement(GBDevice device) { + return false; + } + + @Override + public String getManufacturer() { + return "Unspecified"; //TODO: Show chip manufacturer? + } + + @Override + public boolean supportsAppsManagement() { + return false; + } + + @Override + public Class getAppsManagementActivity() { + return null; + } + + @Override + public boolean supportsCalendarEvents() { + return false; + } + + @Override + public boolean supportsRealtimeData() { + return false; //TODO: RRSI + } + + @Override + public boolean supportsWeather() { + return false; + } + + @Override + public boolean supportsFindDevice() { + return true; + } + + @Override + protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) { + // nothing to delete, yet + } +} 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 dfea02556..6762808a0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -64,6 +64,7 @@ public enum DeviceType { MAKIBESHR3(150, R.drawable.ic_device_default, R.drawable.ic_device_hplus_disabled, R.string.devicetype_makibes_hr3), BANGLEJS(160, R.drawable.ic_device_zetime, R.drawable.ic_device_zetime_disabled, R.string.devicetype_banglejs), MIJIA_LYWSD02(200, R.drawable.ic_device_pebble, R.drawable.ic_device_pebble_disabled, R.string.devicetype_mijia_lywsd02), + ITAG(250, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_itag), VIBRATISSIMO(300, R.drawable.ic_device_lovetoy, R.drawable.ic_device_lovetoy_disabled, R.string.devicetype_vibratissimo), TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test); 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 d5f17b733..858c34954 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java @@ -44,6 +44,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitgts.Ama import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband3.MiBand3Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband4.MiBand4Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.id115.ID115Support; +import nodomain.freeyourgadget.gadgetbridge.service.devices.itag.ITagSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.BFH16DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.TeclastH30.TeclastH30Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.liveview.LiveviewSupport; @@ -222,6 +223,9 @@ public class DeviceSupportFactory { case MAKIBESHR3: deviceSupport = new ServiceDeviceSupport(new MakibesHR3DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); break; + case ITAG: + deviceSupport = new ServiceDeviceSupport(new ITagSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); + break; case BANGLEJS: deviceSupport = new ServiceDeviceSupport(new BangleJSDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); break; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/itag/ITagSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/itag/ITagSupport.java new file mode 100644 index 000000000..55f4a0be7 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/itag/ITagSupport.java @@ -0,0 +1,318 @@ +/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Taavi Eomäe + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.itag; + +import android.bluetooth.BluetoothGatt; +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.deviceevents.GBDeviceEventBatteryInfo; +import nodomain.freeyourgadget.gadgetbridge.devices.itag.ITagConstants; +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.GattService; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; +import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile; +import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile; + +public class ITagSupport extends AbstractBTLEDeviceSupport { + + private static final Logger LOG = LoggerFactory.getLogger(ITagSupport.class); + private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo(); + private final DeviceInfoProfile deviceInfoProfile; + private final BatteryInfoProfile batteryInfoProfile; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String intentAction = intent.getAction(); + if(intentAction != null) { + if (intentAction.equals(DeviceInfoProfile.ACTION_DEVICE_INFO)) { + handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO)); + } else if (intentAction.equals(BatteryInfoProfile.ACTION_BATTERY_INFO)) { + handleBatteryInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo) intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO)); + } + } else{ + LOG.warn("ITagSupport", "Error reading intent action"); + } + } + }; + + public ITagSupport() { + super(LOG); + addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS); + //TODO: Might not exist! Enabling on unsupported devices causes reconnection loops that + // might cause the device to become unresponsive and drain its battery + //addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE); + //addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE); + + addSupportedService(GattService.UUID_SERVICE_IMMEDIATE_ALERT); + addSupportedService(ITagConstants.UUID_SERVICE_BUTTON); + + + + deviceInfoProfile = new DeviceInfoProfile<>(this); + batteryInfoProfile = new BatteryInfoProfile<>(this); + addSupportedProfile(deviceInfoProfile); + addSupportedProfile(batteryInfoProfile); + + LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext()); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(BatteryInfoProfile.ACTION_BATTERY_INFO); + broadcastManager.registerReceiver(mReceiver, intentFilter); + } + + private void handleBatteryInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo info) { + batteryCmd.level = (short) info.getPercentCharged(); + handleGBDeviceEvent(batteryCmd); + } + + @Override + public void dispose() { + LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext()); + broadcastManager.unregisterReceiver(mReceiver); + super.dispose(); + } + + @Override + protected TransactionBuilder initializeDevice(TransactionBuilder builder) { + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); + requestDeviceInfo(builder); + setInitialized(builder); + batteryInfoProfile.requestBatteryInfo(builder); + return builder; + } + + private void requestDeviceInfo(TransactionBuilder builder) { + LOG.debug("Requesting device info!"); + deviceInfoProfile.requestDeviceInfo(builder); + } + + private void setInitialized(TransactionBuilder builder) { + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); + } + + + @Override + public boolean useAutoConnect() { + return true; + } + + private void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) { + ; + } + + @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) { + onSetConstantVibration(start ? 0x02 : 0x00); + } + + @Override + public void onSetConstantVibration(int intensity) { + getQueue().clear(); + BluetoothGattCharacteristic characteristic = getCharacteristic(UUID.fromString("00002a06-0000-1000-8000-00805f9b34fb")); + + TransactionBuilder builder = new TransactionBuilder("beeping"); + builder.write(characteristic, new byte[]{(byte) intensity}); + builder.queue(getQueue()); + } + + @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 boolean onCharacteristicChanged(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic) { + if (super.onCharacteristicChanged(gatt, characteristic)) { + return true; + } + + UUID characteristicUUID = characteristic.getUuid(); + LOG.info("Unhandled characteristic changed: " + characteristicUUID); + return false; + } + + @Override + public boolean onCharacteristicRead(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, int status) { + if (super.onCharacteristicRead(gatt, characteristic, status)) { + return true; + } + UUID characteristicUUID = characteristic.getUuid(); + + LOG.info("Unhandled characteristic read: " + characteristicUUID); + return false; + } + + @Override + public void onSendConfiguration(String config) { + + } + + @Override + public void onReadConfiguration(String config) { + + } + + @Override + public void onTestNewFunction() { + + } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3ecac7f61..2f900b079 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -686,6 +686,7 @@ Y5 Casio GB-6900 Mi Scale 2 + iTag BFH-16 Mijia Smart Clock Makibes HR3