1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-29 13:26:50 +01:00

merge master branch

This commit is contained in:
Sebastian Kranz 2018-09-06 14:46:25 +02:00
commit 88ba3e8e14
207 changed files with 8792 additions and 819 deletions

View File

@ -1,9 +1,51 @@
### Changelog
#### Version 0.28.0 (WIP)
* Inital support for ZeTime: time,weather and activity data sync, notification support and music playback control is working
#### Version 0.29.1
* Mi Band 3: Support setting language to to German, Italian, French, Polish, Japanese, Korean (read wiki)
* Mi Band 3: Support flashing latest RES files
* Mi Band 3: Fix notification text not being displayed
* Mi Band 3/Cor/Bip: Display app name when no app specific icon is available
* Teclast: add/improve H1 and H3 watch recognition
* Support transliteration for Lithuanian and Bengali
* Fix BLE reconnect issues in certain conditions
* Various fixes for display issues on small screens
* Fix some potential NPEs
* WIP: Display start and end of sleep in statistics
#### Version 0.29.0
* New Device: Initial support for ID115
* New Device: Initial support for Lenovo Watch9
* Show splash screen during startup
* Vertically align device icon in main activity
* Try to support the google clock application (untested)
* Amazfit Cor: Allow to configure displayed menu items
* Amazfit Cor: Support basic music control
* Amazfit Cor: Fix flashing font files
* Amazfit Bip: improved GPX export
* Amazfit Bip: Fix exported GPX file names for *FAT storage
* Amazfit Bip: Fix current weather not being displayed with later firmwares
* Amazfit Bip/Cor: Try to fix device being sometimes stuck in connecting state
* Mi Band 2: Put some device specific settings into its own settings category
* Mi Band 3: Support disabling of on-device menu items
* Mi Band 3: Support locking the Mi Band sceen (swipe up to unlock)
* Mi Band 2/3: New icon
* NO1 F1: Set time during initialization
#### Version 0.28.1
* Fix wrong weather icon mapping in rare cases
* Fix device discovery on Android 4.4
* Amazfit Bip: Use UTC in gpx tracks for better compatibility with external software
* Amazfit Bip: Add the (localized) activity type to the gpx filename
* Amazfit Bip: Fix weather on latest firmwares
#### Version 0.28.0
* Initial support for ZeTime: time, weather and activity data sync, notification support and music playback control is working
* Amazfit Bip/Cor: Rework firmware detection to cope with new version scheme
* Amazfit Bip: Support setting language to Russian
* Amazfit Cor: Support language switching on newer firmwares
* Mi Band 3: support setting language (english and spanish tested)
* Mi Band 3: Fix pairing
* Mi Band 3: Send AQI to enable display of current temperature
#### Version 0.27.1
* Pebble: Change appstore search to point to RomanPort's pebble appstore

View File

@ -26,17 +26,21 @@
* Carsten Pfeiffer <cpfeiffer@users.noreply.github.com>
* Daniele Gobbetti <daniele+github@gobbetti.name>
* João Paulo Barraca <jpbarraca@gmail.com>
* Jonas <jonasdcdm@posteo.net>
* Yaron Shahrabani <sh.yaron@gmail.com>
* Jonas <jonasdcdm@posteo.net>
* postsorino <postsorino@krutt.org>
* protomors <protomors@gmail.com>
* Sebastian Kranz <tklightforce@googlemail.com>
* Vadim Kaushan <admin@disasm.info>
* Allan Nordhøy <epost@anotheragency.no>
* protomors <protomors@gmail.com>
* José Rebelo <joserebelo@outlook.com>
* TaaviE <taavi.eomae+weblate@gmail.com>
* mueller-ma <mueller-ma@users.noreply.github.com>
* ivanovlev <ivanovlev@mail.ru>
* naofum <naofum@gmail.com>
* youzhiran <2668760098@qq.com>
* Tijl Schepens <tijl.schepens@hotmail.com>
* TaaviE <taavi.eomae+weblate@gmail.com>
* mesnevi <shams@airpost.net>
* Julien Pivotto <roidelapluie@inuits.eu>
* Taavi Eomäe <taavi.eomae+github@gmail.com>
* Steffen Liebergeld <perl@gmx.org>
@ -45,52 +49,63 @@
* Felix Konstantin Maurer <maufl@maufl.de>
* Sergey Trofimov <sarg@sarg.org.ru>
* Robert Barat <rbarat07@gmail.com>
* José Rebelo <joserebelo@outlook.com>
* Pavel Elagin <pelagin@techcd.ru>
* JohnnySun <bmy001@gmail.com>
* Uwe Hermann <uwe@hermann-uwe.de>
* Kranz <Kranz>
* Edoardo Rosa <edoardo.rosa90@gmail.com>
* Alberto <albertsal83@gmail.com>
* Vladislav Serkov <vladserkoff@protonmail.com>
* Vebryn <vebryn@gmail.com>
* Gilles Émilien MOREL <contact@gilles-morel.fr>
* Gergely Peidl <gergely@peidl.net>
* Emre <wenigerpluesch@mailbox.org>
* Bożydar <trening302@o2.pl>
* 0nse <0nse@users.noreply.github.com>
* Максим Якимчук <xpinovo@gmail.com>
* Rimas Raguliūnas <rarimas@gmail.com>
* nautilusx <mail.ka@mailbox.org>
* masakoodaa <masakoodaa@protonmail.com>
* Marius Cornescu <marius_cornescu@yahoo.com>
* Lukas Veneziano <fs@venezilu.de>
* Kompact <joaorafael123@hotmail.com>
* K0L0B0G <github@gorobav.ru>
* Jasper <jespiex456@hotmail.com>
* Christian Fischer <sw-dev@computerlyrik.de>
* c4ndel4 <hadrian.candela@gmail.com>
* 6arms1leg <m.brnsfld@googlemail.com>
* Zhong Jianxin <azuwis@gmail.com>
* walkjivefly <mark@walkjivefly.com>
* Thomas <tutonis@gmail.com>
* Ted Stein <me@tedstein.net>
* petronovak <petro.novak@gmail.com>
* Pascal <pascal.tannich@gmail.com>
* NotAFIle <nota@notafile.com>
* Normano64 <per.bergqwist@gmail.com>
* NicoBuntu <nicolas__du95@hotmail.fr>
* nautilusx <mail.ka@mailbox.org>
* Minori Hiraoka (미노리) <minori@mnetwork.co.kr>
* Michal Novotny <mignov@gmail.com>
* mesnevi <shams@airpost.net>
* Martin <ritualz@users.noreply.github.com>
* LL <lu.lecocq@free.fr>
* Jesús <zaagur@gmail.com>
* exit-failure <hakrala@web.de>
* Denis <korden@sky-play.ru>
* Avamander <Avamander@users.noreply.github.com>
* AnthonyDiGirolamo <anthony.digirolamo@gmail.com>
* Andreas Kromke <Andreas.Kromke@web.de>
* Ⲇⲁⲛⲓ Φi <daniphii@outlook.com>
* Your Name <you@example.com>
* Yar <yaroslav.isakov@gmail.com>
* xzovy <caleb@caleb-cooper.net>
* xphnx <xphnx@users.noreply.github.com>
* Vitaliy Shuruta <vshuruta@gmail.com>
* Vincèn PUJOL <vincen@vincen.org>
* Tomer Rosenfeld <tomerosenfeld007@gmail.com>
* Tomas Radej <tradej@redhat.com>
* tiparega <11555126+tiparega@users.noreply.github.com>
* Tarik Sekmen <tarik@ilixi.org>
* Szymon Tomasz Stefanek <s.stefanek@gmail.com>
* szilardx <15869670+szilardx@users.noreply.github.com>
* Sergio Lopez <slp@sinrega.org>
* Sami Alaoui <4ndroidgeek@gmail.com>
* Roman Plevka <rplevka@redhat.com>
@ -98,47 +113,61 @@
* redking <redking974@gmail.com>
* Quallenauge <Hamsi2k@freenet.de>
* Pavel Motyrev <legioner.r@gmail.com>
* Pascal <pascal.tannich@gmail.com>
* Olexandr Nesterenko <olexn@ukr.net>
* Nicolò Balzarotti <anothersms@gmail.com>
* Natanael Arndt <arndtn@gmail.com>
* Molnár Barnabás <nsd4rkn3ss@gmail.com>
* Moarc <aldwulf@gmail.com>
* Mike van Rossum <mike@vanrossum.net>
* Michal Novak <michal.novak@post.cz>
* michaelneu <git@michaeln.eu>
* McSym28 <McSym28@users.noreply.github.com>
* MaxL <z60loa8qw3umzu3@my10minutemail.com>
* Martin <ritualz@users.noreply.github.com>
* maxirnilian <maxirnilian@users.noreply.github.com>
* Martin Piatka <chachacha2323@gmail.com>
* Margreet <margreetkeelan@gmail.com>
* Marc Schlaich <marc.schlaich@googlemail.com>
* Marcel pl (m4rcel) <marcel.garbarczyk@gmail.com>
* Manuel Soler <vg8020@gmail.com>
* Luiz Felipe das Neves Lopes <androidfelipe23@gmail.com>
* Leonardo Amaral <contato@leonardoamaral.com.br>
* lazarosfs <lazarosfs@csd.auth.gr>
* ladbsoft <30509719+ladbsoft@users.noreply.github.com>
* Kristjan Räts <kristjanrats@gmail.com>
* Konrad Iturbe <KonradIT@users.noreply.github.com>
* kevlarcade <kevlarcade@gmail.com>
* Kevin Richter <me@kevinrichter.nl>
* Kaz Wolfe <root@kazwolfe.io>
* Kasha <kasha_malaga@hotmail.com>
* kalaee <alex.kalaee@gmail.com>
* Joseph Kim <official.jkim@gmail.com>
* jonnsoft <>
* Jan Lolek <janlolek@seznam.cz>
* Jakub Jelínek <jakub.jelinek@gmail.com>
* Ivan <ivan_tizhanin@mail.ru>
* Hasan Ammar <ammarh@gmail.com>
* Grzegorz Dznsk <grantmlody96@gmail.com>
* Gilles MOREL <contact@gilles-morel.fr>
* Gideão Gomes Ferreira <trjctr@gmail.com>
* Gabe Schrecker <gabe@pbrb.co.uk>
* freezed-or-frozen <freezed.or.frozen@gmail.com>
* Frank Slezak <KazWolfe@users.noreply.github.com>
* Dreamwalker <aristojeff@gmail.com>
* Dougal19 <4662351+Dougal19@users.noreply.github.com>
* Davis Mosenkovs <davikovs@gmail.com>
* Daniel Hauck <maill@dhauck.eu>
* dakhnod <dakhnod@gmail.com>
* criogenic <criogenic@gmail.com>
* clach04 <Chris.Clark@actian.com>
* Chris Perelstein <chris.perelstein@gmail.com>
* chabotsi <chabotsi+github@chabotsi.fr>
* Carlos Ferreira <calbertoferreira@gmail.com>
* bucala <marcel.bucala@gmail.com>
* boun <boun@gmx.de>
* batataspt@gmail.com <batataspt@gmail.com>
* atkyritsis <at.kyritsis@gmail.com>
* Aniruddha Adhikary <aniruddha@adhikary.net>
* andrewlytvyn <indusfreelancer@gmail.com>
* AndrewH <36428679+andrewheadricke@users.noreply.github.com>
* andre <andre.buesgen@yahoo.de>
* Allen B <28495335+Allen-B1@users.noreply.github.com>

View File

@ -1,27 +1,28 @@
## Feature Matrix
| | Pebble OG | Pebble Time/2 | Mi Band | Mi Band 2 | Amazfit Bip |
|-----------------------------------| ----------|---------------|---------|-----------|-------------|
|Calls Notification | YES | YES | YES | YES | YES |
|Reject Calls | YES | YES | NO | NO | YES |
|Accept Calls | NO(2) | NO(2) | NO | NO | NO |
|Generic Notification | YES | YES | YES | YES | YES |
|Dismiss Notifications on Phone | YES | YES | NO | NO | NO |
|Predefined Replies | YES | YES | NO | NO | NO |
|Voice Replies | N/A | NO(3) | N/A | N/A | N/A |
|Calendar Sync | YES | YES | NO | NO | NO |
|Configure alarms from Gadgetbridge | NO | NO | YES | YES | YES |
|Smart alarms | NO(1) | YES | YES | NO | NO |
|Weather | NO(1) | YES | NO | NO | YES |
|Activity Tracking | NO(1) | YES | YES | YES | YES |
|Sleep Tracking | NO(1) | YES | YES | YES | YES |
|HR Tracking | N/A | YES | YES | YES | YES |
|Realtime Activity Tracking | NO | NO | YES | YES | YES |
|Music Control | YES | YES | NO | NO | NO |
|Watchapp/face Installation | YES | YES | NO | NO | YES |
|Firmware Installation | YES | YES | YES | YES | YES |
|Taking Screenshots | YES | YES | NO | NO | NO |
|Support Android Companion Apps | YES | YES | NO | NO | NO |
| | Pebble OG | Pebble Time/2 | Mi Band | Mi Band 2 | Mi Band 3 | Amazfit Bip | Amazfit Cor |
|-----------------------------------| ----------|---------------|---------|-----------|-----------|-------------|-------------|
|Calls Notification | YES | YES | YES | YES | YES | YES | YES |
|Reject Calls | YES | YES | NO | NO | YES | YES | YES |
|Accept Calls | NO(2) | NO(2) | NO | NO | NO | NO | NO |
|Generic Notification | YES | YES | YES | YES | YES | YES | YES |
|Dismiss Notifications on Phone | YES | YES | NO | NO | NO | NO | NO |
|Predefined Replies | YES | YES | NO | NO | NO | NO | NO |
|Voice Replies | N/A | NO(3) | N/A | N/A | N/A | N/A | N/A |
|Calendar Sync | YES | YES | NO | NO | NO | NO(3) | NO |
|Configure alarms from Gadgetbridge | NO | NO | YES | YES | YES | YES | YES |
|Smart alarms | NO(1) | YES | YES | NO | NO | NO | NO |
|Weather | NO(1) | YES | NO | NO | YES | YES | YES |
|Activity Tracking | NO(1) | YES | YES | YES | YES | YES | YES |
|GPS tracks import | NO | NO | NO | NO | NO | YES | NO |
|Sleep Tracking | NO(1) | YES | YES | YES | YES | YES | YES |
|HR Tracking | N/A | YES | YES | YES | YES | YES | YES |
|Realtime Activity Tracking | NO | NO | YES | YES | YES | YES | YES |
|Music Control | YES | YES | NO | NO | NO | NO | YES |
|Watchapp/face Installation | YES | YES | NO | NO | NO | YES | YES |
|Firmware Installation | YES | YES | YES | YES | YES | YES | YES |
|Taking Screenshots | YES | YES | NO | NO | NO | NO | NO |
|Support Android Companion Apps | YES | YES | NO | NO | NO | NO | NO |
(1) Possible via 3rd Party Watchapp
(2) Theoretically possible (works on iOS, would need lot of work)

View File

@ -70,6 +70,7 @@ public class GBDaoGenerator {
addNo1F1ActivitySample(schema, user, device);
addXWatchActivitySample(schema, user, device);
addZeTimeActivitySample(schema, user, device);
addID115ActivitySample(schema, user, device);
addCalendarSyncState(schema, device);
@ -301,6 +302,18 @@ public class GBDaoGenerator {
return activitySample;
}
private static Entity addID115ActivitySample(Schema schema, Entity user, Entity device) {
Entity activitySample = addEntity(schema, "ID115ActivitySample");
activitySample.implementsSerializable();
addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device);
activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE);
activitySample.addIntProperty("caloriesBurnt");
activitySample.addIntProperty("distanceMeters");
activitySample.addIntProperty("activeTimeMinutes");
return activitySample;
}
private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) {
activitySample.setSuperclass(superClass);
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -2,7 +2,7 @@ Gadgetbridge
============
Gadgetbridge is an Android (4.4+) application which will allow you to use your
Pebble, Mi Band, Amazfit Bit and HPlus device (and more) without the vendor's closed source application
Pebble, Mi Band, Amazfit Bip and HPlus device (and more) without the vendor's closed source application
and without the need to create an account and transmit any of your data to the
vendor's servers.
@ -13,27 +13,32 @@ vendor's servers.
[![Donate](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/Gadgetbridge/donate)
[![Build](https://travis-ci.org/Freeyourgadget/Gadgetbridge.svg?branch=master)](https://travis-ci.org/Freeyourgadget/Gadgetbridge)
## Download
[![Gadgetbridge on F-Droid](/Get_it_on_F-Droid.svg.png?raw=true "Download from F-Droid")](https://f-droid.org/repository/browse/?fdid=nodomain.freeyourgadget.gadgetbridge)
[<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="80">](https://f-droid.org/app/nodomain.freeyourgadget.gadgetbridge)
[List of changes](https://github.com/Freeyourgadget/Gadgetbridge/blob/master/CHANGELOG.md)
## Supported Devices
* Pebble, Pebble Steel, Pebble Time, Pebble Time Steel, Pebble Time Round [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble)
* Pebble 2 [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble)
* Mi Band, Mi Band 1A, Mi Band 1S [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band)
* Mi Band 2 [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-2)
* Amazfit Bip [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Bip)
* Amazfit Cor (no maintainer) [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Amazfit-Cor)
* HPlus Devices (e.g. ZeBand) [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/HPlus)
* Teclast H10, H30 (WIP)
* ID115 (WIP)
* Lenovo Watch 9 (WIP)
* Liveview (WIP)
* Mi Band, Mi Band 1A, Mi Band 1S [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band)
* Mi Band 2 [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-2)
* Mi Band 3 [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band-3)
* NO.1 F1 (WIP)
* Liveview
* Vibratissimo (experimental)
* Pebble, Pebble Steel, Pebble Time, Pebble Time Steel, Pebble Time Round [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble)
* Pebble 2 [Wiki](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble)
* Teclast H10, H30 (WIP)
* XWatch (Affordable Chinese Casio-like smartwatches)
* Vibratissimo (experimental)
* ZeTime (WIP)
## Features
@ -92,6 +97,10 @@ For more information read [this wiki article](https://github.com/Freeyourgadget/
* João Paulo Barraca (HPlus)
* Vitaly Svyastyn (NO.1 F1)
* Sami Alaoui (Teclast H30)
* "ladbsoft" (XWatch)
* Sebastian Kranz (ZeTime)
* Vadim Kaushan (ID115)
* "maxirnilian" (Lenovo Watch 9)
## Contribute

View File

@ -25,8 +25,8 @@ android {
targetSdkVersion 27
// Note: always bump BOTH versionCode and versionName!
versionName "0.28.0"
versionCode 134
versionName "0.29.1"
versionCode 137
vectorDrawables.useSupportLibrary = true
}
buildTypes {
@ -85,7 +85,7 @@ dependencies {
implementation "org.greenrobot:greendao:2.2.1"
implementation "org.apache.commons:commons-lang3:3.5"
implementation "org.cyanogenmod:platform.sdk:6.0"
implementation 'com.jaredrummler:colorpicker:1.0.2'
// implementation project(":DaoCore")
}

View File

@ -8,6 +8,7 @@
-->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="android.permission.CALL_PHONE" />
@ -45,7 +46,7 @@
<activity
android:name=".activities.ControlCenterv2"
android:label="@string/title_activity_controlcenter"
android:theme="@style/GadgetbridgeTheme.NoActionBar">
android:theme="@style/SplashTheme">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.MAIN" />
@ -387,6 +388,12 @@
<activity
android:name=".devices.pebble.PebblePairingActivity"
android:label="@string/title_activity_pebble_pairing" />
<activity
android:name=".devices.watch9.Watch9PairingActivity"
android:label="@string/title_activity_watch9_pairing" />
<activity
android:name=".devices.watch9.Watch9CalibrationActivity"
android:label="@string/title_activity_watch9_calibration" />
<activity
android:name=".activities.charts.ChartsActivity"
android:label="@string/title_activity_charts"

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Martin, Normano64, Taavi Eomäe
Gobbetti, Martin, Normano64, Pavel Elagin, Taavi Eomäe
This file is part of Gadgetbridge.
@ -228,6 +228,10 @@ public class GBApplication extends Application {
logging.setupLogging(enabled);
}
public static String getLogPath(){
return logging.getLogPath();
}
private void setupExceptionHandler() {
LoggingExceptionHandler handler = new LoggingExceptionHandler(Thread.getDefaultUncaughtExceptionHandler());
Thread.setDefaultUncaughtExceptionHandler(handler);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2016-2018 Carsten Pfeiffer, Daniele Gobbetti
/* Copyright (C) 2016-2018 Carsten Pfeiffer, Daniele Gobbetti, Pavel Elagin
This file is part of Gadgetbridge.
@ -54,6 +54,13 @@ public abstract class Logging {
}
}
public String getLogPath() {
if (fileLogger != null)
return fileLogger.getFile();
else
return null;
}
public void debugLoggingConfiguration() {
// For debugging problems with the logback configuration
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();

View File

@ -53,15 +53,15 @@ public class AlarmDetails extends AbstractGBActivity {
alarm = getIntent().getParcelableExtra("alarm");
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
timePicker = (TimePicker) findViewById(R.id.alarm_time_picker);
cbSmartWakeup = (CheckedTextView) findViewById(R.id.alarm_cb_smart_wakeup);
cbMonday = (CheckedTextView) findViewById(R.id.alarm_cb_monday);
cbTuesday = (CheckedTextView) findViewById(R.id.alarm_cb_tuesday);
cbWednesday = (CheckedTextView) findViewById(R.id.alarm_cb_wednesday);
cbThursday = (CheckedTextView) findViewById(R.id.alarm_cb_thursday);
cbFriday = (CheckedTextView) findViewById(R.id.alarm_cb_friday);
cbSaturday = (CheckedTextView) findViewById(R.id.alarm_cb_saturday);
cbSunday = (CheckedTextView) findViewById(R.id.alarm_cb_sunday);
timePicker = findViewById(R.id.alarm_time_picker);
cbSmartWakeup = findViewById(R.id.alarm_cb_smart_wakeup);
cbMonday = findViewById(R.id.alarm_cb_monday);
cbTuesday = findViewById(R.id.alarm_cb_tuesday);
cbWednesday = findViewById(R.id.alarm_cb_wednesday);
cbThursday = findViewById(R.id.alarm_cb_thursday);
cbFriday = findViewById(R.id.alarm_cb_friday);
cbSaturday = findViewById(R.id.alarm_cb_saturday);
cbSunday = findViewById(R.id.alarm_cb_sunday);
cbSmartWakeup.setOnClickListener(new View.OnClickListener() {

View File

@ -297,6 +297,8 @@ public class ControlCenterv2 extends AppCompatActivity
wantedPermissions.add(Manifest.permission.READ_CONTACTS);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.CALL_PHONE);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_CALL_LOG);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_DENIED)
wantedPermissions.add(Manifest.permission.READ_PHONE_STATE);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.PROCESS_OUTGOING_CALLS) == PackageManager.PERMISSION_DENIED)

View File

@ -1,5 +1,6 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Frank Slezak, ivanovlev, Kasha, Lem Dulfo, Steffen Liebergeld
Gobbetti, Frank Slezak, ivanovlev, Kasha, Lem Dulfo, Pavel Elagin, Steffen
Liebergeld
This file is part of Gadgetbridge.
@ -17,12 +18,15 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v4.app.NotificationCompat;
@ -39,6 +43,7 @@ import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
import java.util.Objects;
@ -53,9 +58,9 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static android.content.Intent.EXTRA_SUBJECT;
import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_ID;
public class DebugActivity extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(DebugActivity.class);
@ -235,12 +240,58 @@ public class DebugActivity extends AbstractGBActivity {
testNewFunctionality();
}
});
Button shareLogButton = findViewById(R.id.shareLog);
shareLogButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showWarning();
}
});
}
private void showWarning() {
new AlertDialog.Builder(this)
.setCancelable(true)
.setTitle(R.string.warning)
.setMessage(R.string.share_log_warning)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String fileName = GBApplication.getLogPath();
if (fileName != null && fileName.length() > 0) {
Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
emailIntent.setType("*/*");
emailIntent.putExtra(EXTRA_SUBJECT, "Gadgetbridge log file");
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(fileName)));
startActivity(Intent.createChooser(emailIntent, "Share File"));
}
}
})
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// do nothing
}
})
.show();
}
private void testNewFunctionality() {
GBApplication.deviceService().onTestNewFunction();
}
private void shareLog() {
String fileName = GBApplication.getLogPath();
if(fileName != null && fileName.length() > 0) {
Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
emailIntent.setType("*/*");
emailIntent.putExtra(EXTRA_SUBJECT, "Gadgetbridge log file");
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(fileName)));
startActivity(Intent.createChooser(emailIntent, "Share File"));
}
}
private void testNotification() {
Intent notificationIntent = new Intent(getApplicationContext(), DebugActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, JohnnySun, Lem Dulfo, Taavi Eomäe, Uwe Hermann
/* Copyright (C) 2015-2018 Andreas Shimokawa, boun, Carsten Pfeiffer,
Daniele Gobbetti, JohnnySun, jonnsoft, Lem Dulfo, Taavi Eomäe, Uwe Hermann
This file is part of Gadgetbridge.
@ -231,9 +231,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
public void logMessageContent(byte[] value) {
if (value != null) {
for (byte b : value) {
LOG.warn("DATA: " + String.format("0x%2x", b) + " - " + (char) (b & 0xff));
}
LOG.warn("DATA: " + GB.hexdump(value, 0, value.length));
}
}
@ -630,7 +628,7 @@ public class DiscoveryActivity extends AbstractGBActivity implements AdapterView
super.onPause();
stopBTDiscovery();
stopBTLEDiscovery();
if (GB.supportsBluetoothLE()) {
if (GBApplication.isRunningLollipopOrLater()) {
stopNewBTLEDiscovery();
}
}

View File

@ -43,6 +43,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.adapter.ItemWithDetailsAdapter;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
@ -191,7 +192,7 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal
}
private InstallHandler findInstallHandlerFor(Uri uri) {
for (DeviceCoordinator coordinator : DeviceHelper.getInstance().getAllCoordinators()) {
for (DeviceCoordinator coordinator : getAllCoordinatorsConnectedFirst()) {
InstallHandler handler = coordinator.findInstallHandler(uri, this);
if (handler != null) {
return handler;
@ -200,6 +201,29 @@ public class FwAppInstallerActivity extends AbstractGBActivity implements Instal
return null;
}
private List<DeviceCoordinator> getAllCoordinatorsConnectedFirst() {
DeviceManager deviceManager = ((GBApplication) getApplicationContext()).getDeviceManager();
List<DeviceCoordinator> connectedCoordinators = new ArrayList<>();
List<DeviceCoordinator> allCoordinators = DeviceHelper.getInstance().getAllCoordinators();
List<DeviceCoordinator> sortedCoordinators = new ArrayList<>(allCoordinators.size());
GBDevice connectedDevice = deviceManager.getSelectedDevice();
if (connectedDevice != null && connectedDevice.isConnected()) {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(connectedDevice);
if (coordinator != null) {
connectedCoordinators.add(coordinator);
}
}
sortedCoordinators.addAll(connectedCoordinators);
for (DeviceCoordinator coordinator : allCoordinators) {
if (!connectedCoordinators.contains(coordinator)) {
sortedCoordinators.add(coordinator);
}
}
return sortedCoordinators;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {

View File

@ -16,9 +16,14 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class HeartRateUtils {
public static final int MAX_HEART_RATE_VALUE = 250;
public static final int MIN_HEART_RATE_VALUE = 0;
public static final int MIN_HEART_RATE_VALUE = 10;
/**
* The maxiumum gap between two hr measurements in which
* we interpolate between the measurements. Otherwise, two
@ -28,7 +33,37 @@ public class HeartRateUtils {
*/
public static final int MAX_HR_MEASUREMENTS_GAP_MINUTES = 10;
public static boolean isValidHeartRateValue(int value) {
return value > HeartRateUtils.MIN_HEART_RATE_VALUE && value < HeartRateUtils.MAX_HEART_RATE_VALUE;
private int maxHeartRateValue;
private int minHeartRateValue;
private static final HeartRateUtils instance = new HeartRateUtils();
public static HeartRateUtils getInstance() {
return instance;
}
/**
* Singleton - to access this class use the static #getInstance()
*/
private HeartRateUtils() {
updateCachedHeartRatePreferences();
}
public void updateCachedHeartRatePreferences(){
Prefs prefs = GBApplication.getPrefs();
maxHeartRateValue = prefs.getInt(GBPrefs.CHART_MAX_HEART_RATE, MAX_HEART_RATE_VALUE);
minHeartRateValue = prefs.getInt(GBPrefs.CHART_MIN_HEART_RATE, MIN_HEART_RATE_VALUE);
}
public int getMaxHeartRate(){
return maxHeartRateValue;
}
public int getMinHeartRate(){
return minHeartRateValue;
}
public boolean isValidHeartRateValue(int value) {
return value >= getMinHeartRate() && value <= getMaxHeartRate();
}
}

View File

@ -1,5 +1,6 @@
/* Copyright (C) 2015-2018 0nse, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti, Felix Konstantin Maurer, Normano64
Daniele Gobbetti, Felix Konstantin Maurer, José Rebelo, Martin, Normano64,
Pavel Elagin
This file is part of Gadgetbridge.
@ -52,6 +53,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
@ -60,7 +62,15 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DATEFORMAT;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DISPLAY_ITEMS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ENABLE_TEXT_NOTIFICATIONS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_BAND_SCREEN_UNLOCK;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_NIGHT_MODE;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_NIGHT_MODE_END;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_NIGHT_MODE_OFF;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_NIGHT_MODE_SCHEDULED;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI3_NIGHT_MODE_START;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_HEIGHT_CM;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_SLEEP_DURATION;
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_STEPS_GOAL;
@ -84,6 +94,8 @@ public class SettingsActivity extends AbstractSettingsActivity {
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
Prefs prefs = GBApplication.getPrefs();
Preference pref = findPreference("notifications_generic");
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
@ -341,7 +353,7 @@ public class SettingsActivity extends AbstractSettingsActivity {
int autoFetchInterval = GBApplication.getPrefs().getInt("auto_fetch_interval_limit", 0);
summary = String.format(
getApplicationContext().getString(R.string.pref_auto_fetch_limit_fetches_summary),
(int) autoFetchInterval);
autoFetchInterval);
pref.setSummary(summary);
@ -360,6 +372,130 @@ public class SettingsActivity extends AbstractSettingsActivity {
}
});
final Preference setDateFormat = findPreference(PREF_MI2_DATEFORMAT);
setDateFormat.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
invokeLater(new Runnable() {
@Override
public void run() {
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DATEFORMAT);
}
});
return true;
}
});
final Preference miBand2DisplayItems = findPreference(PREF_MI2_DISPLAY_ITEMS);
miBand2DisplayItems.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
invokeLater(new Runnable() {
@Override
public void run() {
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DISPLAY_ITEMS);
}
});
return true;
}
});
final Preference miBand3ScreenUnlock = findPreference(PREF_MI3_BAND_SCREEN_UNLOCK);
miBand3ScreenUnlock.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
invokeLater(new Runnable() {
@Override
public void run() {
GBApplication.deviceService().onSendConfiguration(PREF_MI3_BAND_SCREEN_UNLOCK);
}
});
return true;
}
});
final Preference miBand3DisplayItems = findPreference("miband3_display_items");
miBand3DisplayItems.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
invokeLater(new Runnable() {
@Override
public void run() {
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DISPLAY_ITEMS);
}
});
return true;
}
});
String nightModeState = prefs.getString(MiBandConst.PREF_MI3_NIGHT_MODE, PREF_MI3_NIGHT_MODE_OFF);
boolean nightModeScheduled = nightModeState.equals(PREF_MI3_NIGHT_MODE_SCHEDULED);
final Preference nightModeStart = findPreference(PREF_MI3_NIGHT_MODE_START);
nightModeStart.setEnabled(nightModeScheduled);
nightModeStart.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
invokeLater(new Runnable() {
@Override
public void run() {
GBApplication.deviceService().onSendConfiguration(PREF_MI3_NIGHT_MODE_START);
}
});
return true;
}
});
final Preference nightModeEnd = findPreference(PREF_MI3_NIGHT_MODE_END);
nightModeEnd.setEnabled(nightModeScheduled);
nightModeEnd.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
invokeLater(new Runnable() {
@Override
public void run() {
GBApplication.deviceService().onSendConfiguration(PREF_MI3_NIGHT_MODE_END);
}
});
return true;
}
});
final Preference nightMode = findPreference(PREF_MI3_NIGHT_MODE);
nightMode.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
final boolean scheduled = PREF_MI3_NIGHT_MODE_SCHEDULED.equals(newVal.toString());
nightModeStart.setEnabled(scheduled);
nightModeEnd.setEnabled(scheduled);
invokeLater(new Runnable() {
@Override
public void run() {
GBApplication.deviceService().onSendConfiguration(PREF_MI3_NIGHT_MODE);
}
});
return true;
}
});
final Preference corDisplayItems = findPreference("cor_display_items");
corDisplayItems.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
invokeLater(new Runnable() {
@Override
public void run() {
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DISPLAY_ITEMS);
}
});
return true;
}
});
// Get all receivers of Media Buttons
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
@ -488,6 +624,7 @@ public class SettingsActivity extends AbstractSettingsActivity {
PREF_USER_WEIGHT_KG,
PREF_USER_SLEEP_DURATION,
PREF_USER_STEPS_GOAL,
PREF_MI2_ENABLE_TEXT_NOTIFICATIONS,
"weather_city",
};
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo
Gobbetti, Konrad Iturbe, Lem Dulfo
This file is part of Gadgetbridge.

View File

@ -70,8 +70,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import static nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils.isValidHeartRateValue;
/**
* A base class fragment to be used with ChartsActivity. The fragment can supply
* a title to be displayed in the activity by returning non-null in #getTitle()
@ -443,6 +441,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
List<Entry> heartrateEntries = hr ? new ArrayList<Entry>(numEntries) : null;
List<Integer> colors = new ArrayList<>(numEntries); // this is kinda inefficient...
int lastHrSampleIndex = -1;
HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
for (int i = 0; i < numEntries; i++) {
ActivitySample sample = samples.get(i);
@ -512,7 +511,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
activityEntries.add(createLineEntry(value, ts));
}
if (hr && sample.getKind() != ActivityKind.TYPE_NOT_WORN && HeartRateUtils.isValidHeartRateValue(sample.getHeartRate())) {
if (hr && sample.getKind() != ActivityKind.TYPE_NOT_WORN && heartRateUtilsInstance.isValidHeartRateValue(sample.getHeartRate())) {
if (lastHrSampleIndex > -1 && ts - lastHrSampleIndex > 1800*HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
heartrateEntries.add(createLineEntry(0, lastHrSampleIndex + 1));
heartrateEntries.add(createLineEntry(0, ts - 1));

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Vebryn
Gobbetti, Pavel Elagin, Vebryn
This file is part of Gadgetbridge.
@ -19,7 +19,6 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -95,6 +94,9 @@ class ActivityAnalysis {
}
}
amount.setStartDate(sample.getTimestamp());
amount.setEndDate(sample.getTimestamp());
previousAmount = amount;
previousSample = sample;
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
Gobbetti, Pavel Elagin
This file is part of Gadgetbridge.
@ -27,6 +27,7 @@ import android.view.ViewGroup;
import com.github.mikephil.charting.animation.Easing;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.LegendEntry;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
@ -103,8 +104,8 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(true);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaximum(HeartRateUtils.MAX_HEART_RATE_VALUE);
yAxisRight.setAxisMinimum(HeartRateUtils.MIN_HEART_RATE_VALUE);
yAxisRight.setAxisMaximum(HeartRateUtils.getInstance().getMaxHeartRate());
yAxisRight.setAxisMinimum(HeartRateUtils.getInstance().getMinHeartRate());
// refresh immediately instead of use refreshIfVisible(), for perceived performance
refresh();
@ -177,6 +178,8 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
legendEntries.add(hrEntry);
}
chart.getLegend().setCustom(legendEntries);
chart.getLegend().setWordWrapEnabled(true);
chart.getLegend().setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER);
}
@Override

View File

@ -168,7 +168,7 @@ public class LiveActivityFragment extends AbstractChartFragment {
private void addSample(ActivitySample sample) {
int heartRate = sample.getHeartRate();
int timestamp = tsTranslation.shorten(sample.getTimestamp());
if (HeartRateUtils.isValidHeartRateValue(heartRate)) {
if (HeartRateUtils.getInstance().isValidHeartRateValue(heartRate)) {
setCurrentHeartRate(heartRate, timestamp);
}
int steps = sample.getSteps();
@ -470,8 +470,8 @@ public class LiveActivityFragment extends AbstractChartFragment {
yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(false);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaximum(HeartRateUtils.MAX_HEART_RATE_VALUE);
yAxisRight.setAxisMinimum(HeartRateUtils.MIN_HEART_RATE_VALUE);
yAxisRight.setAxisMaximum(HeartRateUtils.getInstance().getMaxHeartRate());
yAxisRight.setAxisMinimum(HeartRateUtils.getInstance().getMinHeartRate());
mHistorySet = new LineDataSet(new ArrayList<Entry>(), getString(R.string.live_activity_steps_history));
mHistorySet.setAxisDependency(YAxis.AxisDependency.LEFT);

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2018 0nse, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti
Daniele Gobbetti, Pavel Elagin
This file is part of Gadgetbridge.
@ -20,9 +20,11 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.github.mikephil.charting.animation.Easing;
import com.github.mikephil.charting.charts.Chart;
@ -43,6 +45,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -62,6 +65,7 @@ public class SleepChartFragment extends AbstractChartFragment {
private LineChart mActivityChart;
private PieChart mSleepAmountChart;
private TextView mSleepchartInfo;
private int mSmartAlarmFrom = -1;
private int mSmartAlarmTo = -1;
@ -86,9 +90,25 @@ public class SleepChartFragment extends AbstractChartFragment {
List<Integer> colors = new ArrayList<>();
// int index = 0;
long totalSeconds = 0;
Date startSleep = null;
Date endSleep = null;
for (ActivityAmount amount : amounts.getAmounts()) {
if ((amount.getActivityKind() & ActivityKind.TYPE_SLEEP) != 0) {
long value = amount.getTotalSeconds();
if(startSleep == null){
startSleep = amount.getStartDate();
} else {
if(startSleep.after(amount.getStartDate()))
startSleep = amount.getStartDate();
}
if(endSleep == null){
endSleep = amount.getEndDate();
} else {
if(endSleep.before(amount.getEndDate()))
endSleep = amount.getEndDate();
}
totalSeconds += value;
// entries.add(new PieEntry(value, index++));
entries.add(new PieEntry(value, amount.getName(getActivity())));
@ -112,7 +132,7 @@ public class SleepChartFragment extends AbstractChartFragment {
data.setDataSet(set);
//setupLegend(pieChart);
return new MySleepChartsData(totalSleep, data);
return new MySleepChartsData(totalSleep, data, startSleep, endSleep);
}
@Override
@ -124,6 +144,15 @@ public class SleepChartFragment extends AbstractChartFragment {
mActivityChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
mActivityChart.getXAxis().setValueFormatter(mcd.getChartsData().getXValueFormatter());
mActivityChart.setData(mcd.getChartsData().getData());
if (mcd.getPieData().getStartSleep() != null && mcd.getPieData().getEndSleep() != null) {
mSleepchartInfo.setText(getContext().getString(
R.string.you_slept,
DateTimeUtils.timeToString(mcd.getPieData().getStartSleep()),
DateTimeUtils.timeToString(mcd.getPieData().getEndSleep())));
} else {
mSleepchartInfo.setText(getContext().getString(R.string.you_did_not_sleep));
}
}
@Override
@ -136,8 +165,9 @@ public class SleepChartFragment extends AbstractChartFragment {
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_sleepchart, container, false);
mActivityChart = (LineChart) rootView.findViewById(R.id.sleepchart);
mSleepAmountChart = (PieChart) rootView.findViewById(R.id.sleepchart_pie_light_deep);
mActivityChart = rootView.findViewById(R.id.sleepchart);
mSleepAmountChart = rootView.findViewById(R.id.sleepchart_pie_light_deep);
mSleepchartInfo = rootView.findViewById(R.id.sleepchart_info);
setupActivityChart();
setupSleepAmountChart();
@ -203,8 +233,8 @@ public class SleepChartFragment extends AbstractChartFragment {
yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(true);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE);
yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE);
yAxisRight.setAxisMaxValue(HeartRateUtils.getInstance().getMaxHeartRate());
yAxisRight.setAxisMinValue(HeartRateUtils.getInstance().getMinHeartRate());
}
@Override
@ -246,10 +276,14 @@ public class SleepChartFragment extends AbstractChartFragment {
private static class MySleepChartsData extends ChartsData {
private String totalSleep;
private final PieData pieData;
private @Nullable Date startSleep;
private @Nullable Date endSleep;
public MySleepChartsData(String totalSleep, PieData pieData) {
public MySleepChartsData(String totalSleep, PieData pieData, @Nullable Date startSleep, @Nullable Date endSleep) {
this.totalSleep = totalSleep;
this.pieData = pieData;
this.startSleep = startSleep;
this.endSleep = endSleep;
}
public PieData getPieData() {
@ -259,6 +293,16 @@ public class SleepChartFragment extends AbstractChartFragment {
public CharSequence getTotalSleep() {
return totalSleep;
}
@Nullable
public Date getStartSleep() {
return startSleep;
}
@Nullable
public Date getEndSleep() {
return endSleep;
}
}
private static class MyChartsData extends ChartsData {

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2017-2018 Andreas Shimokawa, Carsten Pfeiffer
/* Copyright (C) 2017-2018 Andreas Shimokawa, Carsten Pfeiffer, Pavel Elagin
This file is part of Gadgetbridge.
@ -18,6 +18,7 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.components.AxisBase;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.LegendEntry;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
@ -132,5 +133,7 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
chart.getLegend().setCustom(legendEntries);
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
chart.getLegend().setWordWrapEnabled(true);
chart.getLegend().setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER);
}
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Lem Dulfo
Gobbetti, José Rebelo, Lem Dulfo, maxirnilian
This file is part of Gadgetbridge.
@ -22,16 +22,19 @@ import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.drawable.GradientDrawable;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.text.InputType;
import android.transition.TransitionManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
@ -40,7 +43,11 @@ import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
import java.util.List;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
@ -50,6 +57,7 @@ import nodomain.freeyourgadget.gadgetbridge.activities.VibrationActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9CalibrationActivity;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
@ -108,9 +116,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
return true;
}
});
holder.deviceImageView.setImageResource(R.drawable.level_list_device);
//level-list does not allow negative values, hence we always add 100 to the key.
holder.deviceImageView.setImageLevel(device.getType().getKey() + 100 + (device.isInitialized() ? 100 : 0));
holder.deviceImageView.setImageResource(device.isInitialized() ? device.getType().getIcon() : device.getType().getDisabledIcon());
holder.deviceNameLabel.setText(getUniqueDeviceName(device));
@ -126,16 +132,22 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
//battery
holder.batteryStatusBox.setVisibility(View.GONE);
short batteryLevel = device.getBatteryLevel();
float batteryVoltage = device.getBatteryVoltage();
BatteryState batteryState = device.getBatteryState();
if (batteryLevel != GBDevice.BATTERY_UNKNOWN) {
holder.batteryStatusBox.setVisibility(View.VISIBLE);
holder.batteryStatusLabel.setText(device.getBatteryLevel() + "%");
BatteryState batteryState = device.getBatteryState();
if (BatteryState.BATTERY_CHARGING.equals(batteryState) ||
BatteryState.BATTERY_CHARGING_FULL.equals(batteryState)) {
holder.batteryIcon.setImageLevel(device.getBatteryLevel() + 100);
} else {
holder.batteryIcon.setImageLevel(device.getBatteryLevel());
}
} else if (BatteryState.NO_BATTERY.equals(batteryState) && batteryVoltage != GBDevice.BATTERY_UNKNOWN) {
holder.batteryStatusBox.setVisibility(View.VISIBLE);
holder.batteryStatusLabel.setText(String.format(Locale.getDefault(), "%.1fV", batteryVoltage));
holder.batteryIcon.setImageLevel(200);
}
//fetch activity data
@ -249,7 +261,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
);
holder.findDevice.setVisibility(device.isInitialized() ? View.VISIBLE : View.GONE);
holder.findDevice.setVisibility(device.isInitialized() && coordinator.supportsFindDevice() ? View.VISIBLE : View.GONE);
holder.findDevice.setOnClickListener(new View.OnClickListener()
{
@ -292,6 +304,111 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
);
holder.calibrateDevice.setVisibility(device.isInitialized() && device.getType() == DeviceType.WATCH9 ? View.VISIBLE : View.GONE);
holder.calibrateDevice.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent startIntent = new Intent(context, Watch9CalibrationActivity.class);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent);
}
});
holder.fmFrequencyBox.setVisibility(View.GONE);
if (device.isInitialized() && device.getExtraInfo("fm_frequency") != null) {
holder.fmFrequencyBox.setVisibility(View.VISIBLE);
holder.fmFrequencyLabel.setText(String.format(Locale.getDefault(), "%.1f", (float) device.getExtraInfo("fm_frequency")));
}
final TextView fmFrequencyLabel = holder.fmFrequencyLabel;
holder.fmFrequencyBox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.preferences_fm_frequency);
final EditText input = new EditText(context);
input.setSelection(input.getText().length());
input.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
input.setText(String.format(Locale.getDefault(), "%.1f", (float) device.getExtraInfo("fm_frequency")));
builder.setView(input);
builder.setPositiveButton(context.getResources().getString(android.R.string.ok),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
float frequency = Float.valueOf(input.getText().toString());
// Trim to 1 decimal place, discard the rest
frequency = Float.valueOf(String.format(Locale.getDefault(), "%.1f", frequency));
if (frequency < 87.5 || frequency > 108.0) {
new AlertDialog.Builder(context)
.setTitle(R.string.pref_invalid_frequency_title)
.setMessage(R.string.pref_invalid_frequency_message)
.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
}
})
.show();
} else {
device.setExtraInfo("fm_frequency", frequency);
fmFrequencyLabel.setText(String.format(Locale.getDefault(), "%.1f", (float) device.getExtraInfo("fm_frequency")));
GBApplication.deviceService().onSetFmFrequency(frequency);
}
}
});
builder.setNegativeButton(context.getResources().getString(R.string.Cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.show();
}
});
holder.ledColor.setVisibility(View.GONE);
if (device.isInitialized() && device.getExtraInfo("led_color") != null && coordinator.supportsLedColor()) {
holder.ledColor.setVisibility(View.VISIBLE);
final GradientDrawable ledColor = (GradientDrawable) holder.ledColor.getDrawable().mutate();
ledColor.setColor((int) device.getExtraInfo("led_color"));
holder.ledColor.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ColorPickerDialog.Builder builder = ColorPickerDialog.newBuilder();
builder.setDialogTitle(R.string.preferences_led_color);
builder.setColor((int) device.getExtraInfo("led_color"));
if (coordinator.supportsRgbLedColor()) {
builder.setAllowCustom(true);
builder.setShowAlphaSlider(false);
builder.setAllowPresets(true);
} else {
builder.setAllowCustom(false);
builder.setAllowPresets(true);
builder.setShowColorShades(false);
builder.setPresets(coordinator.getColorPresets());
}
ColorPickerDialog dialog = builder.create();
dialog.setColorPickerDialogListener(new ColorPickerDialogListener() {
@Override
public void onColorSelected(int dialogId, int color) {
ledColor.setColor(color);
device.setExtraInfo("led_color", color);
GBApplication.deviceService().onSetLedColor(color);
}
@Override
public void onDialogDismissed(int dialogId) {
// Nothing to do
}
});
dialog.show(((Activity) context).getFragmentManager(), "color-picker-dialog");
}
});
}
//remove device, hidden under details
holder.removeDevice.setOnClickListener(new View.OnClickListener()
@ -356,6 +473,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
ImageView setAlarmsView;
ImageView showActivityGraphs;
ImageView showActivityTracks;
ImageView calibrateDevice;
ImageView deviceInfoView;
//overflow
@ -363,6 +481,9 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
ListView deviceInfoList;
ImageView findDevice;
ImageView removeDevice;
LinearLayout fmFrequencyBox;
TextView fmFrequencyLabel;
ImageView ledColor;
ViewHolder(View view) {
super(view);
@ -385,12 +506,16 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
showActivityGraphs = view.findViewById(R.id.device_action_show_activity_graphs);
showActivityTracks = view.findViewById(R.id.device_action_show_activity_tracks);
deviceInfoView = view.findViewById(R.id.device_info_image);
calibrateDevice = view.findViewById(R.id.device_action_calibrate);
deviceInfoBox = view.findViewById(R.id.device_item_infos_box);
//overflow
deviceInfoList = view.findViewById(R.id.device_item_infos);
findDevice = view.findViewById(R.id.device_action_find);
removeDevice = view.findViewById(R.id.device_action_remove);
fmFrequencyBox = view.findViewById(R.id.device_fm_frequency_box);
fmFrequencyLabel = view.findViewById(R.id.fm_frequency);
ledColor = view.findViewById(R.id.device_led_color);
}
}

View File

@ -1,4 +1,5 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Daniele Gobbetti
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
This file is part of Gadgetbridge.
@ -18,6 +19,9 @@ package nodomain.freeyourgadget.gadgetbridge.deviceevents;
public abstract class GBDeviceEvent {
@Override
public String toString() {
return getClass().getSimpleName() + ": ";
}
}

View File

@ -26,6 +26,7 @@ public class GBDeviceEventBatteryInfo extends GBDeviceEvent {
public BatteryState state = BatteryState.UNKNOWN;
public short level = 50;
public int numCharges = -1;
public float voltage = -1f;
public boolean extendedInfoAvailable() {
if (numCharges != -1 && lastChargeTime != null) {

View File

@ -0,0 +1,21 @@
/* Copyright (C) 2018 José Rebelo
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
public class GBDeviceEventFmFrequency extends GBDeviceEvent {
public float frequency;
}

View File

@ -0,0 +1,21 @@
/* Copyright (C) 2018 José Rebelo
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
public class GBDeviceEventLEDColor extends GBDeviceEvent {
public int color;
}

View File

@ -22,4 +22,9 @@ import nodomain.freeyourgadget.gadgetbridge.R;
public class GBDeviceEventVersionInfo extends GBDeviceEvent {
public String fwVersion = GBApplication.getContext().getString(R.string.n_a);
public String hwVersion = GBApplication.getContext().getString(R.string.n_a);
@Override
public String toString() {
return super.toString() + "fwVersion: " + fwVersion + "; hwVersion: " + hwVersion;
}
}

View File

@ -1,4 +1,5 @@
/* Copyright (C) 2015-2018 Carsten Pfeiffer, Daniele Gobbetti
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
This file is part of Gadgetbridge.
@ -129,4 +130,23 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
public boolean supportsActivityTracks() {
return false;
}
@Override
public boolean supportsMusicInfo() {
return false;
}
public boolean supportsLedColor() {
return false;
}
@Override
public boolean supportsRgbLedColor() {
return false;
}
@Override
public int[] getColorPresets() {
return new int[0];
}
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, JohnnySun, Uwe Hermann
Gobbetti, JohnnySun, José Rebelo, Uwe Hermann
This file is part of Gadgetbridge.
@ -242,4 +242,33 @@ public interface DeviceCoordinator {
* forecast display.
*/
boolean supportsWeather();
/**
* Indicates whether the device supports being found by vibrating,
* making some sound or lighting up
*/
boolean supportsFindDevice();
/**
* Indicates whether the device supports displaying music information
* like artist, title, album, play state etc.
*/
boolean supportsMusicInfo();
/**
* Indicates whether the device has an led which supports custom colors
*/
boolean supportsLedColor();
/**
* Indicates whether the device's led supports any RGB color,
* or only preset colors
*/
boolean supportsRgbLedColor();
/**
* Returns the preset colors supported by the device, if any, in ARGB, with alpha = 255
*/
@NonNull
int[] getColorPresets();
}

View File

@ -99,4 +99,12 @@ public interface EventHandler {
void onTestNewFunction();
void onSendWeather(WeatherSpec weatherSpec);
void onSetFmFrequency(float frequency);
/**
* Set the device's led color.
* @param color the new color, in ARGB, with alpha = 255
*/
void onSetLedColor(int color);
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
Gobbetti, José Rebelo
This file is part of Gadgetbridge.
@ -181,4 +181,24 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
public boolean supportsWeather() {
return false;
}
@Override
public boolean supportsFindDevice() {
return false;
}
@Override
public boolean supportsLedColor() {
return false;
}
@Override
public boolean supportsRgbLedColor() {
return false;
}
@Override
public int[] getColorPresets() {
return new int[0];
}
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, João
Paulo Barraca
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, João Paulo Barraca, José Rebelo
This file is part of Gadgetbridge.
@ -103,6 +103,11 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
return false;
}
@Override
public boolean supportsFindDevice() {
return true;
}
@Override
public DeviceType getDeviceType() {
return DeviceType.HPLUS;

View File

@ -263,4 +263,9 @@ public abstract class HuamiCoordinator extends AbstractDeviceCoordinator {
public boolean supportsSmartWakeup(GBDevice device) {
return false;
}
@Override
public boolean supportsFindDevice() {
return true;
}
}

View File

@ -15,7 +15,7 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.miband;
package nodomain.freeyourgadget.gadgetbridge.devices.huami;
import java.util.HashMap;
import java.util.Map;
@ -23,7 +23,7 @@ import java.util.UUID;
import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID;
public class MiBand2Service {
public class HuamiService {
public static final UUID UUID_SERVICE_MIBAND_SERVICE = UUID.fromString(String.format(BASE_UUID, "FEE0"));
@ -50,6 +50,8 @@ public class MiBand2Service {
public static final UUID UUID_CHARACTERISTIC_AUTH = UUID.fromString("00000009-0000-3512-2118-0009af100700");
public static final UUID UUID_CHARACTERISTIC_DEVICEEVENT = UUID.fromString("00000010-0000-3512-2118-0009af100700");
public static final UUID UUID_CHARACTERISTIC_CHUNKEDTRANSFER = UUID.fromString("00000020-0000-3512-2118-0009af100700");
public static final int ALERT_LEVEL_NONE = 0;
public static final int ALERT_LEVEL_MESSAGE = 1;
public static final int ALERT_LEVEL_PHONE_CALL = 2;
@ -101,7 +103,7 @@ public class MiBand2Service {
/**
* In some logs it's 0x0...
*/
public static final byte AUTH_BYTE = 0x8;
public static final byte AUTH_BYTE = 0x08;
// maybe not really activity data, but steps?
public static final byte COMMAND_FETCH_DATA = 0x02;
@ -146,6 +148,7 @@ public class MiBand2Service {
public static final byte[] DISPLAY_YYY = new byte[] {ENDPOINT_DISPLAY, 0x10, 0x0, 0x1, 0x1 };
public static final byte[] COMMAND_DISTANCE_UNIT_METRIC = new byte[] { ENDPOINT_DISPLAY, 0x03, 0x00, 0x00 };
public static final byte[] COMMAND_DISTANCE_UNIT_IMPERIAL = new byte[] { ENDPOINT_DISPLAY, 0x03, 0x00, 0x01 };
public static final byte[] COMMAND_SET_LANGUAGE_NEW_TEMPLATE = new byte[]{ENDPOINT_DISPLAY, 0x17, 0x00, 0, 0, 0, 0, 0};
// The third byte controls the threshold, in minutes
// The last 8 bytes represent 2 separate time intervals for the inactivity warnings

View File

@ -19,9 +19,9 @@ package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip;
import java.util.UUID;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service.DISPLAY_ITEM_BIT_CLOCK;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service.ENDPOINT_DISPLAY;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service.ENDPOINT_DISPLAY_ITEMS;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.DISPLAY_ITEM_BIT_CLOCK;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY_ITEMS;
public class AmazfitBipService {
public static final UUID UUID_CHARACTERISTIC_WEATHER = UUID.fromString("0000000e-0000-3512-2118-0009af100700");
@ -35,8 +35,6 @@ public class AmazfitBipService {
public static final byte[] COMMAND_SET_LANGUAGE_TRADITIONAL_CHINESE = new byte[]{ENDPOINT_DISPLAY, 0x13, 0x00, 0x01};
public static final byte[] COMMAND_SET_LANGUAGE_ENGLISH = new byte[]{ENDPOINT_DISPLAY, 0x13, 0x00, 0x02};
public static final byte[] COMMAND_SET_LANGUAGE_SPANISH = new byte[]{ENDPOINT_DISPLAY, 0x13, 0x00, 0x03};
public static final byte[] COMMAND_SET_LANGUAGE_NEW_TEMPLATE = new byte[]{ENDPOINT_DISPLAY, 0x17, 0x00, 0, 0, 0, 0, 0};
public static final byte COMMAND_ACTIVITY_DATA_TYPE_SPORTS_SUMMARIES = 0x05;
public static final byte COMMAND_ACTIVITY_DATA_TYPE_SPORTS_DETAILS = 0x06;

View File

@ -68,4 +68,9 @@ public class AmazfitCorCoordinator extends HuamiCoordinator {
public boolean supportsWeather() {
return true;
}
@Override
public boolean supportsMusicInfo() {
return true;
}
}

View File

@ -0,0 +1,25 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitcor;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.DISPLAY_ITEM_BIT_CLOCK;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY_ITEMS;
public class AmazfitCorService {
public static final byte[] COMMAND_CHANGE_SCREENS = new byte[]{ENDPOINT_DISPLAY_ITEMS, DISPLAY_ITEM_BIT_CLOCK, 0x20, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
}

View File

@ -27,7 +27,7 @@ import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
@ -43,7 +43,7 @@ public class MiBand2Coordinator extends HuamiCoordinator {
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
if (candidate.supportsService(MiBand2Service.UUID_SERVICE_MIBAND2_SERVICE)) {
if (candidate.supportsService(HuamiService.UUID_SERVICE_MIBAND2_SERVICE)) {
return DeviceType.MIBAND2;
}

View File

@ -1,4 +1,5 @@
/* Copyright (C) 2017-2018 Andreas Shimokawa, João Paulo Barraca
/* Copyright (C) 2017-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, João Paulo Barraca
This file is part of Gadgetbridge.

View File

@ -24,12 +24,17 @@ import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class MiBand3Coordinator extends HuamiCoordinator {
private static final Logger LOG = LoggerFactory.getLogger(MiBand3Coordinator.class);
@ -70,4 +75,23 @@ public class MiBand3Coordinator extends HuamiCoordinator {
public boolean supportsWeather() {
return true;
}
public static boolean getBandScreenUnlock() {
Prefs prefs = GBApplication.getPrefs();
return prefs.getBoolean(MiBandConst.PREF_MI3_BAND_SCREEN_UNLOCK, false);
}
public static String getNightMode() {
Prefs prefs = GBApplication.getPrefs();
return prefs.getString(MiBandConst.PREF_MI3_NIGHT_MODE, MiBandConst.PREF_MI3_NIGHT_MODE_OFF);
}
public static Date getNightModeStart() {
return getTimePreference( MiBandConst.PREF_MI3_NIGHT_MODE_START, "16:00");
}
public static Date getNightModeEnd() {
return getTimePreference(MiBandConst.PREF_MI3_NIGHT_MODE_END, "07:00");
}
}

View File

@ -0,0 +1,31 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, José Rebelo
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.huami.miband3;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.DISPLAY_ITEM_BIT_CLOCK;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService.ENDPOINT_DISPLAY_ITEMS;
public class MiBand3Service {
public static final byte[] COMMAND_CHANGE_SCREENS = new byte[]{ENDPOINT_DISPLAY_ITEMS, DISPLAY_ITEM_BIT_CLOCK, 0x30, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00};
public static final byte[] COMMAND_ENABLE_BAND_SCREEN_UNLOCK = new byte[]{ENDPOINT_DISPLAY, 0x16, 0x00, 0x01};
public static final byte[] COMMAND_DISABLE_BAND_SCREEN_UNLOCK = new byte[]{ENDPOINT_DISPLAY, 0x16, 0x00, 0x00};
public static final byte[] COMMAND_NIGHT_MODE_OFF = new byte[]{0x1a, 0x00};
public static final byte[] COMMAND_NIGHT_MODE_SUNSET = new byte[]{0x1a, 0x02};
public static final byte[] COMMAND_NIGHT_MODE_SCHEDULED = new byte[]{0x1a, 0x01, 0x10, 0x00, 0x07, 0x00};
}

View File

@ -0,0 +1,110 @@
/* Copyright (C) 2018 Vadim Kaushan
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.id115;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID;
public class ID115Constants {
public static final String PREF_WRIST = "id115_wrist";
public static final String PREF_SCREEN_ORIENTATION = "id115_screen_orientation";
public static final UUID UUID_SERVICE_ID115 = UUID.fromString(String.format(BASE_UUID, "0AF0"));
public static final UUID UUID_CHARACTERISTIC_WRITE_NORMAL = UUID.fromString(String.format(BASE_UUID, "0AF6"));
public static final UUID UUID_CHARACTERISTIC_NOTIFY_NORMAL = UUID.fromString(String.format(BASE_UUID, "0AF7"));
public static final UUID UUID_CHARACTERISTIC_WRITE_HEALTH = UUID.fromString(String.format(BASE_UUID, "0AF1"));
public static final UUID UUID_CHARACTERISTIC_NOTIFY_HEALTH = UUID.fromString(String.format(BASE_UUID, "0AF2"));
public static final byte CMD_ID_WARE_UPDATE = 0x01;
public static final byte CMD_ID_GET_INFO = 0x02;
public static final byte CMD_ID_SETTINGS = 0x03;
public static final byte CMD_ID_BIND_UNBIND = 0x04;
public static final byte CMD_ID_NOTIFY = 0x05;
public static final byte CMD_ID_APP_CONTROL = 0x06;
public static final byte CMD_ID_BLE_CONTROL = 0x07;
public static final byte CMD_ID_HEALTH_DATA = 0x08;
public static final byte CMD_ID_DUMP_STACK = 0x20;
public static final byte CMD_ID_LOG = 0x21;
public static final byte CMD_ID_FACTORY = (byte)0xaa;
public static final byte CMD_ID_DEVICE_RESTART = (byte)0xf0;
// CMD_ID_SETTINGS
public static final byte CMD_KEY_SET_TIME = 0x01;
public static final byte CMD_KEY_SET_GOAL = 0x03;
public static final byte CMD_KEY_SET_HAND = 0x22;
public static final byte CMD_ARG_LEFT = 0x00;
public static final byte CMD_ARG_RIGHT = 0x01;
public static final byte CMD_KEY_SET_DISPLAY_MODE = 0x2B;
public static final byte CMD_ARG_HORIZONTAL = 0x00;
public static final byte CMD_ARG_VERTICAL = 0x02;
// CMD_ID_NOTIFY
public static final byte CMD_KEY_NOTIFY_CALL = 0x01;
public static final byte CMD_KEY_NOTIFY_STOP = 0x02;
public static final byte CMD_KEY_NOTIFY_MSG = 0x03;
// CMD_ID_HEALTH_DATA
public static final byte CMD_KEY_FETCH_ACTIVITY_TODAY = 0x03;
// CMD_ID_DEVICE_RESTART
public static final byte CMD_KEY_REBOOT = 0x01;
public static byte getNotificationType(NotificationType type) {
switch (type) {
// case GENERIC_EMAIL:
// return 2; // Icon is not supported
case WECHAT:
return 3;
// case QQ:
// return 4;
case FACEBOOK:
return 6;
case TWITTER:
return 7;
case WHATSAPP:
return 8;
case FACEBOOK_MESSENGER:
return 9;
case INSTAGRAM:
return 10;
case LINKEDIN:
return 11;
// case GENERIC_CALENDAR:
// return 12; // Icon is not supported
// case SKYPE:
// return 13; // Icon is not supported
// case LINE:
// return 17; // Icon is not supported
// case VIBER:
// return 18; // Icon is not supported
// case KAKAO_TALK:
// return 19; // Icon is not supported
// case VK:
// return 16; // Icon is not supported
// case GMAIL:
// return 20; // Icon is not supported
// case OUTLOOK:
// return 21; // Icon is not supported
// case SNAPCHAT:
// return 22; // Icon is not supported
}
return 1;
}
}

View File

@ -0,0 +1,157 @@
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Vadim Kaushan
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.id115;
import android.annotation.TargetApi;
import android.app.Activity;
import android.bluetooth.le.ScanFilter;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelUuid;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
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 ID115Coordinator extends AbstractDeviceCoordinator {
@NonNull
@Override
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public Collection<? extends ScanFilter> createBLEScanFilters() {
ParcelUuid service = new ParcelUuid(ID115Constants.UUID_SERVICE_ID115);
ScanFilter filter = new ScanFilter.Builder().setServiceUuid(service).build();
return Collections.singletonList(filter);
}
@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(ID115Constants.UUID_SERVICE_ID115)) {
return DeviceType.ID115;
}
return DeviceType.UNKNOWN;
}
@Override
public int getBondingStyle(GBDevice deviceCandidate){
return BONDING_STYLE_NONE;
}
@Override
public DeviceType getDeviceType() {
return DeviceType.ID115;
}
@Nullable
@Override
public Class<? extends Activity> getPairingActivity() {
return null;
}
@Override
public boolean supportsActivityDataFetching() {
return true;
}
@Override
public boolean supportsActivityTracking() {
return true;
}
@Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return new ID115SampleProvider(device, session);
}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return null;
}
@Override
public boolean supportsScreenshots() {
return false;
}
@Override
public boolean supportsAlarmConfiguration() {
return false;
}
@Override
public boolean supportsSmartWakeup(GBDevice device) {
return false;
}
@Override
public boolean supportsHeartRateMeasurement(GBDevice device) {
return false;
}
@Override
public String getManufacturer() {
return "VeryFit";
}
@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 supportsWeather() {
return false;
}
@Override
public boolean supportsFindDevice() {
return false;
}
}

View File

@ -0,0 +1,77 @@
/* Copyright (C) 2018 Vadim Kaushan
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.id115;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.Property;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.ID115ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.ID115ActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class ID115SampleProvider extends AbstractSampleProvider<ID115ActivitySample> {
public ID115SampleProvider(GBDevice device, DaoSession session) {
super(device, session);
}
@Override
public AbstractDao<ID115ActivitySample, ?> getSampleDao() {
return getSession().getID115ActivitySampleDao();
}
@Nullable
@Override
protected Property getRawKindSampleProperty() {
return ID115ActivitySampleDao.Properties.RawKind;
}
@NonNull
@Override
protected Property getTimestampSampleProperty() {
return ID115ActivitySampleDao.Properties.Timestamp;
}
@NonNull
@Override
protected Property getDeviceIdentifierSampleProperty() {
return ID115ActivitySampleDao.Properties.DeviceId;
}
@Override
public int normalizeType(int rawType) {
return rawType;
}
@Override
public int toRawActivityKind(int activityKind) {
return activityKind;
}
@Override
public float normalizeIntensity(int rawIntensity) {
return rawIntensity;
}
@Override
public ID115ActivitySample createActivitySample() {
return new ID115ActivitySample();
}
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, protomors, Sami Alaoui
Gobbetti, Dougal19, José Rebelo, protomors, Sami Alaoui
This file is part of Gadgetbridge.
@ -45,11 +45,16 @@ import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TeclastH30Coordinator extends AbstractDeviceCoordinator {
protected static final Logger LOG = LoggerFactory.getLogger(TeclastH30Coordinator.class);
// e.g. H3-B20F
private Pattern deviceNamePattern = Pattern.compile("^H[13]-[ABCDEF0123456789]{4}$");
@NonNull
@Override
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@ -62,10 +67,20 @@ public class TeclastH30Coordinator extends AbstractDeviceCoordinator {
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
String name = candidate.getDevice().getName();
if (name != null && (name.startsWith("TECLAST_H30") || name.startsWith("TECLAST_H10"))) {
if (candidate.supportsService(JYouConstants.UUID_SERVICE_JYOU)) {
return DeviceType.TECLASTH30;
}
String name = candidate.getDevice().getName();
if (name != null) {
if (name.startsWith("TECLAST_H30") || name.startsWith("TECLAST_H10")) {
return DeviceType.TECLASTH30;
}
Matcher deviceNameMatcher = deviceNamePattern.matcher(name);
if (deviceNameMatcher.matches()) {
return DeviceType.TECLASTH30;
}
}
return DeviceType.UNKNOWN;
}
@ -89,6 +104,11 @@ public class TeclastH30Coordinator extends AbstractDeviceCoordinator {
return false;
}
@Override
public boolean supportsFindDevice() {
return true;
}
@Override
public DeviceType getDeviceType() {
return DeviceType.TECLASTH30;

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
Gobbetti, José Rebelo
This file is part of Gadgetbridge.
@ -124,6 +124,11 @@ public class LiveviewCoordinator extends AbstractDeviceCoordinator {
return false;
}
@Override
public boolean supportsFindDevice() {
return true;
}
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
// nothing to delete, yet

View File

@ -69,6 +69,13 @@ public final class MiBandConst {
public static final String PREF_MI2_INACTIVITY_WARNINGS_DND_END = "mi2_inactivity_warnings_dnd_end";
public static final String PREF_MIBAND_SETUP_BT_PAIRING = "mi_setup_bt_pairing";
public static final String PREF_MI3_BAND_SCREEN_UNLOCK = "mi3_band_screen_unlock";
public static final String PREF_MI3_NIGHT_MODE = "mi3_night_mode";
public static final String PREF_MI3_NIGHT_MODE_START = "mi3_night_mode_start";
public static final String PREF_MI3_NIGHT_MODE_END = "mi3_night_mode_end";
public static final String PREF_MI3_NIGHT_MODE_OFF = "off";
public static final String PREF_MI3_NIGHT_MODE_SUNSET = "sunset";
public static final String PREF_MI3_NIGHT_MODE_SCHEDULED = "scheduled";
public static final String ORIGIN_INCOMING_CALL = "incoming_call";
public static final String ORIGIN_ALARM_CLOCK = "alarm_clock";

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Christian
Fischer, Daniele Gobbetti, Szymon Tomasz Stefanek
Fischer, Daniele Gobbetti, José Rebelo, Szymon Tomasz Stefanek
This file is part of Gadgetbridge.
@ -176,6 +176,11 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
return false;
}
@Override
public boolean supportsFindDevice() {
return true;
}
public static boolean hasValidUserInfo() {
String dummyMacAddress = MiBandService.MAC_ADDRESS_FILTER_1_1A + ":00:00:00";
try {

View File

@ -109,34 +109,6 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
}
});
final Preference setDateFormat = findPreference(PREF_MI2_DATEFORMAT);
setDateFormat.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
invokeLater(new Runnable() {
@Override
public void run() {
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DATEFORMAT);
}
});
return true;
}
});
final Preference displayPages = findPreference(PREF_MI2_DISPLAY_ITEMS);
displayPages.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
invokeLater(new Runnable() {
@Override
public void run() {
GBApplication.deviceService().onSendConfiguration(PREF_MI2_DISPLAY_ITEMS);
}
});
return true;
}
});
final Preference activateDisplayOnLift = findPreference(PREF_ACTIVATE_DISPLAY_ON_LIFT);
activateDisplayOnLift.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
@ -440,7 +412,6 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
prefKeys.add(ActivityUser.PREF_USER_STEPS_GOAL);
prefKeys.add(PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR);
prefKeys.add(PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS);
prefKeys.add(PREF_MI2_ENABLE_TEXT_NOTIFICATIONS);
prefKeys.add(PREF_MI2_INACTIVITY_WARNINGS_THRESHOLD);
prefKeys.add(getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_ALARM_CLOCK));
prefKeys.add(getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_INCOMING_CALL));

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, protomors
Gobbetti, José Rebelo, protomors
This file is part of Gadgetbridge.
@ -152,6 +152,11 @@ public class No1F1Coordinator extends AbstractDeviceCoordinator {
return false;
}
@Override
public boolean supportsFindDevice() {
return true;
}
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
Long deviceId = device.getId();

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
Gobbetti, José Rebelo
This file is part of Gadgetbridge.
@ -160,4 +160,14 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
public boolean supportsWeather() {
return true;
}
@Override
public boolean supportsFindDevice() {
return true;
}
@Override
public boolean supportsMusicInfo() {
return true;
}
}

View File

@ -0,0 +1,57 @@
/* Copyright (C) 2018 José Rebelo
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.roidmi;
import android.bluetooth.BluetoothDevice;
import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
public class Roidmi1Coordinator extends RoidmiCoordinator {
private static final Logger LOG = LoggerFactory.getLogger(Roidmi1Coordinator.class);
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
try {
BluetoothDevice device = candidate.getDevice();
String name = device.getName();
if (name != null && name.contains("睿米车载蓝牙播放器")) {
return DeviceType.ROIDMI;
}
} catch (Exception ex) {
LOG.error("unable to check device support", ex);
}
return DeviceType.UNKNOWN;
}
@Override
public DeviceType getDeviceType() {
return DeviceType.ROIDMI;
}
@Override
public int[] getColorPresets() {
return RoidmiConst.COLOR_PRESETS;
}
}

View File

@ -0,0 +1,58 @@
/* Copyright (C) 2018 José Rebelo
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.roidmi;
import android.bluetooth.BluetoothDevice;
import android.support.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
public class Roidmi3Coordinator extends RoidmiCoordinator {
private static final Logger LOG = LoggerFactory.getLogger(Roidmi3Coordinator.class);
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
try {
BluetoothDevice device = candidate.getDevice();
String name = device.getName();
if (name != null && name.contains("Roidmi Music Blue C")) {
LOG.warn("Found a Roidmi 3, but support is disabled.");
return DeviceType.UNKNOWN; // TODO Roidmi 3 is not working atm
}
} catch (Exception ex) {
LOG.error("unable to check device support", ex);
}
return DeviceType.UNKNOWN;
}
@Override
public DeviceType getDeviceType() {
return DeviceType.ROIDMI3;
}
@Override
public boolean supportsRgbLedColor() {
return true;
}
}

View File

@ -0,0 +1,36 @@
/* Copyright (C) 2018 José Rebelo
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.roidmi;
import android.graphics.Color;
public class RoidmiConst {
public static final String ACTION_GET_LED_COLOR = "roidmi_get_led_color";
public static final String ACTION_GET_FM_FREQUENCY = "roidmi_get_frequency";
public static final String ACTION_GET_VOLTAGE = "roidmi_get_voltage";
public static final int[] COLOR_PRESETS = new int[]{
Color.rgb(0xFF, 0x00, 0x00), // red
Color.rgb(0x00, 0xFF, 0x00), // green
Color.rgb(0x00, 0x00, 0xFF), // blue
Color.rgb(0xFF, 0xFF, 0x01), // yellow
Color.rgb(0x00, 0xAA, 0xE5), // sky blue
Color.rgb(0xF0, 0x6E, 0xAA), // pink
Color.rgb(0xFF, 0xFF, 0xFF), // white
Color.rgb(0x00, 0x00, 0x00), // black
};
}

View File

@ -0,0 +1,135 @@
/* Copyright (C) 2018 José Rebelo
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.roidmi;
import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.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.model.ActivitySample;
public abstract class RoidmiCoordinator extends AbstractDeviceCoordinator {
private static final Logger LOG = LoggerFactory.getLogger(RoidmiCoordinator.class);
@Override
public String getManufacturer() {
return "Roidmi";
}
@Override
public int getBondingStyle(GBDevice device) {
return BONDING_STYLE_BOND;
}
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
}
@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 boolean supportsAlarmConfiguration() {
return false;
}
@Override
public boolean supportsSmartWakeup(GBDevice device) {
return false;
}
@Override
public boolean supportsHeartRateMeasurement(GBDevice device) {
return false;
}
@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 supportsWeather() {
return false;
}
@Override
public boolean supportsFindDevice() {
return false;
}
@Override
public boolean supportsLedColor() {
return true;
}
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
Gobbetti, José Rebelo
This file is part of Gadgetbridge.
@ -125,6 +125,11 @@ public class VibratissimoCoordinator extends AbstractDeviceCoordinator {
return false;
}
@Override
public boolean supportsFindDevice() {
return true;
}
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
// nothing to delete, yet

View File

@ -0,0 +1,123 @@
/* Copyright (C) 2018 maxirnilian
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.watch9;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.content.LocalBroadcastManager;
import android.view.View;
import android.widget.Button;
import android.widget.NumberPicker;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class Watch9CalibrationActivity extends AbstractGBActivity {
private static final String STATE_DEVICE = "stateDevice";
GBDevice device;
NumberPicker pickerHour, pickerMinute, pickerSecond;
Handler handler;
Runnable holdCalibration;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_watch9_calibration);
pickerHour = findViewById(R.id.np_hour);
pickerMinute = findViewById(R.id.np_minute);
pickerSecond = findViewById(R.id.np_second);
pickerHour.setMinValue(1);
pickerHour.setMaxValue(12);
pickerHour.setValue(12);
pickerMinute.setMinValue(0);
pickerMinute.setMaxValue(59);
pickerMinute.setValue(0);
pickerSecond.setMinValue(0);
pickerSecond.setMaxValue(59);
pickerSecond.setValue(0);
handler = new Handler();
holdCalibration = new Runnable() {
@Override
public void run() {
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(new Intent(Watch9Constants.ACTION_CALIBRATION_HOLD));
handler.postDelayed(this, 10000);
}
};
Intent intent = getIntent();
device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
if (device == null && savedInstanceState != null) {
device = savedInstanceState.getParcelable(STATE_DEVICE);
}
if (device == null) {
finish();
}
final Button btCalibrate = findViewById(R.id.watch9_bt_calibrate);
btCalibrate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
btCalibrate.setEnabled(false);
handler.removeCallbacks(holdCalibration);
Intent calibrationData = new Intent(Watch9Constants.ACTION_CALIBRATION_SEND);
calibrationData.putExtra(Watch9Constants.VALUE_CALIBRATION_HOUR, pickerHour.getValue());
calibrationData.putExtra(Watch9Constants.VALUE_CALIBRATION_MINUTE, pickerMinute.getValue());
calibrationData.putExtra(Watch9Constants.VALUE_CALIBRATION_SECOND, pickerSecond.getValue());
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibrationData);
finish();
}
});
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(STATE_DEVICE, device);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
device = savedInstanceState.getParcelable(STATE_DEVICE);
}
@Override
protected void onStart() {
super.onStart();
Intent calibration = new Intent(Watch9Constants.ACTION_CALIBRATION);
calibration.putExtra(Watch9Constants.ACTION_ENABLE, true);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibration);
handler.postDelayed(holdCalibration, 1000);
}
@Override
protected void onStop() {
super.onStop();
Intent calibration = new Intent(Watch9Constants.ACTION_CALIBRATION);
calibration.putExtra(Watch9Constants.ACTION_ENABLE, false);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(calibration);
handler.removeCallbacks(holdCalibration);
}
}

View File

@ -0,0 +1,92 @@
/* Copyright (C) 2018 maxirnilian
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.watch9;
import java.util.UUID;
public final class Watch9Constants {
public static final UUID UUID_SERVICE_WATCH9 = UUID.fromString("0000a800-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_UNKNOWN_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_CHARACTERISTIC_WRITE = UUID.fromString("0000a801-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_CHARACTERISTIC_UNKNOWN_2 = UUID.fromString("0000a802-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_CHARACTERISTIC_UNKNOWN_3 = UUID.fromString("0000a803-0000-1000-8000-00805f9b34fb");
public static final UUID UUID_CHARACTERISTIC_UNKNOWN_4 = UUID.fromString("0000a804-0000-1000-8000-00805f9b34fb");
public static final int NOTIFICATION_CHANNEL_DEFAULT = 128;
public static final int NOTIFICATION_CHANNEL_PHONE_CALL = 1024;
public static final byte RESPONSE = 0x13;
public static final byte REQUEST = 0x31;
public static final byte WRITE_VALUE = 0x01;
public static final byte READ_VALUE = 0x02;
public static final byte TASK = 0x04;
public static final byte KEEP_ALIVE = -0x80;
public static final byte[] CMD_HEADER = new byte[]{0x23, 0x01, 0x00, 0x00, 0x00};
// byte[] COMMAND = new byte[]{0x23, 0x01, 0x00, 0x31, 0x00, ... , 0x00}
// | | | | | | Checksum
// | | | | | Command + value
// | | | | Sequence number
// | | | Response/Request indicator
// | | Value length
// | |
// ----- Header
public static final byte[] CMD_FIRMWARE_INFO = new byte[]{0x01, 0x02};
public static final byte[] CMD_AUTHORIZATION_TASK = new byte[]{0x01, 0x05};
public static final byte[] CMD_TIME_SETTINGS = new byte[]{0x01, 0x08};
public static final byte[] CMD_ALARM_SETTINGS = new byte[]{0x01, 0x0A};
public static final byte[] CMD_BATTERY_INFO = new byte[]{0x01, 0x14};
public static final byte[] CMD_NOTIFICATION_TASK = new byte[]{0x03, 0x01};
public static final byte[] CMD_NOTIFICATION_SETTINGS = new byte[]{0x03, 0x02};
public static final byte[] CMD_CALIBRATION_INIT_TASK = new byte[]{0x03, 0x31};
public static final byte[] CMD_CALIBRATION_TASK = new byte[]{0x03, 0x33, 0x01};
public static final byte[] CMD_CALIBRATION_KEEP_ALIVE = new byte[]{0x03, 0x34};
public static final byte[] CMD_DO_NOT_DISTURB_SETTINGS = new byte[]{0x03, 0x61};
public static final byte[] CMD_FITNESS_GOAL_SETTINGS = new byte[]{0x10, 0x02};
public static final byte[] RESP_AUTHORIZATION_TASK = new byte[]{0x01, 0x01, 0x05};
public static final byte[] RESP_BUTTON_INDICATOR = new byte[]{0x04, 0x03, 0x11};
public static final byte[] RESP_ALARM_INDICATOR = new byte[]{-0x80, 0x01, 0x0A};
public static final byte[] RESP_FIRMWARE_INFO = new byte[]{0x08, 0x01, 0x02};
public static final byte[] RESP_TIME_SETTINGS = new byte[]{0x08, 0x01, 0x08};
public static final byte[] RESP_BATTERY_INFO = new byte[]{0x08, 0x01, 0x14};
public static final byte[] RESP_NOTIFICATION_SETTINGS = new byte[]{0x08, 0x03, 0x02};
public static final String ACTION_ENABLE = "action.watch9.enable";
public static final String ACTION_CALIBRATION
= "nodomain.freeyourgadget.gadgetbridge.devices.action.watch9.start_calibration";
public static final String ACTION_CALIBRATION_SEND
= "nodomain.freeyourgadget.gadgetbridge.devices.action.watch9.send_calibration";
public static final String ACTION_CALIBRATION_HOLD
= "nodomain.freeyourgadget.gadgetbridge.devices.action.watch9.keep_calibrating";
public static final String VALUE_CALIBRATION_HOUR
= "value.watch9.calibration_hour";
public static final String VALUE_CALIBRATION_MINUTE
= "value.watch9.calibration_minute";
public static final String VALUE_CALIBRATION_SECOND
= "value.watch9.calibration_second";
}

View File

@ -0,0 +1,164 @@
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, maxirnilian, Vadim Kaushan
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.watch9;
import android.annotation.TargetApi;
import android.app.Activity;
import android.bluetooth.le.ScanFilter;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelUuid;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
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 Watch9DeviceCoordinator extends AbstractDeviceCoordinator {
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
}
@NonNull
@Override
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public Collection<? extends ScanFilter> createBLEScanFilters() {
ParcelUuid watch9Service = new ParcelUuid(Watch9Constants.UUID_SERVICE_WATCH9);
ScanFilter filter = new ScanFilter.Builder().setServiceUuid(watch9Service).build();
return Collections.singletonList(filter);
}
@NonNull
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
String macAddress = candidate.getMacAddress().toUpperCase();
String deviceName = candidate.getName().toUpperCase();
if (candidate.supportsService(Watch9Constants.UUID_SERVICE_WATCH9)) {
return DeviceType.WATCH9;
} else if (macAddress.startsWith("1C:87:79")) {
return DeviceType.WATCH9;
} else if (deviceName.equals("WATCH 9")) {
return DeviceType.WATCH9;
}
return DeviceType.UNKNOWN;
}
@Override
public DeviceType getDeviceType() {
return DeviceType.WATCH9;
}
@Override
public int getBondingStyle(GBDevice deviceCandidate) {
return BONDING_STYLE_NONE;
}
@Nullable
@Override
public Class<? extends Activity> getPairingActivity() {
return Watch9PairingActivity.class;
}
@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 boolean supportsAlarmConfiguration() {
return true;
}
@Override
public boolean supportsSmartWakeup(GBDevice device) {
return false;
}
@Override
public boolean supportsHeartRateMeasurement(GBDevice device) {
return false;
}
@Override
public String getManufacturer() {
return "Lenovo";
}
@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 supportsWeather() {
return false;
}
@Override
public boolean supportsFindDevice() {
return false;
}
}

View File

@ -0,0 +1,129 @@
/* Copyright (C) 2018 maxirnilian
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.watch9;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.widget.TextView;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
import nodomain.freeyourgadget.gadgetbridge.activities.DiscoveryActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class Watch9PairingActivity extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(Watch9PairingActivity.class);
private static final String STATE_DEVICE_CANDIDATE = "stateDeviceCandidate";
private TextView message;
private GBDeviceCandidate deviceCandidate;
private final BroadcastReceiver mPairingReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (GBDevice.ACTION_DEVICE_CHANGED.equals(intent.getAction())) {
GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
LOG.debug("pairing activity: device changed: " + device);
if (deviceCandidate.getMacAddress().equals(device.getAddress())) {
if (device.isInitialized()) {
pairingFinished();
} else if (device.isConnecting() || device.isInitializing()) {
LOG.info("still connecting/initializing device...");
}
}
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_watch9_pairing);
message = findViewById(R.id.watch9_pair_message);
Intent intent = getIntent();
deviceCandidate = intent.getParcelableExtra(DeviceCoordinator.EXTRA_DEVICE_CANDIDATE);
if (deviceCandidate == null && savedInstanceState != null) {
deviceCandidate = savedInstanceState.getParcelable(STATE_DEVICE_CANDIDATE);
}
if (deviceCandidate == null) {
Toast.makeText(this, getString(R.string.message_cannot_pair_no_mac), Toast.LENGTH_SHORT).show();
startActivity(new Intent(this, DiscoveryActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
finish();
return;
}
startPairing();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(STATE_DEVICE_CANDIDATE, deviceCandidate);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
deviceCandidate = savedInstanceState.getParcelable(STATE_DEVICE_CANDIDATE);
}
@Override
protected void onDestroy() {
AndroidUtils.safeUnregisterBroadcastReceiver(LocalBroadcastManager.getInstance(this), mPairingReceiver);
super.onDestroy();
}
private void startPairing() {
message.setText(getString(R.string.pairing, deviceCandidate));
IntentFilter filter = new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED);
LocalBroadcastManager.getInstance(this).registerReceiver(mPairingReceiver, filter);
GBApplication.deviceService().disconnect();
GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
if (device != null) {
GBApplication.deviceService().connect(device, true);
} else {
GB.toast(this, "Unable to connect, can't recognize the device type: " + deviceCandidate, Toast.LENGTH_LONG, GB.ERROR);
}
}
private void pairingFinished() {
AndroidUtils.safeUnregisterBroadcastReceiver(LocalBroadcastManager.getInstance(this), mPairingReceiver);
Intent intent = new Intent(this, ControlCenterv2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
}
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, ladbsoft
Gobbetti, José Rebelo, ladbsoft
This file is part of Gadgetbridge.
@ -130,4 +130,9 @@ public class XWatchCoordinator extends AbstractDeviceCoordinator {
public boolean supportsWeather() {
return false;
}
@Override
public boolean supportsFindDevice() {
return true;
}
}

View File

@ -1,3 +1,19 @@
/* Copyright (C) 2018 Kranz, Sebastian Kranz
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.zetime;
/**

View File

@ -1,3 +1,20 @@
/* Copyright (C) 2016-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, José Rebelo, Kranz, Sebastian Kranz
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.zetime;
import android.app.Activity;
@ -67,6 +84,11 @@ public class ZeTimeCoordinator extends AbstractDeviceCoordinator {
return true;
}
@Override
public boolean supportsFindDevice() {
return true;
}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return null;
@ -127,6 +149,11 @@ public class ZeTimeCoordinator extends AbstractDeviceCoordinator {
return false;
}
@Override
public boolean supportsMusicInfo() {
return true;
}
@Override
public int getBondingStyle(GBDevice device) {
return BONDING_STYLE_NONE;

View File

@ -1,3 +1,19 @@
/* Copyright (C) 2018 Sebastian Kranz
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.zetime;
import android.support.annotation.NonNull;

View File

@ -131,7 +131,7 @@ public class GPXExporter implements ActivityTrackExporter {
ser.attribute(NS_DEFAULT, "lon", formatLocation(location.getLongitude()));
ser.attribute(NS_DEFAULT, "lat", formatLocation(location.getLatitude()));
ser.startTag(NS_DEFAULT, "ele").text(formatLocation(location.getAltitude())).endTag(NS_DEFAULT, "ele");
ser.startTag(NS_DEFAULT, "time").text(formatTime(point.getTime())).endTag(NS_DEFAULT, "time");
ser.startTag(NS_DEFAULT, "time").text(DateTimeUtils.formatIso8601UTC(point.getTime())).endTag(NS_DEFAULT, "time");
String description = point.getDescription();
if (description != null) {
ser.startTag(NS_DEFAULT, "desc").text(description).endTag(NS_DEFAULT, "desc");
@ -151,7 +151,7 @@ public class GPXExporter implements ActivityTrackExporter {
}
int hr = point.getHeartRate();
if (!HeartRateUtils.isValidHeartRateValue(hr)) {
if (!HeartRateUtils.getInstance().isValidHeartRateValue(hr)) {
if (!includeHeartRateOfNearestSample) {
return;
}
@ -162,7 +162,7 @@ public class GPXExporter implements ActivityTrackExporter {
}
hr = closestPointItem.getHeartRate();
if (!HeartRateUtils.isValidHeartRateValue(hr)) {
if (!HeartRateUtils.getInstance().isValidHeartRateValue(hr)) {
return;
}
}
@ -177,11 +177,12 @@ public class GPXExporter implements ActivityTrackExporter {
private @Nullable ActivityPoint findClosestSensibleActivityPoint(Date time, List<ActivityPoint> trackPoints) {
ActivityPoint closestPointItem = null;
HeartRateUtils heartRateUtilsInstance = HeartRateUtils.getInstance();
long lowestDifference = 60 * 2 * 1000; // minimum distance is 2min
for (ActivityPoint pointItem : trackPoints) {
int hrItem = pointItem.getHeartRate();
if (HeartRateUtils.isValidHeartRateValue(hrItem)) {
if (heartRateUtilsInstance.isValidHeartRateValue(hrItem)) {
Date timeItem = pointItem.getTime();
if (timeItem.after(time) || timeItem.equals(time)) {
break; // we assume that the given trackPoints are sorted in time ascending order (oldest first)

View File

@ -1,4 +1,5 @@
/* Copyright (C) 2017-2018 Andreas Shimokawa, Carsten Pfeiffer
/* Copyright (C) 2017-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
This file is part of Gadgetbridge.
@ -40,18 +41,20 @@ public class AlarmClockReceiver extends BroadcastReceiver {
/** A public action sent by AlarmService when the alarm has started. */
public static final String ALARM_ALERT_ACTION = "com.android.deskclock.ALARM_ALERT";
public static final String GOOGLE_CLOCK_ALARM_ALERT_ACTION = "com.google.android.deskclock.action.ALARM_ALERT";
/** A public action sent by AlarmService when the alarm has stopped for any reason. */
public static final String ALARM_DONE_ACTION = "com.android.deskclock.ALARM_DONE";
public static final String GOOGLE_CLOCK_ALARM_DONE_ACTION = "com.google.android.deskclock.action.ALARM_DONE";
private int lastId;
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ALARM_ALERT_ACTION.equals(action)) {
if (ALARM_ALERT_ACTION.equals(action) || GOOGLE_CLOCK_ALARM_ALERT_ACTION.equals(action)) {
sendAlarm(true);
} else if (ALARM_DONE_ACTION.equals(action)) {
} else if (ALARM_DONE_ACTION.equals(action) || GOOGLE_CLOCK_ALARM_DONE_ACTION.equals(action)) {
sendAlarm(false);
}
}

View File

@ -76,11 +76,11 @@ public class MusicPlaybackReceiver extends BroadcastReceiver {
stateSpec.state = (byte) (((Boolean) incoming) ? MusicStateSpec.STATE_PLAYING : MusicStateSpec.STATE_PAUSED);
stateSpec.playRate = (byte) (((Boolean) incoming) ? 100 : 0);
} else if (incoming instanceof String && "duration".equals(key)) {
musicSpec.duration = Integer.valueOf((String) incoming) / 1000;
musicSpec.duration = Integer.parseInt((String) incoming) / 1000;
} else if (incoming instanceof String && "trackno".equals(key)) {
musicSpec.trackNr = Integer.valueOf((String) incoming);
musicSpec.trackNr = Integer.parseInt((String) incoming);
} else if (incoming instanceof String && "totaltrack".equals(key)) {
musicSpec.trackCount = Integer.valueOf((String) incoming);
musicSpec.trackCount = Integer.parseInt((String) incoming);
} else if (incoming instanceof Integer && "pos".equals(key)) {
stateSpec.position = (Integer) incoming;
} else if (incoming instanceof Integer && "repeat".equals(key)) {

View File

@ -1,6 +1,6 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Frank Slezak, Hasan Ammar, Julien Pivotto, Kevin Richter, Normano64,
Steffen Liebergeld, Taavi Eomäe, Zhong Jianxin
Gobbetti, Frank Slezak, Hasan Ammar, José Rebelo, Julien Pivotto, Kevin
Richter, Normano64, Steffen Liebergeld, Taavi Eomäe, Zhong Jianxin
This file is part of Gadgetbridge.
@ -50,6 +50,7 @@ import android.support.v7.graphics.Palette;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
@ -86,6 +87,8 @@ public class NotificationListener extends NotificationListenerService {
private LimitedQueue mActionLookup = new LimitedQueue(16);
private HashMap<String, Long> notificationTimes = new HashMap<>();
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
@ -191,6 +194,8 @@ public class NotificationListener extends NotificationListenerService {
if (shouldIgnore(sbn))
return;
Prefs prefs = GBApplication.getPrefs();
switch (GBApplication.getGrantedInterruptionFilter()) {
case NotificationManager.INTERRUPTION_FILTER_ALL:
break;
@ -265,6 +270,18 @@ public class NotificationListener extends NotificationListenerService {
return;
}
// Ignore too frequent notifications, according to user preference
long min_timeout = prefs.getInt("notifications_timeout", 0) * 1000;
Long cur_time = System.currentTimeMillis();
if (notificationTimes.containsKey(source)) {
Long last_time = notificationTimes.get(source);
if (cur_time - last_time < min_timeout) {
LOG.info("Ignoring frequent notification, last one was " + (cur_time - last_time) + "ms ago");
return;
}
}
notificationTimes.put(source, cur_time);
GBApplication.deviceService().onNotification(notificationSpec);
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2017-2018 Daniele Gobbetti
/* Copyright (C) 2017-2018 Carsten Pfeiffer, Daniele Gobbetti
This file is part of Gadgetbridge.
@ -121,7 +121,7 @@ public class OmniJawsObserver extends ContentObserver {
weatherSpec.windSpeed = toKmh(c.getFloat(11));
weatherSpec.windDirection = c.getInt(12);
weatherSpec.timestamp = (int) (Long.valueOf(c.getString(9)) / 1000);
weatherSpec.timestamp = (int) (Long.parseLong(c.getString(9)) / 1000);
} else if (i == 1) {
weatherSpec.todayMinTemp = toKelvin(c.getFloat(5));
weatherSpec.todayMaxTemp = toKelvin(c.getFloat(6));

View File

@ -41,9 +41,11 @@ public class PhoneCallReceiver extends BroadcastReceiver {
if (intent.getAction().equals("android.intent.action.NEW_OUTGOING_CALL")) {
mSavedNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER");
} else {
String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
int state = tm.getCallState();
onCallStateChanged(context, state, number);
if (intent.hasExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)) {
String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
int state = tm.getCallState();
onCallStateChanged(context, state, number);
}
}
}

View File

@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
@ -73,11 +74,13 @@ public class GBDevice implements Parcelable {
private String mModel;
private State mState = State.NOT_CONNECTED;
private short mBatteryLevel = BATTERY_UNKNOWN;
private float mBatteryVoltage = BATTERY_UNKNOWN;
private short mBatteryThresholdPercent = BATTERY_THRESHOLD_PERCENT;
private BatteryState mBatteryState;
private short mRssi = RSSI_UNKNOWN;
private String mBusyTask;
private List<ItemWithDetails> mDeviceInfos;
private HashMap<String, Object> mExtraInfos;
public GBDevice(String address, String name, DeviceType deviceType) {
this(address, null, name, deviceType);
@ -106,6 +109,7 @@ public class GBDevice implements Parcelable {
mRssi = (short) in.readInt();
mBusyTask = in.readString();
mDeviceInfos = in.readArrayList(getClass().getClassLoader());
mExtraInfos = (HashMap) in.readSerializable();
validate();
}
@ -126,6 +130,7 @@ public class GBDevice implements Parcelable {
dest.writeInt(mRssi);
dest.writeString(mBusyTask);
dest.writeList(mDeviceInfos);
dest.writeSerializable(mExtraInfos);
}
private void validate() {
@ -371,6 +376,33 @@ public class GBDevice implements Parcelable {
return mAddress.hashCode() ^ 37;
}
/**
* Returns the extra info value if it is set, null otherwise
* @param key the extra info key
* @return the extra info value if set, null otherwise
*/
public Object getExtraInfo(String key) {
if (mExtraInfos == null) {
return null;
}
return mExtraInfos.get(key);
}
/**
* Sets an extra info value, overwriting the current one, if any
* @param key the extra info key
* @param info the extra info value
*/
public void setExtraInfo(String key, Object info) {
if (mExtraInfos == null) {
mExtraInfos = new HashMap<>();
}
mExtraInfos.put(key, info);
}
/**
* Ranges from 0-100 (percent), or -1 if unknown
*
@ -388,6 +420,23 @@ public class GBDevice implements Parcelable {
}
}
public void setBatteryVoltage(float batteryVoltage) {
if (batteryVoltage >= 0 || batteryVoltage == BATTERY_UNKNOWN) {
mBatteryVoltage = batteryVoltage;
} else {
LOG.error("Battery voltage must be > 0: " + batteryVoltage);
}
}
/**
* Voltage greater than zero (unit: Volt), or -1 if unknown
*
* @return the battery voltage, or -1 if unknown
*/
public float getBatteryVoltage() {
return mBatteryVoltage;
}
public BatteryState getBatteryState() {
return mBatteryState;
}

View File

@ -1,5 +1,6 @@
/* Copyright (C) 2015-2018 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
criogenic, Frank Slezak, ivanovlev, Julien Pivotto, Kasha, Steffen Liebergeld
criogenic, dakhnod, Frank Slezak, ivanovlev, Julien Pivotto, Kasha, Steffen
Liebergeld
This file is part of Gadgetbridge.
@ -41,9 +42,11 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.util.LanguageUtils;
import nodomain.freeyourgadget.gadgetbridge.util.RtlUtils;
import static nodomain.freeyourgadget.gadgetbridge.util.JavaExtensions.coalesce;
public class GBDeviceService implements DeviceService {
protected final Context mContext;
private final Class<? extends Service> mServiceClass;
@ -81,6 +84,14 @@ public class GBDeviceService implements DeviceService {
}
}
if (RtlUtils.rtlSupport()) {
for (String extra : transliterationExtras) {
if (intent.hasExtra(extra)) {
intent.putExtra(extra, RtlUtils.fixRtl(intent.getStringExtra(extra)));
}
}
}
mContext.startService(intent);
}
@ -142,7 +153,8 @@ public class GBDeviceService implements DeviceService {
.putExtra(EXTRA_NOTIFICATION_ID, notificationSpec.id)
.putExtra(EXTRA_NOTIFICATION_TYPE, notificationSpec.type)
.putExtra(EXTRA_NOTIFICATION_SOURCENAME, notificationSpec.sourceName)
.putExtra(EXTRA_NOTIFICATION_PEBBLE_COLOR, notificationSpec.pebbleColor);
.putExtra(EXTRA_NOTIFICATION_PEBBLE_COLOR, notificationSpec.pebbleColor)
.putExtra(EXTRA_NOTIFICATION_SOURCEAPPID, notificationSpec.sourceAppId);
invokeService(intent);
}
@ -403,4 +415,18 @@ public class GBDeviceService implements DeviceService {
return name;
}
@Override
public void onSetFmFrequency(float frequency) {
Intent intent = createIntent().setAction(ACTION_SET_FM_FREQUENCY)
.putExtra(EXTRA_FM_FREQUENCY, frequency);
invokeService(intent);
}
@Override
public void onSetLedColor(int color) {
Intent intent = createIntent().setAction(ACTION_SET_LED_COLOR)
.putExtra(EXTRA_LED_COLOR, color);
invokeService(intent);
}
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Pavel Elagin
This file is part of Gadgetbridge.
@ -18,6 +18,8 @@ package nodomain.freeyourgadget.gadgetbridge.model;
import android.content.Context;
import java.util.Date;
import nodomain.freeyourgadget.gadgetbridge.R;
public class ActivityAmount {
@ -25,6 +27,8 @@ public class ActivityAmount {
private short percent;
private long totalSeconds;
private long totalSteps;
private Date startDate = null;
private Date endDate = null;
public ActivityAmount(int activityKind) {
this.activityKind = activityKind;
@ -67,4 +71,21 @@ public class ActivityAmount {
}
return context.getString(R.string.abstract_chart_fragment_kind_activity);
}
public Date getStartDate() {
return startDate;
}
public void setStartDate(int seconds) {
if(startDate == null)
this.startDate = new Date((long)seconds * 1000);
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(int seconds) {
this.endDate = new Date((long)seconds * 1000);
}
}

View File

@ -22,5 +22,6 @@ public enum BatteryState {
BATTERY_LOW,
BATTERY_CHARGING,
BATTERY_CHARGING_FULL,
BATTERY_NOT_CHARGING_FULL
BATTERY_NOT_CHARGING_FULL,
NO_BATTERY
}

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Frank Slezak,
ivanovlev, JohnnySun, Julien Pivotto, Kasha, Steffen Liebergeld
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, dakhnod,
Frank Slezak, ivanovlev, JohnnySun, Julien Pivotto, Kasha, Steffen Liebergeld
This file is part of Gadgetbridge.
@ -65,12 +65,15 @@ public interface DeviceService extends EventHandler {
String ACTION_SEND_CONFIGURATION = PREFIX + ".action.send_configuration";
String ACTION_SEND_WEATHER = PREFIX + ".action.send_weather";
String ACTION_TEST_NEW_FUNCTION = PREFIX + ".action.test_new_function";
String ACTION_SET_FM_FREQUENCY = PREFIX + ".action.set_fm_frequency";
String ACTION_SET_LED_COLOR = PREFIX + ".action.set_led_color";
String EXTRA_NOTIFICATION_BODY = "notification_body";
String EXTRA_NOTIFICATION_FLAGS = "notification_flags";
String EXTRA_NOTIFICATION_ID = "notification_id";
String EXTRA_NOTIFICATION_PHONENUMBER = "notification_phonenumber";
String EXTRA_NOTIFICATION_SENDER = "notification_sender";
String EXTRA_NOTIFICATION_SOURCENAME = "notification_sourcename";
String EXTRA_NOTIFICATION_SOURCEAPPID = "notification_sourceappid";
String EXTRA_NOTIFICATION_SUBJECT = "notification_subject";
String EXTRA_NOTIFICATION_TITLE = "notification_title";
String EXTRA_NOTIFICATION_TYPE = "notification_type";
@ -105,6 +108,8 @@ public interface DeviceService extends EventHandler {
String EXTRA_INTERVAL_SECONDS = "interval_seconds";
String EXTRA_WEATHER = "weather";
String EXTRA_RECORDED_DATA_TYPES = "data_types";
String EXTRA_FM_FREQUENCY = "fm_frequency";
String EXTRA_LED_COLOR = "led_color";
/**
* Use EXTRA_REALTIME_SAMPLE instead

View File

@ -1,6 +1,6 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, João Paulo Barraca, ladbsoft, protomors, Quallenauge, Sami
Alaoui, tiparega
Gobbetti, João Paulo Barraca, José Rebelo, Kranz, ladbsoft, maxirnilian,
protomors, Quallenauge, Sami Alaoui, tiparega, Vadim Kaushan
This file is part of Gadgetbridge.
@ -33,10 +33,10 @@ public enum DeviceType {
UNKNOWN(-1, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_unknown),
PEBBLE(1, R.drawable.ic_device_pebble, R.drawable.ic_device_pebble_disabled, R.string.devicetype_pebble),
MIBAND(10, R.drawable.ic_device_miband, R.drawable.ic_device_miband_disabled, R.string.devicetype_miband),
MIBAND2(11, R.drawable.ic_device_miband, R.drawable.ic_device_miband_disabled, R.string.devicetype_miband2),
MIBAND2(11, R.drawable.ic_device_miband2, R.drawable.ic_device_miband2_disabled, R.string.devicetype_miband2),
AMAZFITBIP(12, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_amazfit_bip),
AMAZFITCOR(13, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_amazfit_cor),
MIBAND3(14, R.drawable.ic_device_miband, R.drawable.ic_device_miband_disabled, R.string.devicetype_miband3),
MIBAND3(14, R.drawable.ic_device_miband2, R.drawable.ic_device_miband2_disabled, R.string.devicetype_miband3),
VIBRATISSIMO(20, R.drawable.ic_device_lovetoy, R.drawable.ic_device_lovetoy_disabled, R.string.devicetype_vibratissimo),
LIVEVIEW(30, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_liveview),
HPLUS(40, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled, R.string.devicetype_hplus),
@ -47,6 +47,10 @@ public enum DeviceType {
TECLASTH30(60, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled, R.string.devicetype_teclast_h30),
XWATCH(70, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_xwatch),
ZETIME(80, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_mykronoz_zetime),
ID115(90, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled, R.string.devicetype_id115),
WATCH9(100, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_watch9),
ROIDMI(110, R.drawable.ic_device_roidmi, R.drawable.ic_device_roidmi_disabled, R.string.devicetype_roidmi),
ROIDMI3(112, R.drawable.ic_device_roidmi, R.drawable.ic_device_roidmi_disabled, R.string.devicetype_roidmi3),
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
private final int key;

View File

@ -75,4 +75,16 @@ public class MusicSpec {
result = 31 * result + trackNr;
return result;
}
@Override
public String toString() {
return "MusicSpec{" +
"artist='" + artist + '\'' +
", album='" + album + '\'' +
", track='" + track + '\'' +
", duration=" + duration +
", trackCount=" + trackCount +
", trackNr=" + trackNr +
'}';
}
}

View File

@ -71,4 +71,15 @@ public class MusicStateSpec {
result = 31 * result + (int) repeat;
return result;
}
@Override
public String toString() {
return "MusicStateSpec{" +
"state=" + state +
", position=" + position +
", playRate=" + playRate +
", shuffle=" + shuffle +
", repeat=" + repeat +
'}';
}
}

View File

@ -1,4 +1,5 @@
/* Copyright (C) 2016-2018 Andreas Shimokawa, Daniele Gobbetti
/* Copyright (C) 2016-2018 Andreas Shimokawa, Daniele Gobbetti, Sebastian
Kranz
This file is part of Gadgetbridge.
@ -433,7 +434,7 @@ public class Weather {
return 801;
case 45: //thundershowers
case 47: //isolated thundershowers
return 621;
return 211;
case 3200: //not available
default:
return -1;

View File

@ -1,5 +1,5 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, Taavi Eomäe
Gobbetti, Sebastian Kranz, Taavi Eomäe
This file is part of Gadgetbridge.
@ -52,7 +52,9 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFmFrequency;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventLEDColor;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
@ -159,6 +161,10 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
handleGBDeviceEvent((GBDeviceEventBatteryInfo) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventFindPhone) {
handleGBDeviceEvent((GBDeviceEventFindPhone) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventLEDColor) {
handleGBDeviceEvent((GBDeviceEventLEDColor) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventFmFrequency) {
handleGBDeviceEvent((GBDeviceEventFmFrequency) deviceEvent);
}
}
@ -199,7 +205,7 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
protected void handleGBDeviceEvent(GBDeviceEventVersionInfo infoEvent) {
Context context = getContext();
LOG.info("Got event for VERSION_INFO");
LOG.info("Got event for VERSION_INFO: " + infoEvent);
if (gbDevice == null) {
return;
}
@ -208,6 +214,26 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
gbDevice.sendDeviceUpdateIntent(context);
}
protected void handleGBDeviceEvent(GBDeviceEventLEDColor colorEvent) {
Context context = getContext();
LOG.info("Got event for LED Color");
if (gbDevice == null) {
return;
}
gbDevice.setExtraInfo("led_color", colorEvent.color);
gbDevice.sendDeviceUpdateIntent(context);
}
protected void handleGBDeviceEvent(GBDeviceEventFmFrequency frequencyEvent) {
Context context = getContext();
LOG.info("Got event for FM Frequency");
if (gbDevice == null) {
return;
}
gbDevice.setExtraInfo("fm_frequency", frequencyEvent.frequency);
gbDevice.sendDeviceUpdateIntent(context);
}
private void handleGBDeviceEvent(GBDeviceEventAppInfo appInfoEvent) {
Context context = getContext();
LOG.info("Got event for APP_INFO");
@ -328,21 +354,37 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
LOG.info("Got BATTERY_INFO device event");
gbDevice.setBatteryLevel(deviceEvent.level);
gbDevice.setBatteryState(deviceEvent.state);
gbDevice.setBatteryVoltage(deviceEvent.voltage);
//show the notification if the battery level is below threshold and only if not connected to charger
if (deviceEvent.level <= gbDevice.getBatteryThresholdPercent() &&
(BatteryState.BATTERY_LOW.equals(deviceEvent.state) ||
BatteryState.BATTERY_NORMAL.equals(deviceEvent.state))
) {
GB.updateBatteryNotification(context.getString(R.string.notif_battery_low_percent, gbDevice.getName(), String.valueOf(deviceEvent.level)),
deviceEvent.extendedInfoAvailable() ?
context.getString(R.string.notif_battery_low_percent, gbDevice.getName(), String.valueOf(deviceEvent.level)) + "\n" +
context.getString(R.string.notif_battery_low_bigtext_last_charge_time, DateFormat.getDateTimeInstance().format(deviceEvent.lastChargeTime.getTime())) +
context.getString(R.string.notif_battery_low_bigtext_number_of_charges, String.valueOf(deviceEvent.numCharges))
: ""
, context);
if (deviceEvent.level == GBDevice.BATTERY_UNKNOWN) {
// no level available, just "high" or "low"
if (BatteryState.BATTERY_LOW.equals(deviceEvent.state)) {
GB.updateBatteryNotification(context.getString(R.string.notif_battery_low, gbDevice.getName()),
deviceEvent.extendedInfoAvailable() ?
context.getString(R.string.notif_battery_low_extended, gbDevice.getName(),
context.getString(R.string.notif_battery_low_bigtext_last_charge_time, DateFormat.getDateTimeInstance().format(deviceEvent.lastChargeTime.getTime())) +
context.getString(R.string.notif_battery_low_bigtext_number_of_charges, String.valueOf(deviceEvent.numCharges)))
: ""
, context);
} else {
GB.removeBatteryNotification(context);
}
} else {
GB.removeBatteryNotification(context);
//show the notification if the battery level is below threshold and only if not connected to charger
if (deviceEvent.level <= gbDevice.getBatteryThresholdPercent() &&
(BatteryState.BATTERY_LOW.equals(deviceEvent.state) ||
BatteryState.BATTERY_NORMAL.equals(deviceEvent.state))
) {
GB.updateBatteryNotification(context.getString(R.string.notif_battery_low_percent, gbDevice.getName(), String.valueOf(deviceEvent.level)),
deviceEvent.extendedInfoAvailable() ?
context.getString(R.string.notif_battery_low_percent, gbDevice.getName(), String.valueOf(deviceEvent.level)) + "\n" +
context.getString(R.string.notif_battery_low_bigtext_last_charge_time, DateFormat.getDateTimeInstance().format(deviceEvent.lastChargeTime.getTime())) +
context.getString(R.string.notif_battery_low_bigtext_number_of_charges, String.valueOf(deviceEvent.numCharges))
: ""
, context);
} else {
GB.removeBatteryNotification(context);
}
}
gbDevice.sendDeviceUpdateIntent(context);

View File

@ -1,7 +1,7 @@
/* Copyright (C) 2015-2018 Andreas Shimokawa, Avamander, Carsten Pfeiffer,
Daniele Gobbetti, Daniel Hauck, Frank Slezak, ivanovlev, João Paulo Barraca,
Julien Pivotto, Kasha, Sergey Trofimov, Steffen Liebergeld, Taavi Eomäe,
Uwe Hermann
dakhnod, Daniele Gobbetti, Daniel Hauck, Frank Slezak, ivanovlev, João Paulo
Barraca, Julien Pivotto, Kasha, Martin, Sergey Trofimov, Steffen Liebergeld,
Taavi Eomäe, Uwe Hermann
This file is part of Gadgetbridge.
@ -47,6 +47,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmClockReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmReceiver;
@ -105,7 +106,9 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SE
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETTIME;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_ALARMS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_CONSTANT_VIBRATION;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_FM_FREQUENCY;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_HEARTRATE_MEASUREMENT_INTERVAL;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_LED_COLOR;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_TEST_NEW_FUNCTION;
@ -130,7 +133,9 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CAN
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONFIG;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONNECT_FIRST_TIME;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIND_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FM_FREQUENCY;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_INTERVAL_SECONDS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_LED_COLOR;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ALBUM;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ARTIST;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_DURATION;
@ -148,6 +153,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOT
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_PEBBLE_COLOR;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_PHONENUMBER;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SENDER;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SOURCEAPPID;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SOURCENAME;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SUBJECT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TITLE;
@ -348,6 +354,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
notificationSpec.pebbleColor = (byte) intent.getSerializableExtra(EXTRA_NOTIFICATION_PEBBLE_COLOR);
notificationSpec.id = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
notificationSpec.flags = intent.getIntExtra(EXTRA_NOTIFICATION_FLAGS, 0);
notificationSpec.sourceAppId = intent.getStringExtra(EXTRA_NOTIFICATION_SOURCEAPPID);
if (notificationSpec.type == NotificationType.GENERIC_SMS && notificationSpec.phoneNumber != null) {
notificationSpec.id = mRandom.nextInt(); // FIXME: add this in external SMS Receiver?
@ -547,6 +554,18 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
}
break;
}
case ACTION_SET_LED_COLOR:
int color = intent.getIntExtra(EXTRA_LED_COLOR, 0);
if (color != 0) {
mDeviceSupport.onSetLedColor(color);
}
break;
case ACTION_SET_FM_FREQUENCY:
float frequency = intent.getFloatExtra(EXTRA_FM_FREQUENCY, -1);
if (frequency != -1) {
mDeviceSupport.onSetFmFrequency(frequency);
}
break;
}
return START_STICKY;
@ -637,7 +656,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
mPebbleReceiver = new PebbleReceiver();
registerReceiver(mPebbleReceiver, new IntentFilter("com.getpebble.action.SEND_NOTIFICATION"));
}
if (mMusicPlaybackReceiver == null) {
if (mMusicPlaybackReceiver == null && coordinator != null && coordinator.supportsMusicInfo()) {
mMusicPlaybackReceiver = new MusicPlaybackReceiver();
IntentFilter filter = new IntentFilter();
for (String action : mMusicActions) {
@ -665,6 +684,8 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
IntentFilter filter = new IntentFilter();
filter.addAction(AlarmClockReceiver.ALARM_ALERT_ACTION);
filter.addAction(AlarmClockReceiver.ALARM_DONE_ACTION);
filter.addAction(AlarmClockReceiver.GOOGLE_CLOCK_ALARM_ALERT_ACTION);
filter.addAction(AlarmClockReceiver.GOOGLE_CLOCK_ALARM_DONE_ACTION);
registerReceiver(mAlarmClockReceiver, filter);
}
if (mCMWeatherReceiver == null && coordinator != null && coordinator.supportsWeather()) {
@ -764,6 +785,9 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
mDeviceSupport.setAutoReconnect(autoReconnect);
}
}
if (GBPrefs.CHART_MAX_HEART_RATE.equals(key) || GBPrefs.CHART_MIN_HEART_RATE.equals(key)) {
HeartRateUtils.getInstance().updateCachedHeartRatePreferences();
}
}
protected boolean hasPrefs() {

View File

@ -1,6 +1,6 @@
/* Copyright (C) 2015-2018 0nse, Andreas Shimokawa, Carsten Pfeiffer,
Daniele Gobbetti, João Paulo Barraca, ladbsoft, protomors, Quallenauge,
Sami Alaoui, Sergey Trofimov, tiparega
/* Copyright (C) 2015-2018 0nse, Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti, João Paulo Barraca, José Rebelo, Kranz, ladbsoft, maxirnilian,
protomors, Quallenauge, Sami Alaoui, Sergey Trofimov, tiparega, Vadim Kaushan
This file is part of Gadgetbridge.
@ -29,19 +29,22 @@ import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitcor.AmazfitCorSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband3.MiBand3Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.id115.ID115Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.liveview.LiveviewSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.MiBand2Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.no1f1.No1F1Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi.RoidmiSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.hplus.HPlusSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.TeclastH30Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.xwatch.XWatchSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.zetime.ZeTimeDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class DeviceSupportFactory {
@ -115,7 +118,7 @@ public class DeviceSupportFactory {
deviceSupport = new ServiceDeviceSupport(new MiBandSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
case MIBAND2:
deviceSupport = new ServiceDeviceSupport(new MiBand2Support(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING));
deviceSupport = new ServiceDeviceSupport(new HuamiSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
case MIBAND3:
deviceSupport = new ServiceDeviceSupport(new MiBand3Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
@ -152,9 +155,22 @@ public class DeviceSupportFactory {
break;
case XWATCH:
deviceSupport = new ServiceDeviceSupport(new XWatchSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
case ZETIME:
break;
case ZETIME:
deviceSupport = new ServiceDeviceSupport(new ZeTimeDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
case ID115:
deviceSupport = new ServiceDeviceSupport(new ID115Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
case WATCH9:
deviceSupport = new ServiceDeviceSupport(new Watch9DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.THROTTLING, ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
case ROIDMI:
deviceSupport = new ServiceDeviceSupport(new RoidmiSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
case ROIDMI3:
deviceSupport = new ServiceDeviceSupport(new RoidmiSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
}
if (deviceSupport != null) {
deviceSupport.setContext(gbDevice, mBtAdapter, mContext);

View File

@ -374,4 +374,20 @@ public class ServiceDeviceSupport implements DeviceSupport {
}
delegate.onSendWeather(weatherSpec);
}
@Override
public void onSetFmFrequency(float frequency) {
if (checkBusy("set frequency event")) {
return;
}
delegate.onSetFmFrequency(frequency);
}
@Override
public void onSetLedColor(int color) {
if (checkBusy("set led color event")) {
return;
}
delegate.onSetLedColor(color);
}
}

View File

@ -81,6 +81,10 @@ public abstract class BtClassicIoThread extends GBDeviceIoThread {
public synchronized void write(byte[] bytes) {
if (null == bytes)
return;
if (mOutStream == null) {
LOG.error("mOutStream is null");
return;
}
LOG.debug("writing:" + GB.hexdump(bytes, 0, bytes.length));
try {
mOutStream.write(bytes);

View File

@ -318,4 +318,14 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
profile.onReadRemoteRssi(gatt, rssi, status);
}
}
@Override
public void onSetFmFrequency(float frequency) {
}
@Override
public void onSetLedColor(int color) {
}
}

View File

@ -22,6 +22,9 @@ import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@ -44,16 +47,36 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
public abstract class AbstractBleProfile<T extends AbstractBTLEDeviceSupport> extends AbstractGattCallback {
private final T mSupport;
private List<IntentListener> listeners = new ArrayList<IntentListener>(1);
public AbstractBleProfile(T support) {
this.mSupport = support;
}
public void addListener(IntentListener listener) {
if (listener != null && !listeners.contains(listener)) {
listeners.add(listener);
}
}
public boolean removeListener(IntentListener listener) {
return listeners.remove(listener);
}
protected List<IntentListener> getListeners() {
return Collections.unmodifiableList(listeners);
}
/**
* All notifications should be sent through this methods to make them testable.
* @param intent the intent to broadcast
*/
protected void notify(Intent intent) {
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
// note: we send synchronously in order to keep the processing order of BLE events
// in BtleQueue and the reception of results.
for (IntentListener listener : listeners) {
listener.notify(intent);
}
}
/**

View File

@ -0,0 +1,26 @@
/* Copyright (C) 2018 Carsten Pfeiffer
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles;
import android.content.Intent;
/**
* Callback interface for delivering results of ble requests.
*/
public interface IntentListener {
void notify(Intent intent);
}

View File

@ -23,12 +23,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
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 android.support.v4.content.LocalBroadcastManager;
import android.widget.Toast;
import org.apache.commons.lang3.ArrayUtils;
@ -63,7 +58,6 @@ 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.profiles.deviceinfo.DeviceInfo;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
@ -79,35 +73,17 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
private HPlusHandlerThread syncHelper;
private DeviceType deviceType = DeviceType.UNKNOWN;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String s = intent.getAction();
if (s.equals(DeviceInfoProfile.ACTION_DEVICE_INFO)) {
handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
}
}
};
public HPlusSupport(DeviceType type) {
super(LOG);
LOG.info("HPlusSupport Instance Created");
deviceType = type;
addSupportedService(HPlusConstants.UUID_SERVICE_HP);
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
IntentFilter intentFilter = new IntentFilter();
broadcastManager.registerReceiver(mReceiver, intentFilter);
}
@Override
public void dispose() {
LOG.info("Dispose");
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
broadcastManager.unregisterReceiver(mReceiver);
close();
super.dispose();

View File

@ -14,13 +14,14 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2;
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.AbstractMiBandOperation;
public abstract class AbstractMiBand2Operation extends AbstractMiBandOperation<MiBand2Support> {
protected AbstractMiBand2Operation(MiBand2Support support) {
public abstract class AbstractHuamiOperation extends AbstractMiBandOperation<HuamiSupport> {
protected AbstractHuamiOperation(HuamiSupport support) {
super(support);
}

View File

@ -30,4 +30,5 @@ public class HuamiDeviceEvent {
public static final byte BUTTON_PRESSED_LONG = 0x0b;
public static final byte TICK_30MIN = 0x0e; // unsure
public static final byte FIND_PHONE_STOP = 0x0f;
public static final byte MUSIC_CONTROL = (byte) 0xfe;
}

View File

@ -16,7 +16,7 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2;
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
@ -34,6 +34,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
@ -54,22 +56,24 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.ActivateDisplayOnLift;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband2.MiBand2FWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DateTimeDisplay;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DoNotDisturb;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
@ -84,6 +88,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
@ -97,19 +102,21 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.heartrate.HeartRateProfile;
import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2NotificationStrategy;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2TextNotificationStrategy;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.actions.StopNotificationAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchActivityOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSportsSummaryOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.InitOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.UpdateFirmwareOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.RealtimeSamplesSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.actions.StopNotificationAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.operations.FetchActivityOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.operations.FetchSportsSummaryOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.operations.InitOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.operations.UpdateFirmwareOperation;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
@ -134,7 +141,7 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VI
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefIntValue;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefStringValue;
public class MiBand2Support extends AbstractBTLEDeviceSupport {
public class HuamiSupport extends AbstractBTLEDeviceSupport {
// We introduce key press counter for notification purposes
private static int currentButtonActionId = 0;
@ -142,20 +149,21 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
private static long currentButtonPressTime = 0;
private static long currentButtonTimerActivationTime = 0;
private static final Logger LOG = LoggerFactory.getLogger(MiBand2Support.class);
private final DeviceInfoProfile<MiBand2Support> deviceInfoProfile;
private final HeartRateProfile<MiBand2Support> heartRateProfile;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
private static final Logger LOG = LoggerFactory.getLogger(HuamiSupport.class);
private final DeviceInfoProfile<HuamiSupport> deviceInfoProfile;
private final HeartRateProfile<HuamiSupport> heartRateProfile;
private final IntentListener mListener = new IntentListener() {
@Override
public void onReceive(Context context, Intent intent) {
public void notify(Intent intent) {
String s = intent.getAction();
if (s.equals(DeviceInfoProfile.ACTION_DEVICE_INFO)) {
if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(s)) {
handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO));
}
}
};
BluetoothGattCharacteristic characteristicHRControlPoint;
private BluetoothGattCharacteristic characteristicHRControlPoint;
protected BluetoothGattCharacteristic characteristicChunked;
private boolean needsAuth;
private volatile boolean telephoneRinging;
@ -168,11 +176,15 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
private RealtimeSamplesSupport realtimeSamplesSupport;
private boolean alarmClockRinging;
public MiBand2Support() {
private boolean isMusicAppStarted = false;
private MusicSpec bufferMusicSpec = null;
private MusicStateSpec bufferMusicStateSpec = null;
public HuamiSupport() {
this(LOG);
}
public MiBand2Support(Logger logger) {
public HuamiSupport(Logger logger) {
super(logger);
addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
@ -183,25 +195,13 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
addSupportedService(MiBandService.UUID_SERVICE_MIBAND_SERVICE);
addSupportedService(MiBandService.UUID_SERVICE_MIBAND2_SERVICE);
addSupportedService(MiBand2Service.UUID_SERVICE_FIRMWARE_SERVICE);
addSupportedService(HuamiService.UUID_SERVICE_FIRMWARE_SERVICE);
deviceInfoProfile = new DeviceInfoProfile<>(this);
deviceInfoProfile.addListener(mListener);
addSupportedProfile(deviceInfoProfile);
heartRateProfile = new HeartRateProfile<>(this);
addSupportedProfile(heartRateProfile);
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(DeviceInfoProfile.ACTION_DEVICE_INFO);
intentFilter.addAction(DeviceService.ACTION_MIBAND2_AUTH);
broadcastManager.registerReceiver(mReceiver, intentFilter);
}
@Override
public void dispose() {
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
broadcastManager.unregisterReceiver(mReceiver);
super.dispose();
}
@Override
@ -209,8 +209,13 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
try {
boolean authenticate = needsAuth;
needsAuth = false;
new InitOperation(authenticate, this, builder).perform();
byte authFlags = HuamiService.AUTH_BYTE;
if (gbDevice.getType() == DeviceType.MIBAND3) {
authFlags = 0x00;
}
new InitOperation(authenticate, authFlags, this, builder).perform();
characteristicHRControlPoint = getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT);
characteristicChunked = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_CHUNKEDTRANSFER);
} catch (IOException e) {
GB.toast(getContext(), "Initializing Mi Band 2 failed", Toast.LENGTH_SHORT, GB.ERROR, e);
}
@ -246,19 +251,19 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
return timestamp;
}
public MiBand2Support setCurrentTimeWithService(TransactionBuilder builder) {
public HuamiSupport setCurrentTimeWithService(TransactionBuilder builder) {
GregorianCalendar now = BLETypeConversions.createCalendar();
byte[] bytes = getTimeBytes(now, TimeUnit.SECONDS);
builder.write(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_CURRENT_TIME), bytes);
return this;
}
public MiBand2Support setLowLatency(TransactionBuilder builder) {
public HuamiSupport setLowLatency(TransactionBuilder builder) {
// TODO: low latency?
return this;
}
public MiBand2Support setHighLatency(TransactionBuilder builder) {
public HuamiSupport setHighLatency(TransactionBuilder builder) {
// TODO: high latency?
return this;
}
@ -276,18 +281,18 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
// MB2: AVL
// TODO: tear down the notifications on quit
public MiBand2Support enableNotifications(TransactionBuilder builder, boolean enable) {
public HuamiSupport enableNotifications(TransactionBuilder builder, boolean enable) {
builder.notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_NOTIFICATION), enable);
builder.notify(getCharacteristic(GattService.UUID_SERVICE_CURRENT_TIME), enable);
// Notify CHARACTERISTIC9 to receive random auth code
builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_AUTH), enable);
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_AUTH), enable);
return this;
}
public MiBand2Support enableFurtherNotifications(TransactionBuilder builder, boolean enable) {
builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), enable);
builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_6_BATTERY_INFO), enable);
builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_DEVICEEVENT), enable);
public HuamiSupport enableFurtherNotifications(TransactionBuilder builder, boolean enable) {
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), enable);
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_6_BATTERY_INFO), enable);
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT), enable);
return this;
}
@ -303,7 +308,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
return super.connect();
}
private MiBand2Support sendDefaultNotification(TransactionBuilder builder, SimpleNotification simpleNotification, short repeat, BtLEAction extraAction) {
private HuamiSupport sendDefaultNotification(TransactionBuilder builder, SimpleNotification simpleNotification, short repeat, BtLEAction extraAction) {
LOG.info("Sending notification to MiBand: (" + repeat + " times)");
NotificationStrategy strategy = getNotificationStrategy();
for (short i = 0; i < repeat; i++) {
@ -323,7 +328,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
* @param extraAction an extra action to be executed after every vibration and flash sequence. Allows to abort the repetition, for example.
* @param builder
*/
private MiBand2Support sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) {
private HuamiSupport sendCustomNotification(VibrationProfile vibrationProfile, SimpleNotification simpleNotification, int flashTimes, int flashColour, int originalColour, long flashDuration, BtLEAction extraAction, TransactionBuilder builder) {
getNotificationStrategy().sendCustomNotification(vibrationProfile, simpleNotification, flashTimes, flashColour, originalColour, flashDuration, extraAction, builder);
LOG.info("Sending notification to MiBand");
return this;
@ -348,14 +353,14 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
private static final byte[] startHeartMeasurementContinuous = new byte[]{0x15, MiBandService.COMMAND_SET__HR_CONTINUOUS, 1};
private static final byte[] stopHeartMeasurementContinuous = new byte[]{0x15, MiBandService.COMMAND_SET__HR_CONTINUOUS, 0};
private MiBand2Support requestBatteryInfo(TransactionBuilder builder) {
private HuamiSupport requestBatteryInfo(TransactionBuilder builder) {
LOG.debug("Requesting Battery Info!");
BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_6_BATTERY_INFO);
BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_6_BATTERY_INFO);
builder.read(characteristic);
return this;
}
public MiBand2Support requestDeviceInfo(TransactionBuilder builder) {
public HuamiSupport requestDeviceInfo(TransactionBuilder builder) {
LOG.debug("Requesting Device Info!");
deviceInfoProfile.requestDeviceInfo(builder);
return this;
@ -368,16 +373,16 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
* @return
*/
private MiBand2Support setFitnessGoal(TransactionBuilder transaction) {
private HuamiSupport setFitnessGoal(TransactionBuilder transaction) {
LOG.info("Attempting to set Fitness Goal...");
BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_8_USER_SETTINGS);
BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_8_USER_SETTINGS);
if (characteristic != null) {
int fitnessGoal = GBApplication.getPrefs().getInt(ActivityUser.PREF_USER_STEPS_GOAL, 10000);
byte[] bytes = ArrayUtils.addAll(
MiBand2Service.COMMAND_SET_FITNESS_GOAL_START,
HuamiService.COMMAND_SET_FITNESS_GOAL_START,
BLETypeConversions.fromUint16(fitnessGoal));
bytes = ArrayUtils.addAll(bytes,
MiBand2Service.COMMAND_SET_FITNESS_GOAL_END);
HuamiService.COMMAND_SET_FITNESS_GOAL_END);
transaction.write(characteristic, bytes);
} else {
LOG.info("Unable to set Fitness Goal");
@ -392,8 +397,8 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
* @return
*/
private MiBand2Support setUserInfo(TransactionBuilder transaction) {
BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_8_USER_SETTINGS);
private HuamiSupport setUserInfo(TransactionBuilder transaction) {
BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_8_USER_SETTINGS);
if (characteristic == null) {
return this;
}
@ -425,7 +430,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
// FIXME: Do encoding like in PebbleProtocol, this is ugly
byte bytes[] = new byte[]{
MiBand2Service.COMMAND_SET_USERINFO,
HuamiService.COMMAND_SET_USERINFO,
0,
0,
(byte) (birth_year & 0xff),
@ -453,18 +458,18 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
* @param builder
* @return
*/
private MiBand2Support setWearLocation(TransactionBuilder builder) {
private HuamiSupport setWearLocation(TransactionBuilder builder) {
LOG.info("Attempting to set wear location...");
BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_8_USER_SETTINGS);
BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_8_USER_SETTINGS);
if (characteristic != null) {
builder.notify(characteristic, true);
int location = MiBandCoordinator.getWearLocation(getDevice().getAddress());
switch (location) {
case 0: // left hand
builder.write(characteristic, MiBand2Service.WEAR_LOCATION_LEFT_WRIST);
builder.write(characteristic, HuamiService.WEAR_LOCATION_LEFT_WRIST);
break;
case 1: // right hand
builder.write(characteristic, MiBand2Service.WEAR_LOCATION_RIGHT_WRIST);
builder.write(characteristic, HuamiService.WEAR_LOCATION_RIGHT_WRIST);
break;
}
builder.notify(characteristic, false); // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
@ -512,27 +517,27 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
*
* @param builder
*/
private MiBand2Support setHeartrateSleepSupport(TransactionBuilder builder) {
private HuamiSupport setHeartrateSleepSupport(TransactionBuilder builder) {
final boolean enableHrSleepSupport = MiBandCoordinator.getHeartrateSleepSupport(getDevice().getAddress());
if (characteristicHRControlPoint != null) {
builder.notify(characteristicHRControlPoint, true);
if (enableHrSleepSupport) {
LOG.info("Enabling heartrate sleep support...");
builder.write(characteristicHRControlPoint, MiBand2Service.COMMAND_ENABLE_HR_SLEEP_MEASUREMENT);
builder.write(characteristicHRControlPoint, HuamiService.COMMAND_ENABLE_HR_SLEEP_MEASUREMENT);
} else {
LOG.info("Disabling heartrate sleep support...");
builder.write(characteristicHRControlPoint, MiBand2Service.COMMAND_DISABLE_HR_SLEEP_MEASUREMENT);
builder.write(characteristicHRControlPoint, HuamiService.COMMAND_DISABLE_HR_SLEEP_MEASUREMENT);
}
builder.notify(characteristicHRControlPoint, false); // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
}
return this;
}
private MiBand2Support setHeartrateMeasurementInterval(TransactionBuilder builder, int minutes) {
private HuamiSupport setHeartrateMeasurementInterval(TransactionBuilder builder, int minutes) {
if (characteristicHRControlPoint != null) {
builder.notify(characteristicHRControlPoint, true);
LOG.info("Setting heart rate measurement interval to " + minutes + " minutes");
builder.write(characteristicHRControlPoint, new byte[]{MiBand2Service.COMMAND_SET_PERIODIC_HR_MEASUREMENT_INTERVAL, (byte) minutes});
builder.write(characteristicHRControlPoint, new byte[]{HuamiService.COMMAND_SET_PERIODIC_HR_MEASUREMENT_INTERVAL, (byte) minutes});
builder.notify(characteristicHRControlPoint, false); // TODO: this should actually be in some kind of finally-block in the queue. It should also be sent asynchronously after the notifications have completely arrived and processed.
}
return this;
@ -608,7 +613,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
@Override
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
try {
BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION);
BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION);
TransactionBuilder builder = performInitialized("Set alarm");
boolean anyAlarmEnabled = false;
for (Alarm alarm : alarms) {
@ -632,9 +637,9 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
onAlarmClock(notificationSpec);
return;
}
int alertLevel = MiBand2Service.ALERT_LEVEL_MESSAGE;
int alertLevel = HuamiService.ALERT_LEVEL_MESSAGE;
if (notificationSpec.type == NotificationType.UNKNOWN) {
alertLevel = MiBand2Service.ALERT_LEVEL_VIBRATE_ONLY;
alertLevel = HuamiService.ALERT_LEVEL_VIBRATE_ONLY;
}
String message = NotificationUtils.getPreferredTextFor(notificationSpec, 40, 40, getContext()).trim();
String origin = notificationSpec.type.getGenericType();
@ -652,7 +657,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
};
String message = NotificationUtils.getPreferredTextFor(notificationSpec, 40, 40, getContext());
SimpleNotification simpleNotification = new SimpleNotification(message, AlertCategory.HighPriorityAlert, notificationSpec.type);
performPreferredNotification("alarm clock ringing", MiBandConst.ORIGIN_ALARM_CLOCK, simpleNotification, MiBand2Service.ALERT_LEVEL_VIBRATE_ONLY, abortAction);
performPreferredNotification("alarm clock ringing", MiBandConst.ORIGIN_ALARM_CLOCK, simpleNotification, HuamiService.ALERT_LEVEL_VIBRATE_ONLY, abortAction);
}
@Override
@ -685,7 +690,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
};
String message = NotificationUtils.getPreferredTextFor(callSpec);
SimpleNotification simpleNotification = new SimpleNotification(message, AlertCategory.IncomingCall, null);
performPreferredNotification("incoming call", MiBandConst.ORIGIN_INCOMING_CALL, simpleNotification, MiBand2Service.ALERT_LEVEL_PHONE_CALL, abortAction);
performPreferredNotification("incoming call", MiBandConst.ORIGIN_INCOMING_CALL, simpleNotification, HuamiService.ALERT_LEVEL_PHONE_CALL, abortAction);
} else if ((callSpec.command == CallSpec.CALL_START) || (callSpec.command == CallSpec.CALL_END)) {
telephoneRinging = false;
stopCurrentNotification();
@ -718,12 +723,113 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
@Override
public void onSetMusicState(MusicStateSpec stateSpec) {
// not supported
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
if (!coordinator.supportsMusicInfo()) {
return;
}
if (bufferMusicStateSpec != stateSpec) {
bufferMusicStateSpec = stateSpec;
sendMusicStateToDevice();
}
}
@Override
public void onSetMusicInfo(MusicSpec musicSpec) {
// not supported
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
if (!coordinator.supportsMusicInfo()) {
return;
}
if (bufferMusicSpec != musicSpec) {
bufferMusicSpec = musicSpec;
if (isMusicAppStarted) {
sendMusicStateToDevice();
}
}
}
private void sendMusicStateToDevice() {
if (characteristicChunked == null) {
return;
}
if (bufferMusicSpec == null || bufferMusicStateSpec == null) {
try {
TransactionBuilder builder = performInitialized("send dummy playback info to enable music controls");
writeToChunked(builder, 3, new byte[]{1, 0, 1, 0, 0, 0, 1, 0});
builder.queue(getQueue());
} catch (IOException e) {
LOG.error("Unable to send dummy music controls");
}
return;
}
byte flags = 0x00;
flags |= 0x01;
int length = 8;
if (bufferMusicSpec.track != null && bufferMusicSpec.track.getBytes().length > 0) {
length += bufferMusicSpec.track.getBytes().length + 1;
flags |= 0x02;
}
if (bufferMusicSpec.album != null && bufferMusicSpec.album.getBytes().length > 0) {
length += bufferMusicSpec.album.getBytes().length + 1;
flags |= 0x04;
}
if (bufferMusicSpec.artist != null && bufferMusicSpec.artist.getBytes().length > 0) {
length += bufferMusicSpec.artist.getBytes().length + 1;
flags |= 0x08;
}
// LOG.info("Music flags are: " + (flags & 0xff));
try {
ByteBuffer buf = ByteBuffer.allocate(length);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.put(flags);
byte state;
switch (bufferMusicStateSpec.state) {
case MusicStateSpec.STATE_PLAYING:
state = 1;
break;
default:
state = 0;
}
buf.put(state);
buf.put(new byte[]{0x1, 0x0, 0x0, 0x0}); //unknown
buf.put(new byte[]{0x1,0x0}); //show track
// buf.put(new byte[]{0x1,0x1}); //show album
if (bufferMusicSpec.track != null && bufferMusicSpec.track.getBytes().length > 0) {
buf.put(bufferMusicSpec.track.getBytes());
buf.put((byte) 0);
}
if (bufferMusicSpec.album != null && bufferMusicSpec.album.getBytes().length > 0) {
buf.put(bufferMusicSpec.album.getBytes());
buf.put((byte) 0);
}
if (bufferMusicSpec.artist != null && bufferMusicSpec.artist.getBytes().length > 0) {
buf.put(bufferMusicSpec.artist.getBytes());
buf.put((byte) 0);
}
TransactionBuilder builder = performInitialized("send playback info");
writeToChunked(builder, 3, buf.array());
builder.queue(getQueue());
} catch (IOException e) {
LOG.error("Unable to send playback state");
}
// LOG.info("Sent music: " + bufferMusicSpec.toString() + " " + bufferMusicStateSpec.toString());
}
@Override
@ -737,8 +843,8 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
}
}
public MiBand2Support sendReboot(TransactionBuilder builder) {
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_FIRMWARE), new byte[] { MiBand2Service.COMMAND_FIRMWARE_REBOOT});
public HuamiSupport sendReboot(TransactionBuilder builder) {
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_FIRMWARE), new byte[] { HuamiService.COMMAND_FIRMWARE_REBOOT});
return this;
}
@ -817,9 +923,9 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
try {
TransactionBuilder builder = performInitialized(enable ? "Enabling realtime steps notifications" : "Disabling realtime steps notifications");
if (enable) {
builder.read(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_7_REALTIME_STEPS));
builder.read(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_7_REALTIME_STEPS));
}
builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_7_REALTIME_STEPS), enable);
builder.notify(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_7_REALTIME_STEPS), enable);
builder.queue(getQueue());
enableRealtimeSamplesTimer(enable);
} catch (IOException e) {
@ -920,7 +1026,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
LOG.info("Sending " + requiredButtonPressMessage + " with button_id " + currentButtonActionId);
this.getContext().getApplicationContext().sendBroadcast(in);
if (prefs.getBoolean(MiBandConst.PREF_MIBAND_BUTTON_ACTION_VIBRATE, false)) {
performPreferredNotification(null, null, null, MiBand2Service.ALERT_LEVEL_VIBRATE_ONLY, null);
performPreferredNotification(null, null, null, HuamiService.ALERT_LEVEL_VIBRATE_ONLY, null);
}
currentButtonActionId = 0;
@ -930,7 +1036,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
}
public void handleDeviceEvent(byte[] value) {
if (value == null || value.length != 1) {
if (value == null || value.length == 0) {
return;
}
GBDeviceEventCallControl callCmd = new GBDeviceEventCallControl();
@ -981,6 +1087,44 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
evaluateGBDeviceEvent(findPhoneEvent);
break;
case HuamiDeviceEvent.MUSIC_CONTROL:
LOG.info("got music control");
GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
switch (value[1]) {
case 0:
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PLAY;
break;
case 1:
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PAUSE;
break;
case 3:
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.NEXT;
break;
case 4:
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.PREVIOUS;
break;
case 5:
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEUP;
break;
case 6:
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.VOLUMEDOWN;
break;
case (byte) 224:
LOG.info("Music app started");
isMusicAppStarted = true;
sendMusicStateToDevice();
break;
case (byte) 225:
LOG.info("Music app terminated");
isMusicAppStarted = false;
break;
default:
LOG.info("unhandled music control event " + value[1]);
return;
}
evaluateGBDeviceEvent(deviceEventMusicControl);
break;
default:
LOG.warn("unhandled event " + value[0]);
}
@ -990,7 +1134,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
try {
TransactionBuilder builder = performInitialized("acknowledge find phone");
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), AmazfitBipService.COMMAND_ACK_FIND_PHONE_IN_PROGRESS);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), AmazfitBipService.COMMAND_ACK_FIND_PHONE_IN_PROGRESS);
builder.queue(getQueue());
} catch (Exception ex) {
LOG.error("Error sending current weather", ex);
@ -1051,7 +1195,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
super.onCharacteristicChanged(gatt, characteristic);
UUID characteristicUUID = characteristic.getUuid();
if (MiBand2Service.UUID_CHARACTERISTIC_6_BATTERY_INFO.equals(characteristicUUID)) {
if (HuamiService.UUID_CHARACTERISTIC_6_BATTERY_INFO.equals(characteristicUUID)) {
handleBatteryInfo(characteristic.getValue(), BluetoothGatt.GATT_SUCCESS);
return true;
} else if (MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS.equals(characteristicUUID)) {
@ -1060,14 +1204,14 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
} else if (GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristicUUID)) {
handleHeartrate(characteristic.getValue());
return true;
} else if (MiBand2Service.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) {
} else if (HuamiService.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) {
LOG.info("AUTHENTICATION?? " + characteristicUUID);
logMessageContent(characteristic.getValue());
return true;
} else if (MiBand2Service.UUID_CHARACTERISTIC_DEVICEEVENT.equals(characteristicUUID)) {
} else if (HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT.equals(characteristicUUID)) {
handleDeviceEvent(characteristic.getValue());
return true;
} else if (MiBand2Service.UUID_CHARACTERISTIC_7_REALTIME_STEPS.equals(characteristicUUID)) {
} else if (HuamiService.UUID_CHARACTERISTIC_7_REALTIME_STEPS.equals(characteristicUUID)) {
handleRealtimeSteps(characteristic.getValue());
return true;
} else {
@ -1087,16 +1231,16 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
if (GattCharacteristic.UUID_CHARACTERISTIC_GAP_DEVICE_NAME.equals(characteristicUUID)) {
handleDeviceName(characteristic.getValue(), status);
return true;
} else if (MiBand2Service.UUID_CHARACTERISTIC_6_BATTERY_INFO.equals(characteristicUUID)) {
} else if (HuamiService.UUID_CHARACTERISTIC_6_BATTERY_INFO.equals(characteristicUUID)) {
handleBatteryInfo(characteristic.getValue(), status);
return true;
} else if (GattCharacteristic.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristicUUID)) {
logHeartrate(characteristic.getValue(), status);
return true;
} else if (MiBand2Service.UUID_CHARACTERISTIC_7_REALTIME_STEPS.equals(characteristicUUID)) {
} else if (HuamiService.UUID_CHARACTERISTIC_7_REALTIME_STEPS.equals(characteristicUUID)) {
handleRealtimeSteps(characteristic.getValue());
return true;
} else if (MiBand2Service.UUID_CHARACTERISTIC_DEVICEEVENT.equals(characteristicUUID)) {
} else if (HuamiService.UUID_CHARACTERISTIC_DEVICEEVENT.equals(characteristicUUID)) {
handleDeviceEvent(characteristic.getValue());
return true;
} else {
@ -1111,7 +1255,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
public boolean onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
UUID characteristicUUID = characteristic.getUuid();
if (MiBand2Service.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) {
if (HuamiService.UUID_CHARACTERISTIC_AUTH.equals(characteristicUUID)) {
LOG.info("KEY AES SEND");
logMessageContent(characteristic.getValue());
return true;
@ -1282,8 +1426,10 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
LOG.warn("Device info: " + info);
versionCmd.hwVersion = info.getHardwareRevision();
// versionCmd.fwVersion = info.getFirmwareRevision(); // always null
versionCmd.fwVersion = info.getSoftwareRevision();
versionCmd.fwVersion = info.getFirmwareRevision();
if (versionCmd.fwVersion == null) {
versionCmd.fwVersion = info.getSoftwareRevision();
}
if (versionCmd.fwVersion != null && versionCmd.fwVersion.length() > 0 && versionCmd.fwVersion.charAt(0) == 'V') {
versionCmd.fwVersion = versionCmd.fwVersion.substring(1);
}
@ -1305,8 +1451,8 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
* Fetch the events from the android device calendars and set the alarms on the miband.
* @param builder
*/
private MiBand2Support sendCalendarEvents(TransactionBuilder builder) {
BluetoothGattCharacteristic characteristic = getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION);
private HuamiSupport sendCalendarEvents(TransactionBuilder builder) {
BluetoothGattCharacteristic characteristic = getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION);
Prefs prefs = GBApplication.getPrefs();
int availableSlots = prefs.getInt(MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR, 0);
@ -1395,55 +1541,55 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
}
private MiBand2Support setDateDisplay(TransactionBuilder builder) {
private HuamiSupport setDateDisplay(TransactionBuilder builder) {
DateTimeDisplay dateTimeDisplay = HuamiCoordinator.getDateDisplay(getContext());
LOG.info("Setting date display to " + dateTimeDisplay);
switch (dateTimeDisplay) {
case TIME:
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.DATEFORMAT_TIME);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.DATEFORMAT_TIME);
break;
case DATE_TIME:
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.DATEFORMAT_DATE_TIME);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.DATEFORMAT_DATE_TIME);
break;
}
return this;
}
private MiBand2Support setTimeFormat(TransactionBuilder builder) {
private HuamiSupport setTimeFormat(TransactionBuilder builder) {
boolean is24Format = DateFormat.is24HourFormat(getContext());
LOG.info("Setting 24h time format to " + is24Format);
if (is24Format) {
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.DATEFORMAT_TIME_24_HOURS);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.DATEFORMAT_TIME_24_HOURS);
} else {
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.DATEFORMAT_TIME_12_HOURS);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.DATEFORMAT_TIME_12_HOURS);
}
return this;
}
private MiBand2Support setGoalNotification(TransactionBuilder builder) {
private HuamiSupport setGoalNotification(TransactionBuilder builder) {
boolean enable = HuamiCoordinator.getGoalNotification();
LOG.info("Setting goal notification to " + enable);
if (enable) {
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_ENABLE_GOAL_NOTIFICATION);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_ENABLE_GOAL_NOTIFICATION);
} else {
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DISABLE_GOAL_NOTIFICATION);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_DISABLE_GOAL_NOTIFICATION);
}
return this;
}
private MiBand2Support setActivateDisplayOnLiftWrist(TransactionBuilder builder) {
private HuamiSupport setActivateDisplayOnLiftWrist(TransactionBuilder builder) {
ActivateDisplayOnLift displayOnLift = HuamiCoordinator.getActivateDisplayOnLiftWrist(getContext());
LOG.info("Setting activate display on lift wrist to " + displayOnLift);
switch (displayOnLift) {
case ON:
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_ENABLE_DISPLAY_ON_LIFT_WRIST);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_ENABLE_DISPLAY_ON_LIFT_WRIST);
break;
case OFF:
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DISABLE_DISPLAY_ON_LIFT_WRIST);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_DISABLE_DISPLAY_ON_LIFT_WRIST);
break;
case SCHEDULED:
byte[] cmd = MiBand2Service.COMMAND_SCHEDULE_DISPLAY_ON_LIFT_WRIST.clone();
byte[] cmd = HuamiService.COMMAND_SCHEDULE_DISPLAY_ON_LIFT_WRIST.clone();
Calendar calendar = GregorianCalendar.getInstance();
@ -1457,81 +1603,81 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
cmd[6] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
cmd[7] = (byte) calendar.get(Calendar.MINUTE);
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), cmd);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), cmd);
}
return this;
}
protected MiBand2Support setDisplayItems(TransactionBuilder builder) {
protected HuamiSupport setDisplayItems(TransactionBuilder builder) {
Set<String> pages = HuamiCoordinator.getDisplayItems();
LOG.info("Setting display items to " + (pages == null ? "none" : pages));
byte[] data = MiBand2Service.COMMAND_CHANGE_SCREENS.clone();
byte[] data = HuamiService.COMMAND_CHANGE_SCREENS.clone();
if (pages != null) {
if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_STEPS)) {
data[MiBand2Service.SCREEN_CHANGE_BYTE] |= MiBand2Service.DISPLAY_ITEM_BIT_STEPS;
data[HuamiService.SCREEN_CHANGE_BYTE] |= HuamiService.DISPLAY_ITEM_BIT_STEPS;
}
if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_DISTANCE)) {
data[MiBand2Service.SCREEN_CHANGE_BYTE] |= MiBand2Service.DISPLAY_ITEM_BIT_DISTANCE;
data[HuamiService.SCREEN_CHANGE_BYTE] |= HuamiService.DISPLAY_ITEM_BIT_DISTANCE;
}
if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_CALORIES)) {
data[MiBand2Service.SCREEN_CHANGE_BYTE] |= MiBand2Service.DISPLAY_ITEM_BIT_CALORIES;
data[HuamiService.SCREEN_CHANGE_BYTE] |= HuamiService.DISPLAY_ITEM_BIT_CALORIES;
}
if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_HEART_RATE)) {
data[MiBand2Service.SCREEN_CHANGE_BYTE] |= MiBand2Service.DISPLAY_ITEM_BIT_HEART_RATE;
data[HuamiService.SCREEN_CHANGE_BYTE] |= HuamiService.DISPLAY_ITEM_BIT_HEART_RATE;
}
if (pages.contains(MiBandConst.PREF_MI2_DISPLAY_ITEM_BATTERY)) {
data[MiBand2Service.SCREEN_CHANGE_BYTE] |= MiBand2Service.DISPLAY_ITEM_BIT_BATTERY;
data[HuamiService.SCREEN_CHANGE_BYTE] |= HuamiService.DISPLAY_ITEM_BIT_BATTERY;
}
}
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), data);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), data);
return this;
}
private MiBand2Support setRotateWristToSwitchInfo(TransactionBuilder builder) {
private HuamiSupport setRotateWristToSwitchInfo(TransactionBuilder builder) {
boolean enable = HuamiCoordinator.getRotateWristToSwitchInfo();
LOG.info("Setting rotate wrist to cycle info to " + enable);
if (enable) {
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_ENABLE_ROTATE_WRIST_TO_SWITCH_INFO);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_ENABLE_ROTATE_WRIST_TO_SWITCH_INFO);
} else {
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DISABLE_ROTATE_WRIST_TO_SWITCH_INFO);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_DISABLE_ROTATE_WRIST_TO_SWITCH_INFO);
}
return this;
}
private MiBand2Support setDisplayCaller(TransactionBuilder builder) {
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_ENABLE_DISPLAY_CALLER);
private HuamiSupport setDisplayCaller(TransactionBuilder builder) {
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_ENABLE_DISPLAY_CALLER);
return this;
}
private MiBand2Support setDoNotDisturb(TransactionBuilder builder) {
private HuamiSupport setDoNotDisturb(TransactionBuilder builder) {
DoNotDisturb doNotDisturb = HuamiCoordinator.getDoNotDisturb(getContext());
LOG.info("Setting do not disturb to " + doNotDisturb);
switch (doNotDisturb) {
case OFF:
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DO_NOT_DISTURB_OFF);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_DO_NOT_DISTURB_OFF);
break;
case AUTOMATIC:
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DO_NOT_DISTURB_AUTOMATIC);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_DO_NOT_DISTURB_AUTOMATIC);
break;
case SCHEDULED:
byte[] data = MiBand2Service.COMMAND_DO_NOT_DISTURB_SCHEDULED.clone();
byte[] data = HuamiService.COMMAND_DO_NOT_DISTURB_SCHEDULED.clone();
Calendar calendar = GregorianCalendar.getInstance();
Date start = HuamiCoordinator.getDoNotDisturbStart();
calendar.setTime(start);
data[MiBand2Service.DND_BYTE_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
data[MiBand2Service.DND_BYTE_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
data[HuamiService.DND_BYTE_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
data[HuamiService.DND_BYTE_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
Date end = HuamiCoordinator.getDoNotDisturbEnd();
calendar.setTime(end);
data[MiBand2Service.DND_BYTE_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
data[MiBand2Service.DND_BYTE_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
data[HuamiService.DND_BYTE_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
data[HuamiService.DND_BYTE_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), data);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), data);
break;
}
@ -1539,15 +1685,15 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
return this;
}
private MiBand2Support setInactivityWarnings(TransactionBuilder builder) {
private HuamiSupport setInactivityWarnings(TransactionBuilder builder) {
boolean enable = HuamiCoordinator.getInactivityWarnings();
LOG.info("Setting inactivity warnings to " + enable);
if (enable) {
byte[] data = MiBand2Service.COMMAND_ENABLE_INACTIVITY_WARNINGS.clone();
byte[] data = HuamiService.COMMAND_ENABLE_INACTIVITY_WARNINGS.clone();
int threshold = HuamiCoordinator.getInactivityWarningsThreshold();
data[MiBand2Service.INACTIVITY_WARNINGS_THRESHOLD] = (byte) threshold;
data[HuamiService.INACTIVITY_WARNINGS_THRESHOLD] = (byte) threshold;
Calendar calendar = GregorianCalendar.getInstance();
@ -1560,50 +1706,78 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
// The first interval always starts when the warnings interval starts
calendar.setTime(intervalStart);
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
if(enableDnd) {
// The first interval ends when the dnd interval starts
calendar.setTime(dndStart);
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
// The second interval starts when the dnd interval ends
calendar.setTime(dndEnd);
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_2_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_2_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_2_START_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_2_START_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
// ... and it ends when the warnings interval ends
calendar.setTime(intervalEnd);
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_2_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_2_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_2_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_2_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
} else {
// No Dnd, use the first interval
calendar.setTime(intervalEnd);
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
data[MiBand2Service.INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_END_HOURS] = (byte) calendar.get(Calendar.HOUR_OF_DAY);
data[HuamiService.INACTIVITY_WARNINGS_INTERVAL_1_END_MINUTES] = (byte) calendar.get(Calendar.MINUTE);
}
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), data);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), data);
} else {
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DISABLE_INACTIVITY_WARNINGS);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_DISABLE_INACTIVITY_WARNINGS);
}
return this;
}
private MiBand2Support setDistanceUnit(TransactionBuilder builder) {
private HuamiSupport setDistanceUnit(TransactionBuilder builder) {
MiBandConst.DistanceUnit unit = HuamiCoordinator.getDistanceUnit();
LOG.info("Setting distance unit to " + unit);
if (unit == MiBandConst.DistanceUnit.METRIC) {
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DISTANCE_UNIT_METRIC);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_DISTANCE_UNIT_METRIC);
} else {
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), MiBand2Service.COMMAND_DISTANCE_UNIT_IMPERIAL);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), HuamiService.COMMAND_DISTANCE_UNIT_IMPERIAL);
}
return this;
}
protected void writeToChunked(TransactionBuilder builder, int type, byte[] data) {
final int MAX_CHUNKLENGTH = 17;
int remaining = data.length;
byte count = 0;
while (remaining > 0) {
int copybytes = Math.min(remaining, MAX_CHUNKLENGTH);
byte[] chunk = new byte[copybytes + 3];
byte flags = 0;
if (remaining <= MAX_CHUNKLENGTH) {
flags |= 0x80; // last chunk
if (count == 0) {
flags |= 0x40; // weird but true
}
} else if (count > 0) {
flags |= 0x40; // consecutive chunk
}
chunk[0] = 0;
chunk[1] = (byte) (flags | type);
chunk[2] = (byte) (count & 0xff);
System.arraycopy(data, count++ * MAX_CHUNKLENGTH, chunk, 3, copybytes);
builder.write(characteristicChunked, chunk);
remaining -= copybytes;
}
}
public void phase2Initialize(TransactionBuilder builder) {
LOG.info("phase2Initialize...");
requestBatteryInfo(builder);

View File

@ -14,12 +14,12 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.actions;
package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.actions;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction;
public abstract class StopNotificationAction extends AbortTransactionAction {
@ -34,7 +34,7 @@ public abstract class StopNotificationAction extends AbortTransactionAction {
public boolean run(BluetoothGatt gatt) {
if (!super.run(gatt)) {
// send a signal to stop the vibration
alertLevelCharacteristic.setValue(new byte[]{MiBand2Service.ALERT_LEVEL_NONE});
alertLevelCharacteristic.setValue(new byte[]{HuamiService.ALERT_LEVEL_NONE});
gatt.writeCharacteristic(alertLevelCharacteristic);
return false;
}

View File

@ -1,4 +1,5 @@
/* Copyright (C) 2017-2018 Andreas Shimokawa, AndrewH, Carsten Pfeiffer
/* Copyright (C) 2017-2018 Andreas Shimokawa, AndrewH, Carsten Pfeiffer,
szilardx
This file is part of Gadgetbridge.
@ -21,7 +22,10 @@ import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
@ -44,7 +48,7 @@ public class ActivityDetailsParser {
public static final BigDecimal HUAMI_TO_DECIMAL_DEGREES_DIVISOR = new BigDecimal(3000000.0);
private final BaseActivitySummary summary;
private final ActivityTrack activityTrack;
// private final int version;
// private final int version;
private final Date baseDate;
private long baseLongitude;
private long baseLatitude;
@ -126,9 +130,49 @@ public class ActivityDetailsParser {
throw new GBException("Error parsing activity details: " + ex.getMessage(), ex);
}
fixupMissingTimestamps(activityTrack);
return activityTrack;
}
private void fixupMissingTimestamps(ActivityTrack activityTrack) {
try {
int pointer = 0;
List<ActivityPoint> activityPointList = activityTrack.getTrackPoints();
Date gpsStartTime = null;
List<ActivityPoint> entriesToFixUp = new ArrayList<>();
while (pointer < activityPointList.size() - 1) {
ActivityPoint activityPoint = activityPointList.get(pointer);
if (activityPoint.getLocation() == null) {
pointer++;
continue;
}
if (activityPoint.getTime().equals(activityPointList.get(pointer + 1).getTime())) {
entriesToFixUp.add(activityPoint);
} else {
// found the first activity point with a proper timestamp
entriesToFixUp.add(activityPoint);
gpsStartTime = activityPointList.get(pointer + 1).getTime();
break;
}
pointer++;
}
if (gpsStartTime != null) {
// now adjust those entries without a timestamp
long differenceInSec = TimeUnit.SECONDS.convert(Math.abs(gpsStartTime.getTime() - baseDate.getTime()), TimeUnit.MILLISECONDS);
double multiplier = (double) differenceInSec / (double) (entriesToFixUp.size());
for (int j = 0; j < entriesToFixUp.size(); j++) {
long timeOffsetSeconds = Math.round(j * multiplier);
entriesToFixUp.get(j).setTime(makeAbsolute(timeOffsetSeconds));
}
}
} catch (Exception ex) {
LOG.warn("Error cleaning activity details", ex);
}
}
private int consumeGPSAndUpdateBaseLocation(byte[] bytes, int offset, long timeOffset) {
int i = 0;
int longitudeDelta = BLETypeConversions.toInt16(bytes[offset + i++], bytes[offset + i++]);
@ -144,7 +188,7 @@ public class ActivityDetailsParser {
convertHuamiValueToDecimalDegrees(baseLatitude),
baseAltitude);
ActivityPoint ap = getActivityPointFor(timeOffset);
ActivityPoint ap = getActivityPointFor(timeOffset, coordinate);
ap.setLocation(coordinate);
add(ap);
@ -197,6 +241,19 @@ public class ActivityDetailsParser {
return new ActivityPoint(time);
}
private ActivityPoint getActivityPointFor(long timeOffsetSeconds, GPSCoordinate gpsCoordinate) {
Date time = makeAbsolute(timeOffsetSeconds);
if (lastActivityPoint != null) {
if (lastActivityPoint.getTime().equals(time)) {
if (lastActivityPoint.getLocation() != null && !lastActivityPoint.getLocation().equals(gpsCoordinate)) {
return new ActivityPoint(time);
}
return lastActivityPoint;
}
}
return new ActivityPoint(time);
}
private Date makeAbsolute(long timeOffsetSeconds) {
return new Date(baseDate.getTime() + timeOffsetSeconds * 1000);
}

View File

@ -93,6 +93,7 @@ public class AmazfitBipFirmwareInfo extends HuamiFirmwareInfo {
crcToVersion.put(56670, "0.1.1.41");
crcToVersion.put(58736, "0.1.1.45");
crcToVersion.put(2602, "1.0.2.00");
crcToVersion.put(36157, "1.1.2.05");
// resources
crcToVersion.put(12586, "0.0.8.74");
@ -115,6 +116,7 @@ public class AmazfitBipFirmwareInfo extends HuamiFirmwareInfo {
crcToVersion.put(21109, "0.1.1.41");
crcToVersion.put(23073, "0.1.1.45");
crcToVersion.put(59245, "1.0.2.00");
crcToVersion.put(20591, "1.1.2.05");
// gps
crcToVersion.put(61520, "9367,8f79a91,0,0,");

View File

@ -19,6 +19,8 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import org.slf4j.Logger;
@ -34,10 +36,10 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiWeatherConditions;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
@ -50,17 +52,17 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ConditionalWrit
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.operations.AmazfitBipFetchLogsOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiIcon;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.operations.AmazfitBipFetchLogsOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchActivityOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.FetchSportsSummaryOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.MiBand2Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.operations.FetchActivityOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.operations.FetchSportsSummaryOperation;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Version;
public class AmazfitBipSupport extends MiBand2Support {
public class AmazfitBipSupport extends HuamiSupport {
private static final Logger LOG = LoggerFactory.getLogger(AmazfitBipSupport.class);
@ -92,11 +94,8 @@ public class AmazfitBipSupport extends MiBand2Support {
try {
TransactionBuilder builder = performInitialized("new notification");
AlertNotificationProfile<?> profile = new AlertNotificationProfile(this);
profile.setMaxLength(230);
byte customIconId = HuamiIcon.mapToIconId(notificationSpec.type);
AlertCategory alertCategory = AlertCategory.CustomHuami;
// The SMS icon for AlertCategory.SMS is unique and not available as iconId
@ -108,8 +107,54 @@ public class AmazfitBipSupport extends MiBand2Support {
alertCategory = AlertCategory.Email;
}
NewAlert alert = new NewAlert(alertCategory, 1, message, customIconId);
profile.newAlert(builder, alert);
int maxLength = 230;
if (characteristicChunked != null) {
int prefixlength = 2;
// We also need a (fake) source name for Mi Band 3 for SMS/EMAIL, else the message is not displayed
byte[] appSuffix = "\0 \0".getBytes();
int suffixlength = appSuffix.length;
if (alertCategory == AlertCategory.CustomHuami) {
String appName;
prefixlength = 3;
final PackageManager pm = getContext().getPackageManager();
ApplicationInfo ai = null;
try {
ai = pm.getApplicationInfo(notificationSpec.sourceAppId, 0);
} catch (PackageManager.NameNotFoundException ignored) {
}
if (ai != null) {
appName = "\0" + pm.getApplicationLabel(ai) + "\0";
} else {
appName = "\0" + "UNKNOWN" + "\0";
}
appSuffix = appName.getBytes();
suffixlength = appSuffix.length;
}
byte[] rawmessage = message.getBytes();
int length = Math.min(rawmessage.length, maxLength - prefixlength);
byte[] command = new byte[length + prefixlength + suffixlength];
command[0] = (byte) alertCategory.getId();
command[1] = 1;
if (alertCategory == AlertCategory.CustomHuami) {
command[2] = customIconId;
}
System.arraycopy(rawmessage, 0, command, prefixlength, length);
System.arraycopy(appSuffix, 0, command, prefixlength + length, appSuffix.length);
writeToChunked(builder, 0, command);
} else {
AlertNotificationProfile<?> profile = new AlertNotificationProfile(this);
NewAlert alert = new NewAlert(alertCategory, 1, message, customIconId);
profile.setMaxLength(maxLength);
profile.newAlert(builder, alert);
}
builder.queue(getQueue());
} catch (IOException ex) {
LOG.error("Unable to send notification to Amazfit Bip", ex);
@ -131,9 +176,6 @@ public class AmazfitBipSupport extends MiBand2Support {
@Override
protected AmazfitBipSupport setDisplayItems(TransactionBuilder builder) {
if (gbDevice.getType() != DeviceType.AMAZFITBIP) {
return this; // Disable for Cor for now
}
if (gbDevice.getFirmwareVersion() == null) {
LOG.warn("Device not initialized yet, won't set menu items");
return this;
@ -185,7 +227,7 @@ public class AmazfitBipSupport extends MiBand2Support {
shortcut_alipay = true;
}
}
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), command);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), command);
setShortcuts(builder, shortcut_weather, shortcut_alipay);
return this;
@ -201,7 +243,7 @@ public class AmazfitBipSupport extends MiBand2Support {
(byte) ((alipay && weather) ? 0x81 : 0x01), 0x01,
};
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), command);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), command);
}
@Override
@ -241,13 +283,18 @@ public class AmazfitBipSupport extends MiBand2Support {
buf.put((byte) 0);
}
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
if (characteristicChunked != null) {
writeToChunked(builder, 1, buf.array());
} else {
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
}
builder.queue(getQueue());
} catch (Exception ex) {
LOG.error("Error sending current weather", ex);
}
if (gbDevice.getType() == DeviceType.AMAZFITBIP) {
if (gbDevice.getType() != DeviceType.AMAZFITCOR) {
try {
TransactionBuilder builder;
builder = performInitialized("Sending air quality index");
@ -266,7 +313,13 @@ public class AmazfitBipSupport extends MiBand2Support {
buf.put(aqiString.getBytes());
buf.put((byte) 0);
}
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
if (characteristicChunked != null) {
writeToChunked(builder, 1, buf.array());
} else {
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
}
builder.queue(getQueue());
} catch (IOException ex) {
LOG.error("Error sending air quality");
@ -310,7 +363,6 @@ public class AmazfitBipSupport extends MiBand2Support {
for (WeatherSpec.Forecast forecast : weatherSpec.forecasts) {
condition = HuamiWeatherConditions.mapToAmazfitBipWeatherCode(forecast.conditionCode);
buf.put(condition);
buf.put(condition);
buf.put((byte) (forecast.maxTemp - 273));
@ -321,7 +373,12 @@ public class AmazfitBipSupport extends MiBand2Support {
}
}
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
if (characteristicChunked != null) {
writeToChunked(builder, 1, buf.array());
} else {
builder.write(getCharacteristic(AmazfitBipService.UUID_CHARACTERISTIC_WEATHER), buf.array());
}
builder.queue(getQueue());
} catch (Exception ex) {
LOG.error("Error sending weather forecast", ex);
@ -372,7 +429,7 @@ public class AmazfitBipSupport extends MiBand2Support {
boolean handled = super.onCharacteristicChanged(gatt, characteristic);
if (!handled) {
UUID characteristicUUID = characteristic.getUuid();
if (MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION.equals(characteristicUUID)) {
if (HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION.equals(characteristicUUID)) {
return handleConfigurationInfo(characteristic.getValue());
}
}
@ -395,11 +452,11 @@ public class AmazfitBipSupport extends MiBand2Support {
// this probably does more than only getting the GPS version...
private AmazfitBipSupport requestGPSVersion(TransactionBuilder builder) {
LOG.info("Requesting GPS version");
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), AmazfitBipService.COMMAND_REQUEST_GPS_VERSION);
builder.write(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION), AmazfitBipService.COMMAND_REQUEST_GPS_VERSION);
return this;
}
private AmazfitBipSupport setLanguage(TransactionBuilder builder) {
protected AmazfitBipSupport setLanguage(TransactionBuilder builder) {
String language = Locale.getDefault().getLanguage();
String country = Locale.getDefault().getCountry();
@ -427,6 +484,10 @@ public class AmazfitBipSupport extends MiBand2Support {
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_SPANISH;
localeString = "es_ES";
break;
case 4:
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_ENGLISH;
localeString = "ru_RU";
break;
default:
switch (language) {
case "zh":
@ -442,19 +503,24 @@ public class AmazfitBipSupport extends MiBand2Support {
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_SPANISH;
localeString = "es_ES";
break;
case "ru":
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_ENGLISH;
localeString = "ru_RU";
break;
default:
command_old = AmazfitBipService.COMMAND_SET_LANGUAGE_ENGLISH;
localeString = "en_US";
break;
}
}
command_new = AmazfitBipService.COMMAND_SET_LANGUAGE_NEW_TEMPLATE;
command_new = HuamiService.COMMAND_SET_LANGUAGE_NEW_TEMPLATE.clone();
System.arraycopy(localeString.getBytes(), 0, command_new, 3, localeString.getBytes().length);
builder.add(new ConditionalWriteAction(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION)) {
builder.add(new ConditionalWriteAction(getCharacteristic(HuamiService.UUID_CHARACTERISTIC_3_CONFIGURATION)) {
@Override
protected byte[] checkCondition() {
if (gbDevice.getType() == DeviceType.MIBAND3 || (gbDevice.getType() == DeviceType.AMAZFITBIP && new Version(gbDevice.getFirmwareVersion()).compareTo(new Version("0.1.0.77")) >= 0)) {
if ((gbDevice.getType() == DeviceType.AMAZFITBIP && new Version(gbDevice.getFirmwareVersion()).compareTo(new Version("0.1.0.77")) >= 0) ||
(gbDevice.getType() == DeviceType.AMAZFITCOR && new Version(gbDevice.getFirmwareVersion()).compareTo(new Version("1.0.7.23")) >= 0)) {
return command_new;
} else {
return command_old;

View File

@ -26,14 +26,14 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotificat
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.OverflowStrategy;
import nodomain.freeyourgadget.gadgetbridge.service.devices.common.SimpleNotification;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.Mi2TextNotificationStrategy;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.MiBand2Support;
// This class in no longer in use except for incoming calls
class AmazfitBipTextNotificationStrategy extends Mi2TextNotificationStrategy {
AmazfitBipTextNotificationStrategy(MiBand2Support support) {
AmazfitBipTextNotificationStrategy(HuamiSupport support) {
super(support);
}

View File

@ -33,12 +33,12 @@ import java.util.Locale;
import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.amazfitbip.AmazfitBipSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.miband2.operations.AbstractFetchOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations.AbstractFetchOperation;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@ -75,12 +75,12 @@ public class AmazfitBipFetchLogsOperation extends AbstractFetchOperation {
GregorianCalendar sinceWhen = BLETypeConversions.createCalendar();
sinceWhen.add(Calendar.DAY_OF_MONTH, -10);
builder.write(characteristicFetch, BLETypeConversions.join(new byte[]{
MiBand2Service.COMMAND_ACTIVITY_DATA_START_DATE,
HuamiService.COMMAND_ACTIVITY_DATA_START_DATE,
AmazfitBipService.COMMAND_ACTIVITY_DATA_TYPE_DEBUGLOGS},
getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES)));
builder.add(new WaitAction(1000)); // TODO: actually wait for the success-reply
builder.notify(characteristicActivityData, true);
builder.write(characteristicFetch, new byte[]{MiBand2Service.COMMAND_FETCH_DATA});
builder.write(characteristicFetch, new byte[]{HuamiService.COMMAND_FETCH_DATA});
}
@Override

Some files were not shown because too many files have changed in this diff Show More