From ad30c528e5a83de66837756b20174c6dac83ef14 Mon Sep 17 00:00:00 2001 From: TaaviE Date: Tue, 6 Oct 2020 20:35:26 +0300 Subject: [PATCH 01/86] Fixed a bug caused by incorrect PendingIntent.getActivity usage --- .../gadgetbridge/service/AbstractDeviceSupport.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java index 5d7db5401..a98b0029f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java @@ -198,6 +198,7 @@ public abstract class AbstractDeviceSupport implements DeviceSupport { private void handleGBDeviceEventFindPhoneStartNotification() { LOG.info("Got handleGBDeviceEventFindPhoneStartNotification"); Intent intent = new Intent(context, FindPhoneActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent pi = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); @@ -212,7 +213,7 @@ public abstract class AbstractDeviceSupport implements DeviceSupport { .setContentTitle( context.getString( R.string.find_my_phone_notification ) ); notification.setGroup("BackgroundService"); - notificationManager.notify( GB.NOTIFICATION_ID_PHONE_FIND, notification.build()); + notificationManager.notify(GB.NOTIFICATION_ID_PHONE_FIND, notification.build()); } From 3c842bd44187f2180ec904beb67fc1dfe0e48d6d Mon Sep 17 00:00:00 2001 From: TaaviE Date: Tue, 6 Oct 2020 22:00:42 +0300 Subject: [PATCH 02/86] Made the find phone activity start instantly when allowed --- .../service/AbstractDeviceSupport.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java index a98b0029f..4301ac001 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java @@ -21,6 +21,7 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; +import android.companion.CompanionDeviceManager; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; @@ -29,6 +30,7 @@ import android.net.Uri; import android.os.Build; import android.telephony.SmsManager; +import androidx.annotation.RequiresApi; import androidx.core.app.NotificationCompat; import androidx.core.content.FileProvider; import androidx.localbroadcastmanager.content.LocalBroadcastManager; @@ -195,25 +197,35 @@ public abstract class AbstractDeviceSupport implements DeviceSupport { } } + @RequiresApi(Build.VERSION_CODES.Q) private void handleGBDeviceEventFindPhoneStartNotification() { LOG.info("Got handleGBDeviceEventFindPhoneStartNotification"); Intent intent = new Intent(context, FindPhoneActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent pi = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID ); - notification + NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID ) .setSmallIcon(R.drawable.ic_notification) .setOngoing(false) .setFullScreenIntent(pi, true) .setPriority(NotificationCompat.PRIORITY_HIGH) .setAutoCancel(true) .setContentTitle( context.getString( R.string.find_my_phone_notification ) ); + notification.setGroup("BackgroundService"); - notificationManager.notify(GB.NOTIFICATION_ID_PHONE_FIND, notification.build()); + CompanionDeviceManager manager = (CompanionDeviceManager) context.getSystemService(Context.COMPANION_DEVICE_SERVICE); + if (manager.getAssociations().size() > 0) { + notificationManager.notify(GB.NOTIFICATION_ID_PHONE_FIND, notification.build()); + context.startActivity(intent); + LOG.debug("CompanionDeviceManager associations were found, starting intent"); + } else { + notificationManager.notify(GB.NOTIFICATION_ID_PHONE_FIND, notification.build()); + LOG.warn("CompanionDeviceManager associations were not found, can't start intent"); + } } From a9fa82a0757c0d25a6c4f3ce531ec2ede86ff134 Mon Sep 17 00:00:00 2001 From: ssantos Date: Tue, 29 Sep 2020 21:13:28 +0000 Subject: [PATCH 03/86] Translated using Weblate (Portuguese) Currently translated at 89.4% (874 of 977 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/pt/ --- app/src/main/res/values-pt/strings.xml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 7c89871c8..4f7903948 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -42,7 +42,7 @@ Bloqueio de notificações - Instalador de FW/Aplicação + Instalador de FW/app Está prestes a instalar o %s. Estás prestes a instalar os firmwares %1$s e %2$s em vez dos que estão atualmente na sua Mi Band. O firmware foi testado e é compatível com o Gadgetbridge. @@ -129,7 +129,7 @@ Utilizar suporte para Pebble LE em todos os Pebbles em vez do clássico BT. Necessita que se emparelhe um \"Pebble LE\" depois de dispositivos não LE se terem ligado uma vez Pebble 2/LE GATT MTU limite Se o seu Pebble2/Pebble LE não funciona como esperado, ative esta opção para limitar o tamanho das transferências (intervalo válido é 20–512) - Activar Registo da Aplicação do Dispositivo + Ativar registo da app do aparelho Causa que os registos das aplicações do relógio sejam guardados pelo Gadgetbridge (necessita de religação) Antecipar confirmações do PebbleKit Irá fazer com que as mensagens enviadas para aplicações de terceiros sejam sempre imediatamente confirmadas @@ -363,7 +363,13 @@ A emparelhar Pebble É esperado que veja uma notificação de emparelhamento no seu dispositivo Android. Se isso não acontecer, aceda às notificações e aceite o pedido de emparelhamento. Depois aceite igualmente o pedido de emparelhamento no seu Pebble - Garanta que este tema se encontra ativado na Aplicação de Meteorologia para obter informação meteorológica atualizada no seu Pebble.\n\nNão é necessária qualquer configuração aqui.\n\nPode ativar a aplicação nativa de meteorologia do seu Pebble através da gestão de aplicações.\n\nTemas de relógio irão apresentar a informação meteorológica automaticamente. + Garanta que este tema se encontra ativado na App de Meteorologia para obter informação meteorológica atualizada no seu Pebble. +\n +\nNão é necessária qualquer configuração aqui. +\n +\nPode ativar a app nativa de meteorologia do seu Pebble através da gestão de apps. +\n +\nTemas de relógio irão apresentar a informação meteorológica automaticamente. Ativar o emparelhamento Bluetooth Desative isto caso tenha problemas na ligação Métrico @@ -535,7 +541,7 @@ Exportar a base de dados… O local de exportação automática da base de dados foi definido para: Relógio Mundial - Usado para o provedor do clima do LineageOS, outras versões do Android precisam usar uma aplicação como o \"Notificação de clima\". Encontrará mais informações no wiki do Gadgetbridge. + Usado para o provedor do clima do LineageOS, outras versões do Android precisam usar uma app como o \"Notificação de clima\". Encontrará mais informações no wiki do Gadgetbridge. Muito obrigado a todos os contribuidores não listados aqui por contribuição com código, suporte, ideias, motivação, relatórios de erros, dinheiro… ✊ Suporte a aparelhos adicionais Contribuidores @@ -694,7 +700,7 @@ Amazfit Bip Lite Makibes HR3 Configurações de Makibes HR3 - Para visualizar o rastreamento de atividade, instale uma aplicação que consegue manipular ficheiros GPX. + Para visualizar o rastreamento de atividade, instale uma app que consegue manipular ficheiros GPX. Visível apenas se nenhum aparelho estiver adicionado Sempre visível Conectar novo botão de aparelho @@ -827,7 +833,7 @@ Não filtrar Filtro de notificação gravado Insira as palavras desejadas, nova linha para cada uma - Para ser configurada, a aplicação não pode estar na lista negra + Para ser configurada, a app não pode estar na lista negra Filtro de notificação Casio GB-6900 Frequência cardíaca atual / máx: %1$d / %2$d From c37e36fb4644105191008fd48769a83877b1dd43 Mon Sep 17 00:00:00 2001 From: ssantos Date: Thu, 1 Oct 2020 20:51:16 +0000 Subject: [PATCH 04/86] Translated using Weblate (Portuguese) Currently translated at 89.4% (874 of 977 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/pt/ --- app/src/main/res/values-pt/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 4f7903948..49b0c9b69 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -22,7 +22,7 @@ Capturar o ecrã do dispositivo Depurar - Gestor de aplicações + Gestor de apps Aplicações em cache Aplicações instaladas Temas instalados From e003b7bf254e88ab3c7aac3258795feb35d90bc2 Mon Sep 17 00:00:00 2001 From: Mario Rossi Date: Sat, 3 Oct 2020 08:19:44 +0000 Subject: [PATCH 05/86] Translated using Weblate (Italian) Currently translated at 99.1% (969 of 977 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/it/ --- app/src/main/res/values-it/strings.xml | 69 ++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index f57506119..eafb30d58 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -871,7 +871,7 @@ Impossibile avviare il servizio in background ATTENZIONE: Errore durante la verifica delle informazioni sulla versione! Non dovresti continuare! Visto il nome della versione \"%s\" Sono richieste tutte queste autorizzazioni e l\'app potrebbe essere instabile se non concesse - Mille grazie a tutti i collaboratori non elencati per aver contribuito con codice, supporto, idee, motivazione, segnalazioni di bug, donazioni ... ✊ + Grazie mille a tutti i collaboratori non elencati per aver contribuito con codice, traduzioni, supporto, idee, motivazione, segnalazioni di bug, donazioni ... ✊ Supporto a dispositivi aggiuntivi Contributori Team principale (ordinati in base al primo contributo di codice) @@ -942,9 +942,9 @@ La posizione deve essere abilitata TLW64 Mostra Percorso GPS - Durata: - Fine: - Inizio: + Durata + Fine + Inizio Passi Attività Velocità @@ -953,7 +953,7 @@ bpm min/km sec/km - Kcal + kcal giri stile di nuoto s @@ -982,4 +982,63 @@ Distanza PineTime (JF Firmware) Dettagli dell\'Attività Sportiva + bracc + bracc/s + Cricket + Yoga + Lemfo SG2 + Badminton + Ping Pong + Bracciate + Bracciate medie + Distanza media bracciata + Misto + Dorso + Stile libero + Rana + oggi + passato + Tutti i dispositivi + Periodo di tempo + 30 giorni + 7 giorni + Mese scorso + Questo mese + Settimana scorsa + Questa settimana + Tutte le attività + Statistiche + + Elementi selezionati individualmente + Aggiungi al filtro + Applica filtro + Etichetta + Rimuovi filtri + Filtra + A + Da + Statistiche delle attività sportive + Filtro delle attività sportive + Avvia azione + Invia messaggio + Quando non si indossa il dispositivo + Quando ci si sveglia + Quando ci si addormenta + Azioni sul dispositivo + Elevazione + indice swolf + Velocità media + Elevazione base + Salita + Andatura media giro + Collegamenti + Ieri + Oggi + Modifica etichetta + Basket + Remoergometro + Calcio + Salti alla corda + Spinning + Nuoto (all\'aperto) \ No newline at end of file From d90f8cb536dc1e32474ccf605695a9799ba41108 Mon Sep 17 00:00:00 2001 From: lucanomax Date: Tue, 6 Oct 2020 09:42:09 +0000 Subject: [PATCH 06/86] Translated using Weblate (Italian) Currently translated at 99.3% (971 of 977 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/it/ --- app/src/main/res/values-it/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index eafb30d58..bfd02fb23 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1041,4 +1041,6 @@ Salti alla corda Spinning Nuoto (all\'aperto) + No + km \ No newline at end of file From 5b7c604d16c8133446b1b2a67a2c1823ae3bc498 Mon Sep 17 00:00:00 2001 From: TaaviE Date: Sun, 4 Oct 2020 19:05:14 +0300 Subject: [PATCH 07/86] Removed translations that do not exist in main locale --- app/src/main/res/values-ca/strings.xml | 4 ---- app/src/main/res/values-cs/strings.xml | 4 ---- app/src/main/res/values-de/strings.xml | 4 ---- app/src/main/res/values-el/strings.xml | 2 -- app/src/main/res/values-es/strings.xml | 2 -- app/src/main/res/values-fr/strings.xml | 2 -- app/src/main/res/values-gl/strings.xml | 2 -- app/src/main/res/values-he/strings.xml | 13 ------------- app/src/main/res/values-it/strings.xml | 2 -- app/src/main/res/values-ja/strings.xml | 2 -- app/src/main/res/values-ko/strings.xml | 2 -- app/src/main/res/values-nb-rNO/strings.xml | 8 -------- app/src/main/res/values-nl/strings.xml | 5 ----- app/src/main/res/values-pl/strings.xml | 2 -- app/src/main/res/values-pt-rBR/strings.xml | 8 -------- app/src/main/res/values-pt/strings.xml | 1 - app/src/main/res/values-ru/strings.xml | 3 --- app/src/main/res/values-sk/strings.xml | 2 -- app/src/main/res/values-tr/strings.xml | 1 - 19 files changed, 69 deletions(-) diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index e1f13efd2..a1d51c956 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -343,7 +343,6 @@ Activa la vibració de la pulsera en prémer el botó d\'acció Retard màxim entre pulsacions Retard màxim entre pulsacions del botó en mil·lisegons - Retard després de l\'acció de botó Notificació d\'objectiu La polsera vibrarà quan l\'objectiu diari de passes siga assolit Elements a mostrar @@ -438,7 +437,6 @@ Si l\'arribada de les dades d\'activitats no és notificada a la polsera, aquestes dades no s\'eliminaran. Aquesta opció pot ser útil si feu servir el Gadgebridge també amb altres aplicacions. Les dades d\'activitats de la Mi Band es guardaran fins i tot després de sincronitzar. Aquesta opció pot ser útil si feu servir el Gadgebridge també amb altres aplicacions. Difón el missatge enviat amb l\'esdeveniment. El paràmetre `button_id` és afegit automàticament a cada missatge. - Retard després d\'una acció de botó (el nombre és a l\'intent extra de button_id) o 0 per procedir immediatament Desa dades sense processar a la base de dades Desa les dades \"tal qual\", tot augmentant l\'ús de la base de dades per tal de permetre possibles interpretacions més tard. Gestió de la base de dades @@ -858,8 +856,6 @@ Només rellotge Estalvi d\'energia Normal - Període d\'inactivitat (minuts) - Recordatori d\'inactivitat Prem aquí per començar la calibració Calibració Calibració de la pressió sanguínia diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 5bb4a1dcb..cd11fa671 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -419,8 +419,6 @@ Zapnout vibrace náramku při stisknutí tlačítka Maximální zpoždění mezi stisky Maximální zpoždění mezi stisknutím tlačítka v milisekundách - Zpoždění po provedení akce tlačítka - Zpoždění po provedení akce tlačítka (číslo je v button_id) nebo 0 pro okamžitě Chystáte se nainstalovat firmvér %s na vašem Amazfit Bip. \n \nProsím nezapomeňte nainstalovat soubor .fw, potom soubor .res a nakonec soubor .gps. Vaše hodinky se po instalaci souboru .fw restartují. @@ -446,7 +444,6 @@ Složka pro uložení Časový interval Exportovat každou %d hodinu - Nastavení Amazfit Bip Celodenní měření tepu Jednou za minutu Každých 5 minut @@ -947,7 +944,6 @@ Bazénů Plavecký styl Hodnocení swolf - Záběry Průměrné Tempo Bazénu Průměrné Záběry Průměrná Délka Záběru diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8c1fe3436..be39b1f65 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -435,7 +435,6 @@ Bandvibration aktivieren, wenn die Tastenaktion ausgelöst wurde Maximale Verzögerung zwischen den Tastendrücken Maximale Verzögerung zwischen den Tastendrücken in Millisekunden - Verzögerung nach Tastenaktion Auf Android-Gerät öffnen Lautlos Antworten @@ -488,7 +487,6 @@ Alle %d Stunden exportieren Datenbankexport fehlgeschlagen! Bitte überprüfe deine Einstellungen. Broadcast Nachricht, die mit der Aktion gesendet wird. Der Parameter „button_id“ wird automatisch zu jeder Nachricht hinzugefügt. - Verzögerung nach einer Tastenaktion (Zahl in button_id) oder 0 für sofort Spanisch Ein Deine Aktivitätsverläufe @@ -804,7 +802,6 @@ \nINSTALLATION AUF EIGENE GEFAHR! nodomain.freeyourgadget.gadgetbridge.ButtonPressed Fossil Q Hybrid - Q Hybrid Benachrichtigung abspielen Uhr nicht verbunden Vibrationsstärke: Ziel in Schritten @@ -994,7 +991,6 @@ Beim Einschlafen Yoga Schwimmen (Freiwasser) - Schläge Schläge kcal Seilspringen diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 94b7a7b93..5227c67c3 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -349,8 +349,6 @@ Ενεργοποίηση δόνησης όταν πατιέται το κουμπί Μέγιστος χρόνος μεταξύ των χτυπημάτων του κουμπιού Μέγιστος χρόνος μεταξύ των χτυπημάτων του κουμπιού (σε χιλιοστά του δευτερολέπτου) - Χρόνος αναμονής μετά το πάτημα του κουμπιού - Καθυστέρηση μετά από μια ενέργεια αντιστοίχισης του κουμπιού (ο αριθμός καθορίζεται στο button_id) ή βάλτε 0 για να μην υπάρχει καθυστέρηση Ειδοποίηση επίτευξης στόχου Το Mi Band θα κάνει δόνηση όταν ο ημερήσιος στόχος για τα βήματα επιτευχθεί Πληροφορίες που θα εμφανίζονται diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 4be8b9c1b..49416171d 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -430,8 +430,6 @@ Activar vibración por acción Retardo máximo entre pulsaciones Retardo máximo entre pulsaciones en milisegundos - Retardo después de la acción del botón - Retardo después de una acción del botón (el número está en button_id intent extra) o bien 0 para efecto inmediato Abrir en el dispositivo Android Silenciar Responder diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index ed3e2368f..670ae7ae0 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -424,8 +424,6 @@ Temps de sommeil préféré en heures Activer la vibration après déclenchement de l\'action Délai maximum entre pressions Délai maximum entre pressions en millisecondes - Délai après action du bouton - Délai après une pression de bouton (le nombre est dans button_id intent extra) ou 0 pour immédiatement Ouvrir sur le smartphone Android Silencieux Répondre diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 1c502f3b5..d5d321704 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -320,7 +320,6 @@ Activar vibración da banda en acción desencadenada por botón Retardo máximo entre pulsacións Retardo máximo en milisegundos entre pulsacións de botón - Retardo despois da acción do botón Notificación de obxectivo A pulseira vai vibrar cando se acade o obxectivo diario de pasos Elementos a amosar @@ -424,7 +423,6 @@ Isto fará que as mensaxes enviadas para apps de terceiros sexan recoñecidas sempre e inmediatamente Compensación da hora do dispositivo en horas (para detectar o sono de traballadores a turnos) Enviar mensaxe despois dun número definido de pulsacións - Retardo despois dunha acción do botón (o número está en button_id intent extra) ou ben 0 para efecto inmediato Se marcado, os datos son almacenados no seu formato orixinal ficando disponíbeis para posterior interpretación. Nota: A base de datos será máis grande! Verá unha notificación de emparellamento no seu dispositivo Android. Se non, acceda ás notificacións e acepte a solicitude de emparellamento. posteriormente a solicitude de emparellamento no seu Pebble. Asegúrate de que este tema esté activado na aplicación de notificación do clima para obter a información no teu Pebble. diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index e184308fa..397a62c92 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -423,12 +423,10 @@ הפעלת רטט בצמיד עם פעולת כפתור הפרש מרבי בין לחיצות הפרש מרבי בין לחיצות על הכפתור במילישניות - הפרש לאחר לחיצת כפתור פתיחה בהתקן Android השתקה תגובה הפעלת JS ברקע - המתנה לאחר התאמת פעולת כפתור אחת (המספר נמצא בתוספת של intent בשם button_id) או 0 לתגובה מיידית פעילות תצוגת דפדפן התחבר … כשאפשרות זו פעילה, יתאפשר למסיכות השעון להציג מזג אוויר, סוללה וכו׳. @@ -440,7 +438,6 @@ \n \nהמשך מעבר לנקודה זו הוא על אחריותך! הפעלת הסטה לימין/שמאל בפעילות התרשימים - הגדרות Amazfit Bip אוטומטי סינית מפושטת סינית מסורתית @@ -802,7 +799,6 @@ הכפתורים נדרסו דריסת הכפתורים נכשלה השינויים יתבצעו בהקדם… - הקפצת התראה ב־Q Hybrid להשתמש ביד הפעילות כמונה התראות מרחק מאזור זמן מרחק מהשעה @@ -810,11 +806,6 @@ יש לסמן את האפשרות הזאת אם המכשיר שלך לא נמצא במהלך האיתור Bangle.js Y5 - פעולה של אירוע 1 - פעולה של אירוע 2 - פעולה של אירוע 3 - הגדרות נרחבות ללחיצת כפתור - פעולת לחיצה ארוכה על הכפתור פעולה עם אירוע 1 פעולה עם אירוע 2 פעולה עם אירוע 3 @@ -867,9 +858,6 @@ שעון בלבד חיסכון בחשמל רגיל - משך חוסר פעילות (דקות) - תזכורת חוסר פעילות - להזכיר לי במקרה של חוסר פעילות למשך X דקות יש ללחוץ כאן כדי להתחיל כיול כיול לחץ דם סיסטולי (גבוה) @@ -977,7 +965,6 @@ קמ״ש מ׳/שנ׳ צעדים - תנועות חתירה תנועות חתירה בממוצע מרחק תנועת חתירה ממוצע ס״מ diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index bfd02fb23..0c26bc3dd 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -445,8 +445,6 @@ Abilita vibrazioni band con pressione del tasto Massimo ritardo tra due pressioni Massimo ritardo tra due pressioni del pulsante in millisecondi - Ritardo di azione del tasto - Ritardo di esecuzione dopo rilevamento di un\'azione (il numero indica l\'extra dell\'intent per button_id); 0 per eseguire immediatamente Automatico Cinese semplificato Cinese tradizionale diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index c82cd6da4..9d8bc111f 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -421,8 +421,6 @@ ボタンの動作をトリガーに band の振動を有効にします ボタンを押す間の最大遅延時間 ボタンを押す間隔の最大遅延時間 (ミリ秒単位) - ボタン操作の後に遅延 - 1回のボタン操作に一致した後の遅滞 (数字は button_id インテント拡張内) 0 の場合はすぐ 電話で開く ミュート 返信 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index b3c1027dd..099eedb83 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -368,8 +368,6 @@ 버튼 동작이 작동할 경우 기기 진동 버튼 누름간 최대 간격 버튼 누름 간격을 밀리초(Milisecond) 단위로 설정 - 버튼 동작 후 지연 - 버튼 동작을 작동하고 나서의 휴지 기간 (0으로 설정할 경우 지연 없음) 목표 달성 알림 목표 걸음 수 달성시 밴드 진동 항목 표시 diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index f0caf201f..6a28afc1c 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -452,8 +452,6 @@ Skru på knappetrykk ved knappetrykk Maksimal forsinkelse mellom trykk Maksimal forsinkelse mellom knappetrykk i millisekunder - Forsinkelse etter knappetrykkshandling - Forsinkelse etter at én knappetrykkshandling samsvarer (nummeret er i button_id intent extra) eller 0 for umiddelbar Aktiver skjerm ved løfting Databaseoperasjoner bruker følgende sti på din enhet. \nDenne stien er tilgjengelig for andre Android-programmer og din datamaskin. @@ -811,11 +809,6 @@ Skru av ny BLE-skanning Velg dette hvis enheten du ikke finner enheten din under oppdagelse Y5 - Handling for hendelse 1 - Handling for hendelse 2 - Handling for hendelse 3 - Detaljerte knappetrykkingsinnstillinger - Handling for langt knappetrykk Handling for hendelse 1 Handling for hendelse 2 Handling for hendelse 3 @@ -931,7 +924,6 @@ m Flatt Runder - Tak Gjennomsnittlig rundetid Gjennonsnittstak Gjennomsnittlig taklengde diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 8b4f0a58a..1127072a1 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -358,8 +358,6 @@ Schakel band vibratie in als een knopactie gedetecteerd wordt Maximum tijdsvertraging tussen knopdrukken Maximum tijdsvertraging tussen knopdrukken in milliseconden - Vertraging na knopactie - Vertraag na één knopactie (nummer is button_id intent extra) of 0 voor onmiddelijk Doel melding De armband zal trillen als het doel voor het dagelijks aantal stappen bereikt is Toon items @@ -864,9 +862,6 @@ Energiebesparing Normaal Horloge energie mode - Inactiviteitsperiode (minuten) - Inactiviteit herinnering - Herinneren als er X minuten inactiviteit is Druk hier om te beginnen met kalibreren Kalibratie Bloeddruk SYSTOLISCH (hoog) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index d15513634..625208a31 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -330,7 +330,6 @@ Uruchom wibracje opaski Maksymalny opóźnienie pomiędzy wciśnięciami Maksymalny opóźnienie pomiędzy wciśnięciami w milisekundach - Opóźnienie po wciśnięciu przycisku akcji Opaska zawibruje po osiągnięciu dziennego celu kroków Nie przeszkadzać Obsługa aplikacji wysyłających powiadomienia do Pebble za pośrednictwem PebbleKit. @@ -542,7 +541,6 @@ Nie spałeś(-aś) Wiadomość do rozgłoszenia Wiadomość broadcast nadawana wraz z zdarzeniem. Do każdej wiadomości jest automatycznie dodawany parametr `button_id`. - Opóźnienie po wciśnięciu przycisku (liczba jest w button_id) lub 0 dla bez opóźnienia Opaska nie będzie otrzymywać powiadomień gdy jest aktywne Początek Koniec diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index db16f14d3..eedb5f51e 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -481,8 +481,6 @@ Abrir no Android Sem som Responder - Atrasar após ação do botão - Atraso após uma ação do botão ser considerada válida (número está em button_id intent extra) ou 0 para imediatamente Web View Atividades Automático Chinês simplificado @@ -802,7 +800,6 @@ nodomain.freeyourgadget.gadgetbridge.ButtonPressed Fossil Q Hybrid Configurações de Q Hybrid - Reproduzir notificação do Q Hybrid Relógio não conectado força da vibração: Meta em passos @@ -820,11 +817,6 @@ Marque essa opção se seu dispositivo não pode ser encontrado durante descobertas Bangle.js Y5 - Ação do evento 1 - Ação do evento 2 - Ação do evento 3 - Configurações detalhadas de pressionamento do botão - Ação de pressionamento longo de botão Ação do Evento 1 Ação do Evento 2 Ação do Evento 3 diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 49b0c9b69..1e7a82d73 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -446,7 +446,6 @@ Exportação da base de dados falhou. Por favor, verifique as configurações. Ações do botão Especificar ações ao carregar no botão - Atraso após ação do botão Desbloqueio do ecrã da pulseira Automático Chinês Simplificado diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a5d774196..5dc311060 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -438,8 +438,6 @@ Включить вибро-отклик браслета в ответ на исполнение действия при нажатии Максимальная задержка между нажатиями Максимальная задержка между нажатиями в миллисекундах - Задержка после действия при нажатии - Задержка при выполнении заданного количества нажатий для однократного действия (число указано в button_id). Это число равно нулю для действий без задержки Уведомления о достижении цели Браслет завибрирует, когда будет выполнена дневная норма шагов Что показывать на экране @@ -939,7 +937,6 @@ Взмахи взм взм/с - Взмахи Среднее количество взмахов Выполнить действие При пробуждении diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 4764b74f2..c052e3fdf 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -355,8 +355,6 @@ Zapnúť vibrácie náramku pri stlačení tlačidla Maximálne oneskorenie medzi stlačeniami Maximálne oneskorenie medzi stlačením tlačidla v milisekundách - Oneskorenie po vykonaní akcie tlačidla - Oneskorenie po vykonaní akcie tlačidla (číslo je v button_id) alebo 0 pre okamžite Upozornenie na cieľ Náramok zavibruje pri dosiahnutí cieľa denného počtu krokov Zobrazenie položiek diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 4f6f7aae8..a8370df53 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -979,7 +979,6 @@ YÜKLEME RİSKİ RİZE AİTTİR!" Düz yüzme tarzı swolf indeksi - Kulaç Ortalama Kulaç Ortalama Kulaç Uzunluğu Etkinlik From 824784fd430535d4bc9f35f5e83a0294672fad27 Mon Sep 17 00:00:00 2001 From: TaaviE Date: Wed, 7 Oct 2020 17:59:21 +0300 Subject: [PATCH 08/86] Enhanced logging in BondingUtil --- .../nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java index 8622aa6a0..8df8a8f3c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java @@ -277,7 +277,9 @@ public class BondingUtil { .build(); CompanionDeviceManager manager = (CompanionDeviceManager) bondingInterface.getContext().getSystemService(Context.COMPANION_DEVICE_SERVICE); + LOG.debug(String.format("Searching for %s associations", deviceCandidate.getMacAddress())); for (String association : manager.getAssociations()) { + LOG.debug(String.format("Already associated with: %s", association)); if (association.equals(deviceCandidate.getMacAddress())) { LOG.info("The device has already been bonded through CompanionDeviceManager, using regular"); // If it's already "associated", we should immediately pair @@ -287,6 +289,7 @@ public class BondingUtil { } } + LOG.debug("Starting association request"); manager.associate(pairingRequest, getCompanionDeviceManagerCallback(bondingInterface), null); From bd90b59f92c91436418409f6dad727c735413d51 Mon Sep 17 00:00:00 2001 From: vanous Date: Thu, 8 Oct 2020 23:37:23 +0200 Subject: [PATCH 09/86] Initial implementation of Activity list. --- .../activities/SimpleChartsHost.java | 0 .../charts/ActivityListingAdapter.java | 44 ++++++ .../charts/ActivityListingChartFragment.java | 145 +++++++++++++++++ .../activities/charts/ChartsActivity.java | 24 +-- .../activities/charts/StepAnalysis.java | 148 ++++++++++++++++++ .../main/res/layout/fragment_steps_list.xml | 21 +++ app/src/main/res/values/strings.xml | 7 + app/src/main/res/xml/charts_preferences.xml | 31 +++- 8 files changed, 408 insertions(+), 12 deletions(-) delete mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SimpleChartsHost.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java create mode 100644 app/src/main/res/layout/fragment_steps_list.xml diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SimpleChartsHost.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SimpleChartsHost.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java new file mode 100644 index 000000000..99e649730 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java @@ -0,0 +1,44 @@ +package nodomain.freeyourgadget.gadgetbridge.activities.charts; + +import android.content.Context; + +import java.util.Date; + +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.adapter.AbstractItemAdapter; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; + +public class ActivityListingAdapter extends AbstractItemAdapter { + public ActivityListingAdapter(Context context) { + super(context); + } + + @Override + protected String getName(StepAnalysis.StepSession item) { + int activityKind = item.getActivityKind(); + String activityKindLabel = ActivityKind.asString(activityKind, getContext()); + Date start = item.getStepStart(); + Date end = item.getStepEnd(); + if (activityKind == ActivityKind.TYPE_UNKNOWN) { + return getContext().getString(R.string.chart_no_active_data); + } + return activityKindLabel + " " + DateTimeUtils.formatTime(start.getHours(), start.getMinutes()) + " - " + DateTimeUtils.formatTime(end.getHours(), end.getMinutes()); + } + + @Override + protected String getDetails(StepAnalysis.StepSession item) { + if (item.getActivityKind() == ActivityKind.TYPE_UNKNOWN) { + return getContext().getString(R.string.chart_get_active_and_synchronize); + } + return getContext().getString(R.string.steps) +": " + item.getSteps(); + } + + @Override + protected int getIcon(StepAnalysis.StepSession item) { + int activityKind = item.getActivityKind(); + return ActivityKind.getIconId(activityKind); + } + + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java new file mode 100644 index 000000000..05e4553e2 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java @@ -0,0 +1,145 @@ +/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti, Dikay900, Pavel Elagin + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.activities.charts; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; +import android.widget.TextView; + +import com.github.mikephil.charting.charts.Chart; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; + + +public class ActivityListingChartFragment extends AbstractChartFragment { + protected static final Logger LOG = LoggerFactory.getLogger(ActivityListingChartFragment.class); + int tsDataFrom; + private View rootView; + private List activitySamples; + private ActivityListingAdapter stepListAdapter; + private TextView stepsDateView; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + rootView = inflater.inflate(R.layout.fragment_steps_list, container, false); + + ListView stepsList = rootView.findViewById(R.id.itemListView); + stepListAdapter = new ActivityListingAdapter(getContext()); + stepsList.setAdapter(stepListAdapter); + stepsDateView = rootView.findViewById(R.id.stepsDateView); + //refresh(); + return rootView; + } + + @Override + public String getTitle() { + return "Steps list"; + } + + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(ChartsHost.REFRESH)) { + // TODO: use LimitLines to visualize smart alarms? + //refresh(); + } else { + super.onReceive(context, intent); + } + } + + + @Override + protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) { + //trying to fit found peg into square hole of the Gb Charts fragment system + //get the data + activitySamples = getSamples(db, device); + return null; + } + + @Override + protected void updateChartsnUIThread(ChartsData chartsData) { + //top displays selected date + stepsDateView.setText(DateTimeUtils.formatDate(new Date(tsDataFrom * 1000L))); + //calculate active sessions + StepAnalysis stepAnalysis = new StepAnalysis(); + if (activitySamples != null) { + List stepSessions = stepAnalysis.calculateStepSessions(activitySamples); + if (stepSessions.toArray().length == 0) { + stepSessions = create_empty_record(); + } + //push to the adapter + stepListAdapter.setItems(stepSessions, true); + } + } + + @Override + protected void renderCharts() { + + } + + @Override + protected void setupLegend(Chart chart) { + + } + + @Override + protected List getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { + Calendar day = Calendar.getInstance(); + day.setTimeInMillis(tsTo * 1000L); //we need today initially, which is the end of the time range + day.set(Calendar.HOUR_OF_DAY, 0); //and we set time for the start and end of the same day + day.set(Calendar.MINUTE, 0); + day.set(Calendar.SECOND, 0); + + tsFrom = (int) (day.getTimeInMillis() / 1000); + tsTo = tsFrom + 24 * 60 * 60 - 1; + + tsDataFrom = tsFrom; + + return getAllSamples(db, device, tsFrom, tsTo); + } + + private List create_empty_record() { + //have an "Unknown Activity" in the list in case there are no active sessions + List result = new ArrayList<>(); + int tsTo = tsDataFrom + 24 * 60 * 60 - 1; + result.add(new StepAnalysis.StepSession(new Date(tsDataFrom * 1000L), new Date(tsTo * 1000L), 0, ActivityKind.TYPE_UNKNOWN)); + return result; + } + + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsActivity.java index 70f9bb13f..0aea294d8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsActivity.java @@ -355,14 +355,16 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts case 0: return new ActivitySleepChartFragment(); case 1: - return new SleepChartFragment(); + return new ActivityListingChartFragment(); case 2: - return new WeekSleepChartFragment(); + return new SleepChartFragment(); case 3: - return new WeekStepsChartFragment(); + return new WeekSleepChartFragment(); case 4: - return new SpeedZonesFragment(); + return new WeekStepsChartFragment(); case 5: + return new SpeedZonesFragment(); + case 6: return new LiveActivityFragment(); } return null; @@ -373,9 +375,9 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts // Show 5 or 6 total pages. DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice); if (coordinator.supportsRealtimeData()) { - return 6; + return 7; } - return 5; + return 6; } private String getSleepTitle() { @@ -402,14 +404,16 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts case 0: return getString(R.string.activity_sleepchart_activity_and_sleep); case 1: - return getString(R.string.sleepchart_your_sleep); + return getString(R.string.charts_activity_list); case 2: - return getSleepTitle(); + return getString(R.string.sleepchart_your_sleep); case 3: - return getStepsTitle(); + return getSleepTitle(); case 4: - return getString(R.string.stats_title); + return getStepsTitle(); case 5: + return getString(R.string.stats_title); + case 6: return getString(R.string.liveactivity_live_activity); } return super.getPageTitle(position); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java new file mode 100644 index 000000000..b353a469b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java @@ -0,0 +1,148 @@ +/* Copyright (C) 2019-2020 Q-er + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.activities.charts; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; + +public class StepAnalysis { + protected static final Logger LOG = LoggerFactory.getLogger(StepAnalysis.class); + private static final long MIN_SESSION_LENGTH = 60 * GBApplication.getPrefs().getInt("chart_list_min_session_length", 10); + private static final long MAX_IDLE_PHASE_LENGTH = 60 * GBApplication.getPrefs().getInt("chart_list_max_idle_phase_length", 5); + private static final long MIN_STEPS_PER_MINUTE = GBApplication.getPrefs().getInt("chart_list_min_steps_per_minute", 80); + private static final long MIN_STEPS_PER_MINUTE_FOR_RUN = GBApplication.getPrefs().getInt("chart_list_min_steps_per_minute_for_run", 120); + + public List calculateStepSessions(List samples) { + List result = new ArrayList<>(); + + ActivitySample previousSample = null; + Date stepStart = null; + Date stepEnd = null; + int activeSteps = 0; + int stepsBetwenActivities = 0; + long durationSinceLastActiveStep = 0; + int activityKind; + + for (ActivitySample sample : samples) { + if (isStep(sample)) { //TODO we could improve/extend this to other activities as well, if in database + if (stepStart == null) { + if (sample.getSteps() > MIN_STEPS_PER_MINUTE) { //active step + stepStart = getDateFromSample(sample); + activeSteps = sample.getSteps(); + durationSinceLastActiveStep = 0; + stepsBetwenActivities = 0; + } + } + if (previousSample != null) { + long durationSinceLastSample = sample.getTimestamp() - previousSample.getTimestamp(); + + if (sample.getSteps() > MIN_STEPS_PER_MINUTE) { + activeSteps += sample.getSteps() + stepsBetwenActivities; + stepsBetwenActivities = 0; + durationSinceLastActiveStep = 0; + } else { + stepsBetwenActivities += sample.getSteps(); + durationSinceLastActiveStep += durationSinceLastSample; + } + if (stepStart != null && durationSinceLastActiveStep >= MAX_IDLE_PHASE_LENGTH) { + long current = getDateFromSample(sample).getTime() / 1000; + long ending = stepStart.getTime() / 1000; + long session_length = current - ending; + + if (session_length > MIN_SESSION_LENGTH) { + stepEnd = getDateFromSample(sample); + activityKind = detect_activity_from_steps_per_minute(session_length, activeSteps); + result.add(new StepSession(stepStart, stepEnd, activeSteps, activityKind)); + stepStart = null; + } + } + } + previousSample = sample; + } + } + //make sure we save the last portion of the data as well + if (stepStart != null && previousSample != null) { + long current = getDateFromSample(previousSample).getTime() / 1000; + long ending = stepStart.getTime() / 1000; + long session_length = current - ending; + + if (session_length > MIN_SESSION_LENGTH) { + stepEnd = getDateFromSample(previousSample); + activityKind = detect_activity_from_steps_per_minute(session_length, activeSteps); + result.add(new StepSession(stepStart, stepEnd, activeSteps, activityKind)); + } + } + return result; + } + + private int detect_activity_from_steps_per_minute(long session_length, int activeSteps) { + long spm = activeSteps / (session_length / 60); + if (spm > MIN_STEPS_PER_MINUTE_FOR_RUN) { + return ActivityKind.TYPE_RUNNING; + } + return ActivityKind.TYPE_WALKING; + } + + private boolean isStep(ActivitySample sample) { + return sample.getKind() == ActivityKind.TYPE_WALKING || sample.getKind() == ActivityKind.TYPE_RUNNING || sample.getKind() == ActivityKind.TYPE_ACTIVITY; + } + + private Date getDateFromSample(ActivitySample sample) { + return new Date(sample.getTimestamp() * 1000L); + } + + public static class StepSession { + private final Date stepStart; + private final Date stepEnd; + private final int steps; + private final int activityKind; + + StepSession(Date stepStart, + Date stepEnd, + int steps, int activityKind) { + this.stepStart = stepStart; + this.stepEnd = stepEnd; + this.steps = steps; + this.activityKind = activityKind; + } + + public Date getStepStart() { + return stepStart; + } + + public Date getStepEnd() { + return stepEnd; + } + + public long getSteps() { + return steps; + } + + public int getActivityKind() { + return activityKind; + } + + } +} diff --git a/app/src/main/res/layout/fragment_steps_list.xml b/app/src/main/res/layout/fragment_steps_list.xml new file mode 100644 index 000000000..56896de37 --- /dev/null +++ b/app/src/main/res/layout/fragment_steps_list.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c6782d0f0..d142488ad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -418,6 +418,8 @@ There was an error setting the alarms, please try again. Alarms sent to device. No data. Synchronize device? + No activities detected. + Do some activity and synchronize device. About to transfer %1$s of data starting from %2$s Daily step target Error executing \'%1$s\' @@ -439,6 +441,7 @@ Sleep today, target: %1$s Steps per week Activity + Activity list Lack of sleep: %1$s Overslept: %1$s Flashing firmware… @@ -596,6 +599,10 @@ Sleep range Past 24 hours Noon to noon + Minimal steps per minute to detect run + Minimal steps per minute to detect activity + Pause length to separate activities (minutes) + Minimal activity length (minutes) Authenticating Authentication required Preferred sleep duration in hours diff --git a/app/src/main/res/xml/charts_preferences.xml b/app/src/main/res/xml/charts_preferences.xml index 675fee091..d087da647 100644 --- a/app/src/main/res/xml/charts_preferences.xml +++ b/app/src/main/res/xml/charts_preferences.xml @@ -53,7 +53,34 @@ android:summaryOff="@string/pref_charts_range_off" android:summaryOn="@string/pref_charts_range_on" android:title="@string/pref_title_charts_range" /> - - + + + + + + + + + + + From 7d5f9b41d23c5c6dd54ccc65dc949e93738f880e Mon Sep 17 00:00:00 2001 From: vanous Date: Sat, 10 Oct 2020 08:18:25 +0200 Subject: [PATCH 10/86] adding
into template --- .github/ISSUE_TEMPLATE.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 9c853aa9f..26a99596e 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -17,6 +17,16 @@ If you got it from Google Play, please note [that version](https://github.com/Ta #### Your issue is: *If possible, please attach [logs](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Log-Files)! that might help identifying the problem.* +*Long logs can be also included but make sure to tuck them into the `details` tag:* + +
+ Click to see my log, which is a bit longer + +``` +Here go lines of that log. +``` +
+ #### Your wearable device is: From fd56db55c457618957fb6bcbdae630d6c27fa96e Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 28 Sep 2020 20:33:53 -0600 Subject: [PATCH 11/86] Lefun: Add device coordinator --- .../devices/lefun/LefunConstants.java | 31 ++++ .../devices/lefun/LefunDeviceCoordinator.java | 157 ++++++++++++++++++ .../gadgetbridge/model/DeviceType.java | 1 + .../gadgetbridge/util/DeviceHelper.java | 2 + app/src/main/res/values/strings.xml | 1 + 5 files changed, 192 insertions(+) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java new file mode 100644 index 000000000..b3d312391 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java @@ -0,0 +1,31 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun; + +import java.util.UUID; + +import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID; + +public class LefunConstants { + public static final UUID UUID_SERVICE_LEFUN = UUID.fromString(String.format(BASE_UUID, "18D0")); + + public static final String ADVERTISEMENT_NAME = "Lefun"; + public static final String MANUFACTURER_NAME = "Teng Jin Da"; + public static int NUM_ALARM_SLOTS = 5; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java new file mode 100644 index 000000000..fe9aee215 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java @@ -0,0 +1,157 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun; + +import android.app.Activity; +import android.content.Context; +import android.net.Uri; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +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; + +import static nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants.ADVERTISEMENT_NAME; +import static nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants.MANUFACTURER_NAME; +import static nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants.NUM_ALARM_SLOTS; + +public class LefunDeviceCoordinator extends AbstractDeviceCoordinator { + @Override + protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { + + } + + @Override + public int getBondingStyle() { + return BONDING_STYLE_NONE; + } + + @NonNull + @Override + public DeviceType getSupportedType(GBDeviceCandidate candidate) { + // There's a bunch of other names other than "Lefun", but let's just focus on one for now. + if (ADVERTISEMENT_NAME.equals(candidate.getName())) { + // The device does not advertise service UUIDs, so can't check whether it supports + // the proper service. We can check that it doesn't advertise any services, though. + // We're actually supposed to check for presence of the string "TJDR" within the + // manufacturer specific data, which consists of the device's MAC address and said + // string. But we're not being given it, so *shrug*. + if (candidate.getServiceUuids().length == 0) { + return DeviceType.LEFUN; + } + } + + return DeviceType.UNKNOWN; + } + + @Override + public DeviceType getDeviceType() { + return DeviceType.LEFUN; + } + + @Nullable + @Override + public Class getPairingActivity() { + return null; + } + + @Override + public boolean supportsActivityDataFetching() { + return true; + } + + @Override + public boolean supportsActivityTracking() { + return true; + } + + @Override + public SampleProvider getSampleProvider(GBDevice device, DaoSession session) { + return null; + } + + @Override + public InstallHandler findInstallHandler(Uri uri, Context context) { + return null; + } + + @Override + public boolean supportsScreenshots() { + return false; + } + + @Override + public int getAlarmSlotCount() { + return NUM_ALARM_SLOTS; + } + + @Override + public boolean supportsSmartWakeup(GBDevice device) { + return false; + } + + @Override + public boolean supportsHeartRateMeasurement(GBDevice device) { + return true; + } + + @Override + public String getManufacturer() { + return MANUFACTURER_NAME; + } + + @Override + public boolean supportsAppsManagement() { + return false; + } + + @Override + public Class getAppsManagementActivity() { + return null; + } + + @Override + public boolean supportsCalendarEvents() { + return false; + } + + @Override + public boolean supportsRealtimeData() { + return true; + } + + @Override + public boolean supportsWeather() { + return false; + } + + @Override + public boolean supportsFindDevice() { + return true; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java index e3080ff83..02607887f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -73,6 +73,7 @@ public enum DeviceType { TLW64(180, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_tlw64), PINETIME_JF(190, R.drawable.ic_device_pinetime, R.drawable.ic_device_pinetime_disabled, R.string.devicetype_pinetime_jf), MIJIA_LYWSD02(200, R.drawable.ic_device_pebble, R.drawable.ic_device_pebble_disabled, R.string.devicetype_mijia_lywsd02), + LEFUN(210, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled, R.string.devicetype_lefun), ITAG(250, R.drawable.ic_device_itag, R.drawable.ic_device_itag_disabled, R.string.devicetype_itag), VIBRATISSIMO(300, R.drawable.ic_device_lovetoy, R.drawable.ic_device_lovetoy_disabled, R.string.devicetype_vibratissimo), TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java index 5956d7339..921702b23 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -68,6 +68,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.itag.ITagCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.jyou.BFH16DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.jyou.TeclastH30.TeclastH30Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.jyou.y5.Y5Coordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.lenovo.watchxplus.WatchXPlusDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.liveview.LiveviewCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Coordinator; @@ -256,6 +257,7 @@ public class DeviceHelper { result.add(new TLW64Coordinator()); result.add(new PineTimeJFCoordinator()); result.add(new SG2Coordinator()); + result.add(new LefunDeviceCoordinator()); return result; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d142488ad..575016309 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -784,6 +784,7 @@ Gadgetbridge notifications high priority Amazfit GTS Lemfo SG2 + Lefun Alipay (Shortcut) Weather (Shortcut) From 265260810a2661c02be726a8d864ad776dcdd4e2 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Wed, 30 Sep 2020 22:32:13 -0600 Subject: [PATCH 12/86] Lefun: Add command serializers --- .../devices/lefun/LefunConstants.java | 43 +++++ .../devices/lefun/LefunFeatureSupport.java | 41 +++++ .../devices/lefun/commands/AlarmCommand.java | 174 ++++++++++++++++++ .../devices/lefun/commands/BaseCommand.java | 148 +++++++++++++++ .../devices/lefun/commands/Cmd22Command.java | 84 +++++++++ .../devices/lefun/commands/Cmd25Command.java | 86 +++++++++ .../lefun/commands/FeaturesCommand.java | 95 ++++++++++ .../lefun/commands/FindDeviceCommand.java | 43 +++++ .../lefun/commands/FindPhoneCommand.java | 35 ++++ .../commands/GetActivityDataCommand.java | 110 +++++++++++ .../commands/GetBatteryLevelCommand.java | 43 +++++ .../commands/GetFirmwareInfoCommand.java | 78 ++++++++ .../lefun/commands/GetPpgDataCommand.java | 118 ++++++++++++ .../lefun/commands/GetSleepDataCommand.java | 102 ++++++++++ .../lefun/commands/GetSleepTimeCommand.java | 75 ++++++++ .../lefun/commands/GetStepsDataCommand.java | 86 +++++++++ .../HydrationReminderIntervalCommand.java | 86 +++++++++ .../lefun/commands/NotificationCommand.java | 121 ++++++++++++ .../lefun/commands/PpgResultCommand.java | 72 ++++++++ .../lefun/commands/ProfileCommand.java | 130 +++++++++++++ .../RemoteCameraTriggeredCommand.java | 36 ++++ .../lefun/commands/RequestBondingCommand.java | 46 +++++ .../SedentaryReminderIntervalCommand.java | 86 +++++++++ .../lefun/commands/SetLanguageCommand.java | 54 ++++++ .../commands/SetRemoteCameraCommand.java | 54 ++++++ .../lefun/commands/SettingsCommand.java | 117 ++++++++++++ .../commands/StartPpgSensingCommand.java | 57 ++++++ .../devices/lefun/commands/TimeCommand.java | 149 +++++++++++++++ .../lefun/commands/UiPagesCommand.java | 91 +++++++++ 29 files changed, 2460 insertions(+) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunFeatureSupport.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindDeviceCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindPhoneCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetActivityDataCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetBatteryLevelCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetFirmwareInfoCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepDataCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepTimeCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetStepsDataCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RemoteCameraTriggeredCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RequestBondingCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetLanguageCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetRemoteCameraCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/StartPpgSensingCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java index b3d312391..fb36779fa 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java @@ -23,9 +23,52 @@ import java.util.UUID; import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID; public class LefunConstants { + // BLE UUIDs public static final UUID UUID_SERVICE_LEFUN = UUID.fromString(String.format(BASE_UUID, "18D0")); + // Coordinator constants public static final String ADVERTISEMENT_NAME = "Lefun"; public static final String MANUFACTURER_NAME = "Teng Jin Da"; public static int NUM_ALARM_SLOTS = 5; + + // Commands + public static final byte CMD_REQUEST_ID = (byte)0xab; + public static final byte CMD_RESPONSE_ID = 0x55; + + public static final int CMD_MAX_LENGTH = 20; + // 3 header bytes plus checksum + public static final int CMD_HEADER_LENGTH = 4; + + public static final byte CMD_FIRMWARE_INFO = 0x00; + public static final byte CMD_BONDING_REQUEST = 0x01; + public static final byte CMD_SETTINGS = 0x02; + public static final byte CMD_BATTERY_LEVEL = 0x03; + public static final byte CMD_TIME = 0x04; + public static final byte CMD_ALARM = 0x05; + public static final byte CMD_PROFILE = 0x06; + public static final byte CMD_UI_PAGES = 0x07; + public static final byte CMD_FEATURES = 0x08; + public static final byte CMD_FIND_DEVICE = 0x09; + public static final byte CMD_FIND_PHONE = 0x0a; + public static final byte CMD_SEDENTARY_REMINDER_INTERVAL = 0x0b; + public static final byte CMD_HYDRATION_REMINDER_INTERVAL = 0x0c; + public static final byte CMD_REMOTE_CAMERA = 0x0d; + public static final byte CMD_REMOTE_CAMERA_TRIGGERED = 0x0e; + public static final byte CMD_PPG_START = 0x0f; + public static final byte CMD_PPG_RESULT = 0x10; + public static final byte CMD_PPG_DATA = 0x11; + public static final byte CMD_STEPS_DATA = 0x12; + public static final byte CMD_ACTIVITY_DATA = 0x13; + public static final byte CMD_SLEEP_TIME_DATA = 0x14; + public static final byte CMD_SLEEP_DATA = 0x15; + public static final byte CMD_NOTIFICATION = 0x17; + public static final byte CMD_LANGUAGE = 0x21; + public static final byte CMD_UNKNOWN_22 = 0x22; + public static final byte CMD_UNKNOWN_25 = 0x25; + public static final byte CMD_UNKNOWN_80 = (byte)0x80; + + public static final int PPG_TYPE_INVALID = -1; + public static final int PPG_TYPE_HEART_RATE = 0; + public static final int PPG_TYPE_BLOOD_PRESSURE = 1; + public static final int PPG_TYPE_BLOOD_OXYGEN = 2; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunFeatureSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunFeatureSupport.java new file mode 100644 index 000000000..ba76fb046 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunFeatureSupport.java @@ -0,0 +1,41 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun; + +public class LefunFeatureSupport { + public static final int SUPPORT_HEART_RATE = 1 << 2; + public static final int SUPPORT_BLOOD_PRESSURE = 1 << 3; + public static final int SUPPORT_FAKE_ECG = 1 << 10; + public static final int SUPPORT_ECG = 1 << 11; + public static final int SUPPORT_WALLPAPER_UPLOAD = 1 << 12; + + public static final int RESERVE_BLOOD_OXYGEN = 1 << 0; + public static final int RESERVE_CLOCK_FACE_UPLOAD = 1 << 3; + public static final int RESERVE_CONTACTS = 1 << 5; + public static final int RESERVE_WALLPAPER = 1 << 6; + public static final int RESERVE_REMOTE_CAMERA = 1 << 7; + + public static boolean checkSupported(short deviceSupport, int featureSupport) { + return (deviceSupport & featureSupport) == featureSupport; + } + + public static boolean checkNotReserved(short deviceReserve, int featureReserve) { + return !((deviceReserve & featureReserve) == featureReserve); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java new file mode 100644 index 000000000..a351920b4 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java @@ -0,0 +1,174 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class AlarmCommand extends BaseCommand { + public static final int DOW_SUNDAY = 0; + public static final int DOW_MONDAY = 1; + public static final int DOW_TUESDAY = 2; + public static final int DOW_WEDNESDAY = 3; + public static final int DOW_THURSDAY = 4; + public static final int DOW_FRIDAY = 5; + public static final int DOW_SATURDAY = 6; + + private byte op; + private byte index; + private boolean enabled; + // Snooze is not implemented how you think it would be + // Number of snoozes is decremented every time the alarm triggers, and the alarm time + // is moved forward by number of minutes in snooze time. It never gets reset to the + // original time. + private byte numOfSnoozes; + private byte snoozeTime; + private byte dayOfWeek; + private byte hour; + private byte minute; + + private boolean setSuccess; + + public byte getOp() { + return op; + } + + public void setOp(byte op) { + if (op != OP_GET && op != OP_SET) + throw new IllegalArgumentException("Operation must be get or set"); + this.op = op; + } + + public byte getIndex() { + return index; + } + + public void setIndex(byte index) { + if (index < 0 || index >= LefunConstants.NUM_ALARM_SLOTS) + throw new IllegalArgumentException("Index must be between 0 and " + + (LefunConstants.NUM_ALARM_SLOTS - 1) + " inclusive"); + this.index = index; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public byte getNumOfSnoozes() { + return numOfSnoozes; + } + + public void setNumOfSnoozes(byte numOfSnoozes) { + this.numOfSnoozes = numOfSnoozes; + } + + public byte getSnoozeTime() { + return snoozeTime; + } + + public void setSnoozeTime(byte snoozeTime) { + this.snoozeTime = snoozeTime; + } + + public boolean getDayOfWeek(int day) { + if (day < 0 || day > 6) + throw new IllegalArgumentException("Invalid day of week"); + return getBit(dayOfWeek, 1 << day); + } + + public void setDayOfWeek(int day, boolean enabled) { + if (day < 0 || day > 6) + throw new IllegalArgumentException("Invalid day of week"); + dayOfWeek = setBit(dayOfWeek, 1 << day, enabled); + } + + public byte getHour() { + return hour; + } + + public void setHour(byte hour) { + if (hour < 0 || hour > 23) + throw new IllegalArgumentException("Hour must be between 0 and 23 inclusive"); + this.hour = hour; + } + + public byte getMinute() { + return minute; + } + + public void setMinute(byte minute) { + if (minute < 0 || minute > 59) + throw new IllegalArgumentException("Minute must be between 0 and 59 inclusive"); + this.minute = minute; + } + + public boolean isSetSuccess() { + return setSuccess; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateId(id, LefunConstants.CMD_ALARM); + + if (params.limit() < 2) + throwUnexpectedLength(); + + op = params.get(); + index = params.get(); + if (op == OP_GET) { + if (params.limit() != 8) + throwUnexpectedLength(); + + enabled = params.get() == 1; + numOfSnoozes = params.get(); + snoozeTime = params.get(); + dayOfWeek = params.get(); + hour = params.get(); + minute = params.get(); + } else if (op == OP_SET) { + if (params.limit() != 3) + throwUnexpectedLength(); + + setSuccess = params.get() == 1; + } else { + throw new IllegalArgumentException("Invalid operation type received"); + } + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(op); + // No alarm ID for get; all of them are returned + if (op == OP_SET) { + params.put(index); + params.put((byte)(enabled ? 1: 0)); + params.put(numOfSnoozes); + params.put(snoozeTime); + params.put(dayOfWeek); + params.put(hour); + params.put(minute); + } + return LefunConstants.CMD_ALARM; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java new file mode 100644 index 000000000..7a8ac77d3 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java @@ -0,0 +1,148 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public abstract class BaseCommand { + // Common constants + public static final byte OP_GET = 0; + public static final byte OP_SET = 1; + + abstract protected void deserializeParams(byte id, ByteBuffer params); + abstract protected byte serializeParams(ByteBuffer params); + + public void deserialize(byte[] response) { + if (response.length < LefunConstants.CMD_HEADER_LENGTH) + throw new IllegalArgumentException("Response is too short"); + if (calculateChecksum(response, 0, response.length - 1) != response[response.length - 1]) + throw new IllegalArgumentException("Incorrect message checksum"); + ByteBuffer buffer = ByteBuffer.wrap(response, LefunConstants.CMD_HEADER_LENGTH - 1, + response.length - LefunConstants.CMD_HEADER_LENGTH); + buffer.order(ByteOrder.BIG_ENDIAN); + deserializeParams(response[2], buffer); + } + + public byte[] serialize() { + ByteBuffer buffer = ByteBuffer.allocate(LefunConstants.CMD_MAX_LENGTH - LefunConstants.CMD_HEADER_LENGTH); + buffer.order(ByteOrder.BIG_ENDIAN); + byte id = serializeParams(buffer); + return makeCommand(id, buffer); + } + + static protected byte calculateChecksum(byte[] data, int offset, int length) { + byte checksum = 0; + for (int i = offset; i < offset + length; ++i) { + byte b = data[i]; + for (int j = 0; j < 8; ++j) { + if (((b ^ checksum) & 1) == 0) { + checksum >>= 1; + } else { + checksum = (byte) ((checksum ^ 0x18) >> 1 | 0x80); + } + b >>= 1; + } + } + return checksum; + } + + /** + * Builds a command given ID and parameters buffer. + * + * @param id the command ID + * @param params the parameters buffer + * @return the assembled command buffer + */ + protected byte[] makeCommand(byte id, ByteBuffer params) { + if (params.position() > LefunConstants.CMD_MAX_LENGTH - LefunConstants.CMD_HEADER_LENGTH) + throw new IllegalArgumentException("params is too long to fit"); + + int paramsLength = params.position(); + byte[] request = new byte[paramsLength + LefunConstants.CMD_HEADER_LENGTH]; + request[0] = LefunConstants.CMD_REQUEST_ID; + request[1] = (byte) request.length; + request[2] = id; + params.flip(); + params.get(request, LefunConstants.CMD_HEADER_LENGTH - 1, paramsLength); + request[request.length - 1] = calculateChecksum(request, 0, request.length - 1); + return request; + } + + protected void throwUnexpectedLength() { + throw new IllegalArgumentException("Unexpected parameters length"); + } + + protected void validateId(byte id, byte expectedId) { + if (id != expectedId) + throw new IllegalArgumentException("Wrong command ID"); + } + + protected void validateIdAndLength(byte id, ByteBuffer params, byte expectedId, int expectedLength) { + validateId(id, expectedId); + if (params.limit() != expectedLength) + throwUnexpectedLength(); + } + + protected boolean getBit(int value, int mask) { + return (value & mask) != 0; + } + + protected int setBit(int value, int mask, boolean set) { + if (set) { + return value | mask; + } else { + return value & ~mask; + } + } + + protected short setBit(short value, int mask, boolean set) { + if (set) { + return (short)(value | mask); + } else { + return (short)(value & ~mask); + } + } + + protected byte setBit(byte value, int mask, boolean set) { + if (set) { + return (byte)(value | mask); + } else { + return (byte)(value & ~mask); + } + } + + /** + * Find index of first bit that is set + * @param value + * @return + */ + protected int getLowestSetBitIndex(int value) { + if (value == 0) return -1; + + int i = 0; + while ((value & 1) == 0) { + ++i; + value >>= 1; + } + return i; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java new file mode 100644 index 000000000..c40a92fc3 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java @@ -0,0 +1,84 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class Cmd22Command extends BaseCommand { + private byte op; + private short unknown; + + private boolean setSuccess; + + public byte getOp() { + return op; + } + + public void setOp(byte op) { + if (op != OP_GET && op != OP_SET) + throw new IllegalArgumentException("Operation must be get or set"); + this.op = op; + } + + public short getUnknown() { + return unknown; + } + + public void setUnknown(short unknown) { + this.unknown = unknown; + } + + public boolean isSetSuccess() { + return setSuccess; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateId(id, LefunConstants.CMD_UNKNOWN_22); + + if (params.limit() < 1) + throwUnexpectedLength(); + + op = params.get(); + if (op == OP_GET) { + if (params.limit() != 3) + throwUnexpectedLength(); + + unknown = params.getShort(); + } else if (op == OP_SET) { + if (params.limit() != 2) + throwUnexpectedLength(); + + setSuccess = params.get() == 1; + } else { + throw new IllegalArgumentException("Invalid operation type received"); + } + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(op); + if (op == OP_SET) { + params.putShort(unknown); + } + return LefunConstants.CMD_UNKNOWN_22; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java new file mode 100644 index 000000000..9bc49813d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java @@ -0,0 +1,86 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class Cmd25Command extends BaseCommand { + private byte op; + private int unknown; + + private boolean setSuccess; + + public byte getOp() { + return op; + } + + public void setOp(byte op) { + if (op != OP_GET && op != OP_SET) + throw new IllegalArgumentException("Operation must be get or set"); + this.op = op; + } + + public int getUnknown() { + return unknown; + } + + public void setUnknown(int unknown) { + if (unknown < 0 || unknown > 65000) + throw new IllegalArgumentException("Value must be between 0 and 65000 inclusive"); + this.unknown = unknown; + } + + public boolean isSetSuccess() { + return setSuccess; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateId(id, LefunConstants.CMD_UNKNOWN_25); + + if (params.limit() < 1) + throwUnexpectedLength(); + + op = params.get(); + if (op == OP_GET) { + if (params.limit() != 5) + throwUnexpectedLength(); + + unknown = params.getInt(); + } else if (op == OP_SET) { + if (params.limit() != 2) + throwUnexpectedLength(); + + setSuccess = params.get() == 1; + } else { + throw new IllegalArgumentException("Invalid operation type received"); + } + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(op); + if (op == OP_SET) { + params.putInt(unknown); + } + return LefunConstants.CMD_UNKNOWN_25; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java new file mode 100644 index 000000000..b0006d4ed --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java @@ -0,0 +1,95 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class FeaturesCommand extends BaseCommand { + public static final int FEATURE_RAISE_TO_WAKE = 0; + public static final int FEATURE_SEDENTARY_REMINDER = 1; + public static final int FEATURE_HYDRATION_REMINDER = 2; + public static final int FEATURE_REMOTE_CAMERA = 3; + public static final int FEATURE_UNKNOWN_4 = 4; + public static final int FEATURE_ANTI_LOST = 5; + + private byte op; + private short features; + + private boolean setSuccess; + + public byte getOp() { + return op; + } + + public void setOp(byte op) { + if (op != OP_GET && op != OP_SET) + throw new IllegalArgumentException("Operation must be get or set"); + this.op = op; + } + + public boolean getFeature(int index) { + if (index < 0 || index > 5) + throw new IllegalArgumentException("Index must be between 0 and 5 inclusive"); + return getBit(features, 1 << index); + } + + public void setFeature(int index, boolean enabled) { + if (index < 0 || index > 5) + throw new IllegalArgumentException("Index must be between 0 and 5 inclusive"); + features = setBit(features, 1 << index, enabled); + } + + public boolean isSetSuccess() { + return setSuccess; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateId(id, LefunConstants.CMD_FEATURES); + + if (params.limit() < 1) + throwUnexpectedLength(); + + op = params.get(); + if (op == OP_GET) { + if (params.limit() != 3) + throwUnexpectedLength(); + + features = params.getShort(); + } else if (op == OP_SET) { + if (params.limit() != 2) + throwUnexpectedLength(); + + setSuccess = params.get() == 1; + } else { + throw new IllegalArgumentException("Invalid operation type received"); + } + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(op); + if (op == OP_SET) { + params.putShort(features); + } + return LefunConstants.CMD_FEATURES; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindDeviceCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindDeviceCommand.java new file mode 100644 index 000000000..433bb9faa --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindDeviceCommand.java @@ -0,0 +1,43 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + 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 . */ +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class FindDeviceCommand extends BaseCommand { + private boolean success; + + public boolean isSuccess() { + return success; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateIdAndLength(id, params, LefunConstants.CMD_FIND_DEVICE, 1); + + success = params.get() == 1; + } + + @Override + protected byte serializeParams(ByteBuffer params) { + return LefunConstants.CMD_FIND_DEVICE; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindPhoneCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindPhoneCommand.java new file mode 100644 index 000000000..4190929a6 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindPhoneCommand.java @@ -0,0 +1,35 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class FindPhoneCommand extends BaseCommand { + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateIdAndLength(id, params, LefunConstants.CMD_FIND_PHONE, 0); + } + + @Override + protected byte serializeParams(ByteBuffer params) { + return LefunConstants.CMD_FIND_PHONE; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetActivityDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetActivityDataCommand.java new file mode 100644 index 000000000..92d2a8ac8 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetActivityDataCommand.java @@ -0,0 +1,110 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class GetActivityDataCommand extends BaseCommand { + private byte daysAgo; + private byte totalRecords; + private byte currentRecord; + private byte year; + private byte month; + private byte day; + private byte hour; + private byte minute; + private short steps; + private short distance; // m + private short calories; // calories + + public byte getDaysAgo() { + return daysAgo; + } + + public void setDaysAgo(byte daysAgo) { + if (daysAgo < 0 || daysAgo > 6) + throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive"); + this.daysAgo = daysAgo; + } + + public byte getTotalRecords() { + return totalRecords; + } + + public byte getCurrentRecord() { + return currentRecord; + } + + public byte getYear() { + return year; + } + + public byte getMonth() { + return month; + } + + public byte getDay() { + return day; + } + + public byte getHour() { + return hour; + } + + public byte getMinute() { + return minute; + } + + public short getSteps() { + return steps; + } + + public short getDistance() { + return distance; + } + + public short getCalories() { + return calories; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateIdAndLength(id, params, LefunConstants.CMD_ACTIVITY_DATA, 14); + + daysAgo = params.get(); + totalRecords = params.get(); + currentRecord = params.get(); + year = params.get(); + month = params.get(); + day = params.get(); + hour = params.get(); + minute = params.get(); + steps = params.getShort(); + distance = params.getShort(); + calories = params.getShort(); + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(daysAgo); + return LefunConstants.CMD_ACTIVITY_DATA; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetBatteryLevelCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetBatteryLevelCommand.java new file mode 100644 index 000000000..d0ff51d29 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetBatteryLevelCommand.java @@ -0,0 +1,43 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class GetBatteryLevelCommand extends BaseCommand { + private byte batteryLevel; + + public byte getBatteryLevel() { + return batteryLevel; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateIdAndLength(id, params, LefunConstants.CMD_BATTERY_LEVEL, 1); + + batteryLevel = params.get(); + } + + @Override + protected byte serializeParams(ByteBuffer params) { + return LefunConstants.CMD_BATTERY_LEVEL; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetFirmwareInfoCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetFirmwareInfoCommand.java new file mode 100644 index 000000000..301efdff9 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetFirmwareInfoCommand.java @@ -0,0 +1,78 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + 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 . */ +import java.nio.charset.StandardCharsets; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class GetFirmwareInfoCommand extends BaseCommand { + private short supportCode; + private short devTypeReserveCode; + private String typeCode; + private short hardwareVersion; + private short softwareVersion; + private String vendorCode; + + public short getSupportCode() { + return supportCode; + } + + public short getDevTypeReserveCode() { + return devTypeReserveCode; + } + + public String getTypeCode() { + return typeCode; + } + + public short getHardwareVersion() { + return hardwareVersion; + } + + public short getSoftwareVersion() { + return softwareVersion; + } + + public String getVendorCode() { + return vendorCode; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateIdAndLength(id, params, LefunConstants.CMD_FIRMWARE_INFO, 16); + + supportCode = (short)(params.get() | (params.get() << 8)); + devTypeReserveCode = params.getShort(); + byte[] typeCodeBytes = new byte[4]; + params.get(typeCodeBytes); + typeCode = new String(typeCodeBytes, StandardCharsets.US_ASCII); + hardwareVersion = params.getShort(); + softwareVersion = params.getShort(); + byte[] vendorCodeBytes = new byte[4]; + params.get(vendorCodeBytes); + vendorCode = new String(vendorCodeBytes, StandardCharsets.US_ASCII); + } + + @Override + protected byte serializeParams(ByteBuffer params) { + return LefunConstants.CMD_FIRMWARE_INFO; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java new file mode 100644 index 000000000..25a812944 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java @@ -0,0 +1,118 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class GetPpgDataCommand extends BaseCommand { + private byte ppgType; + private short totalRecords; + private short currentRecord; + private byte year; + private byte month; + private byte day; + private byte hour; + private byte minute; + private byte second; + private byte[] ppgData; + + public int getPpgType() { + return getLowestSetBitIndex(ppgType); + } + + public void setPpgType(int type) { + if (type < 0 || type > 2) + throw new IllegalArgumentException("Invalid PPG type"); + this.ppgType = (byte)(1 << type); + } + + public short getTotalRecords() { + return totalRecords; + } + + public short getCurrentRecord() { + return currentRecord; + } + + public byte getYear() { + return year; + } + + public byte getMonth() { + return month; + } + + public byte getDay() { + return day; + } + + public byte getHour() { + return hour; + } + + public byte getMinute() { + return minute; + } + + public byte getSecond() { + return second; + } + + public byte[] getPpgData() { + return ppgData; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateId(id, LefunConstants.CMD_PPG_DATA); + + if (params.limit() < 9) + throwUnexpectedLength(); + + ppgType = params.get(); + totalRecords = params.get(); + currentRecord = params.get(); + year = params.get(); + month = params.get(); + day = params.get(); + hour = params.get(); + minute = params.get(); + second = params.get(); + + int typeIndex = getPpgType(); + int dataLength; + switch (typeIndex) { + case LefunConstants.PPG_TYPE_HEART_RATE: + case LefunConstants.PPG_TYPE_BLOOD_OXYGEN: + dataLength = 1; + break; + case LefunConstants.PPG_TYPE_BLOOD_PRESSURE: + dataLength = 2; + break; + default: + throw new IllegalArgumentException("Unknown PPG type"); + } + + if (params.limit() < dataLength + 9) + throwUnexpectedLength(); + + ppgData = new byte[dataLength]; + params.get(ppgData); + + // Extended count/index + if (params.limit() == dataLength + 11) + { + totalRecords |= params.get() << 8; + currentRecord |= params.get() << 8; + } + else if (params.limit() > dataLength + 11) { + throwUnexpectedLength(); + } + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(ppgType); + return LefunConstants.CMD_PPG_DATA; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepDataCommand.java new file mode 100644 index 000000000..c0ebc6381 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepDataCommand.java @@ -0,0 +1,102 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + 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 . */ +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class GetSleepDataCommand extends BaseCommand { + public static final int SLEEP_TYPE_AWAKE = 1; + public static final int SLEEP_TYPE_LIGHT_SLEEP = 2; + public static final int SLEEP_TYPE_DEEP_SLEEP = 3; + + private byte daysAgo; + private short totalRecords; + private short currentRecord; + private byte year; + private byte month; + private byte day; + private byte hour; + private byte minute; + private byte sleepType; + + public byte getDaysAgo() { + return daysAgo; + } + + public void setDaysAgo(byte daysAgo) { + if (daysAgo < 0 || daysAgo > 6) + throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive"); + this.daysAgo = daysAgo; + } + + public short getTotalRecords() { + return totalRecords; + } + + public short getCurrentRecord() { + return currentRecord; + } + + public byte getYear() { + return year; + } + + public byte getMonth() { + return month; + } + + public byte getDay() { + return day; + } + + public byte getHour() { + return hour; + } + + public byte getMinute() { + return minute; + } + + public byte getSleepType() { + return sleepType; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateIdAndLength(id, params, LefunConstants.CMD_SLEEP_DATA, 11); + + daysAgo = params.get(); + totalRecords = params.getShort(); + currentRecord = params.getShort(); + year = params.get(); + month = params.get(); + day = params.get(); + hour = params.get(); + minute = params.get(); + sleepType = params.get(); + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(daysAgo); + return LefunConstants.CMD_SLEEP_DATA; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepTimeCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepTimeCommand.java new file mode 100644 index 000000000..d0f7d722e --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepTimeCommand.java @@ -0,0 +1,75 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class GetSleepTimeCommand extends BaseCommand { + private byte daysAgo; + private byte year; + private byte month; + private byte day; + private short minutes; + + public byte getDaysAgo() { + return daysAgo; + } + + public void setDaysAgo(byte daysAgo) { + if (daysAgo < 0 || daysAgo > 6) + throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive"); + this.daysAgo = daysAgo; + } + + public byte getYear() { + return year; + } + + public byte getMonth() { + return month; + } + + public byte getDay() { + return day; + } + + public short getMinutes() { + return minutes; + } + + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateIdAndLength(id, params, LefunConstants.CMD_SLEEP_TIME_DATA, 6); + + daysAgo = params.get(); + year = params.get(); + month = params.get(); + day = params.get(); + minutes = params.getShort(); + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(daysAgo); + return LefunConstants.CMD_SLEEP_TIME_DATA; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetStepsDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetStepsDataCommand.java new file mode 100644 index 000000000..f365c94d6 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetStepsDataCommand.java @@ -0,0 +1,86 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class GetStepsDataCommand extends BaseCommand { + private byte daysAgo; + private byte year; + private byte month; + private byte day; + private int steps; + private int distance; // m + private int calories; // calories + + public byte getDaysAgo() { + return daysAgo; + } + + public void setDaysAgo(byte daysAgo) { + if (daysAgo < 0 || daysAgo > 6) + throw new IllegalArgumentException("Days ago must be between 0 and 6 inclusive"); + this.daysAgo = daysAgo; + } + + public byte getYear() { + return year; + } + + public byte getMonth() { + return month; + } + + public byte getDay() { + return day; + } + + public int getSteps() { + return steps; + } + + public int getDistance() { + return distance; + } + + public int getCalories() { + return calories; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateIdAndLength(id, params, LefunConstants.CMD_STEPS_DATA, 16); + + daysAgo = params.get(); + year = params.get(); + month = params.get(); + day = params.get(); + steps = params.getInt(); + distance = params.getInt(); + calories = params.getInt(); + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(daysAgo); + return LefunConstants.CMD_STEPS_DATA; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java new file mode 100644 index 000000000..e92fa8aee --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java @@ -0,0 +1,86 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class HydrationReminderIntervalCommand extends BaseCommand { + private byte op; + private byte hydrationReminderInterval; + + private boolean setSuccess; + + public byte getOp() { + return op; + } + + public void setOp(byte op) { + if (op != OP_GET && op != OP_SET) + throw new IllegalArgumentException("Operation must be get or set"); + this.op = op; + } + + public byte getHydrationReminderInterval() { + return hydrationReminderInterval; + } + + public void setHydrationReminderInterval(byte hydrationReminderInterval) { + if (hydrationReminderInterval == 0) + throw new IllegalArgumentException("Interval must be non-zero"); + this.hydrationReminderInterval = hydrationReminderInterval; + } + + public boolean isSetSuccess() { + return setSuccess; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateId(id, LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL); + + if (params.limit() < 1) + throwUnexpectedLength(); + + op = params.get(); + if (op == OP_GET) { + if (params.limit() != 2) + throwUnexpectedLength(); + + hydrationReminderInterval = params.get(); + } else if (op == OP_SET) { + if (params.limit() != 2) + throwUnexpectedLength(); + + setSuccess = params.get() == 1; + } else { + throw new IllegalArgumentException("Invalid operation type received"); + } + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(op); + if (op == OP_SET) { + params.put(hydrationReminderInterval); + } + return LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java new file mode 100644 index 000000000..6335c8f40 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java @@ -0,0 +1,121 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class NotificationCommand extends BaseCommand { + public static final byte SERVICE_TYPE_CALL = 0; + public static final byte SERVICE_TYPE_TEXT = 1; + public static final byte SERVICE_TYPE_QQ = 2; + public static final byte SERVICE_TYPE_WECHAT = 3; + public static final byte SERVICE_TYPE_EXTENDED = 4; + + public static final byte EXTENDED_SERVICE_TYPE_FACEBOOK = 1; + public static final byte EXTENDED_SERVICE_TYPE_TWITTER = 2; + public static final byte EXTENDED_SERVICE_TYPE_LINKEDIN = 3; + public static final byte EXTENDED_SERVICE_TYPE_WHATSAPP = 4; + public static final byte EXTENDED_SERVICE_TYPE_LINE = 5; + public static final byte EXTENDED_SERVICE_TYPE_KAKAOTALK = 6; + + private byte serviceType; + private byte totalPieces; + private byte currentPiece; + private byte extendedServiceType; + private byte[] payload; + + public int getServiceType() { + return getLowestSetBitIndex(serviceType); + } + + public void setServiceType(int type) { + if (type < 0 || type > 4) + throw new IllegalArgumentException("Invalid service type"); + this.serviceType = (byte)(1 << type); + } + + public byte getTotalPieces() { + return totalPieces; + } + + public void setTotalPieces(byte totalPieces) { + // This check isn't on device, but should probably be added + if (totalPieces == 0) + throw new IllegalArgumentException("Total pieces must not be 0"); + this.totalPieces = totalPieces; + } + + public byte getCurrentPiece() { + return currentPiece; + } + + public void setCurrentPiece(byte currentPiece) { + // This check isn't on device, but should probably be added + if (currentPiece == 0) + throw new IllegalArgumentException("Current piece must not be 0"); + this.currentPiece = currentPiece; + } + + public byte getExtendedServiceType() { + return extendedServiceType; + } + + public void setExtendedServiceType(byte extendedServiceType) { + this.extendedServiceType = extendedServiceType; + } + + public byte[] getPayload() { + return payload; + } + + public void setPayload(byte[] payload) { + if (payload == null) + throw new IllegalArgumentException("Payload must not be null"); + if (payload.length > 13) + throw new IllegalArgumentException("Payload is too long"); + this.payload = payload; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + // We should not receive a response for this + throw new UnsupportedOperationException(); + } + + @Override + protected byte serializeParams(ByteBuffer params) { + boolean hasExtendedServiceType = (serviceType & (1 << SERVICE_TYPE_EXTENDED)) != 0 + && (extendedServiceType & 0x0f) != 0 ; + int maxPayloadLength = hasExtendedServiceType ? 12 : 13; + + if (payload.length > maxPayloadLength) + throw new IllegalStateException("Payload is too long"); + + params.put(serviceType); + params.put(totalPieces); + params.put(currentPiece); + if (hasExtendedServiceType) + params.put(extendedServiceType); + params.put(payload); + + return LefunConstants.CMD_NOTIFICATION; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java new file mode 100644 index 000000000..0a55a54a8 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java @@ -0,0 +1,72 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class PpgResultCommand extends BaseCommand { + private byte ppgType; + private byte[] ppgData; + + public int getPpgType() { + return getLowestSetBitIndex(ppgType); + } + + public byte[] getPpgData() { + return ppgData; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateId(id, LefunConstants.CMD_PPG_RESULT); + + if (params.limit() < 1) + throwUnexpectedLength(); + + ppgType = params.get(); + + int typeIndex = getPpgType(); + int dataLength; + switch (typeIndex) { + case LefunConstants.PPG_TYPE_HEART_RATE: + case LefunConstants.PPG_TYPE_BLOOD_OXYGEN: + dataLength = 1; + break; + case LefunConstants.PPG_TYPE_BLOOD_PRESSURE: + dataLength = 2; + break; + default: + throw new IllegalArgumentException("Unknown PPG type"); + } + + if (params.limit() != dataLength + 1) + throwUnexpectedLength(); + + ppgData = new byte[dataLength]; + params.get(ppgData); + } + + @Override + protected byte serializeParams(ByteBuffer params) { + // No handler on device side + throw new UnsupportedOperationException(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java new file mode 100644 index 000000000..e4b806108 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java @@ -0,0 +1,130 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class ProfileCommand extends BaseCommand { + public static final byte GENDER_FEMALE = 0; + public static final byte GENDER_MALE = 1; + + private byte op; + private byte gender; + private byte height; // cm + private byte weight; // kg + private byte age; // years + + private boolean setSuccess; + + public byte getOp() { + return op; + } + + public void setOp(byte op) { + if (op != OP_GET && op != OP_SET) + throw new IllegalArgumentException("Operation must be get or set"); + this.op = op; + } + + public byte getGender() { + return gender; + } + + public void setGender(byte gender) { + if (gender != GENDER_FEMALE && gender != GENDER_MALE) + throw new IllegalArgumentException("Invalid gender"); + this.gender = gender; + } + + public byte getHeight() { + return height; + } + + public void setHeight(byte height) { + int intHeight = (int)height & 0xff; + if (intHeight < 40 || intHeight > 210) + throw new IllegalArgumentException("Height must be between 40 and 210 cm inclusive"); + this.height = height; + } + + public byte getWeight() { + return weight; + } + + public void setWeight(byte weight) { + int intWeight = (int)weight & 0xff; + if (intWeight < 5 || intWeight > 200) + throw new IllegalArgumentException("Weight must be between 5 and 200 kg inclusive"); + this.weight = weight; + } + + public byte getAge() { + return age; + } + + public void setAge(byte age) { + if (age < 0 || age > 110) + throw new IllegalArgumentException("Age must be between 0 and 110 years inclusive"); + this.age = age; + } + + public boolean isSetSuccess() { + return setSuccess; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateId(id, LefunConstants.CMD_PROFILE); + + if (params.limit() < 1) + throwUnexpectedLength(); + + op = params.get(); + if (op == OP_GET) { + if (params.limit() != 5) + throwUnexpectedLength(); + + gender = params.get(); + height = params.get(); + weight = params.get(); + age = params.get(); + } else if (op == OP_SET) { + if (params.limit() != 2) + throwUnexpectedLength(); + + setSuccess = params.get() == 1; + } else { + throw new IllegalArgumentException("Invalid operation type received"); + } + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(op); + if (op == OP_SET) { + params.put(gender); + params.put(height); + params.put(weight); + params.put(age); + } + return LefunConstants.CMD_PROFILE; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RemoteCameraTriggeredCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RemoteCameraTriggeredCommand.java new file mode 100644 index 000000000..f385e3fb1 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RemoteCameraTriggeredCommand.java @@ -0,0 +1,36 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class RemoteCameraTriggeredCommand extends BaseCommand { + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateIdAndLength(id, params, LefunConstants.CMD_REMOTE_CAMERA_TRIGGERED, 0); + } + + @Override + protected byte serializeParams(ByteBuffer params) { + // No handler on device side + throw new UnsupportedOperationException(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RequestBondingCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RequestBondingCommand.java new file mode 100644 index 000000000..b4b925538 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/RequestBondingCommand.java @@ -0,0 +1,46 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class RequestBondingCommand extends BaseCommand { + public static final byte STATUS_ALREADY_BONDED = 0; + public static final byte STATUS_BONDING_SUCCESSFUL = 1; + + private byte status; + + public byte getStatus() { + return status; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateIdAndLength(id, params, LefunConstants.CMD_BONDING_REQUEST, 1); + + status = params.get(); + } + + @Override + protected byte serializeParams(ByteBuffer params) { + return LefunConstants.CMD_BONDING_REQUEST; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java new file mode 100644 index 000000000..fa116134d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java @@ -0,0 +1,86 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class SedentaryReminderIntervalCommand extends BaseCommand { + private byte op; + private byte sedentaryReminderInterval; + + private boolean setSuccess; + + public byte getOp() { + return op; + } + + public void setOp(byte op) { + if (op != OP_GET && op != OP_SET) + throw new IllegalArgumentException("Operation must be get or set"); + this.op = op; + } + + public byte getSedentaryReminderInterval() { + return sedentaryReminderInterval; + } + + public void setSedentaryReminderInterval(byte sedentaryReminderInterval) { + if (sedentaryReminderInterval == 0) + throw new IllegalArgumentException("Interval must be non-zero"); + this.sedentaryReminderInterval = sedentaryReminderInterval; + } + + public boolean isSetSuccess() { + return setSuccess; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateId(id, LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL); + + if (params.limit() < 1) + throwUnexpectedLength(); + + op = params.get(); + if (op == OP_GET) { + if (params.limit() != 2) + throwUnexpectedLength(); + + sedentaryReminderInterval = params.get(); + } else if (op == OP_SET) { + if (params.limit() != 2) + throwUnexpectedLength(); + + setSuccess = params.get() == 1; + } else { + throw new IllegalArgumentException("Invalid operation type received"); + } + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(op); + if (op == OP_SET) { + params.put(sedentaryReminderInterval); + } + return LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetLanguageCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetLanguageCommand.java new file mode 100644 index 000000000..63455fac2 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetLanguageCommand.java @@ -0,0 +1,54 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + 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 . */ +public class SetLanguageCommand extends BaseCommand { + private byte language; + + private boolean setSuccess; + + public byte getLanguage() { + return language; + } + + public void setLanguage(byte language) { + this.language = language; + } + + public boolean isSetSuccess() { + return setSuccess; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateIdAndLength(id, params, LefunConstants.CMD_LANGUAGE, 1); + + setSuccess = params.get() == 1; + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(language); + return LefunConstants.CMD_LANGUAGE; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetRemoteCameraCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetRemoteCameraCommand.java new file mode 100644 index 000000000..c4dc1ae73 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetRemoteCameraCommand.java @@ -0,0 +1,54 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class SetRemoteCameraCommand extends BaseCommand { + private boolean remoteCameraEnabled; + + private boolean setSuccess; + + public boolean getRemoteCameraEnabled() { + return remoteCameraEnabled; + } + + public void setRemoteCameraEnabled(boolean remoteCameraEnabled) { + this.remoteCameraEnabled = remoteCameraEnabled; + } + + public boolean isSetSuccess() { + return setSuccess; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateIdAndLength(id, params, LefunConstants.CMD_REMOTE_CAMERA, 1); + + setSuccess = params.get() == 1; + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put((byte)(remoteCameraEnabled ? 1 : 0)); + return LefunConstants.CMD_REMOTE_CAMERA; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java new file mode 100644 index 000000000..1c76a8711 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java @@ -0,0 +1,117 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class SettingsCommand extends BaseCommand { + public static final byte AM_PM_24_HOUR = 0; + public static final byte AM_PM_12_HOUR = 1; + public static final byte MEASUREMENT_UNIT_METRIC = 0; + public static final byte MEASUREMENT_UNIT_IMPERIAL = 1; + + private byte op; + private byte option1; + private byte amPmIndicator; + private byte measurementUnit; + + private boolean setSuccess; + + public byte getOp() { + return op; + } + + public void setOp(byte op) { + if (op != OP_GET && op != OP_SET) + throw new IllegalArgumentException("Operation must be get or set"); + this.op = op; + } + + public byte getOption1() { + return option1; + } + + public void setOption1(byte option1) { + if (option1 < 0 || option1 > 24) + throw new IllegalArgumentException("option1 must be between 0 and 24 inclusive"); + this.option1 = option1; + } + + public byte getAmPmIndicator() { + return amPmIndicator; + } + + public void setAmPmIndicator(byte amPmIndicator) { + if (amPmIndicator != AM_PM_12_HOUR && amPmIndicator != AM_PM_24_HOUR) + throw new IllegalArgumentException("Indicator must be 12 or 24 hours"); + this.amPmIndicator = amPmIndicator; + } + + public byte getMeasurementUnit() { + return measurementUnit; + } + + public void setMeasurementUnit(byte measurementUnit) { + if (measurementUnit != MEASUREMENT_UNIT_METRIC && measurementUnit != MEASUREMENT_UNIT_IMPERIAL) + throw new IllegalArgumentException(("Unit must be metric or imperial")); + this.measurementUnit = measurementUnit; + } + + public boolean isSetSuccess() { + return setSuccess; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateId(id, LefunConstants.CMD_SETTINGS); + + if (params.limit() < 1) + throwUnexpectedLength(); + + op = params.get(); + if (op == OP_GET) { + if (params.limit() != 4) + throwUnexpectedLength(); + + option1 = params.get(); + amPmIndicator = params.get(); + measurementUnit = params.get(); + } else if (op == OP_SET) { + if (params.limit() != 2) + throwUnexpectedLength(); + + setSuccess = params.get() == 1; + } else { + throw new IllegalArgumentException("Invalid operation type received"); + } + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(op); + if (op == OP_SET) { + params.put(option1); + params.put(amPmIndicator); + params.put(measurementUnit); + } + return LefunConstants.CMD_SETTINGS; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/StartPpgSensingCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/StartPpgSensingCommand.java new file mode 100644 index 000000000..f03403251 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/StartPpgSensingCommand.java @@ -0,0 +1,57 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class StartPpgSensingCommand extends BaseCommand { + private byte ppgType; + + private boolean setSuccess; + + public int getPpgType() { + return getLowestSetBitIndex(ppgType); + } + + public void setPpgType(int type) { + if (type < 0 || type > 2) + throw new IllegalArgumentException("Invalid PPG type"); + this.ppgType = (byte)(1 << type); + } + + public boolean isSetSuccess() { + return setSuccess; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateIdAndLength(id, params, LefunConstants.CMD_PPG_START, 2); + + ppgType = params.get(); + setSuccess = params.get() == 1; + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(ppgType); + return LefunConstants.CMD_PPG_START; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java new file mode 100644 index 000000000..f25a6c27f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java @@ -0,0 +1,149 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class TimeCommand extends BaseCommand { + private byte op; + private byte year; + private byte month; + private byte day; + private byte hour; + private byte minute; + private byte second; + + private boolean setSuccess; + + public byte getOp() { + if (op != OP_GET && op != OP_SET) + throw new IllegalArgumentException("Operation must be get or set"); + return op; + } + + public void setOp(byte op) { + this.op = op; + } + + public byte getYear() { + return year; + } + + public void setYear(byte year) { + this.year = year; + } + + public byte getMonth() { + return month; + } + + public void setMonth(byte month) { + if (month < 1 || month > 12) + throw new IllegalArgumentException("Month must be between 1 and 12 inclusive"); + this.month = month; + } + + public byte getDay() { + return day; + } + + public void setDay(byte day) { + if (day < 1 || day > 31) + throw new IllegalArgumentException("Day must be between 1 and 31 inclusive"); + this.day = day; + } + + public byte getHour() { + return hour; + } + + public void setHour(byte hour) { + if (hour < 0 || hour > 23) + throw new IllegalArgumentException("Hour must be between 0 and 23 inclusive"); + this.hour = hour; + } + + public byte getMinute() { + return minute; + } + + public void setMinute(byte minute) { + if (minute < 0 || minute > 59) + throw new IllegalArgumentException("Minute must be between 0 and 59 inclusive"); + this.minute = minute; + } + + public byte getSecond() { + return second; + } + + public void setSecond(byte second) { + if (second < 0 || second > 59) + throw new IllegalArgumentException("Second must be between 0 and 59 inclusive"); + this.second = second; + } + + public boolean isSetSuccess() { + return setSuccess; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateId(id, LefunConstants.CMD_TIME); + + if (params.limit() < 1) + throwUnexpectedLength(); + + op = params.get(); + if (op == OP_GET) { + if (params.limit() != 7) + throwUnexpectedLength(); + + year = params.get(); + month = params.get(); + day = params.get(); + hour = params.get(); + minute = params.get(); + second = params.get(); + } else if (op == OP_SET) { + if (params.limit() != 2) + throwUnexpectedLength(); + + setSuccess = params.get() == 1; + } else { + throw new IllegalArgumentException("Invalid operation type received"); + } + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(op); + if (op == OP_SET) { + params.put(year); + params.put(month); + params.put(day); + params.put(hour); + params.put(minute); + params.put(second); + } + return LefunConstants.CMD_TIME; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java new file mode 100644 index 000000000..77deae3cc --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java @@ -0,0 +1,91 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + +public class UiPagesCommand extends BaseCommand { + // I don't know which pages since they're not implemented in my watch, so no + // constants here for now + + private byte op; + private short pages; + + private boolean setSuccess; + + public byte getOp() { + return op; + } + + public void setOp(byte op) { + if (op != OP_GET && op != OP_SET) + throw new IllegalArgumentException("Operation must be get or set"); + this.op = op; + } + + public boolean getPage(int index) { + if (index < 0 || index >= 16) + throw new IllegalArgumentException("Index must be between 0 and 15 inclusive"); + return getBit(pages, 1 << index); + } + + public void setPage(int index, boolean enabled) { + if (index < 0 || index >= 16) + throw new IllegalArgumentException("Index must be between 0 and 15 inclusive"); + pages = setBit(pages, 1 << index, enabled); + } + + public boolean isSetSuccess() { + return setSuccess; + } + + @Override + protected void deserializeParams(byte id, ByteBuffer params) { + validateId(id, LefunConstants.CMD_UI_PAGES); + + if (params.limit() < 1) + throwUnexpectedLength(); + + op = params.get(); + if (op == OP_GET) { + if (params.limit() != 3) + throwUnexpectedLength(); + + pages = params.getShort(); + } else if (op == OP_SET) { + if (params.limit() != 2) + throwUnexpectedLength(); + + setSuccess = params.get() == 1; + } else { + throw new IllegalArgumentException("Invalid operation type received"); + } + } + + @Override + protected byte serializeParams(ByteBuffer params) { + params.put(op); + if (op == OP_SET) { + params.putShort(pages); + } + return LefunConstants.CMD_UI_PAGES; + } +} From c7edfc0b54ab17fdbeb5482d4fbaa4d3385f1524 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Sun, 4 Oct 2020 20:58:06 -0600 Subject: [PATCH 13/86] Lefun: Fix response ID --- .../gadgetbridge/devices/lefun/LefunConstants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java index fb36779fa..c184fc260 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java @@ -33,7 +33,7 @@ public class LefunConstants { // Commands public static final byte CMD_REQUEST_ID = (byte)0xab; - public static final byte CMD_RESPONSE_ID = 0x55; + public static final byte CMD_RESPONSE_ID = 0x5a; public static final int CMD_MAX_LENGTH = 20; // 3 header bytes plus checksum From 8662041e5a76fc3f0b7a4022eb3e40840eef82ff Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Sun, 4 Oct 2020 20:59:24 -0600 Subject: [PATCH 14/86] Lefun: Fix checksum generation --- .../gadgetbridge/devices/lefun/commands/BaseCommand.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java index 7a8ac77d3..486dc0b25 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java @@ -32,12 +32,12 @@ public abstract class BaseCommand { abstract protected byte serializeParams(ByteBuffer params); public void deserialize(byte[] response) { - if (response.length < LefunConstants.CMD_HEADER_LENGTH) + if (response.length < LefunConstants.CMD_HEADER_LENGTH || response.length < response[1]) throw new IllegalArgumentException("Response is too short"); - if (calculateChecksum(response, 0, response.length - 1) != response[response.length - 1]) + if (calculateChecksum(response, 0, response[1] - 1) != response[response[1] - 1]) throw new IllegalArgumentException("Incorrect message checksum"); ByteBuffer buffer = ByteBuffer.wrap(response, LefunConstants.CMD_HEADER_LENGTH - 1, - response.length - LefunConstants.CMD_HEADER_LENGTH); + response[1] - LefunConstants.CMD_HEADER_LENGTH); buffer.order(ByteOrder.BIG_ENDIAN); deserializeParams(response[2], buffer); } From b46960e0a7328b4df2e0ddfca77921ae2417053a Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Sun, 4 Oct 2020 20:59:55 -0600 Subject: [PATCH 15/86] Lefun: Fix response parsing --- .../devices/lefun/commands/AlarmCommand.java | 6 +++--- .../devices/lefun/commands/BaseCommand.java | 10 +++++----- .../devices/lefun/commands/Cmd22Command.java | 6 +++--- .../devices/lefun/commands/Cmd25Command.java | 6 +++--- .../devices/lefun/commands/FeaturesCommand.java | 6 +++--- .../devices/lefun/commands/GetPpgDataCommand.java | 8 ++++---- .../commands/HydrationReminderIntervalCommand.java | 6 +++--- .../devices/lefun/commands/PpgResultCommand.java | 4 ++-- .../devices/lefun/commands/ProfileCommand.java | 6 +++--- .../commands/SedentaryReminderIntervalCommand.java | 6 +++--- .../devices/lefun/commands/SettingsCommand.java | 6 +++--- .../devices/lefun/commands/TimeCommand.java | 6 +++--- .../devices/lefun/commands/UiPagesCommand.java | 6 +++--- 13 files changed, 41 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java index a351920b4..b443eb6fb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java @@ -131,13 +131,13 @@ public class AlarmCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_ALARM); - if (params.limit() < 2) + if (params.limit() - params.position() < 2) throwUnexpectedLength(); op = params.get(); index = params.get(); if (op == OP_GET) { - if (params.limit() != 8) + if (params.limit() - params.position() != 8) throwUnexpectedLength(); enabled = params.get() == 1; @@ -147,7 +147,7 @@ public class AlarmCommand extends BaseCommand { hour = params.get(); minute = params.get(); } else if (op == OP_SET) { - if (params.limit() != 3) + if (params.limit() - params.position() != 3) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java index 486dc0b25..2a90fbcb2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/BaseCommand.java @@ -49,20 +49,20 @@ public abstract class BaseCommand { return makeCommand(id, buffer); } - static protected byte calculateChecksum(byte[] data, int offset, int length) { - byte checksum = 0; + public static byte calculateChecksum(byte[] data, int offset, int length) { + int checksum = 0; for (int i = offset; i < offset + length; ++i) { byte b = data[i]; for (int j = 0; j < 8; ++j) { if (((b ^ checksum) & 1) == 0) { checksum >>= 1; } else { - checksum = (byte) ((checksum ^ 0x18) >> 1 | 0x80); + checksum = (checksum ^ 0x18) >> 1 | 0x80; } b >>= 1; } } - return checksum; + return (byte)checksum; } /** @@ -98,7 +98,7 @@ public abstract class BaseCommand { protected void validateIdAndLength(byte id, ByteBuffer params, byte expectedId, int expectedLength) { validateId(id, expectedId); - if (params.limit() != expectedLength) + if (params.limit() - params.position() != expectedLength) throwUnexpectedLength(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java index c40a92fc3..2cd78b5a3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java @@ -54,17 +54,17 @@ public class Cmd22Command extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_UNKNOWN_22); - if (params.limit() < 1) + if (params.limit() - params.position() < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() != 3) + if (params.limit() - params.position() != 3) throwUnexpectedLength(); unknown = params.getShort(); } else if (op == OP_SET) { - if (params.limit() != 2) + if (params.limit() - params.position() != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java index 9bc49813d..e90f5a410 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java @@ -56,17 +56,17 @@ public class Cmd25Command extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_UNKNOWN_25); - if (params.limit() < 1) + if (params.limit() - params.position() < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() != 5) + if (params.limit() - params.position() != 5) throwUnexpectedLength(); unknown = params.getInt(); } else if (op == OP_SET) { - if (params.limit() != 2) + if (params.limit() - params.position() != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java index b0006d4ed..22c5fba2c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java @@ -65,17 +65,17 @@ public class FeaturesCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_FEATURES); - if (params.limit() < 1) + if (params.limit() - params.position() < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() != 3) + if (params.limit() - params.position() != 3) throwUnexpectedLength(); features = params.getShort(); } else if (op == OP_SET) { - if (params.limit() != 2) + if (params.limit() - params.position() != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java index 25a812944..d17c4b0c6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java @@ -66,7 +66,7 @@ public class GetPpgDataCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_PPG_DATA); - if (params.limit() < 9) + if (params.limit() - params.position() < 9) throwUnexpectedLength(); ppgType = params.get(); @@ -93,19 +93,19 @@ public class GetPpgDataCommand extends BaseCommand { throw new IllegalArgumentException("Unknown PPG type"); } - if (params.limit() < dataLength + 9) + if (params.limit() - params.position() < dataLength + 9) throwUnexpectedLength(); ppgData = new byte[dataLength]; params.get(ppgData); // Extended count/index - if (params.limit() == dataLength + 11) + if (params.limit() - params.position() == dataLength + 11) { totalRecords |= params.get() << 8; currentRecord |= params.get() << 8; } - else if (params.limit() > dataLength + 11) { + else if (params.limit() - params.position() > dataLength + 11) { throwUnexpectedLength(); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java index e92fa8aee..801d90fb2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java @@ -56,17 +56,17 @@ public class HydrationReminderIntervalCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL); - if (params.limit() < 1) + if (params.limit() - params.position() < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() != 2) + if (params.limit() - params.position() != 2) throwUnexpectedLength(); hydrationReminderInterval = params.get(); } else if (op == OP_SET) { - if (params.limit() != 2) + if (params.limit() - params.position() != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java index 0a55a54a8..c4d89e7f5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java @@ -38,7 +38,7 @@ public class PpgResultCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_PPG_RESULT); - if (params.limit() < 1) + if (params.limit() - params.position() < 1) throwUnexpectedLength(); ppgType = params.get(); @@ -57,7 +57,7 @@ public class PpgResultCommand extends BaseCommand { throw new IllegalArgumentException("Unknown PPG type"); } - if (params.limit() != dataLength + 1) + if (params.limit() - params.position() != dataLength + 1) throwUnexpectedLength(); ppgData = new byte[dataLength]; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java index e4b806108..dc8910c89 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java @@ -94,12 +94,12 @@ public class ProfileCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_PROFILE); - if (params.limit() < 1) + if (params.limit() - params.position() < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() != 5) + if (params.limit() - params.position() != 5) throwUnexpectedLength(); gender = params.get(); @@ -107,7 +107,7 @@ public class ProfileCommand extends BaseCommand { weight = params.get(); age = params.get(); } else if (op == OP_SET) { - if (params.limit() != 2) + if (params.limit() - params.position() != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java index fa116134d..fcf43a255 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java @@ -56,17 +56,17 @@ public class SedentaryReminderIntervalCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL); - if (params.limit() < 1) + if (params.limit() - params.position() < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() != 2) + if (params.limit() - params.position() != 2) throwUnexpectedLength(); sedentaryReminderInterval = params.get(); } else if (op == OP_SET) { - if (params.limit() != 2) + if (params.limit() - params.position() != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java index 1c76a8711..f0828a7d9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java @@ -83,19 +83,19 @@ public class SettingsCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_SETTINGS); - if (params.limit() < 1) + if (params.limit() - params.position() < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() != 4) + if (params.limit() - params.position() != 4) throwUnexpectedLength(); option1 = params.get(); amPmIndicator = params.get(); measurementUnit = params.get(); } else if (op == OP_SET) { - if (params.limit() != 2) + if (params.limit() - params.position() != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java index f25a6c27f..7c35a22ad 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java @@ -109,12 +109,12 @@ public class TimeCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_TIME); - if (params.limit() < 1) + if (params.limit() - params.position() < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() != 7) + if (params.limit() - params.position() != 7) throwUnexpectedLength(); year = params.get(); @@ -124,7 +124,7 @@ public class TimeCommand extends BaseCommand { minute = params.get(); second = params.get(); } else if (op == OP_SET) { - if (params.limit() != 2) + if (params.limit() - params.position() != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java index 77deae3cc..3c2e595c5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java @@ -61,17 +61,17 @@ public class UiPagesCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_UI_PAGES); - if (params.limit() < 1) + if (params.limit() - params.position() < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() != 3) + if (params.limit() - params.position() != 3) throwUnexpectedLength(); pages = params.getShort(); } else if (op == OP_SET) { - if (params.limit() != 2) + if (params.limit() - params.position() != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; From 9454684974bf4add544351adf33d41d7d7f02fdf Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Sun, 4 Oct 2020 21:00:56 -0600 Subject: [PATCH 16/86] Lefun: Add device support and initialization code --- .../devices/lefun/LefunConstants.java | 2 + .../service/DeviceSupportFactory.java | 4 + .../devices/lefun/LefunDeviceSupport.java | 297 ++++++++++++++++++ .../requests/GetBatteryLevelRequest.java | 52 +++ .../requests/GetFirmwareInfoRequest.java | 57 ++++ .../devices/lefun/requests/Request.java | 74 +++++ .../lefun/requests/SetTimeRequest.java | 63 ++++ 7 files changed, 549 insertions(+) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetTimeRequest.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java index c184fc260..a4b972634 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java @@ -25,6 +25,8 @@ import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDevi public class LefunConstants { // BLE UUIDs public static final UUID UUID_SERVICE_LEFUN = UUID.fromString(String.format(BASE_UUID, "18D0")); + public static final UUID UUID_CHARACTERISTIC_LEFUN_WRITE = UUID.fromString(String.format(BASE_UUID, "2D01")); + public static final UUID UUID_CHARACTERISTIC_LEFUN_NOTIFY = UUID.fromString(String.format(BASE_UUID, "2D00")); // Coordinator constants public static final String ADVERTISEMENT_NAME = "Lefun"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java index e158a94a6..44b1b3ef4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java @@ -51,6 +51,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.id115.ID115Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.itag.ITagSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.BFH16DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.TeclastH30.TeclastH30Support; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.liveview.LiveviewSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.makibeshr3.MakibesHR3DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport; @@ -260,6 +261,9 @@ public class DeviceSupportFactory { case SG2: deviceSupport = new ServiceDeviceSupport(new HPlusSupport(DeviceType.SG2), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); break; + case LEFUN: + deviceSupport = new ServiceDeviceSupport(new LefunDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); + break; } if (deviceSupport != null) { deviceSupport.setContext(gbDevice, mBtAdapter, mContext); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java new file mode 100644 index 000000000..27292b93d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java @@ -0,0 +1,297 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.net.Uri; +import android.widget.Toast; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; +import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetBatteryLevelRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetFirmwareInfoRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.Request; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetTimeRequest; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { + private static final Logger LOG = LoggerFactory.getLogger(LefunDeviceSupport.class); + + private final List inProgressRequests = Collections.synchronizedList(new ArrayList()); + + public LefunDeviceSupport() { + super(LOG); + addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS); + addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE); + addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION); + addSupportedService(LefunConstants.UUID_SERVICE_LEFUN); + } + + @Override + protected TransactionBuilder initializeDevice(TransactionBuilder builder) { + builder.setGattCallback(this); + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); + + // Enable notification + builder.notify(getCharacteristic(LefunConstants.UUID_CHARACTERISTIC_LEFUN_NOTIFY), true); + + // Init device (just get version and battery) + try { + GetFirmwareInfoRequest firmwareReq = new GetFirmwareInfoRequest(this, builder); + firmwareReq.perform(); + inProgressRequests.add(firmwareReq); + + SetTimeRequest timeReq = new SetTimeRequest(this, builder); + timeReq.perform(); + inProgressRequests.add(timeReq); + + GetBatteryLevelRequest batReq = new GetBatteryLevelRequest(this, builder); + batReq.perform(); + inProgressRequests.add(batReq); + } catch (IOException e) { + GB.toast(getContext(), "Failed to initialize Lefun device", Toast.LENGTH_SHORT, + GB.ERROR, e); + } + + return builder; + } + + @Override + public boolean useAutoConnect() { + return true; + } + + @Override + public void onNotification(NotificationSpec notificationSpec) { + + } + + @Override + public void onDeleteNotification(int id) { + + } + + @Override + public void onSetTime() { + + } + + @Override + public void onSetAlarms(ArrayList alarms) { + + } + + @Override + public void onSetCallState(CallSpec callSpec) { + + } + + @Override + public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) { + + } + + @Override + public void onSetMusicState(MusicStateSpec stateSpec) { + + } + + @Override + public void onSetMusicInfo(MusicSpec musicSpec) { + + } + + @Override + public void onEnableRealtimeSteps(boolean enable) { + + } + + @Override + public void onInstallApp(Uri uri) { + + } + + @Override + public void onAppInfoReq() { + + } + + @Override + public void onAppStart(UUID uuid, boolean start) { + + } + + @Override + public void onAppDelete(UUID uuid) { + + } + + @Override + public void onAppConfiguration(UUID appUuid, String config, Integer id) { + + } + + @Override + public void onAppReorder(UUID[] uuids) { + + } + + @Override + public void onFetchRecordedData(int dataTypes) { + + } + + @Override + public void onReset(int flags) { + + } + + @Override + public void onHeartRateTest() { + + } + + @Override + public void onEnableRealtimeHeartRateMeasurement(boolean enable) { + + } + + @Override + public void onFindDevice(boolean start) { + + } + + @Override + public void onSetConstantVibration(int integer) { + + } + + @Override + public void onScreenshotReq() { + + } + + @Override + public void onEnableHeartRateSleepSupport(boolean enable) { + + } + + @Override + public void onSetHeartRateMeasurementInterval(int seconds) { + + } + + @Override + public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) { + + } + + @Override + public void onDeleteCalendarEvent(byte type, long id) { + + } + + @Override + public void onSendConfiguration(String config) { + + } + + @Override + public void onReadConfiguration(String config) { + + } + + @Override + public void onTestNewFunction() { + + } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + + } + + @Override + public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + if (characteristic.getUuid().equals(LefunConstants.UUID_CHARACTERISTIC_LEFUN_NOTIFY)) { + byte[] data = characteristic.getValue(); + // Parse response + if (data.length >= LefunConstants.CMD_HEADER_LENGTH && data[0] == LefunConstants.CMD_RESPONSE_ID) { + // Note: full validation is done within the request + byte commandId = data[2]; + synchronized (inProgressRequests) { + for (Request req : inProgressRequests) { + if (req.expectsResponse() && req.getCommandId() == commandId) { + try { + req.handleResponse(data); + inProgressRequests.remove(req); + return true; + } catch (IllegalArgumentException e) { + LOG.error("Failed to handle response", e); + } + } + } + } + + if (handleAsynchronousResponse(data)) + return true; + + LOG.error(String.format("No handler for response 0x%02x", commandId)); + return false; + } + + LOG.error("Invalid response received"); + return false; + } + + return super.onCharacteristicChanged(gatt, characteristic); + } + + private boolean handleAsynchronousResponse(byte[] data) { + // Assume data already checked for correct response code and length + return false; + } + + public void completeInitialization() { + gbDevice.setState(GBDevice.State.INITIALIZED); + gbDevice.sendDeviceUpdateIntent(getContext()); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java new file mode 100644 index 000000000..35964c120 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java @@ -0,0 +1,52 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetBatteryLevelCommand; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; + +public class GetBatteryLevelRequest extends Request { + public GetBatteryLevelRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + @Override + public byte[] createRequest() { + GetBatteryLevelCommand cmd = new GetBatteryLevelCommand(); + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + GetBatteryLevelCommand cmd = new GetBatteryLevelCommand(); + cmd.deserialize(data); + + GBDevice device = getSupport().getDevice(); + device.setBatteryLevel(cmd.getBatteryLevel()); + device.setBatteryThresholdPercent((short)15); + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_BATTERY_LEVEL; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java new file mode 100644 index 000000000..f3c900a12 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java @@ -0,0 +1,57 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetFirmwareInfoCommand; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; + +public class GetFirmwareInfoRequest extends Request { + public GetFirmwareInfoRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + @Override + public byte[] createRequest() { + GetFirmwareInfoCommand cmd = new GetFirmwareInfoCommand(); + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + GetFirmwareInfoCommand cmd = new GetFirmwareInfoCommand(); + cmd.deserialize(data); + + GBDevice device = getSupport().getDevice(); + // Last character is a \x1f? Not printable either way. + device.setModel(cmd.getTypeCode().substring(0, 3)); + int hardwareVersion = cmd.getHardwareVersion() & 0xffff; + int softwareVersion = cmd.getSoftwareVersion() & 0xffff; + device.setFirmwareVersion(String.format("%d.%d", softwareVersion >> 8, softwareVersion & 0xff)); + device.setFirmwareVersion2(String.format("%d.%d", hardwareVersion >> 8, hardwareVersion & 0xff)); + getSupport().completeInitialization(); + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_FIRMWARE_INFO; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java new file mode 100644 index 000000000..6121fd30c --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java @@ -0,0 +1,74 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import android.bluetooth.BluetoothGattCharacteristic; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; + +// Ripped from nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request +public abstract class Request extends AbstractBTLEOperation { + private Logger logger = (Logger) LoggerFactory.getLogger(getName()); + protected TransactionBuilder builder; + + protected Request(LefunDeviceSupport support, TransactionBuilder builder) { + super(support); + this.builder = builder; + } + + @Override + protected void doPerform() throws IOException { + BluetoothGattCharacteristic characteristic = getSupport() + .getCharacteristic(LefunConstants.UUID_CHARACTERISTIC_LEFUN_WRITE); + builder.write(characteristic, createRequest()); + } + + public abstract byte[] createRequest(); + + public void handleResponse(byte[] data) { + } + + public String getName() { + Class thisClass = getClass(); + while (thisClass.isAnonymousClass()) thisClass = thisClass.getSuperclass(); + return thisClass.getSimpleName(); + } + + protected void log(String message) { + logger.debug(message); + } + + public abstract int getCommandId(); + + public boolean expectsResponse() { + return true; + } + + protected void reportFailure(String message) { + // TODO: Toast here or something + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetTimeRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetTimeRequest.java new file mode 100644 index 000000000..ac6f47940 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetTimeRequest.java @@ -0,0 +1,63 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import java.util.Calendar; +import java.util.TimeZone; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.TimeCommand; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; + +public class SetTimeRequest extends Request { + public SetTimeRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + @Override + public byte[] createRequest() { + TimeCommand cmd = new TimeCommand(); + Calendar c = Calendar.getInstance(); + + cmd.setOp(BaseCommand.OP_SET); + cmd.setYear((byte)(c.get(Calendar.YEAR) - 2000)); + cmd.setMonth((byte)c.get(Calendar.MONTH)); + cmd.setDay((byte)c.get(Calendar.DAY_OF_MONTH)); + cmd.setHour((byte)c.get(Calendar.HOUR_OF_DAY)); + cmd.setMinute((byte)c.get(Calendar.MINUTE)); + cmd.setSecond((byte)c.get(Calendar.SECOND)); + + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + TimeCommand cmd = new TimeCommand(); + cmd.deserialize(data); + if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess()) + reportFailure("Could not set time"); + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_TIME; + } +} From 61039f17490b5bd0c89b436d7285892e0467ed5d Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Sun, 4 Oct 2020 21:19:38 -0600 Subject: [PATCH 17/86] Lefun: Add find device support, toast on error --- .../devices/lefun/LefunDeviceSupport.java | 27 ++++++++++++++-- .../lefun/requests/FindDeviceRequest.java | 32 +++++++++++++++++++ .../devices/lefun/requests/Request.java | 7 ++-- 3 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java index 27292b93d..519eaba0f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java @@ -46,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSuppo import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.FindDeviceRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetBatteryLevelRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetFirmwareInfoRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.Request; @@ -73,7 +74,7 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { // Enable notification builder.notify(getCharacteristic(LefunConstants.UUID_CHARACTERISTIC_LEFUN_NOTIFY), true); - // Init device (just get version and battery) + // Init device (get version info, battery level, and set time) try { GetFirmwareInfoRequest firmwareReq = new GetFirmwareInfoRequest(this, builder); firmwareReq.perform(); @@ -111,7 +112,16 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onSetTime() { - + try { + TransactionBuilder builder = createTransactionBuilder(SetTimeRequest.class.getSimpleName()); + SetTimeRequest request = new SetTimeRequest(this, builder); + request.perform(); + inProgressRequests.add(request); + builder.queue(getQueue()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to set time", Toast.LENGTH_SHORT, + GB.ERROR, e); + } } @Override @@ -196,7 +206,18 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onFindDevice(boolean start) { - + if (start) { + try { + TransactionBuilder builder = createTransactionBuilder(FindDeviceRequest.class.getSimpleName()); + FindDeviceRequest request = new FindDeviceRequest(this, builder); + request.perform(); + inProgressRequests.add(request); + builder.queue(getQueue()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to initiate find device", Toast.LENGTH_SHORT, + GB.ERROR, e); + } + } } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java new file mode 100644 index 000000000..1bb172590 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java @@ -0,0 +1,32 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FindDeviceCommand; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; + +public class FindDeviceRequest extends Request { + public FindDeviceRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + @Override + public byte[] createRequest() { + FindDeviceCommand cmd = new FindDeviceCommand(); + return cmd.serialize(); + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_FIND_DEVICE; + } + + @Override + public void handleResponse(byte[] data) { + FindDeviceCommand cmd = new FindDeviceCommand(); + cmd.deserialize(data); + + if (!cmd.isSuccess()) + reportFailure("Could not initiate find device"); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java index 6121fd30c..b82e08dee 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java @@ -19,6 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; import android.bluetooth.BluetoothGattCharacteristic; +import android.widget.Toast; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,11 +30,12 @@ import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.util.GB; // Ripped from nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request public abstract class Request extends AbstractBTLEOperation { - private Logger logger = (Logger) LoggerFactory.getLogger(getName()); protected TransactionBuilder builder; + private Logger logger = (Logger) LoggerFactory.getLogger(getName()); protected Request(LefunDeviceSupport support, TransactionBuilder builder) { super(support); @@ -50,6 +52,7 @@ public abstract class Request extends AbstractBTLEOperation public abstract byte[] createRequest(); public void handleResponse(byte[] data) { + } public String getName() { @@ -69,6 +72,6 @@ public abstract class Request extends AbstractBTLEOperation } protected void reportFailure(String message) { - // TODO: Toast here or something + GB.toast(getContext(), message, Toast.LENGTH_SHORT, GB.ERROR); } } From 4992e4c15b3abc55afa29263eb25c6ec2c3ed563 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Sun, 4 Oct 2020 21:31:37 -0600 Subject: [PATCH 18/86] Lefun: Fix command length checks, again --- .../devices/lefun/commands/AlarmCommand.java | 7 ++++--- .../devices/lefun/commands/Cmd22Command.java | 7 ++++--- .../devices/lefun/commands/Cmd25Command.java | 7 ++++--- .../devices/lefun/commands/FeaturesCommand.java | 7 ++++--- .../devices/lefun/commands/GetPpgDataCommand.java | 9 +++++---- .../lefun/commands/HydrationReminderIntervalCommand.java | 7 ++++--- .../devices/lefun/commands/PpgResultCommand.java | 5 +++-- .../devices/lefun/commands/ProfileCommand.java | 7 ++++--- .../lefun/commands/SedentaryReminderIntervalCommand.java | 7 ++++--- .../devices/lefun/commands/SettingsCommand.java | 7 ++++--- .../gadgetbridge/devices/lefun/commands/TimeCommand.java | 7 ++++--- .../devices/lefun/commands/UiPagesCommand.java | 7 ++++--- 12 files changed, 48 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java index b443eb6fb..fea4806c5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/AlarmCommand.java @@ -131,13 +131,14 @@ public class AlarmCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_ALARM); - if (params.limit() - params.position() < 2) + int paramsLength = params.limit() - params.position(); + if (paramsLength < 2) throwUnexpectedLength(); op = params.get(); index = params.get(); if (op == OP_GET) { - if (params.limit() - params.position() != 8) + if (paramsLength != 8) throwUnexpectedLength(); enabled = params.get() == 1; @@ -147,7 +148,7 @@ public class AlarmCommand extends BaseCommand { hour = params.get(); minute = params.get(); } else if (op == OP_SET) { - if (params.limit() - params.position() != 3) + if (paramsLength != 3) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java index 2cd78b5a3..d95391814 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd22Command.java @@ -54,17 +54,18 @@ public class Cmd22Command extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_UNKNOWN_22); - if (params.limit() - params.position() < 1) + int paramsLength = params.limit() - params.position(); + if (paramsLength < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() - params.position() != 3) + if (paramsLength != 3) throwUnexpectedLength(); unknown = params.getShort(); } else if (op == OP_SET) { - if (params.limit() - params.position() != 2) + if (paramsLength != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java index e90f5a410..9c86d400d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/Cmd25Command.java @@ -56,17 +56,18 @@ public class Cmd25Command extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_UNKNOWN_25); - if (params.limit() - params.position() < 1) + int paramsLength = params.limit() - params.position(); + if (paramsLength < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() - params.position() != 5) + if (paramsLength != 5) throwUnexpectedLength(); unknown = params.getInt(); } else if (op == OP_SET) { - if (params.limit() - params.position() != 2) + if (paramsLength != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java index 22c5fba2c..892d83c58 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FeaturesCommand.java @@ -65,17 +65,18 @@ public class FeaturesCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_FEATURES); - if (params.limit() - params.position() < 1) + int paramsLength = params.limit() - params.position(); + if (paramsLength < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() - params.position() != 3) + if (paramsLength != 3) throwUnexpectedLength(); features = params.getShort(); } else if (op == OP_SET) { - if (params.limit() - params.position() != 2) + if (paramsLength != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java index d17c4b0c6..03b2e4246 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java @@ -66,7 +66,8 @@ public class GetPpgDataCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_PPG_DATA); - if (params.limit() - params.position() < 9) + int paramsLength = params.limit() - params.position(); + if (paramsLength < 9) throwUnexpectedLength(); ppgType = params.get(); @@ -93,19 +94,19 @@ public class GetPpgDataCommand extends BaseCommand { throw new IllegalArgumentException("Unknown PPG type"); } - if (params.limit() - params.position() < dataLength + 9) + if (paramsLength < dataLength + 9) throwUnexpectedLength(); ppgData = new byte[dataLength]; params.get(ppgData); // Extended count/index - if (params.limit() - params.position() == dataLength + 11) + if (paramsLength == dataLength + 11) { totalRecords |= params.get() << 8; currentRecord |= params.get() << 8; } - else if (params.limit() - params.position() > dataLength + 11) { + else if (paramsLength > dataLength + 11) { throwUnexpectedLength(); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java index 801d90fb2..2ec2ea956 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/HydrationReminderIntervalCommand.java @@ -56,17 +56,18 @@ public class HydrationReminderIntervalCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL); - if (params.limit() - params.position() < 1) + int paramsLength = params.limit() - params.position(); + if (paramsLength < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() - params.position() != 2) + if (paramsLength != 2) throwUnexpectedLength(); hydrationReminderInterval = params.get(); } else if (op == OP_SET) { - if (params.limit() - params.position() != 2) + if (paramsLength != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java index c4d89e7f5..1e66ee9d5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/PpgResultCommand.java @@ -38,7 +38,8 @@ public class PpgResultCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_PPG_RESULT); - if (params.limit() - params.position() < 1) + int paramsLength = params.limit() - params.position(); + if (paramsLength < 1) throwUnexpectedLength(); ppgType = params.get(); @@ -57,7 +58,7 @@ public class PpgResultCommand extends BaseCommand { throw new IllegalArgumentException("Unknown PPG type"); } - if (params.limit() - params.position() != dataLength + 1) + if (paramsLength != dataLength + 1) throwUnexpectedLength(); ppgData = new byte[dataLength]; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java index dc8910c89..e95f57d83 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/ProfileCommand.java @@ -94,12 +94,13 @@ public class ProfileCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_PROFILE); - if (params.limit() - params.position() < 1) + int paramsLength = params.limit() - params.position(); + if (paramsLength < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() - params.position() != 5) + if (paramsLength != 5) throwUnexpectedLength(); gender = params.get(); @@ -107,7 +108,7 @@ public class ProfileCommand extends BaseCommand { weight = params.get(); age = params.get(); } else if (op == OP_SET) { - if (params.limit() - params.position() != 2) + if (paramsLength != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java index fcf43a255..d85bd860e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SedentaryReminderIntervalCommand.java @@ -56,17 +56,18 @@ public class SedentaryReminderIntervalCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL); - if (params.limit() - params.position() < 1) + int paramsLength = params.limit() - params.position(); + if (paramsLength < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() - params.position() != 2) + if (paramsLength != 2) throwUnexpectedLength(); sedentaryReminderInterval = params.get(); } else if (op == OP_SET) { - if (params.limit() - params.position() != 2) + if (paramsLength != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java index f0828a7d9..0fb40964c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java @@ -83,19 +83,20 @@ public class SettingsCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_SETTINGS); - if (params.limit() - params.position() < 1) + int paramsLength = params.limit() - params.position(); + if (paramsLength < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() - params.position() != 4) + if (paramsLength != 4) throwUnexpectedLength(); option1 = params.get(); amPmIndicator = params.get(); measurementUnit = params.get(); } else if (op == OP_SET) { - if (params.limit() - params.position() != 2) + if (paramsLength != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java index 7c35a22ad..dd60185f0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/TimeCommand.java @@ -109,12 +109,13 @@ public class TimeCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_TIME); - if (params.limit() - params.position() < 1) + int paramsLength = params.limit() - params.position(); + if (paramsLength < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() - params.position() != 7) + if (paramsLength != 7) throwUnexpectedLength(); year = params.get(); @@ -124,7 +125,7 @@ public class TimeCommand extends BaseCommand { minute = params.get(); second = params.get(); } else if (op == OP_SET) { - if (params.limit() - params.position() != 2) + if (paramsLength != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java index 3c2e595c5..a304eff55 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/UiPagesCommand.java @@ -61,17 +61,18 @@ public class UiPagesCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateId(id, LefunConstants.CMD_UI_PAGES); - if (params.limit() - params.position() < 1) + int paramsLength = params.limit() - params.position(); + if (paramsLength < 1) throwUnexpectedLength(); op = params.get(); if (op == OP_GET) { - if (params.limit() - params.position() != 3) + if (paramsLength != 3) throwUnexpectedLength(); pages = params.getShort(); } else if (op == OP_SET) { - if (params.limit() - params.position() != 2) + if (paramsLength != 2) throwUnexpectedLength(); setSuccess = params.get() == 1; From 9fbfee945c0fba437aebdd2fcdf9379627b71fd5 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Sun, 4 Oct 2020 21:47:27 -0600 Subject: [PATCH 19/86] Lefun: Allow keeping request in queue (for multirecord responses) --- .../service/devices/lefun/LefunDeviceSupport.java | 3 ++- .../gadgetbridge/service/devices/lefun/requests/Request.java | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java index 519eaba0f..cb31b802c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java @@ -283,7 +283,8 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { if (req.expectsResponse() && req.getCommandId() == commandId) { try { req.handleResponse(data); - inProgressRequests.remove(req); + if (req.shouldRemoveAfterHandling()) + inProgressRequests.remove(req); return true; } catch (IllegalArgumentException e) { LOG.error("Failed to handle response", e); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java index b82e08dee..6c3a62bfe 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java @@ -35,6 +35,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB; // Ripped from nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request public abstract class Request extends AbstractBTLEOperation { protected TransactionBuilder builder; + protected boolean removeAfterHandling = true; private Logger logger = (Logger) LoggerFactory.getLogger(getName()); protected Request(LefunDeviceSupport support, TransactionBuilder builder) { @@ -71,6 +72,10 @@ public abstract class Request extends AbstractBTLEOperation return true; } + public boolean shouldRemoveAfterHandling() { + return removeAfterHandling; + } + protected void reportFailure(String message) { GB.toast(getContext(), message, Toast.LENGTH_SHORT, GB.ERROR); } From 1fc23561890998d0b1c9db7f55388c6ebc6bdac8 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Sun, 4 Oct 2020 21:47:48 -0600 Subject: [PATCH 20/86] Lefun: Change queuing --- .../service/devices/lefun/LefunDeviceSupport.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java index cb31b802c..f5ed6ee54 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java @@ -117,7 +117,7 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { SetTimeRequest request = new SetTimeRequest(this, builder); request.perform(); inProgressRequests.add(request); - builder.queue(getQueue()); + performConnected(builder.getTransaction()); } catch (IOException e) { GB.toast(getContext(), "Failed to set time", Toast.LENGTH_SHORT, GB.ERROR, e); @@ -212,7 +212,7 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { FindDeviceRequest request = new FindDeviceRequest(this, builder); request.perform(); inProgressRequests.add(request); - builder.queue(getQueue()); + performConnected(builder.getTransaction()); } catch (IOException e) { GB.toast(getContext(), "Failed to initiate find device", Toast.LENGTH_SHORT, GB.ERROR, e); From 6c32a3a99d8424d1fb00e5da91f0d195ea7d0dfa Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 03:12:30 -0600 Subject: [PATCH 21/86] Lefun: Add operation status to requests --- .../devices/lefun/requests/FindDeviceRequest.java | 3 +++ .../lefun/requests/GetBatteryLevelRequest.java | 3 +++ .../lefun/requests/GetFirmwareInfoRequest.java | 3 +++ .../service/devices/lefun/requests/Request.java | 13 ++++++++++++- .../devices/lefun/requests/SetTimeRequest.java | 5 ++++- 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java index 1bb172590..c86d421da 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java @@ -4,6 +4,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FindDeviceCommand; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; public class FindDeviceRequest extends Request { public FindDeviceRequest(LefunDeviceSupport support, TransactionBuilder builder) { @@ -28,5 +29,7 @@ public class FindDeviceRequest extends Request { if (!cmd.isSuccess()) reportFailure("Could not initiate find device"); + + operationStatus = OperationStatus.FINISHED; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java index 35964c120..7a44c83b4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java @@ -23,6 +23,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetBatteryLev import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; public class GetBatteryLevelRequest extends Request { public GetBatteryLevelRequest(LefunDeviceSupport support, TransactionBuilder builder) { @@ -43,6 +44,8 @@ public class GetBatteryLevelRequest extends Request { GBDevice device = getSupport().getDevice(); device.setBatteryLevel(cmd.getBatteryLevel()); device.setBatteryThresholdPercent((short)15); + + operationStatus = OperationStatus.FINISHED; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java index f3c900a12..2186b22d3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java @@ -23,6 +23,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetFirmwareIn import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; public class GetFirmwareInfoRequest extends Request { public GetFirmwareInfoRequest(LefunDeviceSupport support, TransactionBuilder builder) { @@ -48,6 +49,8 @@ public class GetFirmwareInfoRequest extends Request { device.setFirmwareVersion(String.format("%d.%d", softwareVersion >> 8, softwareVersion & 0xff)); device.setFirmwareVersion2(String.format("%d.%d", hardwareVersion >> 8, hardwareVersion & 0xff)); getSupport().completeInitialization(); + + operationStatus = OperationStatus.FINISHED; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java index 6c3a62bfe..c11c33678 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java @@ -30,6 +30,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEOperation; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; import nodomain.freeyourgadget.gadgetbridge.util.GB; // Ripped from nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request @@ -43,17 +44,23 @@ public abstract class Request extends AbstractBTLEOperation this.builder = builder; } + public TransactionBuilder getTransactionBuilder() { + return builder; + } + @Override protected void doPerform() throws IOException { BluetoothGattCharacteristic characteristic = getSupport() .getCharacteristic(LefunConstants.UUID_CHARACTERISTIC_LEFUN_WRITE); builder.write(characteristic, createRequest()); + if (isSelfQueue()) + getSupport().performConnected(builder.getTransaction()); } public abstract byte[] createRequest(); public void handleResponse(byte[] data) { - + operationStatus = OperationStatus.FINISHED; } public String getName() { @@ -68,6 +75,10 @@ public abstract class Request extends AbstractBTLEOperation public abstract int getCommandId(); + public boolean isSelfQueue() { + return false; + } + public boolean expectsResponse() { return true; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetTimeRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetTimeRequest.java index ac6f47940..ff2a193cf 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetTimeRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetTimeRequest.java @@ -26,6 +26,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.TimeCommand; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; public class SetTimeRequest extends Request { public SetTimeRequest(LefunDeviceSupport support, TransactionBuilder builder) { @@ -39,7 +40,7 @@ public class SetTimeRequest extends Request { cmd.setOp(BaseCommand.OP_SET); cmd.setYear((byte)(c.get(Calendar.YEAR) - 2000)); - cmd.setMonth((byte)c.get(Calendar.MONTH)); + cmd.setMonth((byte)(c.get(Calendar.MONTH) + 1)); cmd.setDay((byte)c.get(Calendar.DAY_OF_MONTH)); cmd.setHour((byte)c.get(Calendar.HOUR_OF_DAY)); cmd.setMinute((byte)c.get(Calendar.MINUTE)); @@ -54,6 +55,8 @@ public class SetTimeRequest extends Request { cmd.deserialize(data); if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess()) reportFailure("Could not set time"); + + operationStatus = OperationStatus.FINISHED; } @Override From d161415046df399a293b1c51bd42dfc1269613f8 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 03:13:22 -0600 Subject: [PATCH 22/86] Lefun: Implement activity data sampling --- .../gadgetbridge/daogen/GBDaoGenerator.java | 47 ++++- .../devices/lefun/LefunConstants.java | 20 ++ .../devices/lefun/LefunDeviceCoordinator.java | 2 +- .../devices/lefun/LefunSampleProvider.java | 101 +++++++++ .../devices/lefun/LefunDeviceSupport.java | 197 +++++++++++++++++- .../requests/GetActivityDataRequest.java | 87 ++++++++ .../lefun/requests/GetPpgDataRequest.java | 87 ++++++++ .../lefun/requests/GetSleepDataRequest.java | 87 ++++++++ .../lefun/requests/MultiFetchRequest.java | 101 +++++++++ 9 files changed, 725 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunSampleProvider.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetActivityDataRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetPpgDataRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSleepDataRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/MultiFetchRequest.java diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index ff9fe81a6..1b7c08465 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -43,7 +43,7 @@ public class GBDaoGenerator { public static void main(String[] args) throws Exception { - Schema schema = new Schema(30, MAIN_PACKAGE + ".entities"); + Schema schema = new Schema(31, MAIN_PACKAGE + ".entities"); Entity userAttributes = addUserAttributes(schema); Entity user = addUserInfo(schema, userAttributes); @@ -74,6 +74,9 @@ public class GBDaoGenerator { addWatchXPlusHealthActivitySample(schema, user, device); addWatchXPlusHealthActivityKindOverlay(schema, user, device); addTLW64ActivitySample(schema, user, device); + addLefunActivitySample(schema, user, device); + addLefunBiometricSample(schema,user,device); + addLefunSleepSample(schema, user, device); addHybridHRActivitySample(schema, user, device); addCalendarSyncState(schema, device); @@ -404,6 +407,48 @@ public class GBDaoGenerator { return activitySample; } + private static Entity addLefunActivitySample(Schema schema, Entity user, Entity device) { + Entity activitySample = addEntity(schema, "LefunActivitySample"); + activitySample.implementsSerializable(); + addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device); + activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE); + activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE); + activitySample.addIntProperty("distance").notNull(); + activitySample.addIntProperty("calories").notNull(); + addHeartRateProperties(activitySample); + return activitySample; + } + + private static Entity addLefunBiometricSample(Schema schema, Entity user, Entity device) { + Entity biometricSample = addEntity(schema, "LefunBiometricSample"); + biometricSample.implementsSerializable(); + + biometricSample.addIntProperty("timestamp").notNull().primaryKey(); + Property deviceId = biometricSample.addLongProperty("deviceId").primaryKey().notNull().getProperty(); + biometricSample.addToOne(device, deviceId); + Property userId = biometricSample.addLongProperty("userId").notNull().getProperty(); + biometricSample.addToOne(user, userId); + + biometricSample.addIntProperty("type").notNull(); + biometricSample.addIntProperty("value1").notNull(); + biometricSample.addIntProperty("value2"); + return biometricSample; + } + + private static Entity addLefunSleepSample(Schema schema, Entity user, Entity device) { + Entity sleepSample = addEntity(schema, "LefunSleepSample"); + sleepSample.implementsSerializable(); + + sleepSample.addIntProperty("timestamp").notNull().primaryKey(); + Property deviceId = sleepSample.addLongProperty("deviceId").primaryKey().notNull().getProperty(); + sleepSample.addToOne(device, deviceId); + Property userId = sleepSample.addLongProperty("userId").notNull().getProperty(); + sleepSample.addToOne(user, userId); + + sleepSample.addIntProperty("type").notNull(); + return sleepSample; + } + private static void addCommonActivitySampleProperties(String superClass, Entity activitySample, Entity user, Entity device) { activitySample.setSuperclass(superClass); activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider"); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java index a4b972634..0f98fa608 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java @@ -73,4 +73,24 @@ public class LefunConstants { public static final int PPG_TYPE_HEART_RATE = 0; public static final int PPG_TYPE_BLOOD_PRESSURE = 1; public static final int PPG_TYPE_BLOOD_OXYGEN = 2; + public static final int PPG_TYPE_COUNT = 3; + + // Extended DB types + public static final int DB_SAMPLE_TYPE_PPG = 1; + public static final int DB_SAMPLE_TYPE_SLEEP_DAY = 2; + public static final int DB_SAMPLE_TYPE_SLEEP_PERIODS = 3; + + // DB activity kinds + public static final int DB_ACTIVITY_KIND_UNKNOWN = 0; + public static final int DB_ACTIVITY_KIND_ACTIVITY = 1; + public static final int DB_ACTIVITY_KIND_HEART_RATE = 2; + public static final int DB_ACTIVITY_KIND_LIGHT_SLEEP = 3; + public static final int DB_ACTIVITY_KIND_DEEP_SLEEP = 4; + + // Pseudo-intensity + public static final int INTENSITY_MIN = 0; + public static final int INTENSITY_DEEP_SLEEP = 1; + public static final int INTENSITY_LIGHT_SLEEP = 2; + public static final int INTENSITY_AWAKE = 3; + public static final int INTENSITY_MAX = 4; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java index fe9aee215..9e501b765 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java @@ -92,7 +92,7 @@ public class LefunDeviceCoordinator extends AbstractDeviceCoordinator { @Override public SampleProvider getSampleProvider(GBDevice device, DaoSession session) { - return null; + return new LefunSampleProvider(device, session); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunSampleProvider.java new file mode 100644 index 000000000..861cd089d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunSampleProvider.java @@ -0,0 +1,101 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import de.greenrobot.dao.AbstractDao; +import de.greenrobot.dao.Property; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider; +import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySample; +import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySampleDao; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; + +public class LefunSampleProvider extends AbstractSampleProvider { + public LefunSampleProvider(GBDevice device, DaoSession session) { + super(device, session); + } + + @Override + public AbstractDao getSampleDao() { + return getSession().getLefunActivitySampleDao(); + } + + @Nullable + @Override + protected Property getRawKindSampleProperty() { + return LefunActivitySampleDao.Properties.RawKind; + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return LefunActivitySampleDao.Properties.Timestamp; + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return LefunActivitySampleDao.Properties.DeviceId; + } + + @Override + public int normalizeType(int rawType) { + switch (rawType) { + case LefunConstants.DB_ACTIVITY_KIND_ACTIVITY: + case LefunConstants.DB_ACTIVITY_KIND_HEART_RATE: + return ActivityKind.TYPE_ACTIVITY; + case LefunConstants.DB_ACTIVITY_KIND_LIGHT_SLEEP: + return ActivityKind.TYPE_LIGHT_SLEEP; + case LefunConstants.DB_ACTIVITY_KIND_DEEP_SLEEP: + return ActivityKind.TYPE_DEEP_SLEEP; + default: + return ActivityKind.TYPE_UNKNOWN; + } + } + + @Override + public int toRawActivityKind(int activityKind) { + switch (activityKind) { + case ActivityKind.TYPE_ACTIVITY: + return LefunConstants.DB_ACTIVITY_KIND_ACTIVITY; + case ActivityKind.TYPE_LIGHT_SLEEP: + return LefunConstants.DB_ACTIVITY_KIND_LIGHT_SLEEP; + case ActivityKind.TYPE_DEEP_SLEEP: + return LefunConstants.DB_ACTIVITY_KIND_DEEP_SLEEP; + default: + return LefunConstants.DB_ACTIVITY_KIND_UNKNOWN; + } + } + + @Override + public float normalizeIntensity(int rawIntensity) { + return rawIntensity / (float)LefunConstants.INTENSITY_MAX; + } + + @Override + public LefunActivitySample createActivitySample() { + return new LefunActivitySample(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java index f5ed6ee54..80753ecce 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java @@ -28,11 +28,27 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collections; import java.util.List; +import java.util.Queue; import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; +import de.greenrobot.dao.query.Query; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetActivityDataCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetPpgDataCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetSleepDataCommand; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySample; +import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySampleDao; +import nodomain.freeyourgadget.gadgetbridge.entities.LefunBiometricSample; +import nodomain.freeyourgadget.gadgetbridge.entities.LefunBiometricSampleDao; +import nodomain.freeyourgadget.gadgetbridge.entities.LefunSleepSample; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; @@ -41,14 +57,18 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.FindDeviceRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetActivityDataRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetBatteryLevelRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetFirmwareInfoRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetPpgDataRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetSleepDataRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.Request; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetTimeRequest; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -57,6 +77,7 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(LefunDeviceSupport.class); private final List inProgressRequests = Collections.synchronizedList(new ArrayList()); + private final Queue queuedRequests = new ConcurrentLinkedQueue<>(); public LefunDeviceSupport() { super(LOG); @@ -113,7 +134,7 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onSetTime() { try { - TransactionBuilder builder = createTransactionBuilder(SetTimeRequest.class.getSimpleName()); + TransactionBuilder builder = performInitialized(SetTimeRequest.class.getSimpleName()); SetTimeRequest request = new SetTimeRequest(this, builder); request.perform(); inProgressRequests.add(request); @@ -186,7 +207,27 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onFetchRecordedData(int dataTypes) { + if ((dataTypes & RecordedDataTypes.TYPE_ACTIVITY) != 0) { + for (int i = 0; i < 7; ++i) { + GetActivityDataRequest req = new GetActivityDataRequest(this); + req.setDaysAgo(i); + queuedRequests.add(req); + } + for (int i = 0; i < LefunConstants.PPG_TYPE_COUNT; ++i) { + GetPpgDataRequest req = new GetPpgDataRequest(this); + req.setPpgType(i); + queuedRequests.add(req); + } + + for (int i = 0; i < 7; ++i) { + GetSleepDataRequest req = new GetSleepDataRequest(this); + req.setDaysAgo(i); + queuedRequests.add(req); + } + + runNextQueuedRequest(); + } } @Override @@ -208,7 +249,7 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { public void onFindDevice(boolean start) { if (start) { try { - TransactionBuilder builder = createTransactionBuilder(FindDeviceRequest.class.getSimpleName()); + TransactionBuilder builder = performInitialized(FindDeviceRequest.class.getSimpleName()); FindDeviceRequest request = new FindDeviceRequest(this, builder); request.perform(); inProgressRequests.add(request); @@ -296,10 +337,12 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { if (handleAsynchronousResponse(data)) return true; + logMessageContent(data); LOG.error(String.format("No handler for response 0x%02x", commandId)); return false; } + logMessageContent(data); LOG.error("Invalid response received"); return false; } @@ -316,4 +359,154 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { gbDevice.setState(GBDevice.State.INITIALIZED); gbDevice.sendDeviceUpdateIntent(getContext()); } + + private int dateToTimestamp(byte year, byte month, byte day, byte hour, byte minute, byte second) { + Calendar calendar = Calendar.getInstance(); + calendar.set( + ((int) year & 0xff) + 2000, + ((int) month & 0xff) - 1, + (int) day, + (int) hour, + (int) minute, + (int) second + ); + return (int) (calendar.getTimeInMillis() / 1000); + } + + private LefunActivitySample getActivitySample(DaoSession session, int timestamp) { + LefunActivitySampleDao dao = session.getLefunActivitySampleDao(); + Long userId = DBHelper.getUser(session).getId(); + Long deviceId = DBHelper.getDevice(getDevice(), session).getId(); + Query q = dao.queryBuilder() + .where(LefunActivitySampleDao.Properties.Timestamp.eq(timestamp)) + .where(LefunActivitySampleDao.Properties.DeviceId.eq(deviceId)) + .where(LefunActivitySampleDao.Properties.UserId.eq(userId)) + .build(); + return q.unique(); + } + + public void handleActivityData(GetActivityDataCommand command) { + try (DBHandler handler = GBApplication.acquireDB()) { + DaoSession session = handler.getDaoSession(); + int timestamp = dateToTimestamp(command.getYear(), command.getMonth(), command.getDay(), + command.getHour(), command.getMinute(), (byte) 0); + // For the most part I'm ignoring the sample provider, because it doesn't really help + // when I need to combine sample data instead of replacing + LefunActivitySample sample = getActivitySample(session, timestamp); + if (sample == null) { + sample = new LefunActivitySample(timestamp, + DBHelper.getDevice(getDevice(), session).getId()); + sample.setUserId(DBHelper.getUser(session).getId()); + sample.setRawKind(LefunConstants.DB_ACTIVITY_KIND_ACTIVITY); + } + + sample.setSteps(command.getSteps()); + sample.setDistance(command.getDistance()); + sample.setCalories(command.getCalories()); + sample.setRawIntensity(LefunConstants.INTENSITY_AWAKE); + + session.getLefunActivitySampleDao().insertOrReplace(sample); + } catch (Exception e) { + LOG.error("Error handling activity data", e); + } + } + + public void handlePpgData(GetPpgDataCommand command) { + byte[] ppgData = command.getPpgData(); + int ppgData0 = ppgData[0] & 0xff; + int ppgData1 = ppgData.length > 1 ? ppgData[1] & 0xff : 0; + + try (DBHandler handler = GBApplication.acquireDB()) { + DaoSession session = handler.getDaoSession(); + int timestamp = dateToTimestamp(command.getYear(), command.getMonth(), command.getDay(), + command.getHour(), command.getMinute(), command.getSecond()); + + if (command.getPpgType() == LefunConstants.PPG_TYPE_HEART_RATE) { + LefunActivitySample sample = getActivitySample(session, timestamp); + if (sample == null) { + sample = new LefunActivitySample(timestamp, + DBHelper.getDevice(getDevice(), session).getId()); + sample.setUserId(DBHelper.getUser(session).getId()); + sample.setRawKind(LefunConstants.DB_ACTIVITY_KIND_HEART_RATE); + } + + sample.setHeartRate(ppgData0); + + session.getLefunActivitySampleDao().insertOrReplace(sample); + } + + LefunBiometricSample bioSample = new LefunBiometricSample(timestamp, + DBHelper.getDevice(getDevice(), session).getId()); + bioSample.setUserId(DBHelper.getUser(session).getId()); + bioSample.setType(command.getPpgType()); + bioSample.setValue1(ppgData0); + bioSample.setValue2(ppgData1); + session.getLefunBiometricSampleDao().insertOrReplace(bioSample); + } catch (Exception e) { + LOG.error("Error handling PPG data", e); + } + } + + public void handleSleepData(GetSleepDataCommand command) { + try (DBHandler handler = GBApplication.acquireDB()) { + DaoSession session = handler.getDaoSession(); + int timestamp = dateToTimestamp(command.getYear(), command.getMonth(), command.getDay(), + command.getHour(), command.getMinute(), (byte) 0); + + LefunActivitySample sample = getActivitySample(session, timestamp); + if (sample == null) { + sample = new LefunActivitySample(timestamp, + DBHelper.getDevice(getDevice(), session).getId()); + sample.setUserId(DBHelper.getUser(session).getId()); + } + + int rawKind; + int intensity; + switch (command.getSleepType()) { + case GetSleepDataCommand.SLEEP_TYPE_AWAKE: + rawKind = LefunConstants.DB_ACTIVITY_KIND_ACTIVITY; + intensity = LefunConstants.INTENSITY_AWAKE; + break; + case GetSleepDataCommand.SLEEP_TYPE_LIGHT_SLEEP: + rawKind = LefunConstants.DB_ACTIVITY_KIND_LIGHT_SLEEP; + intensity = LefunConstants.INTENSITY_LIGHT_SLEEP; + break; + case GetSleepDataCommand.SLEEP_TYPE_DEEP_SLEEP: + rawKind = LefunConstants.DB_ACTIVITY_KIND_DEEP_SLEEP; + intensity = LefunConstants.INTENSITY_DEEP_SLEEP; + break; + default: + rawKind = LefunConstants.DB_ACTIVITY_KIND_UNKNOWN; + intensity = LefunConstants.INTENSITY_AWAKE; + break; + } + + sample.setRawKind(rawKind); + sample.setRawIntensity(intensity); + + session.getLefunActivitySampleDao().insertOrReplace(sample); + + LefunSleepSample sleepSample = new LefunSleepSample(timestamp, + DBHelper.getDevice(getDevice(), session).getId()); + sleepSample.setUserId(DBHelper.getUser(session).getId()); + sleepSample.setType(command.getSleepType()); + session.getLefunSleepSampleDao().insertOrReplace(sleepSample); + } catch (Exception e) { + LOG.error("Error handling sleep data", e); + } + } + + public void runNextQueuedRequest() { + Request request = queuedRequests.poll(); + if (request != null) { + try { + request.perform(); + if (!request.isSelfQueue()) + performConnected(request.getTransactionBuilder().getTransaction()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to run next queued request", Toast.LENGTH_SHORT, + GB.ERROR, e); + } + } + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetActivityDataRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetActivityDataRequest.java new file mode 100644 index 000000000..2c1adb8c8 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetActivityDataRequest.java @@ -0,0 +1,87 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetActivityDataCommand; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; + +public class GetActivityDataRequest extends MultiFetchRequest { + private int daysAgo; + + public GetActivityDataRequest(LefunDeviceSupport support) { + super(support); + } + + public int getDaysAgo() { + return daysAgo; + } + + public void setDaysAgo(int daysAgo) { + this.daysAgo = daysAgo; + } + + @Override + public byte[] createRequest() { + GetActivityDataCommand cmd = new GetActivityDataCommand(); + cmd.setDaysAgo((byte) daysAgo); + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + GetActivityDataCommand cmd = new GetActivityDataCommand(); + cmd.deserialize(data); + + if (daysAgo != (cmd.getDaysAgo() & 0xff)) { + throw new IllegalArgumentException("Mismatching days ago"); + } + + if (totalRecords == -1) { + totalRecords = cmd.getTotalRecords() & 0xff; + } else if (totalRecords != (cmd.getTotalRecords() & 0xff)) { + throw new IllegalArgumentException("Total records mismatch"); + } + + if (totalRecords != 0) { + int currentRecord = cmd.getCurrentRecord() & 0xff; + if (lastRecord + 1 != currentRecord) { + throw new IllegalArgumentException("Records received out of sequence"); + } + lastRecord = currentRecord; + + getSupport().handleActivityData(cmd); + } else { + lastRecord = totalRecords; + } + + if (lastRecord == totalRecords) + operationFinished(); + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_ACTIVITY_DATA; + } + + @Override + protected String getOperationName() { + return "Getting activity data"; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetPpgDataRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetPpgDataRequest.java new file mode 100644 index 000000000..eb7c4a9f0 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetPpgDataRequest.java @@ -0,0 +1,87 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetPpgDataCommand; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; + +public class GetPpgDataRequest extends MultiFetchRequest { + private int ppgType; + + public GetPpgDataRequest(LefunDeviceSupport support) { + super(support); + } + + public int getPpgType() { + return ppgType; + } + + public void setPpgType(int ppgType) { + this.ppgType = ppgType; + } + + @Override + public byte[] createRequest() { + GetPpgDataCommand cmd = new GetPpgDataCommand(); + cmd.setPpgType(ppgType); + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + GetPpgDataCommand cmd = new GetPpgDataCommand(); + cmd.deserialize(data); + + if (cmd.getPpgType() != ppgType) { + throw new IllegalArgumentException("Mismatching PPG type"); + } + + if (totalRecords == -1) { + totalRecords = cmd.getTotalRecords() & 0xffff; + } else if (totalRecords != (cmd.getTotalRecords() & 0xffff)) { + throw new IllegalArgumentException("Total records mismatch"); + } + + if (totalRecords != 0) { + int currentRecord = cmd.getCurrentRecord() & 0xffff; + if (lastRecord + 1 != currentRecord) { + throw new IllegalArgumentException("Records received out of sequence"); + } + lastRecord = currentRecord; + + getSupport().handlePpgData(cmd); + } else { + lastRecord = totalRecords; + } + + if (lastRecord == totalRecords) + operationFinished(); + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_PPG_DATA; + } + + @Override + protected String getOperationName() { + return "Getting PPG data"; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSleepDataRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSleepDataRequest.java new file mode 100644 index 000000000..ab53855a3 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSleepDataRequest.java @@ -0,0 +1,87 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetSleepDataCommand; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; + +public class GetSleepDataRequest extends MultiFetchRequest { + private int daysAgo; + + public GetSleepDataRequest(LefunDeviceSupport support) { + super(support); + } + + public int getDaysAgo() { + return daysAgo; + } + + public void setDaysAgo(int daysAgo) { + this.daysAgo = daysAgo; + } + + @Override + public byte[] createRequest() { + GetSleepDataCommand cmd = new GetSleepDataCommand(); + cmd.setDaysAgo((byte) daysAgo); + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + GetSleepDataCommand cmd = new GetSleepDataCommand(); + cmd.deserialize(data); + + if (daysAgo != (cmd.getDaysAgo() & 0xff)) { + throw new IllegalArgumentException("Mismatching days ago"); + } + + if (totalRecords == -1) { + totalRecords = cmd.getTotalRecords() & 0xff; + } else if (totalRecords != (cmd.getTotalRecords() & 0xff)) { + throw new IllegalArgumentException("Total records mismatch"); + } + + if (totalRecords != 0) { + int currentRecord = cmd.getCurrentRecord() & 0xff; + if (lastRecord + 1 != currentRecord) { + throw new IllegalArgumentException("Records received out of sequence"); + } + lastRecord = currentRecord; + + getSupport().handleSleepData(cmd); + } else { + lastRecord = totalRecords; + } + + if (lastRecord == totalRecords) + operationFinished(); + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_SLEEP_DATA; + } + + @Override + protected String getOperationName() { + return "Getting sleep data"; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/MultiFetchRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/MultiFetchRequest.java new file mode 100644 index 000000000..031d8f6bd --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/MultiFetchRequest.java @@ -0,0 +1,101 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.widget.Toast; + +import java.io.IOException; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +public abstract class MultiFetchRequest extends Request { + protected MultiFetchRequest(LefunDeviceSupport support) { + super(support, null); + removeAfterHandling = false; + } + + protected int lastRecord = 0; + protected int totalRecords = -1; + + @Override + protected void prePerform() throws IOException { + super.prePerform(); + builder = performInitialized(getClass().getSimpleName()); + if (getDevice().isBusy()) { + throw new IllegalStateException("Device is busy"); + } + builder.add(new SetDeviceBusyAction(getDevice(), getOperationName(), getContext())); + builder.wait(1000); // Wait a bit (after previous operation), or device sometimes won't respond + } + + @Override + protected void operationFinished() { + if (lastRecord == totalRecords) + removeAfterHandling = true; + try { + super.operationFinished(); + TransactionBuilder builder = performInitialized("Finishing operation"); + builder.setGattCallback(null); + builder.queue(getQueue()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to reset callback", Toast.LENGTH_SHORT, + GB.ERROR, e); + } + unsetBusy(); + operationStatus = OperationStatus.FINISHED; + getSupport().runNextQueuedRequest(); + } + + @Override + public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + if (characteristic.getUuid().equals(LefunConstants.UUID_CHARACTERISTIC_LEFUN_NOTIFY)) { + byte[] data = characteristic.getValue(); + // Parse response + if (data.length >= LefunConstants.CMD_HEADER_LENGTH && data[0] == LefunConstants.CMD_RESPONSE_ID) { + try { + handleResponse(data); + return true; + } catch (IllegalArgumentException e) { + log("Failed to handle response"); + operationFinished(); + } + } + + getSupport().logMessageContent(data); + log("Invalid response received"); + return false; + } + + return super.onCharacteristicChanged(gatt, characteristic); + } + + @Override + public boolean isSelfQueue() { + return true; + } + + protected abstract String getOperationName(); +} From 3d108d9d1147b41bb3f856ca589a4725a53b4344 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 03:31:30 -0600 Subject: [PATCH 23/86] Lefun: Implement heart rate test --- .../devices/lefun/LefunConstants.java | 5 -- .../devices/lefun/LefunDeviceSupport.java | 59 +++++++++++++++---- .../lefun/requests/StartPpgRequest.java | 47 +++++++++++++++ 3 files changed, 96 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/StartPpgRequest.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java index 0f98fa608..77a6b1afe 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java @@ -75,11 +75,6 @@ public class LefunConstants { public static final int PPG_TYPE_BLOOD_OXYGEN = 2; public static final int PPG_TYPE_COUNT = 3; - // Extended DB types - public static final int DB_SAMPLE_TYPE_PPG = 1; - public static final int DB_SAMPLE_TYPE_SLEEP_DAY = 2; - public static final int DB_SAMPLE_TYPE_SLEEP_PERIODS = 3; - // DB activity kinds public static final int DB_ACTIVITY_KIND_UNKNOWN = 0; public static final int DB_ACTIVITY_KIND_ACTIVITY = 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java index 80753ecce..1dd1cdaa9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java @@ -43,11 +43,11 @@ import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetActivityDataCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetPpgDataCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetSleepDataCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.PpgResultCommand; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.LefunBiometricSample; -import nodomain.freeyourgadget.gadgetbridge.entities.LefunBiometricSampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.LefunSleepSample; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; @@ -71,6 +71,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetPp import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetSleepDataRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.Request; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetTimeRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.StartPpgRequest; import nodomain.freeyourgadget.gadgetbridge.util.GB; public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { @@ -237,7 +238,17 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onHeartRateTest() { - + try { + TransactionBuilder builder = performInitialized(StartPpgRequest.class.getSimpleName()); + StartPpgRequest request = new StartPpgRequest(this, builder); + request.setPpgType(LefunConstants.PPG_TYPE_HEART_RATE); + request.perform(); + inProgressRequests.add(request); + performConnected(builder.getTransaction()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to start heart rate test", Toast.LENGTH_SHORT, + GB.ERROR, e); + } } @Override @@ -334,7 +345,7 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } - if (handleAsynchronousResponse(data)) + if (handleAsynchronousResponse(commandId, data)) return true; logMessageContent(data); @@ -350,11 +361,27 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { return super.onCharacteristicChanged(gatt, characteristic); } - private boolean handleAsynchronousResponse(byte[] data) { + private boolean handleAsynchronousResponse(byte commandId, byte[] data) { // Assume data already checked for correct response code and length + switch (commandId) { + case LefunConstants.CMD_PPG_RESULT: + return handleAsynchronousPpgResult(data); + } return false; } + private boolean handleAsynchronousPpgResult(byte[] data) { + try { + PpgResultCommand cmd = new PpgResultCommand(); + cmd.deserialize(data); + handlePpgData(cmd); + return true; + } catch (IllegalArgumentException e) { + LOG.error("Failed to handle response", e); + return false; + } + } + public void completeInitialization() { gbDevice.setState(GBDevice.State.INITIALIZED); gbDevice.sendDeviceUpdateIntent(getContext()); @@ -411,17 +438,14 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } - public void handlePpgData(GetPpgDataCommand command) { - byte[] ppgData = command.getPpgData(); + private void handlePpgData(int timestamp, int ppgType, byte[] ppgData) { int ppgData0 = ppgData[0] & 0xff; int ppgData1 = ppgData.length > 1 ? ppgData[1] & 0xff : 0; try (DBHandler handler = GBApplication.acquireDB()) { DaoSession session = handler.getDaoSession(); - int timestamp = dateToTimestamp(command.getYear(), command.getMonth(), command.getDay(), - command.getHour(), command.getMinute(), command.getSecond()); - if (command.getPpgType() == LefunConstants.PPG_TYPE_HEART_RATE) { + if (ppgType == LefunConstants.PPG_TYPE_HEART_RATE) { LefunActivitySample sample = getActivitySample(session, timestamp); if (sample == null) { sample = new LefunActivitySample(timestamp, @@ -438,7 +462,7 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { LefunBiometricSample bioSample = new LefunBiometricSample(timestamp, DBHelper.getDevice(getDevice(), session).getId()); bioSample.setUserId(DBHelper.getUser(session).getId()); - bioSample.setType(command.getPpgType()); + bioSample.setType(ppgType); bioSample.setValue1(ppgData0); bioSample.setValue2(ppgData1); session.getLefunBiometricSampleDao().insertOrReplace(bioSample); @@ -447,6 +471,21 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } + public void handlePpgData(GetPpgDataCommand command) { + int timestamp = dateToTimestamp(command.getYear(), command.getMonth(), command.getDay(), + command.getHour(), command.getMinute(), command.getSecond()); + int ppgType = command.getPpgType(); + byte[] ppgData = command.getPpgData(); + handlePpgData(timestamp, ppgType, ppgData); + } + + public void handlePpgData(PpgResultCommand command) { + int timestamp = (int) (Calendar.getInstance().getTimeInMillis() / 1000); + int ppgType = command.getPpgType(); + byte[] ppgData = command.getPpgData(); + handlePpgData(timestamp, ppgType, ppgData); + } + public void handleSleepData(GetSleepDataCommand command) { try (DBHandler handler = GBApplication.acquireDB()) { DaoSession session = handler.getDaoSession(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/StartPpgRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/StartPpgRequest.java new file mode 100644 index 000000000..883e08691 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/StartPpgRequest.java @@ -0,0 +1,47 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FindDeviceCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.StartPpgSensingCommand; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; + +public class StartPpgRequest extends Request { + public StartPpgRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + int ppgType; + + public int getPpgType() { + return ppgType; + } + + public void setPpgType(int ppgType) { + this.ppgType = ppgType; + } + + @Override + public byte[] createRequest() { + StartPpgSensingCommand cmd = new StartPpgSensingCommand(); + cmd.setPpgType(ppgType); + return cmd.serialize(); + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_PPG_START; + } + + @Override + public void handleResponse(byte[] data) { + StartPpgSensingCommand cmd = new StartPpgSensingCommand(); + cmd.deserialize(data); + + if (!cmd.isSetSuccess() || cmd.getPpgType() != ppgType) + reportFailure("Could not start PPG sensing"); + + operationStatus = OperationStatus.FINISHED; + } +} From b7abd328bbf5be1ee2611df78230f90150d18a14 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 03:51:41 -0600 Subject: [PATCH 24/86] Lefun: Implement set alarm --- .../devices/lefun/LefunDeviceSupport.java | 21 +++- .../lefun/requests/SetAlarmRequest.java | 100 ++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetAlarmRequest.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java index 1dd1cdaa9..c9b706fad 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java @@ -70,6 +70,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetFi import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetPpgDataRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetSleepDataRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.Request; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetAlarmRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetTimeRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.StartPpgRequest; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -148,7 +149,25 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onSetAlarms(ArrayList alarms) { - + int i = 0; + for (Alarm alarm : alarms) { + try { + TransactionBuilder builder = performInitialized(SetAlarmRequest.class.getSimpleName()); + SetAlarmRequest request = new SetAlarmRequest(this, builder); + request.setIndex(i); + request.setEnabled(alarm.getEnabled()); + request.setDayOfWeek(alarm.getRepetition()); + request.setHour(alarm.getHour()); + request.setMinute(alarm.getMinute()); + request.perform(); + inProgressRequests.add(request); + performConnected(builder.getTransaction()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to set alarm", Toast.LENGTH_SHORT, + GB.ERROR, e); + } + ++i; + } } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetAlarmRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetAlarmRequest.java new file mode 100644 index 000000000..e4e73b4f8 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetAlarmRequest.java @@ -0,0 +1,100 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.AlarmCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand; +import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; + +public class SetAlarmRequest extends Request { + private int index; + private boolean enabled; + private int dayOfWeek; + private int hour; + private int minute; + public SetAlarmRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getDayOfWeek() { + return dayOfWeek; + } + + public void setDayOfWeek(int dayOfWeek) { + this.dayOfWeek = dayOfWeek; + } + + public int getHour() { + return hour; + } + + public void setHour(int hour) { + this.hour = hour; + } + + public int getMinute() { + return minute; + } + + public void setMinute(int minute) { + this.minute = minute; + } + + @Override + public byte[] createRequest() { + AlarmCommand cmd = new AlarmCommand(); + cmd.setOp(BaseCommand.OP_SET); + cmd.setIndex((byte) index); + cmd.setEnabled(enabled); + cmd.setNumOfSnoozes((byte) 0); + cmd.setHour((byte) hour); + cmd.setMinute((byte) minute); + + // Translate GB alarm day of week to Lefun day of week + // GB starts on Monday, Lefun starts on Sunday + for (int i = 0; i < 6; ++i) { + if ((dayOfWeek & (1 << i)) != 0) { + cmd.setDayOfWeek(i + 1, true); + } + } + if ((dayOfWeek & Alarm.ALARM_SUN) != 0) { + cmd.setDayOfWeek(AlarmCommand.DOW_SUNDAY, true); + } + + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + AlarmCommand cmd = new AlarmCommand(); + cmd.deserialize(data); + + if (cmd.getOp() != BaseCommand.OP_SET || cmd.getIndex() != index || !cmd.isSetSuccess()) + reportFailure("Could not set alarm"); + + operationStatus = OperationStatus.FINISHED; + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_ALARM; + } +} From 0dd44ae2d664275a66e004ad56606192cc7fbc69 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 04:04:28 -0600 Subject: [PATCH 25/86] Lefun: Implement limited find phone support --- .../devices/lefun/LefunDeviceSupport.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java index c9b706fad..427622a87 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java @@ -40,6 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FindPhoneCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetActivityDataCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetPpgDataCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetSleepDataCommand; @@ -385,6 +386,8 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { switch (commandId) { case LefunConstants.CMD_PPG_RESULT: return handleAsynchronousPpgResult(data); + case LefunConstants.CMD_FIND_PHONE: + return handleAntiLoss(data); } return false; } @@ -401,6 +404,19 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } + private boolean handleAntiLoss(byte[] data) { + try { + FindPhoneCommand cmd = new FindPhoneCommand(); + cmd.deserialize(data); + // TODO: actually pop something that makes sound + GB.toast("Your device is trying to find your phone", Toast.LENGTH_LONG, GB.INFO); + return true; + } catch (IllegalArgumentException e) { + LOG.error("Failed to handle anti-loss", e); + return false; + } + } + public void completeInitialization() { gbDevice.setState(GBDevice.State.INITIALIZED); gbDevice.sendDeviceUpdateIntent(getContext()); From a1737c39fb6ae1b788792409ae18b13620160440 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 04:09:23 -0600 Subject: [PATCH 26/86] Lefun: Add missing license headers --- .../lefun/requests/FindDeviceRequest.java | 18 ++++++++++++++++++ .../lefun/requests/SetAlarmRequest.java | 18 ++++++++++++++++++ .../lefun/requests/StartPpgRequest.java | 19 ++++++++++++++++++- 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java index c86d421da..8c4fb1d35 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/FindDeviceRequest.java @@ -1,3 +1,21 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetAlarmRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetAlarmRequest.java index e4e73b4f8..bae512d30 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetAlarmRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetAlarmRequest.java @@ -1,3 +1,21 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/StartPpgRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/StartPpgRequest.java index 883e08691..1491a2a9d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/StartPpgRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/StartPpgRequest.java @@ -1,7 +1,24 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; -import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FindDeviceCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.StartPpgSensingCommand; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; From c2375f95f40ea1356b605f2d446ded091a2de0e5 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 05:30:07 -0600 Subject: [PATCH 27/86] Lefun: Implement notification support --- .../lefun/commands/NotificationCommand.java | 9 +- .../devices/lefun/LefunDeviceSupport.java | 29 ++++- .../AbstractSendNotificationRequest.java | 98 ++++++++++++++++ .../requests/SendCallNotificationRequest.java | 67 +++++++++++ .../requests/SendNotificationRequest.java | 111 ++++++++++++++++++ 5 files changed, 309 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendCallNotificationRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java index 6335c8f40..bde32b448 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java @@ -36,6 +36,8 @@ public class NotificationCommand extends BaseCommand { public static final byte EXTENDED_SERVICE_TYPE_LINE = 5; public static final byte EXTENDED_SERVICE_TYPE_KAKAOTALK = 6; + public static final int MAX_PAYLOAD_LENGTH = 13; + private byte serviceType; private byte totalPieces; private byte currentPiece; @@ -49,7 +51,7 @@ public class NotificationCommand extends BaseCommand { public void setServiceType(int type) { if (type < 0 || type > 4) throw new IllegalArgumentException("Invalid service type"); - this.serviceType = (byte)(1 << type); + this.serviceType = (byte) (1 << type); } public byte getTotalPieces() { @@ -103,8 +105,9 @@ public class NotificationCommand extends BaseCommand { @Override protected byte serializeParams(ByteBuffer params) { boolean hasExtendedServiceType = (serviceType & (1 << SERVICE_TYPE_EXTENDED)) != 0 - && (extendedServiceType & 0x0f) != 0 ; - int maxPayloadLength = hasExtendedServiceType ? 12 : 13; + && (extendedServiceType & 0x0f) != 0; + int maxPayloadLength = MAX_PAYLOAD_LENGTH; + if (hasExtendedServiceType) maxPayloadLength -= 1; if (payload.length > maxPayloadLength) throw new IllegalStateException("Payload is too long"); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java index 427622a87..a39b63c75 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java @@ -71,6 +71,9 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetFi import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetPpgDataRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetSleepDataRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.Request; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.AbstractSendNotificationRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SendCallNotificationRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SendNotificationRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetAlarmRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetTimeRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.StartPpgRequest; @@ -126,7 +129,16 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onNotification(NotificationSpec notificationSpec) { - + try { + TransactionBuilder builder = performInitialized(SetTimeRequest.class.getSimpleName()); + SendNotificationRequest request = new SendNotificationRequest(this, builder); + request.setNotification(notificationSpec); + request.perform(); + performConnected(builder.getTransaction()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to send notification", Toast.LENGTH_SHORT, + GB.ERROR, e); + } } @Override @@ -173,7 +185,20 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onSetCallState(CallSpec callSpec) { - + switch (callSpec.command) { + case CallSpec.CALL_INCOMING: + try { + TransactionBuilder builder = performInitialized(SetTimeRequest.class.getSimpleName()); + SendCallNotificationRequest request = new SendCallNotificationRequest(this, builder); + request.setCallNotification(callSpec); + request.perform(); + performConnected(builder.getTransaction()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to send call notification", Toast.LENGTH_SHORT, + GB.ERROR, e); + } + break; + } } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java new file mode 100644 index 000000000..acfb01935 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java @@ -0,0 +1,98 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import android.bluetooth.BluetoothGattCharacteristic; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.NotificationCommand; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; + +public abstract class AbstractSendNotificationRequest extends Request { + protected AbstractSendNotificationRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + protected abstract String getMessage(); + + protected abstract byte getNotificationType(); + + protected abstract byte getExtendedNotificationType(); + + @Override + public byte[] createRequest() { + return new byte[0]; + } + + @Override + protected void doPerform() throws IOException { + byte notificationType = getNotificationType(); + byte extendedNotificationType = getExtendedNotificationType(); + boolean reserveSpaceForExtended = notificationType == NotificationCommand.SERVICE_TYPE_EXTENDED; + byte[] encoded = getMessage().getBytes(StandardCharsets.UTF_8); + ByteBuffer buffer = ByteBuffer.wrap(encoded); + + BluetoothGattCharacteristic characteristic = getSupport() + .getCharacteristic(LefunConstants.UUID_CHARACTERISTIC_LEFUN_WRITE); + + List commandList = new ArrayList<>(); + for (int i = 0; i < 0xff; ++i) { + NotificationCommand cmd = new NotificationCommand(); + cmd.setServiceType(notificationType); + cmd.setExtendedServiceType(extendedNotificationType); + cmd.setCurrentPiece((byte) (i + 1)); + + int maxPayloadLength = NotificationCommand.MAX_PAYLOAD_LENGTH; + if (reserveSpaceForExtended) maxPayloadLength -= 1; + maxPayloadLength = Math.min(maxPayloadLength, buffer.limit() - buffer.position()); + if (maxPayloadLength == 0) break; + + byte[] payload = new byte[maxPayloadLength]; + buffer.get(payload); + cmd.setPayload(payload); + + commandList.add(cmd); + } + + for (NotificationCommand cmd : commandList) { + cmd.setTotalPieces((byte) commandList.size()); + builder.write(characteristic, cmd.serialize()); + } + + if (isSelfQueue()) + getSupport().performConnected(builder.getTransaction()); + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_NOTIFICATION; + } + + @Override + public boolean expectsResponse() { + return false; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendCallNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendCallNotificationRequest.java new file mode 100644 index 000000000..bd572f62a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendCallNotificationRequest.java @@ -0,0 +1,67 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.NotificationCommand; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; + +public class SendCallNotificationRequest extends AbstractSendNotificationRequest { + public SendCallNotificationRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + private CallSpec callNotification; + + public CallSpec getCallNotification() { + return callNotification; + } + + public void setCallNotification(CallSpec callNotification) { + this.callNotification = callNotification; + } + + @Override + protected String getMessage() { + String message = ""; + if (callNotification.number != null) { + message = callNotification.number; + } + + if (callNotification.name != null) { + if (message.length() > 0) { + message += " - "; + } + message += callNotification.name; + } + + return message; + } + + @Override + protected byte getNotificationType() { + return NotificationCommand.SERVICE_TYPE_CALL; + } + + @Override + protected byte getExtendedNotificationType() { + return 0; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java new file mode 100644 index 000000000..98405d0bc --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java @@ -0,0 +1,111 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.NotificationCommand; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; + +public class SendNotificationRequest extends AbstractSendNotificationRequest { + NotificationSpec notification; + + public SendNotificationRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + @Override + protected byte getNotificationType() { + switch (notification.type) { + case GENERIC_PHONE: + return NotificationCommand.SERVICE_TYPE_CALL; + case GENERIC_SMS: + case GENERIC_EMAIL: + default: + return NotificationCommand.SERVICE_TYPE_TEXT; + case WECHAT: + return NotificationCommand.SERVICE_TYPE_WECHAT; + case FACEBOOK: + case FACEBOOK_MESSENGER: + case TWITTER: + case LINKEDIN: + case WHATSAPP: + case LINE: + case KAKAO_TALK: + return NotificationCommand.SERVICE_TYPE_EXTENDED; + } + } + + @Override + protected byte getExtendedNotificationType() { + switch (notification.type) { + case GENERIC_PHONE: + case GENERIC_SMS: + case GENERIC_EMAIL: + default: + case WECHAT: + return 0; + case FACEBOOK: + case FACEBOOK_MESSENGER: + return NotificationCommand.EXTENDED_SERVICE_TYPE_FACEBOOK; + case TWITTER: + return NotificationCommand.EXTENDED_SERVICE_TYPE_TWITTER; + case LINKEDIN: + return NotificationCommand.EXTENDED_SERVICE_TYPE_LINKEDIN; + case WHATSAPP: + return NotificationCommand.EXTENDED_SERVICE_TYPE_WHATSAPP; + case LINE: + return NotificationCommand.EXTENDED_SERVICE_TYPE_LINE; + case KAKAO_TALK: + return NotificationCommand.EXTENDED_SERVICE_TYPE_KAKAOTALK; + } + } + + public NotificationSpec getNotification() { + return notification; + } + + public void setNotification(NotificationSpec notification) { + this.notification = notification; + } + + @Override + protected String getMessage() { + // Based on nodomain.freeyourgadget.gadgetbridge.service.devices.id115.SendNotificationOperation + String message = ""; + + if (notification.phoneNumber != null) { + message += notification.phoneNumber + ": "; + } + + if (notification.sender != null) { + message += notification.sender + " - "; + } else if (notification.title != null) { + message += notification.title + " - "; + } else if (notification.subject != null) { + message += notification.subject + " - "; + } + + if (notification.body != null) { + message += notification.body; + } + + return message; + } +} From 741fdbcbd2414a2b967d203153762da8a4d22ecc Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 05:34:45 -0600 Subject: [PATCH 28/86] Lefun: Tweak notifications for empty message handling --- .../requests/AbstractSendNotificationRequest.java | 2 +- .../lefun/requests/SendCallNotificationRequest.java | 4 ++-- .../lefun/requests/SendNotificationRequest.java | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java index acfb01935..d462674b7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java @@ -68,7 +68,7 @@ public abstract class AbstractSendNotificationRequest extends Request { int maxPayloadLength = NotificationCommand.MAX_PAYLOAD_LENGTH; if (reserveSpaceForExtended) maxPayloadLength -= 1; maxPayloadLength = Math.min(maxPayloadLength, buffer.limit() - buffer.position()); - if (maxPayloadLength == 0) break; + if (maxPayloadLength == 0 && i != 0) break; byte[] payload = new byte[maxPayloadLength]; buffer.get(payload); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendCallNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendCallNotificationRequest.java index bd572f62a..2b68ba517 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendCallNotificationRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendCallNotificationRequest.java @@ -41,11 +41,11 @@ public class SendCallNotificationRequest extends AbstractSendNotificationRequest @Override protected String getMessage() { String message = ""; - if (callNotification.number != null) { + if (callNotification.number != null &&!callNotification.number.isEmpty()) { message = callNotification.number; } - if (callNotification.name != null) { + if (callNotification.name != null && !callNotification.name.isEmpty()) { if (message.length() > 0) { message += " - "; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java index 98405d0bc..7f4262f9c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java @@ -90,19 +90,19 @@ public class SendNotificationRequest extends AbstractSendNotificationRequest { // Based on nodomain.freeyourgadget.gadgetbridge.service.devices.id115.SendNotificationOperation String message = ""; - if (notification.phoneNumber != null) { + if (notification.phoneNumber != null && !notification.phoneNumber.isEmpty()) { message += notification.phoneNumber + ": "; } - if (notification.sender != null) { + if (notification.sender != null && !notification.sender.isEmpty()) { message += notification.sender + " - "; - } else if (notification.title != null) { + } else if (notification.title != null && !notification.title.isEmpty()) { message += notification.title + " - "; - } else if (notification.subject != null) { + } else if (notification.subject != null && !notification.sender.isEmpty()) { message += notification.subject + " - "; } - if (notification.body != null) { + if (notification.body != null && !notification.body.isEmpty()) { message += notification.body; } From b225ab98a80b72c992cd58045982e9b1105e7630 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 05:35:41 -0600 Subject: [PATCH 29/86] Lefun: Fix typo --- .../service/devices/lefun/requests/SendNotificationRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java index 7f4262f9c..689fb6f1f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SendNotificationRequest.java @@ -98,7 +98,7 @@ public class SendNotificationRequest extends AbstractSendNotificationRequest { message += notification.sender + " - "; } else if (notification.title != null && !notification.title.isEmpty()) { message += notification.title + " - "; - } else if (notification.subject != null && !notification.sender.isEmpty()) { + } else if (notification.subject != null && !notification.subject.isEmpty()) { message += notification.subject + " - "; } From e220acb1b406916fed59b8f897e1187a9985fdfc Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 06:00:59 -0600 Subject: [PATCH 30/86] Lefun: Implement live step counting --- .../devices/lefun/LefunDeviceSupport.java | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java index a39b63c75..ed0d1816d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java @@ -20,9 +20,12 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; +import android.content.Intent; import android.net.Uri; import android.widget.Toast; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +47,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FindPhoneComm import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetActivityDataCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetPpgDataCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetSleepDataCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetStepsDataCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.PpgResultCommand; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySample; @@ -55,6 +59,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; @@ -71,7 +76,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetFi import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetPpgDataRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetSleepDataRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.Request; -import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.AbstractSendNotificationRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SendCallNotificationRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SendNotificationRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetAlarmRequest; @@ -85,6 +89,9 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { private final List inProgressRequests = Collections.synchronizedList(new ArrayList()); private final Queue queuedRequests = new ConcurrentLinkedQueue<>(); + private int lastStepsCount = -1; + private int lastStepsTimestamp; + public LefunDeviceSupport() { super(LOG); addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS); @@ -413,10 +420,45 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { return handleAsynchronousPpgResult(data); case LefunConstants.CMD_FIND_PHONE: return handleAntiLoss(data); + case LefunConstants.CMD_STEPS_DATA: + return handleAsynchronousActivity(data); } return false; } + private boolean handleAsynchronousActivity(byte[] data) { + try { + GetStepsDataCommand cmd = new GetStepsDataCommand(); + cmd.deserialize(data); + broadcastSample(cmd); + return true; + } catch (IllegalArgumentException e) { + LOG.error("Failed to handle live activity update", e); + return false; + } + } + + // Adapted from nodomain.freeyourgadget.gadgetbridge.service.devices.makibeshr3.MakibesHR3DeviceSupport.broadcastSample + private void broadcastSample(GetStepsDataCommand command) { + Calendar now = Calendar.getInstance(); + int timestamp = (int) (now.getTimeInMillis() / 1000); + // Workaround for a world where sub-second time resolution is not a thing + if (lastStepsTimestamp == timestamp) return; + lastStepsTimestamp = timestamp; + LefunActivitySample sample = new LefunActivitySample(); + sample.setTimestamp(timestamp); + if (lastStepsCount == -1 || command.getSteps() < lastStepsCount) { + lastStepsCount = command.getSteps(); + } + int diff = command.getSteps() - lastStepsCount; + sample.setSteps(diff); + lastStepsCount = command.getSteps(); + Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES) + .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample) + .putExtra(DeviceService.EXTRA_TIMESTAMP, sample.getTimestamp()); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); + } + private boolean handleAsynchronousPpgResult(byte[] data) { try { PpgResultCommand cmd = new PpgResultCommand(); @@ -424,7 +466,7 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { handlePpgData(cmd); return true; } catch (IllegalArgumentException e) { - LOG.error("Failed to handle response", e); + LOG.error("Failed to PPG result", e); return false; } } From a2090eeccb1a7862a602d4a304559e6f46bb1d52 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 06:17:42 -0600 Subject: [PATCH 31/86] Lefun: Use proper handlers for find phone and battery level --- .../service/devices/lefun/LefunDeviceSupport.java | 6 ++++-- .../devices/lefun/requests/GetBatteryLevelRequest.java | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java index ed0d1816d..960803b9b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java @@ -42,6 +42,7 @@ import de.greenrobot.dao.query.Query; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FindPhoneCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetActivityDataCommand; @@ -475,8 +476,9 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { try { FindPhoneCommand cmd = new FindPhoneCommand(); cmd.deserialize(data); - // TODO: actually pop something that makes sound - GB.toast("Your device is trying to find your phone", Toast.LENGTH_LONG, GB.INFO); + GBDeviceEventFindPhone event = new GBDeviceEventFindPhone(); + event.event = GBDeviceEventFindPhone.Event.START; + evaluateGBDeviceEvent(event); return true; } catch (IllegalArgumentException e) { LOG.error("Failed to handle anti-loss", e); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java index 7a44c83b4..bfef58ab3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetBatteryLevelRequest.java @@ -18,6 +18,7 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetBatteryLevelCommand; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -42,9 +43,12 @@ public class GetBatteryLevelRequest extends Request { cmd.deserialize(data); GBDevice device = getSupport().getDevice(); - device.setBatteryLevel(cmd.getBatteryLevel()); device.setBatteryThresholdPercent((short)15); + GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo(); + batteryInfo.level = (short)((int)cmd.getBatteryLevel() & 0xff); + getSupport().evaluateGBDeviceEvent(batteryInfo); + operationStatus = OperationStatus.FINISHED; } From 2c1167b1b3df950116be4ec5d6338ef1623071e0 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 06:24:57 -0600 Subject: [PATCH 32/86] Lefun: Use proper handler for firmware info --- .../lefun/requests/GetFirmwareInfoRequest.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java index 2186b22d3..7bdab9a78 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetFirmwareInfoRequest.java @@ -18,6 +18,7 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetFirmwareInfoCommand; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -41,13 +42,18 @@ public class GetFirmwareInfoRequest extends Request { GetFirmwareInfoCommand cmd = new GetFirmwareInfoCommand(); cmd.deserialize(data); - GBDevice device = getSupport().getDevice(); - // Last character is a \x1f? Not printable either way. - device.setModel(cmd.getTypeCode().substring(0, 3)); int hardwareVersion = cmd.getHardwareVersion() & 0xffff; int softwareVersion = cmd.getSoftwareVersion() & 0xffff; - device.setFirmwareVersion(String.format("%d.%d", softwareVersion >> 8, softwareVersion & 0xff)); + + GBDeviceEventVersionInfo versionInfo = new GBDeviceEventVersionInfo(); + versionInfo.fwVersion = String.format("%d.%d", softwareVersion >> 8, softwareVersion & 0xff); + // Last character is a \x1f? Not printable either way. + versionInfo.hwVersion = cmd.getTypeCode().substring(0, 3); + getSupport().evaluateGBDeviceEvent(versionInfo); + + GBDevice device = getSupport().getDevice(); device.setFirmwareVersion2(String.format("%d.%d", hardwareVersion >> 8, hardwareVersion & 0xff)); + getSupport().completeInitialization(); operationStatus = OperationStatus.FINISHED; From 31078647b12e3801c0b685b4efd51779a781e493 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 11:35:31 -0600 Subject: [PATCH 33/86] Lefun: Limit notification length further In addition to the 255 pieces limit, the band also uses a byte for total message length, so cap that too to not run over previous message data. --- .../devices/lefun/commands/NotificationCommand.java | 1 + .../requests/AbstractSendNotificationRequest.java | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java index bde32b448..c6dd79a3f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/NotificationCommand.java @@ -37,6 +37,7 @@ public class NotificationCommand extends BaseCommand { public static final byte EXTENDED_SERVICE_TYPE_KAKAOTALK = 6; public static final int MAX_PAYLOAD_LENGTH = 13; + public static final int MAX_MESSAGE_LENGTH = 254; private byte serviceType; private byte totalPieces; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java index d462674b7..4cdb9f3a9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/AbstractSendNotificationRequest.java @@ -59,20 +59,23 @@ public abstract class AbstractSendNotificationRequest extends Request { .getCharacteristic(LefunConstants.UUID_CHARACTERISTIC_LEFUN_WRITE); List commandList = new ArrayList<>(); + int charsWritten = 0; for (int i = 0; i < 0xff; ++i) { - NotificationCommand cmd = new NotificationCommand(); - cmd.setServiceType(notificationType); - cmd.setExtendedServiceType(extendedNotificationType); - cmd.setCurrentPiece((byte) (i + 1)); - int maxPayloadLength = NotificationCommand.MAX_PAYLOAD_LENGTH; if (reserveSpaceForExtended) maxPayloadLength -= 1; maxPayloadLength = Math.min(maxPayloadLength, buffer.limit() - buffer.position()); + maxPayloadLength = Math.min(maxPayloadLength, NotificationCommand.MAX_MESSAGE_LENGTH - charsWritten); if (maxPayloadLength == 0 && i != 0) break; byte[] payload = new byte[maxPayloadLength]; buffer.get(payload); + + NotificationCommand cmd = new NotificationCommand(); + cmd.setServiceType(notificationType); + cmd.setExtendedServiceType(extendedNotificationType); + cmd.setCurrentPiece((byte) (i + 1)); cmd.setPayload(payload); + charsWritten += maxPayloadLength; commandList.add(cmd); } From c8d3d6df2075f83e18eb08890bdc1e02a5b3fee0 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 14:19:46 -0600 Subject: [PATCH 34/86] Fix weird quoting --- app/src/main/res/values-en-rGB/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index 6b636b250..fbd4a9a98 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -104,7 +104,7 @@ Duration Ring duration in seconds Use your band to play your phone\'s ringtone. - Turn on \\\'Find phone\\\' + Turn on \'Find phone\' Find phone Amazfit Bip Lite Wearing left or right\? diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 575016309..489860d61 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -505,7 +505,7 @@ Use heart rate sensor to improve sleep detection Device time offset in hours (for detecting sleep of shift workers) Find phone - Turn on \\\'Find phone\\\' + Turn on \'Find phone\' Use your band to play your phone\'s ringtone. Ring duration in seconds Date format From 6974a86b8773750e4f0b82c669a84130656f4847 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 14:20:20 -0600 Subject: [PATCH 35/86] Lefun: Implement settings --- .../DeviceSettingsPreferenceConst.java | 7 + .../DeviceSpecificSettingsFragment.java | 12 + .../devices/lefun/LefunDeviceCoordinator.java | 13 + .../lefun/commands/SettingsCommand.java | 6 +- .../devices/lefun/LefunDeviceSupport.java | 276 ++++++++++++++++++ .../requests/GetEnabledFeaturesRequest.java | 57 ++++ .../requests/GetGeneralSettingsRequest.java | 60 ++++ .../GetHydrationReminderIntervalRequest.java | 57 ++++ .../GetSedentaryReminderIntervalRequest.java | 57 ++++ .../requests/SetEnabledFeaturesRequest.java | 63 ++++ .../requests/SetGeneralSettingsRequest.java | 78 +++++ .../SetHydrationReminderIntervalRequest.java | 67 +++++ .../lefun/requests/SetLanguageRequest.java | 65 +++++ .../lefun/requests/SetProfileRequest.java | 74 +++++ .../SetSedentaryReminderIntervalRequest.java | 67 +++++ app/src/main/res/values/arrays.xml | 10 + app/src/main/res/values/strings.xml | 8 + app/src/main/res/xml/devicesettings_ampm.xml | 9 + .../main/res/xml/devicesettings_antilost.xml | 9 + .../xml/devicesettings_hydration_reminder.xml | 20 ++ ...evicesettings_lefun_interface_language.xml | 11 + 21 files changed, 1023 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetEnabledFeaturesRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetGeneralSettingsRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetHydrationReminderIntervalRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSedentaryReminderIntervalRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetEnabledFeaturesRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetGeneralSettingsRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetHydrationReminderIntervalRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetLanguageRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetProfileRequest.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetSedentaryReminderIntervalRequest.java create mode 100644 app/src/main/res/xml/devicesettings_ampm.xml create mode 100644 app/src/main/res/xml/devicesettings_antilost.xml create mode 100644 app/src/main/res/xml/devicesettings_hydration_reminder.xml create mode 100644 app/src/main/res/xml/devicesettings_lefun_interface_language.xml diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java index a81ab8f1f..2e574f94b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java @@ -45,4 +45,11 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_LONGSIT_SWITCH = "pref_longsit_switch"; public static final String PREF_LONGSIT_SWITCH_NOSHED = "screen_longsit_noshed"; public static final String PREF_DO_NOT_DISTURB_NOAUTO = "do_not_disturb_no_auto"; + public static final String PREF_FIND_PHONE_ENABLED = "prefs_find_phone"; + + public static final String PREF_ANTILOST_ENABLED = "pref_antilost_enabled"; + public static final String PREF_HYDRATION_SWITCH = "pref_hydration_switch"; + public static final String PREF_HYDRATION_PERIOD = "pref_hydration_period"; + public static final String PREF_AMPM_ENABLED = "pref_ampm_enabled"; + public static final String PREF_LEFUN_INTERFACE_LANGUAGE = "pref_lefun_interface_language"; } \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java index bf753a4e6..0dd9bf6ce 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java @@ -41,6 +41,8 @@ import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference; import nodomain.freeyourgadget.gadgetbridge.util.XTimePreferenceFragment; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ALTITUDE_CALIBRATE; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_AMPM_ENABLED; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ANTILOST_ENABLED; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION; @@ -48,10 +50,14 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DISCONNECTNOTIF_NOSHED; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DO_NOT_DISTURB_NOAUTO; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_FIND_PHONE_ENABLED; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYBRID_HR_DRAW_WIDGET_CIRCLES; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYBRID_HR_FORCE_WHITE_COLOR; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LEFUN_INTERFACE_LANGUAGE; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LONGSIT_PERIOD; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LONGSIT_SWITCH; @@ -351,6 +357,12 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat { addPreferenceHandlerFor(PREF_LONGSIT_PERIOD); addPreferenceHandlerFor(PREF_LONGSIT_SWITCH); addPreferenceHandlerFor(PREF_DO_NOT_DISTURB_NOAUTO); + addPreferenceHandlerFor(PREF_FIND_PHONE_ENABLED); + addPreferenceHandlerFor(PREF_ANTILOST_ENABLED); + addPreferenceHandlerFor(PREF_HYDRATION_SWITCH); + addPreferenceHandlerFor(PREF_HYDRATION_PERIOD); + addPreferenceHandlerFor(PREF_AMPM_ENABLED); + addPreferenceHandlerFor(PREF_LEFUN_INTERFACE_LANGUAGE); addPreferenceHandlerFor(PREF_HYBRID_HR_DRAW_WIDGET_CIRCLES); addPreferenceHandlerFor(PREF_HYBRID_HR_FORCE_WHITE_COLOR); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java index 9e501b765..c8e59bfde 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java @@ -26,6 +26,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import nodomain.freeyourgadget.gadgetbridge.GBException; +import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; @@ -154,4 +155,16 @@ public class LefunDeviceCoordinator extends AbstractDeviceCoordinator { public boolean supportsFindDevice() { return true; } + + @Override + public int[] getSupportedDeviceSpecificSettings(GBDevice device) { + return new int[]{ + R.xml.devicesettings_liftwrist_display_noshed, + R.xml.devicesettings_ampm, + R.xml.devicesettings_antilost, + R.xml.devicesettings_longsit, + R.xml.devicesettings_hydration_reminder, + R.xml.devicesettings_lefun_interface_language, + }; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java index 0fb40964c..10322ecbc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SettingsCommand.java @@ -50,7 +50,7 @@ public class SettingsCommand extends BaseCommand { } public void setOption1(byte option1) { - if (option1 < 0 || option1 > 24) + if (option1 != (byte)0xff && (option1 < 0 || option1 > 24)) throw new IllegalArgumentException("option1 must be between 0 and 24 inclusive"); this.option1 = option1; } @@ -60,7 +60,7 @@ public class SettingsCommand extends BaseCommand { } public void setAmPmIndicator(byte amPmIndicator) { - if (amPmIndicator != AM_PM_12_HOUR && amPmIndicator != AM_PM_24_HOUR) + if (amPmIndicator != (byte)0xff && (amPmIndicator != AM_PM_12_HOUR && amPmIndicator != AM_PM_24_HOUR)) throw new IllegalArgumentException("Indicator must be 12 or 24 hours"); this.amPmIndicator = amPmIndicator; } @@ -70,7 +70,7 @@ public class SettingsCommand extends BaseCommand { } public void setMeasurementUnit(byte measurementUnit) { - if (measurementUnit != MEASUREMENT_UNIT_METRIC && measurementUnit != MEASUREMENT_UNIT_IMPERIAL) + if (measurementUnit != (byte)0xff && (measurementUnit != MEASUREMENT_UNIT_METRIC && measurementUnit != MEASUREMENT_UNIT_IMPERIAL)) throw new IllegalArgumentException(("Unit must be metric or imperial")); this.measurementUnit = measurementUnit; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java index 960803b9b..9ae87a092 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java @@ -21,6 +21,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.content.Intent; +import android.content.SharedPreferences; import android.net.Uri; import android.widget.Toast; @@ -40,22 +41,28 @@ import java.util.concurrent.ConcurrentLinkedQueue; import de.greenrobot.dao.query.Query; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FeaturesCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FindPhoneCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetActivityDataCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetPpgDataCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetSleepDataCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.GetStepsDataCommand; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.PpgResultCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SettingsCommand; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySampleDao; import nodomain.freeyourgadget.gadgetbridge.entities.LefunBiometricSample; import nodomain.freeyourgadget.gadgetbridge.entities.LefunSleepSample; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; @@ -73,16 +80,27 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateA import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.FindDeviceRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetActivityDataRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetBatteryLevelRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetEnabledFeaturesRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetFirmwareInfoRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetGeneralSettingsRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetHydrationReminderIntervalRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetPpgDataRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetSedentaryReminderIntervalRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.GetSleepDataRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.Request; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SendCallNotificationRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SendNotificationRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetAlarmRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetEnabledFeaturesRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetGeneralSettingsRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetHydrationReminderIntervalRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetLanguageRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetProfileRequest; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetSedentaryReminderIntervalRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.SetTimeRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.StartPpgRequest; import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(LefunDeviceSupport.class); @@ -122,6 +140,9 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { GetBatteryLevelRequest batReq = new GetBatteryLevelRequest(this, builder); batReq.perform(); inProgressRequests.add(batReq); + + sendUnitsSetting(builder); + sendUserProfile(builder); } catch (IOException e) { GB.toast(getContext(), "Failed to initialize Lefun device", Toast.LENGTH_SHORT, GB.ERROR, e); @@ -357,12 +378,266 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { @Override public void onSendConfiguration(String config) { + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + switch (config) { + case DeviceSettingsPreferenceConst.PREF_AMPM_ENABLED: { + boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_AMPM_ENABLED, false); + byte ampmValue = enabled ? SettingsCommand.AM_PM_12_HOUR : SettingsCommand.AM_PM_24_HOUR; + sendGeneralSettings(null, ampmValue, (byte) 0xff); + break; + } + case DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED: { + boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED, true); + FeaturesCommand features = getCurrentEnabledFeatures(); + features.setFeature(FeaturesCommand.FEATURE_RAISE_TO_WAKE, enabled); + sendEnabledFeaturesSetting(features); + break; + } + case DeviceSettingsPreferenceConst.PREF_ANTILOST_ENABLED: { + boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_ANTILOST_ENABLED, true); + FeaturesCommand features = getCurrentEnabledFeatures(); + features.setFeature(FeaturesCommand.FEATURE_ANTI_LOST, enabled); + sendEnabledFeaturesSetting(features); + break; + } + case DeviceSettingsPreferenceConst.PREF_LONGSIT_SWITCH: { + boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_LONGSIT_SWITCH, false); + FeaturesCommand features = getCurrentEnabledFeatures(); + features.setFeature(FeaturesCommand.FEATURE_SEDENTARY_REMINDER, enabled); + sendEnabledFeaturesSetting(features); + break; + } + case DeviceSettingsPreferenceConst.PREF_LONGSIT_PERIOD: { + String periodStr = prefs.getString(DeviceSettingsPreferenceConst.PREF_LONGSIT_PERIOD, "60"); + try { + int period = Integer.parseInt(periodStr); + sendSedentaryReminderIntervalSetting(period); + } catch (NumberFormatException e) { + GB.toast(getContext(), "Invalid sedentary reminder interval value", Toast.LENGTH_SHORT, + GB.ERROR, e); + } + break; + } + case DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH: { + boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH, false); + FeaturesCommand features = getCurrentEnabledFeatures(); + features.setFeature(FeaturesCommand.FEATURE_HYDRATION_REMINDER, enabled); + sendEnabledFeaturesSetting(features); + break; + } + case DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD: { + String periodStr = prefs.getString(DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD, "60"); + try { + int period = Integer.parseInt(periodStr); + sendHydrationReminderIntervalSetting(period); + } catch (NumberFormatException e) { + GB.toast(getContext(), "Invalid sedentary reminder interval value", Toast.LENGTH_SHORT, + GB.ERROR, e); + } + break; + } + case SettingsActivity.PREF_MEASUREMENT_SYSTEM: { + sendUnitsSetting(null); + break; + } + case DeviceSettingsPreferenceConst.PREF_LEFUN_INTERFACE_LANGUAGE: { + String value = prefs.getString(DeviceSettingsPreferenceConst.PREF_LEFUN_INTERFACE_LANGUAGE, "0"); + int intValue = Integer.parseInt(value); + sendLanguageSetting((byte) intValue); + break; + } + } + } + private void sendUnitsSetting(TransactionBuilder builder) { + Prefs prefs = GBApplication.getPrefs(); + String units = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, + getContext().getString(R.string.p_unit_metric)); + + byte lefunUnits; + if (getContext().getString(R.string.p_unit_metric).equals(units)) { + lefunUnits = SettingsCommand.MEASUREMENT_UNIT_METRIC; + } else { + lefunUnits = SettingsCommand.MEASUREMENT_UNIT_IMPERIAL; + } + sendGeneralSettings(builder, (byte) 0xff, lefunUnits); + } + + private FeaturesCommand getCurrentEnabledFeatures() { + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + boolean raiseToWakeEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED, true); + boolean antilostEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_ANTILOST_ENABLED, true); + boolean sedentaryEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_LONGSIT_SWITCH, false); + boolean hydrationEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH, false); + + FeaturesCommand cmd = new FeaturesCommand(); + cmd.setFeature(FeaturesCommand.FEATURE_RAISE_TO_WAKE, raiseToWakeEnabled); + cmd.setFeature(FeaturesCommand.FEATURE_ANTI_LOST, antilostEnabled); + cmd.setFeature(FeaturesCommand.FEATURE_SEDENTARY_REMINDER, sedentaryEnabled); + cmd.setFeature(FeaturesCommand.FEATURE_HYDRATION_REMINDER, hydrationEnabled); + + return cmd; + } + + private void sendGeneralSettings(TransactionBuilder builder, byte amPm, byte units) { + boolean givenBuilder = builder != null; + try { + if (!givenBuilder) + builder = performInitialized(SetGeneralSettingsRequest.class.getSimpleName()); + SetGeneralSettingsRequest request = new SetGeneralSettingsRequest(this, builder); + request.setAmPm(amPm); + request.setUnits(units); + request.perform(); + inProgressRequests.add(request); + if (!givenBuilder) + performConnected(builder.getTransaction()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to set settings", Toast.LENGTH_SHORT, + GB.ERROR, e); + } + } + + private void sendUserProfile(TransactionBuilder builder) { + boolean givenBuilder = builder != null; + try { + if (!givenBuilder) + builder = performInitialized(SetProfileRequest.class.getSimpleName()); + SetProfileRequest request = new SetProfileRequest(this, builder); + ActivityUser user = new ActivityUser(); + request.setUser(user); + request.perform(); + inProgressRequests.add(request); + if (!givenBuilder) + performConnected(builder.getTransaction()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to send profile", Toast.LENGTH_SHORT, + GB.ERROR, e); + } + } + + private void sendEnabledFeaturesSetting(FeaturesCommand cmd) { + try { + TransactionBuilder builder = performInitialized(SetEnabledFeaturesRequest.class.getSimpleName()); + SetEnabledFeaturesRequest request = new SetEnabledFeaturesRequest(this, builder); + request.setCmd(cmd); + request.perform(); + inProgressRequests.add(request); + performConnected(builder.getTransaction()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to set enabled features", Toast.LENGTH_SHORT, + GB.ERROR, e); + } + } + + private void sendSedentaryReminderIntervalSetting(int period) { + try { + TransactionBuilder builder = performInitialized(SetSedentaryReminderIntervalRequest.class.getSimpleName()); + SetSedentaryReminderIntervalRequest request = new SetSedentaryReminderIntervalRequest(this, builder); + request.setInterval(period); + request.perform(); + inProgressRequests.add(request); + performConnected(builder.getTransaction()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to set sedentary reminder interval", Toast.LENGTH_SHORT, + GB.ERROR, e); + } + } + + private void sendHydrationReminderIntervalSetting(int period) { + try { + TransactionBuilder builder = performInitialized(SetHydrationReminderIntervalRequest.class.getSimpleName()); + SetHydrationReminderIntervalRequest request = new SetHydrationReminderIntervalRequest(this, builder); + request.setInterval(period); + request.perform(); + inProgressRequests.add(request); + performConnected(builder.getTransaction()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to set hydration reminder interval", Toast.LENGTH_SHORT, + GB.ERROR, e); + } + } + + private void sendLanguageSetting(byte language) { + try { + TransactionBuilder builder = performInitialized(SetLanguageRequest.class.getSimpleName()); + SetLanguageRequest request = new SetLanguageRequest(this, builder); + request.setLanguage(language); + request.perform(); + inProgressRequests.add(request); + performConnected(builder.getTransaction()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to set language", Toast.LENGTH_SHORT, + GB.ERROR, e); + } + } + + public void receiveGeneralSettings(int amPm, int units) { + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + boolean ampmEnabled = amPm == SettingsCommand.AM_PM_12_HOUR; + prefs.edit() + .putBoolean(DeviceSettingsPreferenceConst.PREF_AMPM_ENABLED, ampmEnabled) + .apply(); + } + + public void receiveEnabledFeaturesSetting(FeaturesCommand cmd) { + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + prefs.edit() + .putBoolean(DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED, + cmd.getFeature(FeaturesCommand.FEATURE_RAISE_TO_WAKE)) + .putBoolean(DeviceSettingsPreferenceConst.PREF_LONGSIT_SWITCH, + cmd.getFeature(FeaturesCommand.FEATURE_SEDENTARY_REMINDER)) + .putBoolean(DeviceSettingsPreferenceConst.PREF_HYDRATION_SWITCH, + cmd.getFeature(FeaturesCommand.FEATURE_HYDRATION_REMINDER)) + .putBoolean(DeviceSettingsPreferenceConst.PREF_ANTILOST_ENABLED, + cmd.getFeature(FeaturesCommand.FEATURE_ANTI_LOST)) + .apply(); + } + + public void receiveSedentaryReminderIntervalSetting(int period) { + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + prefs.edit() + .putString(DeviceSettingsPreferenceConst.PREF_LONGSIT_PERIOD, String.valueOf(period)) + .apply(); + } + + public void receiveHydrationReminderIntervalSetting(int period) { + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + prefs.edit() + .putString(DeviceSettingsPreferenceConst.PREF_HYDRATION_PERIOD, String.valueOf(period)) + .apply(); } @Override public void onReadConfiguration(String config) { + // Just going to read all the settings + try { + TransactionBuilder builder = performInitialized("Read settings"); + GetGeneralSettingsRequest getGeneralSettingsRequest + = new GetGeneralSettingsRequest(this, builder); + getGeneralSettingsRequest.perform(); + inProgressRequests.add(getGeneralSettingsRequest); + + GetEnabledFeaturesRequest getEnabledFeaturesRequest + = new GetEnabledFeaturesRequest(this, builder); + getEnabledFeaturesRequest.perform(); + inProgressRequests.add(getEnabledFeaturesRequest); + + GetSedentaryReminderIntervalRequest getSedentaryReminderIntervalRequest + = new GetSedentaryReminderIntervalRequest(this, builder); + getSedentaryReminderIntervalRequest.perform(); + inProgressRequests.add(getSedentaryReminderIntervalRequest); + + GetHydrationReminderIntervalRequest getHydrationReminderIntervalRequest + = new GetHydrationReminderIntervalRequest(this, builder); + getHydrationReminderIntervalRequest.perform(); + inProgressRequests.add(getHydrationReminderIntervalRequest); + + performConnected(builder.getTransaction()); + } catch (IOException e) { + GB.toast(getContext(), "Failed to retrieve settings", Toast.LENGTH_SHORT, + GB.ERROR, e); + } } @Override @@ -489,6 +764,7 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { public void completeInitialization() { gbDevice.setState(GBDevice.State.INITIALIZED); gbDevice.sendDeviceUpdateIntent(getContext()); + onReadConfiguration(""); } private int dateToTimestamp(byte year, byte month, byte day, byte hour, byte minute, byte second) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetEnabledFeaturesRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetEnabledFeaturesRequest.java new file mode 100644 index 000000000..3f7759b02 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetEnabledFeaturesRequest.java @@ -0,0 +1,57 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FeaturesCommand; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; + +public class GetEnabledFeaturesRequest extends Request { + public GetEnabledFeaturesRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + @Override + public byte[] createRequest() { + FeaturesCommand cmd = new FeaturesCommand(); + + cmd.setOp(BaseCommand.OP_GET); + + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + FeaturesCommand cmd = new FeaturesCommand(); + cmd.deserialize(data); + if (cmd.getOp() == BaseCommand.OP_GET) { + getSupport().receiveEnabledFeaturesSetting(cmd); + } + + operationStatus = OperationStatus.FINISHED; + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_FEATURES; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetGeneralSettingsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetGeneralSettingsRequest.java new file mode 100644 index 000000000..9efe28fd7 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetGeneralSettingsRequest.java @@ -0,0 +1,60 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SettingsCommand; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; + +public class GetGeneralSettingsRequest extends Request { + public GetGeneralSettingsRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + @Override + public byte[] createRequest() { + SettingsCommand cmd = new SettingsCommand(); + + cmd.setOp(BaseCommand.OP_GET); + + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + SettingsCommand cmd = new SettingsCommand(); + cmd.deserialize(data); + if (cmd.getOp() == BaseCommand.OP_GET) { + getSupport().receiveGeneralSettings( + (int) cmd.getAmPmIndicator() & 0xff, + (int) cmd.getMeasurementUnit() & 0xff + ); + } + + operationStatus = OperationStatus.FINISHED; + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_SETTINGS; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetHydrationReminderIntervalRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetHydrationReminderIntervalRequest.java new file mode 100644 index 000000000..275effa88 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetHydrationReminderIntervalRequest.java @@ -0,0 +1,57 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.HydrationReminderIntervalCommand; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; + +public class GetHydrationReminderIntervalRequest extends Request { + public GetHydrationReminderIntervalRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + @Override + public byte[] createRequest() { + HydrationReminderIntervalCommand cmd = new HydrationReminderIntervalCommand(); + + cmd.setOp(BaseCommand.OP_GET); + + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + HydrationReminderIntervalCommand cmd = new HydrationReminderIntervalCommand(); + cmd.deserialize(data); + if (cmd.getOp() == BaseCommand.OP_GET) { + getSupport().receiveHydrationReminderIntervalSetting((int) cmd.getHydrationReminderInterval() & 0xff); + } + + operationStatus = OperationStatus.FINISHED; + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSedentaryReminderIntervalRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSedentaryReminderIntervalRequest.java new file mode 100644 index 000000000..8b17df11f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/GetSedentaryReminderIntervalRequest.java @@ -0,0 +1,57 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SedentaryReminderIntervalCommand; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; + +public class GetSedentaryReminderIntervalRequest extends Request { + public GetSedentaryReminderIntervalRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + @Override + public byte[] createRequest() { + SedentaryReminderIntervalCommand cmd = new SedentaryReminderIntervalCommand(); + + cmd.setOp(BaseCommand.OP_GET); + + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + SedentaryReminderIntervalCommand cmd = new SedentaryReminderIntervalCommand(); + cmd.deserialize(data); + if (cmd.getOp() == BaseCommand.OP_GET) { + getSupport().receiveSedentaryReminderIntervalSetting((int) cmd.getSedentaryReminderInterval() & 0xff); + } + + operationStatus = OperationStatus.FINISHED; + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetEnabledFeaturesRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetEnabledFeaturesRequest.java new file mode 100644 index 000000000..fe0cc289a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetEnabledFeaturesRequest.java @@ -0,0 +1,63 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.FeaturesCommand; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; + +public class SetEnabledFeaturesRequest extends Request { + private FeaturesCommand cmd; + + public SetEnabledFeaturesRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + public FeaturesCommand getCmd() { + return cmd; + } + + public void setCmd(FeaturesCommand cmd) { + this.cmd = cmd; + } + + @Override + public byte[] createRequest() { + cmd.setOp(BaseCommand.OP_SET); + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + FeaturesCommand cmd = new FeaturesCommand(); + cmd.deserialize(data); + if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess()) + reportFailure("Could not set enabled features"); + + operationStatus = OperationStatus.FINISHED; + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_FEATURES; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetGeneralSettingsRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetGeneralSettingsRequest.java new file mode 100644 index 000000000..4848daba1 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetGeneralSettingsRequest.java @@ -0,0 +1,78 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SettingsCommand; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; + +public class SetGeneralSettingsRequest extends Request { + private byte amPm; + private byte units; + + public SetGeneralSettingsRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + public byte getAmPm() { + return amPm; + } + + public void setAmPm(byte amPm) { + this.amPm = amPm; + } + + public byte getUnits() { + return units; + } + + public void setUnits(byte units) { + this.units = units; + } + + @Override + public byte[] createRequest() { + SettingsCommand cmd = new SettingsCommand(); + + cmd.setOp(BaseCommand.OP_SET); + cmd.setOption1((byte) 0xff); // Don't set + cmd.setAmPmIndicator(amPm); + cmd.setMeasurementUnit(units); + + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + SettingsCommand cmd = new SettingsCommand(); + cmd.deserialize(data); + if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess()) + reportFailure("Could not set settings"); + + operationStatus = OperationStatus.FINISHED; + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_SETTINGS; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetHydrationReminderIntervalRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetHydrationReminderIntervalRequest.java new file mode 100644 index 000000000..85339e189 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetHydrationReminderIntervalRequest.java @@ -0,0 +1,67 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.HydrationReminderIntervalCommand; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; + +public class SetHydrationReminderIntervalRequest extends Request { + private int interval; + + public SetHydrationReminderIntervalRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + public int getInterval() { + return interval; + } + + public void setInterval(int interval) { + this.interval = interval; + } + + @Override + public byte[] createRequest() { + HydrationReminderIntervalCommand cmd = new HydrationReminderIntervalCommand(); + + cmd.setOp(BaseCommand.OP_SET); + cmd.setHydrationReminderInterval((byte) interval); + + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + HydrationReminderIntervalCommand cmd = new HydrationReminderIntervalCommand(); + cmd.deserialize(data); + if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess()) + reportFailure("Could not set hydration reminder interval"); + + operationStatus = OperationStatus.FINISHED; + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_HYDRATION_REMINDER_INTERVAL; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetLanguageRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetLanguageRequest.java new file mode 100644 index 000000000..db168bf8a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetLanguageRequest.java @@ -0,0 +1,65 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SetLanguageCommand; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; + +public class SetLanguageRequest extends Request { + private byte language; + + public SetLanguageRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + public byte getLanguage() { + return language; + } + + public void setLanguage(byte language) { + this.language = language; + } + + @Override + public byte[] createRequest() { + SetLanguageCommand cmd = new SetLanguageCommand(); + + cmd.setLanguage(language); + + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + SetLanguageCommand cmd = new SetLanguageCommand(); + cmd.deserialize(data); + if (!cmd.isSetSuccess()) + reportFailure("Could not set language"); + + operationStatus = OperationStatus.FINISHED; + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_LANGUAGE; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetProfileRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetProfileRequest.java new file mode 100644 index 000000000..7b1aa29ac --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetProfileRequest.java @@ -0,0 +1,74 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.ProfileCommand; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; + +public class SetProfileRequest extends Request { + private ActivityUser user; + + public SetProfileRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + public ActivityUser getUser() { + return user; + } + + public void setUser(ActivityUser user) { + this.user = user; + } + + @Override + public byte[] createRequest() { + ProfileCommand cmd = new ProfileCommand(); + + cmd.setOp(BaseCommand.OP_SET); + // No "other" option available, only male or female + cmd.setGender(user.getGender() == ActivityUser.GENDER_FEMALE + ? ProfileCommand.GENDER_FEMALE + : ProfileCommand.GENDER_MALE); + cmd.setHeight((byte) user.getHeightCm()); + cmd.setWeight((byte) user.getWeightKg()); + cmd.setAge((byte) user.getAge()); + + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + ProfileCommand cmd = new ProfileCommand(); + cmd.deserialize(data); + if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess()) + reportFailure("Could not set profile"); + + operationStatus = OperationStatus.FINISHED; + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_PROFILE; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetSedentaryReminderIntervalRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetSedentaryReminderIntervalRequest.java new file mode 100644 index 000000000..c048c6ccb --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/SetSedentaryReminderIntervalRequest.java @@ -0,0 +1,67 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.BaseCommand; +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands.SedentaryReminderIntervalCommand; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; + +public class SetSedentaryReminderIntervalRequest extends Request { + private int interval; + + public SetSedentaryReminderIntervalRequest(LefunDeviceSupport support, TransactionBuilder builder) { + super(support, builder); + } + + public int getInterval() { + return interval; + } + + public void setInterval(int interval) { + this.interval = interval; + } + + @Override + public byte[] createRequest() { + SedentaryReminderIntervalCommand cmd = new SedentaryReminderIntervalCommand(); + + cmd.setOp(BaseCommand.OP_SET); + cmd.setSedentaryReminderInterval((byte) interval); + + return cmd.serialize(); + } + + @Override + public void handleResponse(byte[] data) { + SedentaryReminderIntervalCommand cmd = new SedentaryReminderIntervalCommand(); + cmd.deserialize(data); + if (cmd.getOp() == BaseCommand.OP_SET && !cmd.isSetSuccess()) + reportFailure("Could not set sedentary reminder interval"); + + operationStatus = OperationStatus.FINISHED; + } + + @Override + public int getCommandId() { + return LefunConstants.CMD_SEDENTARY_REMINDER_INTERVAL; + } +} diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 346b118c0..fb85f5c2e 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -976,4 +976,14 @@ 7days 30days + + + @string/english + @string/simplified_chinese + + + + 0 + 1 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 489860d61..a06de974a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -547,6 +547,14 @@ Choose the shortcuts on the band screen Force black on white color scheme Useful if you your watch has dark hands + Hydration reminder + The band will vibrate to remind you to drink water + Hydration reminder interval (in minutes) + Anti-loss + The band will vibrate if the Bluetooth connection to your phone disconnects + 12-hour display + Display time in 12-hour format with AM/PM indicator + Interface language Automatic Simplified Chinese Traditional Chinese diff --git a/app/src/main/res/xml/devicesettings_ampm.xml b/app/src/main/res/xml/devicesettings_ampm.xml new file mode 100644 index 000000000..a8e96db05 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_ampm.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/xml/devicesettings_antilost.xml b/app/src/main/res/xml/devicesettings_antilost.xml new file mode 100644 index 000000000..a4c9dc705 --- /dev/null +++ b/app/src/main/res/xml/devicesettings_antilost.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/xml/devicesettings_hydration_reminder.xml b/app/src/main/res/xml/devicesettings_hydration_reminder.xml new file mode 100644 index 000000000..95edf232b --- /dev/null +++ b/app/src/main/res/xml/devicesettings_hydration_reminder.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/app/src/main/res/xml/devicesettings_lefun_interface_language.xml b/app/src/main/res/xml/devicesettings_lefun_interface_language.xml new file mode 100644 index 000000000..a1943847c --- /dev/null +++ b/app/src/main/res/xml/devicesettings_lefun_interface_language.xml @@ -0,0 +1,11 @@ + + + + From 1242009b5514472457fbf24d0f9d4f045866e38c Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 15:08:11 -0600 Subject: [PATCH 36/86] Lefun: Add comments --- .../devices/lefun/LefunConstants.java | 15 +- .../devices/lefun/LefunDeviceCoordinator.java | 3 + .../devices/lefun/LefunFeatureSupport.java | 20 +++ .../devices/lefun/LefunSampleProvider.java | 7 +- .../devices/lefun/commands/BaseCommand.java | 145 +++++++++++++---- .../devices/lefun/LefunDeviceSupport.java | 151 ++++++++++++++++++ .../lefun/requests/MultiFetchRequest.java | 11 ++ .../devices/lefun/requests/Request.java | 60 +++++++ 8 files changed, 372 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java index 77a6b1afe..4af1aee5e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunConstants.java @@ -22,6 +22,9 @@ import java.util.UUID; import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID; +/** + * Constants used with Lefun device support + */ public class LefunConstants { // BLE UUIDs public static final UUID UUID_SERVICE_LEFUN = UUID.fromString(String.format(BASE_UUID, "18D0")); @@ -31,16 +34,12 @@ public class LefunConstants { // Coordinator constants public static final String ADVERTISEMENT_NAME = "Lefun"; public static final String MANUFACTURER_NAME = "Teng Jin Da"; - public static int NUM_ALARM_SLOTS = 5; - // Commands - public static final byte CMD_REQUEST_ID = (byte)0xab; + public static final byte CMD_REQUEST_ID = (byte) 0xab; public static final byte CMD_RESPONSE_ID = 0x5a; - public static final int CMD_MAX_LENGTH = 20; // 3 header bytes plus checksum public static final int CMD_HEADER_LENGTH = 4; - public static final byte CMD_FIRMWARE_INFO = 0x00; public static final byte CMD_BONDING_REQUEST = 0x01; public static final byte CMD_SETTINGS = 0x02; @@ -67,25 +66,23 @@ public class LefunConstants { public static final byte CMD_LANGUAGE = 0x21; public static final byte CMD_UNKNOWN_22 = 0x22; public static final byte CMD_UNKNOWN_25 = 0x25; - public static final byte CMD_UNKNOWN_80 = (byte)0x80; - + public static final byte CMD_UNKNOWN_80 = (byte) 0x80; public static final int PPG_TYPE_INVALID = -1; public static final int PPG_TYPE_HEART_RATE = 0; public static final int PPG_TYPE_BLOOD_PRESSURE = 1; public static final int PPG_TYPE_BLOOD_OXYGEN = 2; public static final int PPG_TYPE_COUNT = 3; - // DB activity kinds public static final int DB_ACTIVITY_KIND_UNKNOWN = 0; public static final int DB_ACTIVITY_KIND_ACTIVITY = 1; public static final int DB_ACTIVITY_KIND_HEART_RATE = 2; public static final int DB_ACTIVITY_KIND_LIGHT_SLEEP = 3; public static final int DB_ACTIVITY_KIND_DEEP_SLEEP = 4; - // Pseudo-intensity public static final int INTENSITY_MIN = 0; public static final int INTENSITY_DEEP_SLEEP = 1; public static final int INTENSITY_LIGHT_SLEEP = 2; public static final int INTENSITY_AWAKE = 3; public static final int INTENSITY_MAX = 4; + public static int NUM_ALARM_SLOTS = 5; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java index c8e59bfde..47c02022d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java @@ -41,6 +41,9 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants. import static nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants.MANUFACTURER_NAME; import static nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants.NUM_ALARM_SLOTS; +/** + * Device coordinator for Lefun band + */ public class LefunDeviceCoordinator extends AbstractDeviceCoordinator { @Override protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunFeatureSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunFeatureSupport.java index ba76fb046..9b7a11df3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunFeatureSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunFeatureSupport.java @@ -18,6 +18,9 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.lefun; +/** + * Feature support utilities for Lefun devices + */ public class LefunFeatureSupport { public static final int SUPPORT_HEART_RATE = 1 << 2; public static final int SUPPORT_BLOOD_PRESSURE = 1 << 3; @@ -31,10 +34,27 @@ public class LefunFeatureSupport { public static final int RESERVE_WALLPAPER = 1 << 6; public static final int RESERVE_REMOTE_CAMERA = 1 << 7; + /** + * Checks whether a feature is supported + * + * @param deviceSupport the feature flags from the device + * @param featureSupport the feature you want to check + * @return whether feature is supported + */ public static boolean checkSupported(short deviceSupport, int featureSupport) { return (deviceSupport & featureSupport) == featureSupport; } + /** + * Checks whether a feature is not reserved + *

+ * Reserve flags indicate a feature is not available if set. This function takes care of the + * inverting for you, so if you get true, the feature is available. + * + * @param deviceReserve the reserve flags from the device + * @param featureReserve the reserve flag you want to check + * @return whether feature is supported + */ public static boolean checkNotReserved(short deviceReserve, int featureReserve) { return !((deviceReserve & featureReserve) == featureReserve); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunSampleProvider.java index 861cd089d..606782fc8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunSampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunSampleProvider.java @@ -24,14 +24,15 @@ import androidx.annotation.Nullable; import de.greenrobot.dao.AbstractDao; import de.greenrobot.dao.Property; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider; -import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; -import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.LefunActivitySampleDao; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +/** + * Sample provider for Lefun devices + */ public class LefunSampleProvider extends AbstractSampleProvider { public LefunSampleProvider(GBDevice device, DaoSession session) { super(device, session); @@ -91,7 +92,7 @@ public class LefunSampleProvider extends AbstractSampleProvider>= 1; } } - return (byte)checksum; + return (byte) checksum; + } + + /** + * When implemented in a subclass, parses the response from a device + * + * @param id the command ID + * @param params the params buffer + */ + abstract protected void deserializeParams(byte id, ByteBuffer params); + + /** + * When implemented in a subclass, provides the arguments to send in the command + * + * @param params the params buffer to write to + * @return the command ID + */ + abstract protected byte serializeParams(ByteBuffer params); + + /** + * Deserialize a response from the device + * + * @param response the response data to deserialize + */ + public void deserialize(byte[] response) { + if (response.length < LefunConstants.CMD_HEADER_LENGTH || response.length < response[1]) + throw new IllegalArgumentException("Response is too short"); + if (calculateChecksum(response, 0, response[1] - 1) != response[response[1] - 1]) + throw new IllegalArgumentException("Incorrect message checksum"); + ByteBuffer buffer = ByteBuffer.wrap(response, LefunConstants.CMD_HEADER_LENGTH - 1, + response[1] - LefunConstants.CMD_HEADER_LENGTH); + buffer.order(ByteOrder.BIG_ENDIAN); + deserializeParams(response[2], buffer); + } + + /** + * Serializes a command to send to the device + * + * @return the data to send to the device + */ + public byte[] serialize() { + ByteBuffer buffer = ByteBuffer.allocate(LefunConstants.CMD_MAX_LENGTH - LefunConstants.CMD_HEADER_LENGTH); + buffer.order(ByteOrder.BIG_ENDIAN); + byte id = serializeParams(buffer); + return makeCommand(id, buffer); } /** @@ -87,25 +127,57 @@ public abstract class BaseCommand { return request; } + /** + * Throws a standard parameters length exception + */ protected void throwUnexpectedLength() { throw new IllegalArgumentException("Unexpected parameters length"); } + /** + * Checks for valid command ID and throws if wrong ID provided + * + * @param id command ID from device + * @param expectedId expected command ID + */ protected void validateId(byte id, byte expectedId) { if (id != expectedId) throw new IllegalArgumentException("Wrong command ID"); } + /** + * Checks for valid command ID and command length + * + * @param id command ID from device + * @param params params buffer from device + * @param expectedId expected command ID + * @param expectedLength expected params length + */ protected void validateIdAndLength(byte id, ByteBuffer params, byte expectedId, int expectedLength) { validateId(id, expectedId); if (params.limit() - params.position() != expectedLength) throwUnexpectedLength(); } + /** + * Gets whether a bit is set + * + * @param value the value to check against + * @param mask the bitmask + * @return whether the bits indicated by the bitmask are set + */ protected boolean getBit(int value, int mask) { return (value & mask) != 0; } + /** + * Sets a bit in a value + * + * @param value the value to modify + * @param mask the bitmask + * @param set whether to set or clear the bits + * @return the modified value + */ protected int setBit(int value, int mask, boolean set) { if (set) { return value | mask; @@ -114,26 +186,43 @@ public abstract class BaseCommand { } } + /** + * Sets a bit in a value + * + * @param value the value to modify + * @param mask the bitmask + * @param set whether to set or clear the bits + * @return the modified value + */ protected short setBit(short value, int mask, boolean set) { if (set) { - return (short)(value | mask); + return (short) (value | mask); } else { - return (short)(value & ~mask); + return (short) (value & ~mask); } } + /** + * Sets a bit in a value + * + * @param value the value to modify + * @param mask the bitmask + * @param set whether to set or clear the bits + * @return the modified value + */ protected byte setBit(byte value, int mask, boolean set) { if (set) { - return (byte)(value | mask); + return (byte) (value | mask); } else { - return (byte)(value & ~mask); + return (byte) (value & ~mask); } } /** * Find index of first bit that is set - * @param value - * @return + * + * @param value the value to look at + * @return the index of the lowest set bit, starting at 0 for least significant bit; -1 if no bits set */ protected int getLowestSetBitIndex(int value) { if (value == 0) return -1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java index 9ae87a092..a71511160 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java @@ -102,6 +102,9 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.requests.Start import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; +/** + * Device support class for Lefun devices + */ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { private static final Logger LOG = LoggerFactory.getLogger(LefunDeviceSupport.class); @@ -111,6 +114,9 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { private int lastStepsCount = -1; private int lastStepsTimestamp; + /** + * Instantiates a new instance of LefunDeviceSupport + */ public LefunDeviceSupport() { super(LOG); addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS); @@ -449,6 +455,11 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } + /** + * Sends unit of measurement to the device + * + * @param builder the transaction builder to append to + */ private void sendUnitsSetting(TransactionBuilder builder) { Prefs prefs = GBApplication.getPrefs(); String units = prefs.getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, @@ -463,6 +474,11 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { sendGeneralSettings(builder, (byte) 0xff, lefunUnits); } + /** + * Gets a features command with the currently enabled features set + * + * @return the features command + */ private FeaturesCommand getCurrentEnabledFeatures() { SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); boolean raiseToWakeEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED, true); @@ -479,6 +495,13 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { return cmd; } + /** + * Sends general settings to the device + * + * @param builder the transaction builder to append to + * @param amPm AM/PM indicator setting + * @param units units of measurement setting + */ private void sendGeneralSettings(TransactionBuilder builder, byte amPm, byte units) { boolean givenBuilder = builder != null; try { @@ -497,6 +520,11 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } + /** + * Sends the user profile to the device + * + * @param builder the transaction builder to append to + */ private void sendUserProfile(TransactionBuilder builder) { boolean givenBuilder = builder != null; try { @@ -515,6 +543,11 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } + /** + * Sends enabled features settings to the device + * + * @param cmd the features command to send + */ private void sendEnabledFeaturesSetting(FeaturesCommand cmd) { try { TransactionBuilder builder = performInitialized(SetEnabledFeaturesRequest.class.getSimpleName()); @@ -529,6 +562,11 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } + /** + * Sends the sedentary reminder interval setting to the device + * + * @param period the reminder interval + */ private void sendSedentaryReminderIntervalSetting(int period) { try { TransactionBuilder builder = performInitialized(SetSedentaryReminderIntervalRequest.class.getSimpleName()); @@ -543,6 +581,11 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } + /** + * Sends the hydration reminder interval setting to the device + * + * @param period the reminder interval + */ private void sendHydrationReminderIntervalSetting(int period) { try { TransactionBuilder builder = performInitialized(SetHydrationReminderIntervalRequest.class.getSimpleName()); @@ -557,6 +600,11 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } + /** + * Sends the language selection to the device + * + * @param language the language selection + */ private void sendLanguageSetting(byte language) { try { TransactionBuilder builder = performInitialized(SetLanguageRequest.class.getSimpleName()); @@ -571,6 +619,12 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } + /** + * Stores received general settings to prefs + * + * @param amPm AM/PM indicator setting + * @param units units of measurement setting + */ public void receiveGeneralSettings(int amPm, int units) { SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); boolean ampmEnabled = amPm == SettingsCommand.AM_PM_12_HOUR; @@ -579,6 +633,11 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { .apply(); } + /** + * Stores received enabled features settings to prefs + * + * @param cmd the features command + */ public void receiveEnabledFeaturesSetting(FeaturesCommand cmd) { SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); prefs.edit() @@ -593,6 +652,11 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { .apply(); } + /** + * Stores received sedentary reminder interval setting to prefs + * + * @param period the interval + */ public void receiveSedentaryReminderIntervalSetting(int period) { SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); prefs.edit() @@ -600,6 +664,11 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { .apply(); } + /** + * Stores received hydration reminder interval setting to prefs + * + * @param period the interval + */ public void receiveHydrationReminderIntervalSetting(int period) { SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); prefs.edit() @@ -689,6 +758,13 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { return super.onCharacteristicChanged(gatt, characteristic); } + /** + * Handles commands from the device that are not typically associated with a request + * + * @param commandId the command ID + * @param data the entire response + * @return whether the response has been handled + */ private boolean handleAsynchronousResponse(byte commandId, byte[] data) { // Assume data already checked for correct response code and length switch (commandId) { @@ -702,6 +778,12 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { return false; } + /** + * Handles live steps data + * + * @param data the response + * @return whether the response has been handled + */ private boolean handleAsynchronousActivity(byte[] data) { try { GetStepsDataCommand cmd = new GetStepsDataCommand(); @@ -715,6 +797,12 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } // Adapted from nodomain.freeyourgadget.gadgetbridge.service.devices.makibeshr3.MakibesHR3DeviceSupport.broadcastSample + + /** + * Broadcasts live sample + * + * @param command the steps data + */ private void broadcastSample(GetStepsDataCommand command) { Calendar now = Calendar.getInstance(); int timestamp = (int) (now.getTimeInMillis() / 1000); @@ -735,6 +823,12 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); } + /** + * Handles PPG result from earlier request + * + * @param data the response + * @return whether the response has been handled + */ private boolean handleAsynchronousPpgResult(byte[] data) { try { PpgResultCommand cmd = new PpgResultCommand(); @@ -747,6 +841,12 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } + /** + * Handles find phone request + * + * @param data the response + * @return whether the response has been handled + */ private boolean handleAntiLoss(byte[] data) { try { FindPhoneCommand cmd = new FindPhoneCommand(); @@ -761,12 +861,26 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } + /** + * Callback when device info has been obtained + */ public void completeInitialization() { gbDevice.setState(GBDevice.State.INITIALIZED); gbDevice.sendDeviceUpdateIntent(getContext()); onReadConfiguration(""); } + /** + * Converts Lefun datetime format to Unix timestamp + * + * @param year the year (2 digits based on 2000) + * @param month the month + * @param day the day + * @param hour the hour + * @param minute the minute + * @param second the second + * @return Unix timestamp of the datetime + */ private int dateToTimestamp(byte year, byte month, byte day, byte hour, byte minute, byte second) { Calendar calendar = Calendar.getInstance(); calendar.set( @@ -780,6 +894,13 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { return (int) (calendar.getTimeInMillis() / 1000); } + /** + * Fetches an activity sample given the timestamp + * + * @param session DAO session + * @param timestamp the timestamp + * @return fetched activity or null if none exists + */ private LefunActivitySample getActivitySample(DaoSession session, int timestamp) { LefunActivitySampleDao dao = session.getLefunActivitySampleDao(); Long userId = DBHelper.getUser(session).getId(); @@ -792,6 +913,11 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { return q.unique(); } + /** + * Processes activity data and stores it + * + * @param command the activity data + */ public void handleActivityData(GetActivityDataCommand command) { try (DBHandler handler = GBApplication.acquireDB()) { DaoSession session = handler.getDaoSession(); @@ -818,6 +944,13 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } + /** + * Processes PPG data and stores it + * + * @param timestamp the timestamp + * @param ppgType the PPG type + * @param ppgData the data from the PPG operation + */ private void handlePpgData(int timestamp, int ppgType, byte[] ppgData) { int ppgData0 = ppgData[0] & 0xff; int ppgData1 = ppgData.length > 1 ? ppgData[1] & 0xff : 0; @@ -851,6 +984,11 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } + /** + * Processes PPG data from bulk get operation + * + * @param command the PPG data + */ public void handlePpgData(GetPpgDataCommand command) { int timestamp = dateToTimestamp(command.getYear(), command.getMonth(), command.getDay(), command.getHour(), command.getMinute(), command.getSecond()); @@ -859,6 +997,11 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { handlePpgData(timestamp, ppgType, ppgData); } + /** + * Processes PPG result received as a result of requesting PPG operation + * + * @param command the PPG result + */ public void handlePpgData(PpgResultCommand command) { int timestamp = (int) (Calendar.getInstance().getTimeInMillis() / 1000); int ppgType = command.getPpgType(); @@ -866,6 +1009,11 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { handlePpgData(timestamp, ppgType, ppgData); } + /** + * Processes bulk sleep data + * + * @param command the sleep data + */ public void handleSleepData(GetSleepDataCommand command) { try (DBHandler handler = GBApplication.acquireDB()) { DaoSession session = handler.getDaoSession(); @@ -915,6 +1063,9 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { } } + /** + * Runs the next queued request + */ public void runNextQueuedRequest() { Request request = queuedRequests.poll(); if (request != null) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/MultiFetchRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/MultiFetchRequest.java index 031d8f6bd..1713739f4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/MultiFetchRequest.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/MultiFetchRequest.java @@ -31,7 +31,14 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.lefun.LefunDeviceSup import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.OperationStatus; import nodomain.freeyourgadget.gadgetbridge.util.GB; +/** + * Represents a request that receives several responses + */ public abstract class MultiFetchRequest extends Request { + /** + * Instantiates a new MultiFetchRequest + * @param support the device support + */ protected MultiFetchRequest(LefunDeviceSupport support) { super(support, null); removeAfterHandling = false; @@ -97,5 +104,9 @@ public abstract class MultiFetchRequest extends Request { return true; } + /** + * Gets the display operation name + * @return the operation name + */ protected abstract String getOperationName(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java index c11c33678..95d50e2cc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/requests/Request.java @@ -34,16 +34,31 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.Op import nodomain.freeyourgadget.gadgetbridge.util.GB; // Ripped from nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request + +/** + * Basic request for operations with Lefun devices + */ public abstract class Request extends AbstractBTLEOperation { protected TransactionBuilder builder; protected boolean removeAfterHandling = true; private Logger logger = (Logger) LoggerFactory.getLogger(getName()); + /** + * Instantiates Request + * + * @param support the device support + * @param builder the transaction builder to use + */ protected Request(LefunDeviceSupport support, TransactionBuilder builder) { super(support); this.builder = builder; } + /** + * Gets the transaction builder + * + * @return the transaction builder + */ public TransactionBuilder getTransactionBuilder() { return builder; } @@ -57,36 +72,81 @@ public abstract class Request extends AbstractBTLEOperation getSupport().performConnected(builder.getTransaction()); } + /** + * When implemented in a subclass, provides the request bytes to send to the device + * + * @return the request bytes + */ public abstract byte[] createRequest(); + /** + * When overridden in a subclass, handles the response to the current command + * + * @param data the response data + */ public void handleResponse(byte[] data) { operationStatus = OperationStatus.FINISHED; } + /** + * Gets the class name of this instance + * + * @return the class name + */ public String getName() { Class thisClass = getClass(); while (thisClass.isAnonymousClass()) thisClass = thisClass.getSuperclass(); return thisClass.getSimpleName(); } + /** + * Logs a debug message + * + * @param message the message to log + */ protected void log(String message) { logger.debug(message); } + /** + * When implemented in a subclass, returns the command ID associated with the current request + * + * @return the command ID + */ public abstract int getCommandId(); + /** + * Gets whether the request will queue itself + * + * @return whether the request is self-queuing + */ public boolean isSelfQueue() { return false; } + /** + * Gets whether the request expects a response + * + * @return whether the request expects a response + */ public boolean expectsResponse() { return true; } + /** + * Gets whether the response should be removed from in progress requests list after handling + * + * @return whether the response should be removed after handling + */ public boolean shouldRemoveAfterHandling() { return removeAfterHandling; } + /** + * Reports an error to the user + * + * @param message the message to show + */ protected void reportFailure(String message) { GB.toast(getContext(), message, Toast.LENGTH_SHORT, GB.ERROR); } From 0f123022f921b4e2ca16d2f7037f9d22a44cc47a Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 15:18:22 -0600 Subject: [PATCH 37/86] Lefun: Fix license headers --- .../lefun/commands/FindDeviceCommand.java | 8 ++++---- .../lefun/commands/GetFirmwareInfoCommand.java | 8 ++++---- .../lefun/commands/GetPpgDataCommand.java | 18 ++++++++++++++++++ .../lefun/commands/GetSleepDataCommand.java | 4 ++-- .../lefun/commands/SetLanguageCommand.java | 12 ++++++------ 5 files changed, 34 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindDeviceCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindDeviceCommand.java index 433bb9faa..aa3d694bc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindDeviceCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/FindDeviceCommand.java @@ -1,7 +1,3 @@ -package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; - -import java.nio.ByteBuffer; - /* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele Gobbetti Copyright (C) 2020 Yukai Li @@ -20,6 +16,10 @@ import java.nio.ByteBuffer; You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; public class FindDeviceCommand extends BaseCommand { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetFirmwareInfoCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetFirmwareInfoCommand.java index 301efdff9..5dc94bd65 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetFirmwareInfoCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetFirmwareInfoCommand.java @@ -1,6 +1,3 @@ -package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; - -import java.nio.ByteBuffer; /* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele Gobbetti Copyright (C) 2020 Yukai Li @@ -19,6 +16,9 @@ import java.nio.ByteBuffer; You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; @@ -59,7 +59,7 @@ public class GetFirmwareInfoCommand extends BaseCommand { protected void deserializeParams(byte id, ByteBuffer params) { validateIdAndLength(id, params, LefunConstants.CMD_FIRMWARE_INFO, 16); - supportCode = (short)(params.get() | (params.get() << 8)); + supportCode = (short) (params.get() | (params.get() << 8)); devTypeReserveCode = params.getShort(); byte[] typeCodeBytes = new byte[4]; params.get(typeCodeBytes); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java index 03b2e4246..96e1e08a3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetPpgDataCommand.java @@ -1,3 +1,21 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti + Copyright (C) 2020 Yukai Li + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; import java.nio.ByteBuffer; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepDataCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepDataCommand.java index c0ebc6381..00fa6c0bf 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepDataCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/GetSleepDataCommand.java @@ -1,5 +1,3 @@ -package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; - /* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele Gobbetti Copyright (C) 2020 Yukai Li @@ -18,6 +16,8 @@ package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + import java.nio.ByteBuffer; import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetLanguageCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetLanguageCommand.java index 63455fac2..1ea1e7d06 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetLanguageCommand.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/commands/SetLanguageCommand.java @@ -1,9 +1,3 @@ -package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; - -import java.nio.ByteBuffer; - -import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; - /* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele Gobbetti Copyright (C) 2020 Yukai Li @@ -22,6 +16,12 @@ import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ +package nodomain.freeyourgadget.gadgetbridge.devices.lefun.commands; + +import java.nio.ByteBuffer; + +import nodomain.freeyourgadget.gadgetbridge.devices.lefun.LefunConstants; + public class SetLanguageCommand extends BaseCommand { private byte language; From b0621028ae95a12edbf461a58fac6dc6400f3d17 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 16:09:05 -0600 Subject: [PATCH 38/86] Lefun: Improve wording --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a06de974a..8b873b16a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -551,7 +551,7 @@ The band will vibrate to remind you to drink water Hydration reminder interval (in minutes) Anti-loss - The band will vibrate if the Bluetooth connection to your phone disconnects + The band will vibrate if your phone disconnects from the band 12-hour display Display time in 12-hour format with AM/PM indicator Interface language From bc33b4b6e03d5609bdef724b7d0758d188d797c6 Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Mon, 5 Oct 2020 18:47:12 -0600 Subject: [PATCH 39/86] Add Lefun to supported devices in readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 71edbade7..3e31b8871 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ vendor's servers. * iTag * ID115 * JYou Y5 +* Lefun * Lenovo Watch 9 * Lenovo Watch X (Plus) [Wiki](https://codeberg.org/mamutcho/Gadgetbridge/wiki) * Liveview @@ -94,6 +95,7 @@ Please see [FEATURES.md](https://codeberg.org/Freeyourgadget/Gadgetbridge/src/ma * Pavel Elagin (JYou Y5) * Taavi Eomäe (iTag) * Erik Bloß (TLW64) +* Yukai Li (Lefun) ## Contribute From d6bed776c5c51aa5f19e2d8414b46e47aacd370d Mon Sep 17 00:00:00 2001 From: Yukai Li Date: Tue, 6 Oct 2020 09:22:04 -0600 Subject: [PATCH 40/86] Lefun: Use existing time format setting with handling for automatic format Also fix "interface language" string name --- .../devices/lefun/LefunDeviceCoordinator.java | 2 +- .../devices/lefun/LefunDeviceSupport.java | 65 +++++++++++++++++-- app/src/main/res/values/strings.xml | 4 +- app/src/main/res/xml/devicesettings_ampm.xml | 9 --- ...evicesettings_lefun_interface_language.xml | 2 +- 5 files changed, 61 insertions(+), 21 deletions(-) delete mode 100644 app/src/main/res/xml/devicesettings_ampm.xml diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java index 47c02022d..3ffc33e06 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/lefun/LefunDeviceCoordinator.java @@ -163,7 +163,7 @@ public class LefunDeviceCoordinator extends AbstractDeviceCoordinator { public int[] getSupportedDeviceSpecificSettings(GBDevice device) { return new int[]{ R.xml.devicesettings_liftwrist_display_noshed, - R.xml.devicesettings_ampm, + R.xml.devicesettings_timeformat, R.xml.devicesettings_antilost, R.xml.devicesettings_longsit, R.xml.devicesettings_hydration_reminder, diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java index a71511160..489eedc3b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/lefun/LefunDeviceSupport.java @@ -23,6 +23,7 @@ import android.bluetooth.BluetoothGattCharacteristic; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; +import android.text.format.DateFormat; import android.widget.Toast; import androidx.localbroadcastmanager.content.LocalBroadcastManager; @@ -147,6 +148,7 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { batReq.perform(); inProgressRequests.add(batReq); + sendAmPmSettingIfNecessary(builder); sendUnitsSetting(builder); sendUserProfile(builder); } catch (IOException e) { @@ -386,10 +388,8 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { public void onSendConfiguration(String config) { SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); switch (config) { - case DeviceSettingsPreferenceConst.PREF_AMPM_ENABLED: { - boolean enabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_AMPM_ENABLED, false); - byte ampmValue = enabled ? SettingsCommand.AM_PM_12_HOUR : SettingsCommand.AM_PM_24_HOUR; - sendGeneralSettings(null, ampmValue, (byte) 0xff); + case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT: { + sendAmPmSetting(null); break; } case DeviceSettingsPreferenceConst.PREF_LIFTWRIST_NOSHED: { @@ -474,6 +474,47 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { sendGeneralSettings(builder, (byte) 0xff, lefunUnits); } + /** + * Send AM/PM indicator setting based on time format pref + * + * @param builder the transaction builder to append to + */ + private void sendAmPmSetting(TransactionBuilder builder) { + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + String ampmSetting = prefs.getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, + getContext().getString(R.string.p_timeformat_auto)); + + byte ampmDeviceSetting = (byte) 0xff; + if (getContext().getString(R.string.p_timeformat_auto).equals(ampmSetting)) { + if (DateFormat.is24HourFormat(getContext())) { + ampmDeviceSetting = SettingsCommand.AM_PM_24_HOUR; + } else { + ampmDeviceSetting = SettingsCommand.AM_PM_12_HOUR; + } + } else if (getContext().getString(R.string.p_timeformat_24h).equals(ampmSetting)) { + ampmDeviceSetting = SettingsCommand.AM_PM_24_HOUR; + } else if (getContext().getString(R.string.p_timeformat_am_pm).equals(ampmSetting)) { + ampmDeviceSetting = SettingsCommand.AM_PM_12_HOUR; + } + + sendGeneralSettings(builder, ampmDeviceSetting, (byte) 0xff); + } + + /** + * Send AM/PM indicator setting only if time format pref is set to auto + * + * @param builder the transaction builder to append to + */ + private void sendAmPmSettingIfNecessary(TransactionBuilder builder) { + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + String ampmSetting = prefs.getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, + getContext().getString(R.string.p_timeformat_auto)); + + if (getContext().getString(R.string.p_timeformat_auto).equals(ampmSetting)) { + sendAmPmSetting(builder); + } + } + /** * Gets a features command with the currently enabled features set * @@ -628,9 +669,19 @@ public class LefunDeviceSupport extends AbstractBTLEDeviceSupport { public void receiveGeneralSettings(int amPm, int units) { SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); boolean ampmEnabled = amPm == SettingsCommand.AM_PM_12_HOUR; - prefs.edit() - .putBoolean(DeviceSettingsPreferenceConst.PREF_AMPM_ENABLED, ampmEnabled) - .apply(); + String currAmpmSetting = prefs.getString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, + getContext().getString(R.string.p_timeformat_auto)); + + SharedPreferences.Editor editor = prefs.edit(); + + // Only update AM/PM indicator setting if it is not currently set to auto + if (!getContext().getString(R.string.p_timeformat_auto).equals(currAmpmSetting)) { + String ampmValue = getContext().getString(ampmEnabled ? R.string.p_timeformat_am_pm + : R.string.p_timeformat_24h); + editor.putString(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT, ampmValue); + } + + editor.apply(); } /** diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8b873b16a..24cd7e73c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -552,9 +552,7 @@ Hydration reminder interval (in minutes) Anti-loss The band will vibrate if your phone disconnects from the band - 12-hour display - Display time in 12-hour format with AM/PM indicator - Interface language + Interface language Automatic Simplified Chinese Traditional Chinese diff --git a/app/src/main/res/xml/devicesettings_ampm.xml b/app/src/main/res/xml/devicesettings_ampm.xml deleted file mode 100644 index a8e96db05..000000000 --- a/app/src/main/res/xml/devicesettings_ampm.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/app/src/main/res/xml/devicesettings_lefun_interface_language.xml b/app/src/main/res/xml/devicesettings_lefun_interface_language.xml index a1943847c..b6dab3ceb 100644 --- a/app/src/main/res/xml/devicesettings_lefun_interface_language.xml +++ b/app/src/main/res/xml/devicesettings_lefun_interface_language.xml @@ -7,5 +7,5 @@ android:icon="@drawable/ic_language" android:key="pref_lefun_interface_language" android:summary="%s" - android:title="@string/lefun_interface_language_title" /> + android:title="@string/lefun_prefs_interface_language_title" /> From 0b7d37c7ebc1afee8bc206cb3c8e65125893623d Mon Sep 17 00:00:00 2001 From: vanous Date: Sun, 11 Oct 2020 09:24:57 +0200 Subject: [PATCH 41/86] Improve Activity List processing --- .../charts/ActivityListingAdapter.java | 19 ++- .../charts/ActivityListingChartFragment.java | 4 +- .../activities/charts/StepAnalysis.java | 111 ++++++++++++------ app/src/main/res/xml/charts_preferences.xml | 4 + 4 files changed, 93 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java index 99e649730..ec648091e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingAdapter.java @@ -3,6 +3,7 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts; import android.content.Context; import java.util.Date; +import java.util.concurrent.TimeUnit; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.adapter.AbstractItemAdapter; @@ -18,20 +19,30 @@ public class ActivityListingAdapter extends AbstractItemAdapter 50) { + heartRate = " ❤️ " + item.getHeartRateAverage(); + } + + return "👣 " + item.getSteps() + heartRate; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java index 05e4553e2..593fe811a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java @@ -127,9 +127,7 @@ public class ActivityListingChartFragment extends AbstractChartFragment { tsFrom = (int) (day.getTimeInMillis() / 1000); tsTo = tsFrom + 24 * 60 * 60 - 1; - tsDataFrom = tsFrom; - return getAllSamples(db, device, tsFrom, tsTo); } @@ -137,7 +135,7 @@ public class ActivityListingChartFragment extends AbstractChartFragment { //have an "Unknown Activity" in the list in case there are no active sessions List result = new ArrayList<>(); int tsTo = tsDataFrom + 24 * 60 * 60 - 1; - result.add(new StepAnalysis.StepSession(new Date(tsDataFrom * 1000L), new Date(tsTo * 1000L), 0, ActivityKind.TYPE_UNKNOWN)); + result.add(new StepAnalysis.StepSession(new Date(tsDataFrom * 1000L), new Date(tsTo * 1000L), 0, 0, ActivityKind.TYPE_UNKNOWN)); return result; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java index b353a469b..774798f0b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/StepAnalysis.java @@ -29,80 +29,109 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; public class StepAnalysis { protected static final Logger LOG = LoggerFactory.getLogger(StepAnalysis.class); - private static final long MIN_SESSION_LENGTH = 60 * GBApplication.getPrefs().getInt("chart_list_min_session_length", 10); - private static final long MAX_IDLE_PHASE_LENGTH = 60 * GBApplication.getPrefs().getInt("chart_list_max_idle_phase_length", 5); - private static final long MIN_STEPS_PER_MINUTE = GBApplication.getPrefs().getInt("chart_list_min_steps_per_minute", 80); - private static final long MIN_STEPS_PER_MINUTE_FOR_RUN = GBApplication.getPrefs().getInt("chart_list_min_steps_per_minute_for_run", 120); + private final int MIN_SESSION_STEPS = 100; public List calculateStepSessions(List samples) { List result = new ArrayList<>(); + final int MIN_SESSION_LENGTH = 60 * GBApplication.getPrefs().getInt("chart_list_min_session_length", 5); + final int MAX_IDLE_PHASE_LENGTH = 60 * GBApplication.getPrefs().getInt("chart_list_max_idle_phase_length", 5); + final int MIN_STEPS_PER_MINUTE = GBApplication.getPrefs().getInt("chart_list_min_steps_per_minute", 40); ActivitySample previousSample = null; Date stepStart = null; Date stepEnd = null; int activeSteps = 0; - int stepsBetwenActivities = 0; - long durationSinceLastActiveStep = 0; + int heartRateForAverage = 0; + int heartRateToAdd = 0; + int activeSamplesForAverage = 0; + int activeSamplesToAdd = 0; + int stepsBetweenActivities = 0; + int heartRateBetweenActivities = 0; + int durationSinceLastActiveStep = 0; int activityKind; for (ActivitySample sample : samples) { if (isStep(sample)) { //TODO we could improve/extend this to other activities as well, if in database + + if (sample.getHeartRate() != 255 && sample.getHeartRate() != -1) { + heartRateToAdd = sample.getHeartRate(); + activeSamplesToAdd = 1; + } else { + heartRateToAdd = 0; + activeSamplesToAdd = 0; + } + if (stepStart == null) { - if (sample.getSteps() > MIN_STEPS_PER_MINUTE) { //active step - stepStart = getDateFromSample(sample); - activeSteps = sample.getSteps(); - durationSinceLastActiveStep = 0; - stepsBetwenActivities = 0; - } + stepStart = getDateFromSample(sample); + activeSteps = sample.getSteps(); + heartRateForAverage = heartRateToAdd; + activeSamplesForAverage = activeSamplesToAdd; + durationSinceLastActiveStep = 0; + stepsBetweenActivities = 0; + heartRateBetweenActivities = 0; + previousSample = null; } if (previousSample != null) { - long durationSinceLastSample = sample.getTimestamp() - previousSample.getTimestamp(); - + int durationSinceLastSample = sample.getTimestamp() - previousSample.getTimestamp(); + activeSamplesForAverage += activeSamplesToAdd; if (sample.getSteps() > MIN_STEPS_PER_MINUTE) { - activeSteps += sample.getSteps() + stepsBetwenActivities; - stepsBetwenActivities = 0; + activeSteps += sample.getSteps() + stepsBetweenActivities; + heartRateForAverage += heartRateToAdd + heartRateBetweenActivities; + stepsBetweenActivities = 0; + heartRateBetweenActivities = 0; durationSinceLastActiveStep = 0; } else { - stepsBetwenActivities += sample.getSteps(); + stepsBetweenActivities += sample.getSteps(); + heartRateBetweenActivities += heartRateToAdd; durationSinceLastActiveStep += durationSinceLastSample; } - if (stepStart != null && durationSinceLastActiveStep >= MAX_IDLE_PHASE_LENGTH) { - long current = getDateFromSample(sample).getTime() / 1000; - long ending = stepStart.getTime() / 1000; - long session_length = current - ending; + if (durationSinceLastActiveStep >= MAX_IDLE_PHASE_LENGTH) { - if (session_length > MIN_SESSION_LENGTH) { - stepEnd = getDateFromSample(sample); - activityKind = detect_activity_from_steps_per_minute(session_length, activeSteps); - result.add(new StepSession(stepStart, stepEnd, activeSteps, activityKind)); - stepStart = null; + int current = sample.getTimestamp(); + int starting = (int) (stepStart.getTime() / 1000); + int session_length = current - starting - durationSinceLastActiveStep; + int heartRateAverage = activeSamplesForAverage > 0 ? heartRateForAverage / activeSamplesForAverage : 0; + + if (session_length >= MIN_SESSION_LENGTH) { + stepEnd = new Date((sample.getTimestamp() - durationSinceLastActiveStep) * 1000L); + activityKind = detect_activity(session_length, activeSteps, heartRateAverage); + result.add(new StepSession(stepStart, stepEnd, activeSteps, heartRateAverage, activityKind)); } + stepStart = null; } } previousSample = sample; } } - //make sure we save the last portion of the data as well + //make sure we show the last portion of the data as well in case no further activity is recorded yet if (stepStart != null && previousSample != null) { - long current = getDateFromSample(previousSample).getTime() / 1000; - long ending = stepStart.getTime() / 1000; - long session_length = current - ending; + int current = previousSample.getTimestamp(); + int starting = (int) (stepStart.getTime() / 1000); + int session_length = current - starting - durationSinceLastActiveStep; + int heartRateAverage = activeSamplesForAverage > 0 ? heartRateForAverage / activeSamplesForAverage : 0; - if (session_length > MIN_SESSION_LENGTH) { + if (session_length > MIN_SESSION_LENGTH && activeSteps > MIN_SESSION_STEPS) { stepEnd = getDateFromSample(previousSample); - activityKind = detect_activity_from_steps_per_minute(session_length, activeSteps); - result.add(new StepSession(stepStart, stepEnd, activeSteps, activityKind)); + activityKind = detect_activity(session_length, activeSteps, heartRateAverage); + result.add(new StepSession(stepStart, stepEnd, activeSteps, heartRateAverage, activityKind)); } } return result; } - private int detect_activity_from_steps_per_minute(long session_length, int activeSteps) { - long spm = activeSteps / (session_length / 60); + private int detect_activity(int session_length, int activeSteps, int heartRateAverage) { + final int MIN_STEPS_PER_MINUTE_FOR_RUN = GBApplication.getPrefs().getInt("chart_list_min_steps_per_minute_for_run", 120); + int spm = (int) (activeSteps / (session_length / 60)); if (spm > MIN_STEPS_PER_MINUTE_FOR_RUN) { return ActivityKind.TYPE_RUNNING; } - return ActivityKind.TYPE_WALKING; + if (activeSteps > 200) { + return ActivityKind.TYPE_WALKING; + } + if (heartRateAverage > 90) { + return ActivityKind.TYPE_EXERCISE; + } + return ActivityKind.TYPE_ACTIVITY; } private boolean isStep(ActivitySample sample) { @@ -117,14 +146,16 @@ public class StepAnalysis { private final Date stepStart; private final Date stepEnd; private final int steps; + private final int heartRateAverage; private final int activityKind; StepSession(Date stepStart, Date stepEnd, - int steps, int activityKind) { + int steps, int heartRateAverage, int activityKind) { this.stepStart = stepStart; this.stepEnd = stepEnd; this.steps = steps; + this.heartRateAverage = heartRateAverage; this.activityKind = activityKind; } @@ -136,10 +167,14 @@ public class StepAnalysis { return stepEnd; } - public long getSteps() { + public int getSteps() { return steps; } + public int getHeartRateAverage() { + return heartRateAverage; + } + public int getActivityKind() { return activityKind; } diff --git a/app/src/main/res/xml/charts_preferences.xml b/app/src/main/res/xml/charts_preferences.xml index d087da647..2c625fb51 100644 --- a/app/src/main/res/xml/charts_preferences.xml +++ b/app/src/main/res/xml/charts_preferences.xml @@ -59,24 +59,28 @@ android:title="@string/charts_activity_list"> Date: Sun, 11 Oct 2020 14:23:13 +0300 Subject: [PATCH 42/86] Added support for more music information with backwards compatibility --- .../gadgetbridge/model/MusicSpec.java | 14 +- .../gadgetbridge/model/MusicStateSpec.java | 30 ++- .../devices/pinetime/PineTimeJFSupport.java | 216 +++++++++++------- 3 files changed, 161 insertions(+), 99 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java index 8b819e773..7c51ca12f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java @@ -20,6 +20,8 @@ package nodomain.freeyourgadget.gadgetbridge.model; import java.util.Objects; public class MusicSpec { + public static final int MUSIC_UNKNOWN = -1; + public static final int MUSIC_UNDEFINED = 0; public static final int MUSIC_PLAY = 1; public static final int MUSIC_PAUSE = 2; @@ -27,12 +29,12 @@ public class MusicSpec { public static final int MUSIC_NEXT = 4; public static final int MUSIC_PREVIOUS = 5; - public String artist; - public String album; - public String track; - public int duration; - public int trackCount; - public int trackNr; + public String artist = null; + public String album = null; + public String track = null; + public int duration = MUSIC_UNKNOWN; + public int trackCount = MUSIC_UNKNOWN; + public int trackNr = MUSIC_UNKNOWN; public MusicSpec() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java index 23aa23593..ef0c44f8a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java @@ -17,20 +17,26 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.model; -/** - * Created by steffen on 07.06.16. - */ public class MusicStateSpec { - public static final int STATE_PLAYING = 0; - public static final int STATE_PAUSED = 1; - public static final int STATE_STOPPED = 2; - public static final int STATE_UNKNOWN = 3; + public static final int STATE_UNKNOWN = -1; - public byte state; - public int position; // Position of the current media in seconds - public int playRate; // Speed of playback, usually 0 or 100 (full speed) - public byte shuffle; - public byte repeat; + public static final int STATE_PLAYING = 0; + public static final int STATE_PAUSED = 1; + public static final int STATE_STOPPED = 2; + + public static final int STATE_SHUFFLE_ENABLED = 1; + + public byte state = STATE_UNKNOWN; + /** + * Position of the current media in seconds + */ + public int position = STATE_UNKNOWN; + /** + * Speed of playback, usually 0 or 100 (full speed) + */ + public int playRate = STATE_UNKNOWN; + public byte shuffle = STATE_UNKNOWN; + public byte repeat = STATE_UNKNOWN; public MusicStateSpec() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java index 7f1a89f65..b48e2c83f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Andreas Shimokawa +/* Copyright (C) 2020 Andreas Shimokawa, Taavi Eomäe This file is part of Gadgetbridge. @@ -24,13 +24,14 @@ import android.net.Uri; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.GregorianCalendar; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; -import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService; +import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeJFConstants; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; @@ -59,46 +60,14 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport { private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo(); private final DeviceInfoProfile deviceInfoProfile; - private static final UUID UUID_SERVICE_MUSICCONTROL = UUID.fromString("c7e50001-00fc-48fe-8e23-433b3a1942d0"); - private static final UUID UUID_CHARACTERISTICS_MUSIC_EVENT = UUID.fromString("c7e50002-00fc-48fe-8e23-433b3a1942d0"); - private static final UUID UUID_CHARACTERISTICS_MUSIC_STATUS = UUID.fromString("c7e50003-00fc-48fe-8e23-433b3a1942d0"); - private static final UUID UUID_CHARACTERISTICS_MUSIC_TRACK = UUID.fromString("c7e50004-00fc-48fe-8e23-433b3a1942d0"); - private static final UUID UUID_CHARACTERISTICS_MUSIC_ARTIST = UUID.fromString("c7e50005-00fc-48fe-8e23-433b3a1942d0"); - private static final UUID UUID_CHARACTERISTICS_MUSIC_ALBUM = UUID.fromString("c7e50006-00fc-48fe-8e23-433b3a1942d0"); - - public PineTimeJFSupport() { - super(LOG); - addSupportedService(GattService.UUID_SERVICE_ALERT_NOTIFICATION); - addSupportedService(GattService.UUID_SERVICE_CURRENT_TIME); - addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION); - addSupportedService(UUID_SERVICE_MUSICCONTROL); - deviceInfoProfile = new DeviceInfoProfile<>(this); - IntentListener mListener = new IntentListener() { - @Override - public void notify(Intent intent) { - String action = intent.getAction(); - if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) { - handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO)); - } - } - }; - deviceInfoProfile.addListener(mListener); - AlertNotificationProfile alertNotificationProfile = new AlertNotificationProfile<>(this); - addSupportedProfile(alertNotificationProfile); - addSupportedProfile(deviceInfoProfile); - } - - - @Override - protected TransactionBuilder initializeDevice(TransactionBuilder builder) { - builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); - requestDeviceInfo(builder); - onSetTime(); - builder.notify(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_EVENT), true); - setInitialized(builder); - return builder; - } - + /** + * These are used to keep track when long strings haven't changed, + * thus avoiding unnecessary transfers that are (potentially) very slow. + *

+ * Makes the device's UI more responsive. + */ + String lastAlbum; + String lastTrack; private void setInitialized(TransactionBuilder builder) { builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); @@ -163,7 +132,6 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport { } - @Override public void onEnableRealtimeSteps(boolean enable) { @@ -253,19 +221,73 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport { } + String lastArtist; + + public PineTimeJFSupport() { + super(LOG); + addSupportedService(GattService.UUID_SERVICE_ALERT_NOTIFICATION); + addSupportedService(GattService.UUID_SERVICE_CURRENT_TIME); + addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION); + addSupportedService(PineTimeJFConstants.UUID_SERVICE_MUSIC_CONTROL); + deviceInfoProfile = new DeviceInfoProfile<>(this); + IntentListener mListener = new IntentListener() { + @Override + public void notify(Intent intent) { + String action = intent.getAction(); + if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) { + handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO)); + } + } + }; + deviceInfoProfile.addListener(mListener); + AlertNotificationProfile alertNotificationProfile = new AlertNotificationProfile<>(this); + addSupportedProfile(alertNotificationProfile); + addSupportedProfile(deviceInfoProfile); + } + + /** + * Helper function that ust converts an integer into a byte array + */ + private static byte[] intToBytes(int source) { + return ByteBuffer.allocate(4).putInt(source).array(); + } + + @Override + protected TransactionBuilder initializeDevice(TransactionBuilder builder) { + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); + requestDeviceInfo(builder); + onSetTime(); + builder.notify(getCharacteristic(PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_EVENT), true); + setInitialized(builder); + return builder; + } + @Override public void onSetMusicInfo(MusicSpec musicSpec) { try { TransactionBuilder builder = performInitialized("send playback info"); - if (musicSpec.album != null) { - builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_TRACK), musicSpec.track.getBytes()); + if (musicSpec.album != null && !musicSpec.album.equals(lastAlbum)) { + lastAlbum = musicSpec.album; + safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_ALBUM, musicSpec.album.getBytes()); } - if (musicSpec.artist != null) { - builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_ARTIST), musicSpec.artist.getBytes()); + if (musicSpec.track != null && !musicSpec.track.equals(lastTrack)) { + lastTrack = musicSpec.track; + safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_TRACK, musicSpec.track.getBytes()); } - if (musicSpec.album != null) { - builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_ALBUM), musicSpec.album.getBytes()); + if (musicSpec.artist != null && !musicSpec.artist.equals(lastArtist)) { + lastArtist = musicSpec.artist; + safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_ARTIST, musicSpec.artist.getBytes()); + } + + if (musicSpec.duration != MusicSpec.MUSIC_UNKNOWN) { + safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_LENGTH_TOTAL, intToBytes(musicSpec.duration)); + } + if (musicSpec.trackNr != MusicSpec.MUSIC_UNKNOWN) { + safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_TRACK_NUMBER, intToBytes(musicSpec.trackNr)); + } + if (musicSpec.trackCount != MusicSpec.MUSIC_UNKNOWN) { + safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_TRACK_TOTAL, intToBytes(musicSpec.trackCount)); } builder.queue(getQueue()); @@ -279,11 +301,30 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport { try { TransactionBuilder builder = performInitialized("send playback state"); - byte[] state = new byte[]{0}; - if (stateSpec.state == MusicStateSpec.STATE_PLAYING) { - state[0] = 1; + if (stateSpec.state != MusicStateSpec.STATE_UNKNOWN) { + byte[] state = new byte[1]; + if (stateSpec.state == MusicStateSpec.STATE_PLAYING) { + state[0] = 0x01; + } + safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_STATUS, state); } - builder.write(getCharacteristic(UUID_CHARACTERISTICS_MUSIC_STATUS), state); + + if (stateSpec.playRate != MusicStateSpec.STATE_UNKNOWN) { + safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_PLAYBACK_SPEED, intToBytes(stateSpec.playRate)); + } + + if (stateSpec.position != MusicStateSpec.STATE_UNKNOWN) { + safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_POSITION, intToBytes(stateSpec.position)); + } + + if (stateSpec.repeat != MusicStateSpec.STATE_UNKNOWN) { + safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_REPEAT, intToBytes(stateSpec.repeat)); + } + + if (stateSpec.shuffle != MusicStateSpec.STATE_UNKNOWN) { + safeWriteToCharacteristic(builder, PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_SHUFFLE, intToBytes(stateSpec.repeat)); + } + builder.queue(getQueue()); } catch (Exception e) { @@ -292,6 +333,33 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport { } + @Override + public boolean onCharacteristicRead(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, int status) { + if (super.onCharacteristicRead(gatt, characteristic, status)) { + return true; + } + UUID characteristicUUID = characteristic.getUuid(); + + LOG.info("Unhandled characteristic read: " + characteristicUUID); + return false; + } + + @Override + public void onSendConfiguration(String config) { + + } + + @Override + public void onReadConfiguration(String config) { + + } + + @Override + public void onTestNewFunction() { + + } + @Override public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { @@ -300,7 +368,7 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport { } UUID characteristicUUID = characteristic.getUuid(); - if (characteristicUUID.equals(UUID_CHARACTERISTICS_MUSIC_EVENT)) { + if (characteristicUUID.equals(PineTimeJFConstants.UUID_CHARACTERISTICS_MUSIC_EVENT)) { byte[] value = characteristic.getValue(); GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl(); @@ -333,35 +401,21 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport { return false; } - @Override - public boolean onCharacteristicRead(BluetoothGatt gatt, - BluetoothGattCharacteristic characteristic, int status) { - if (super.onCharacteristicRead(gatt, characteristic, status)) { - return true; - } - UUID characteristicUUID = characteristic.getUuid(); - - LOG.info("Unhandled characteristic read: " + characteristicUUID); - return false; - } - - @Override - public void onSendConfiguration(String config) { - - } - - @Override - public void onReadConfiguration(String config) { - - } - - @Override - public void onTestNewFunction() { - - } - @Override public void onSendWeather(WeatherSpec weatherSpec) { } + + /** + * This will check if the characteristic exists and can be written + *

+ * Keeps backwards compatibility with firmware that can't take all the information + */ + private void safeWriteToCharacteristic(TransactionBuilder builder, UUID uuid, byte[] data) { + BluetoothGattCharacteristic characteristic = getCharacteristic(uuid); + if (characteristic != null && + (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) { + builder.write(characteristic, data); + } + } } From d892e8d963b69c441555da236adefaf20717bb03 Mon Sep 17 00:00:00 2001 From: TaaviE Date: Sun, 11 Oct 2020 14:26:03 +0300 Subject: [PATCH 43/86] Separated InfiniTime characteristic UUIDs into a separate file --- .../devices/pinetime/PineTimeJFConstants.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeJFConstants.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeJFConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeJFConstants.java new file mode 100644 index 000000000..4195d66b6 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/PineTimeJFConstants.java @@ -0,0 +1,20 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.pinetime; + +import java.util.UUID; + +public class PineTimeJFConstants { + public static final UUID UUID_SERVICE_MUSIC_CONTROL = UUID.fromString("c7e50001-00fc-48fe-8e23-433b3a1942d0"); + + public static final UUID UUID_CHARACTERISTICS_MUSIC_EVENT = UUID.fromString("c7e50002-00fc-48fe-8e23-433b3a1942d0"); + public static final UUID UUID_CHARACTERISTICS_MUSIC_STATUS = UUID.fromString("c7e50003-00fc-48fe-8e23-433b3a1942d0"); + public static final UUID UUID_CHARACTERISTICS_MUSIC_ARTIST = UUID.fromString("c7e50004-00fc-48fe-8e23-433b3a1942d0"); + public static final UUID UUID_CHARACTERISTICS_MUSIC_TRACK = UUID.fromString("c7e50005-00fc-48fe-8e23-433b3a1942d0"); + public static final UUID UUID_CHARACTERISTICS_MUSIC_ALBUM = UUID.fromString("c7e50006-00fc-48fe-8e23-433b3a1942d0"); + public static final UUID UUID_CHARACTERISTICS_MUSIC_POSITION = UUID.fromString("c7e50007-00fc-48fe-8e23-433b3a1942d0"); + public static final UUID UUID_CHARACTERISTICS_MUSIC_LENGTH_TOTAL = UUID.fromString("c7e50008-00fc-48fe-8e23-433b3a1942d0"); + public static final UUID UUID_CHARACTERISTICS_MUSIC_TRACK_NUMBER = UUID.fromString("c7e50009-00fc-48fe-8e23-433b3a1942d0"); + public static final UUID UUID_CHARACTERISTICS_MUSIC_TRACK_TOTAL = UUID.fromString("c7e5000a-00fc-48fe-8e23-433b3a1942d0"); + public static final UUID UUID_CHARACTERISTICS_MUSIC_PLAYBACK_SPEED = UUID.fromString("c7e5000b-00fc-48fe-8e23-433b3a1942d0"); + public static final UUID UUID_CHARACTERISTICS_MUSIC_REPEAT = UUID.fromString("c7e5000c-00fc-48fe-8e23-433b3a1942d0"); + public static final UUID UUID_CHARACTERISTICS_MUSIC_SHUFFLE = UUID.fromString("c7e5000d-00fc-48fe-8e23-433b3a1942d0"); +} From 48abd91e72dbf72f7f201471b93891f39b6ebe9d Mon Sep 17 00:00:00 2001 From: TaaviE Date: Sun, 11 Oct 2020 14:29:14 +0300 Subject: [PATCH 44/86] Reordered a few functions, constructor to be more sensible --- .../devices/pinetime/PineTimeJFSupport.java | 89 +++++++++---------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java index b48e2c83f..62f50dc69 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java @@ -68,21 +68,28 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport { */ String lastAlbum; String lastTrack; + String lastArtist; - private void setInitialized(TransactionBuilder builder) { - builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); - } - - private void requestDeviceInfo(TransactionBuilder builder) { - LOG.debug("Requesting Device Info!"); - deviceInfoProfile.requestDeviceInfo(builder); - } - - private void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) { - LOG.warn("Device info: " + info); - versionCmd.hwVersion = info.getHardwareRevision(); - versionCmd.fwVersion = info.getFirmwareRevision(); - handleGBDeviceEvent(versionCmd); + public PineTimeJFSupport() { + super(LOG); + addSupportedService(GattService.UUID_SERVICE_ALERT_NOTIFICATION); + addSupportedService(GattService.UUID_SERVICE_CURRENT_TIME); + addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION); + addSupportedService(PineTimeJFConstants.UUID_SERVICE_MUSIC_CONTROL); + deviceInfoProfile = new DeviceInfoProfile<>(this); + IntentListener mListener = new IntentListener() { + @Override + public void notify(Intent intent) { + String action = intent.getAction(); + if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) { + handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO)); + } + } + }; + deviceInfoProfile.addListener(mListener); + AlertNotificationProfile alertNotificationProfile = new AlertNotificationProfile<>(this); + addSupportedProfile(alertNotificationProfile); + addSupportedProfile(deviceInfoProfile); } @Override @@ -221,37 +228,6 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport { } - String lastArtist; - - public PineTimeJFSupport() { - super(LOG); - addSupportedService(GattService.UUID_SERVICE_ALERT_NOTIFICATION); - addSupportedService(GattService.UUID_SERVICE_CURRENT_TIME); - addSupportedService(GattService.UUID_SERVICE_DEVICE_INFORMATION); - addSupportedService(PineTimeJFConstants.UUID_SERVICE_MUSIC_CONTROL); - deviceInfoProfile = new DeviceInfoProfile<>(this); - IntentListener mListener = new IntentListener() { - @Override - public void notify(Intent intent) { - String action = intent.getAction(); - if (DeviceInfoProfile.ACTION_DEVICE_INFO.equals(action)) { - handleDeviceInfo((nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo) intent.getParcelableExtra(DeviceInfoProfile.EXTRA_DEVICE_INFO)); - } - } - }; - deviceInfoProfile.addListener(mListener); - AlertNotificationProfile alertNotificationProfile = new AlertNotificationProfile<>(this); - addSupportedProfile(alertNotificationProfile); - addSupportedProfile(deviceInfoProfile); - } - - /** - * Helper function that ust converts an integer into a byte array - */ - private static byte[] intToBytes(int source) { - return ByteBuffer.allocate(4).putInt(source).array(); - } - @Override protected TransactionBuilder initializeDevice(TransactionBuilder builder) { builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); @@ -406,6 +382,13 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport { } + /** + * Helper function that just converts an integer into a byte array + */ + private static byte[] intToBytes(int source) { + return ByteBuffer.allocate(4).putInt(source).array(); + } + /** * This will check if the characteristic exists and can be written *

@@ -418,4 +401,20 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport { builder.write(characteristic, data); } } + + private void setInitialized(TransactionBuilder builder) { + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); + } + + private void requestDeviceInfo(TransactionBuilder builder) { + LOG.debug("Requesting Device Info!"); + deviceInfoProfile.requestDeviceInfo(builder); + } + + private void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) { + LOG.warn("Device info: " + info); + versionCmd.hwVersion = info.getHardwareRevision(); + versionCmd.fwVersion = info.getFirmwareRevision(); + handleGBDeviceEvent(versionCmd); + } } From 1e223bd9e69024b958cff1ec14f53f2b2bd42142 Mon Sep 17 00:00:00 2001 From: LizardWithHat Date: Sat, 10 Oct 2020 14:01:52 +0000 Subject: [PATCH 45/86] Translated using Weblate (German) Currently translated at 99.3% (978 of 984 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/de/ --- app/src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index be39b1f65..65c43c032 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1058,4 +1058,5 @@ Fußball Heute Vergangenheit + Keine Aktivitäten gefunden. \ No newline at end of file From 310ba2e968fef2f07122f7fe96c31dda9669e08c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vinc=C3=A8n=20PUJOL?= Date: Sat, 10 Oct 2020 07:42:25 +0000 Subject: [PATCH 46/86] Translated using Weblate (French) Currently translated at 100.0% (984 of 984 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/ --- app/src/main/res/values-fr/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 670ae7ae0..5f02d6939 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1043,4 +1043,11 @@ Temps de sommeil préféré en heures \nNote : vous n\'avez pas à installer les fichier .res et .gps si ceux ci sont exactement les même qu\'installé précédemment. \n \nÀ VOS RISQUES ET PÉRILS ! + Durée d\'activité minimale (minutes) + Longueur de la pause pour séparer les activités (minutes) + Nombre de pas minimum par minute pour détecter une activité + Nombre de pas minimum par minute pour détecter une course + Liste d\'activités + Avoir une activité et synchroniser l\'appareil. + Aucune activité détectée. \ No newline at end of file From 76ead68a112d3fb7352199839aa72e25e9ee1a66 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Sat, 10 Oct 2020 06:42:05 +0000 Subject: [PATCH 47/86] Translated using Weblate (Hebrew) Currently translated at 99.8% (983 of 984 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/he/ --- app/src/main/res/values-he/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 397a62c92..b7bcaad7f 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -1044,4 +1044,11 @@ היום מרחק שעבר קישורים + משך מזערי לפעילות (דקות) + משך השהייה להפרדת פעילויות (דקות) + כמות מזערית של צעדים לדקה לזיהוי פעילות + כמות מזערית של צעדים לדקה לזיהוי ריצה + רשימת פעילויות + יש לעשות פעילות כלשהי ולסנכרן את ההתקן. + לא זוהו פעילויות. \ No newline at end of file From 58388ab58d02afa4b98d00bb2d53c341fd5f9566 Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Sat, 10 Oct 2020 06:07:02 +0000 Subject: [PATCH 48/86] Translated using Weblate (Russian) Currently translated at 100.0% (984 of 984 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/ru/ --- app/src/main/res/values-ru/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 5dc311060..1712e1f2a 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1050,4 +1050,11 @@ далёкое прошлое сегодня Ссылки + Минимальная продолжительность активностей (минуты) + Продолжительность паузы для разделения активностей (минуты) + Минимальные шаги в минуту для обнаружения активности + Минимальные шаги в минуту для обнаружения бега + Список активностей + Сделайте некоторую активность и синхронизируйте устройство. + Никакой активности не обнаружено. \ No newline at end of file From ed79fc8d8e209e34ee743e9e845fa3a9d34159fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Sat, 10 Oct 2020 10:21:42 +0000 Subject: [PATCH 49/86] Translated using Weblate (Turkish) Currently translated at 100.0% (984 of 984 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/tr/ --- app/src/main/res/values-tr/strings.xml | 1263 ++++++++++++------------ 1 file changed, 635 insertions(+), 628 deletions(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index a8370df53..efcf310bc 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1,177 +1,177 @@ İptal - "KalpRitmi: " + "Kalp ritmi: " Sil - %1$s den itibaren aktarmak üzere + %1$s tarihinden itibaren verileri aktarmak üzere (%1$s) Sessiz - Android Cihazda Aç + Android aygıtında aç Cevapla (bilinmeyen) Etkinlik - Derin Uyku - Hafif Uyku - BilgiYok - VeriTabanı Yönetimi + Derin uyku + Hafif uyku + Giyilmedi + Veri tabanı yönetimi Hata Ayıklama - Yeni Cihaz Bağla + Yeni aygıt bağla Bağış Yap Çıkış Ayarlar - VeriTabanını Dışa Aktar - VeriTabanını Sil - VeriTabanını Boşalt - VeriTabanını İçe Aktar + Veri tabanını dışa aktar + Eski veri tabanını sil + Veri tabanını boşalt + Veri tabanını içe aktar Otomatik Dışa Aktarmayı Başlat - VeriTabanı Dışa Aktarılıyor… - VeriTabanının Otomatik Dışa Aktarılacağı Yer: + Veri tabanı dışa aktarılıyor… + Veri tabanı otomatik dışa aktarma konumu: Otomatik Dışa Aktarma - VeriTabanını Boşalt - Dikkat! Bu düğmeye basarak veritabanınızı silecek ve sıfırdan başlayacaksınız. - DışaAktarma ve İçeAktarma - Veritabanı işlemleri cihazınızda aşağıdaki yolu kullanır. -\nBu yola diğer Android uygulamaları ve bilgisayarınız tarafından erişilebilir. -\nDışa aktarılan veritabanınızı bulmayı bekleyin (veya içe aktarmak istediğiniz veritabanını yerleştirin): - Eski VeriTabanını Silme - Aktivite izlemesini görüntülemek için GPX dosyalarını işleyebilen bir uygulama yükleyin. + Veri tabanını boşalt + Dikkat! Bu düğmeye basarak veri tabanınızı silecek ve sıfırdan başlayacaksınız. + Dışa ve İçe Aktar + Veri tabanı işlemleri aygıtınızda aşağıdaki yolu kullanır. +\nBu yola diğer Android uygulamaları ve bilgisayarınız tarafından erişilebilir. +\nDışa aktarılan veri tabanınızı burada bulmayı bekleyin (veya içe aktarmak istediğiniz veri tabanını buraya koyun): + Eski veri tabanını silme + Etkinlik izlemesini görüntülemek için GPX dosyalarını işleyebilen bir uygulama kurun. Hakkınızda - Günlük Hedef: Dakika Olarak Aktif Zaman - EnYüksek Kalp Ritmi - EnDüşük Kalp Ritmi - Günlük Hedef: Harcanacak Kalori - EnYüksek Kalp Atış Ritmi - EnDüşük Kalp Atış Ritmi - Gösterge Ayarları - Günlük Hedef: Metre Mesafe + Günlük hedef: dakika cinsinden etkin zaman + En yüksek kalp ritmi + En düşük kalp ritmi + Günlük hedef: harcanan kalori + En yüksek kalp ritmi + En düşük kalp ritmi + Çizelge ayarları + Günlük hedef: metre cinsinden mesafe Cinsiyet - Boy Santim - Hedeflenen Uyku Saati Süresi - Ağırlık Kilogram - Doğum Yılı + Boy (cm) + Tercih edilen uyku süresi (saat cinsinden) + Ağırlık (kg) + Doğum yılı Etkinlik Spor Etkinlikleri Etkinlik Bisiklet - Derin Uyku + Derin uyku Egzersiz - Hafif Uyku + Hafif uyku Ölçülmedi - Cihaz Kullanılmamış + Aygıt giyilmedi Koşu Yüzme Koşu bandı Bilinmeyen etkinlik Yürüme - Aktivite Web Görünümü - AnaSayfa Göstergesi (Widget) Ekle + Etkinlik Web Görünümü + Widget ekle Cum Pzt Cts - Akıllı Uyandırma - Durdur + Akıllı uyandırma + Ertele Paz Per Sal Çar - Daima Bildir - Cihazı eşleştirmek için Android Bluetooth Eşleştirme İletişimini kullan. + Her zaman + Aygıtı eşleştirmek için Android Bluetooth eşleştirme iletişimini kullan. Yapılandır - Aşağıdaki uygulamayı yüklemek üzeresiniz: Sürüm: + Aşağıdaki uygulamayı kurmak üzeresiniz: \n \n -\n%1$s - %2$s Sahibi: %3$s +\n%1$s Sürüm %2$s, %3$s tarafından \n - Yukarı Taşı - GadgetBridge + Yukarı taşı + Gadgetbridge Kur - Pebble Uygulama Mağazasında Ara - ÖnBellekteki Uygulamalar - Aktifleştir - Pasifleştir - KalpRitmi Kontrolünü Aktifleştir - KalpRitmi Kontrolünü Pasifleştir - Yüklenmiş Uygulamalar - Yüklenmiş Saat AraYüzleri - Sistem Hava Durumu uygulamasını Aktifleştir - Sistem Hava Durumu uygulamasını Pasifleştir - Weather Notification Uygulamasını Yükle + Pebble uygulama mağazasında ara + Önbellekteki uygulamalar + Etkinleştir + Devre dışı bırak + Kalp ritmi izlemeyi etkinleştir + Kalp ritmi izlemeyi devre dışı bırak + Kurulu uygulamalar + Kurulu saat arayüzleri + Sistem hava durumu uygulamasını etkinleştir + Sistem hava durumu uygulamasını devre dışı bırak + Hava durumu bildirim uygulaması kur Sil - Sil ve ÖnBellekten Kaldır - Yeniden Kur - %1$s Sahibi %2$s - Bağlanmadı, Alarm Kurulmadı. - Alarmı Kur %1$02d:%2$02d + Sil ve önbellekten kaldır + Yeniden kur + %1$s, %2$s tarafından + Bağlı değil, alarm kurulmadı. + %1$02d:%2$02d için alarm kur Uyku Alarmı Zzz - Arabic - Kimlik Doğrulanıyor - Kimlik Doğrulama Gerekiyor + Arapça + Kimlik doğrulanıyor + Kimlik doğrulama gerekiyor Otomatik Ortalama: %1$s - Batarya - Bildirimler için Hepsini KaraListeye Al - Bluetooth Kapalı. - Bluetooth Desteklenmiyor. + Pil + Bildirimler için hepsini kara listeye al + Bluetooth devre dışı. + Bluetooth desteklenmiyor. Etkinlik verileri getiriliyor Kalori - Cihaz Görüntüsü + Aygıt görüntüsü Bağlanamıyor: %1$s - Bağlanamadı. Bluetooth Adresi Geçersiz\? - Veri Yok! Cihazı Senkronize Et\? + Bağlanamıyor. Bluetooth adresi geçersiz mi\? + Veri yok! Aygıt eşzamanlansın mı\? Adım - Kalp Ritmi - Dışa Aktarılacak Yeri Seç + Kalp ritmi + Dışa aktarma konumunu seç Saat Bağlandı Bağlanıyor - Titreşimi Durdurmak için iptal et. - Kayıp Cihaz aranıyor - Cihazı Kalibre Et + Titreşimi durdurmak için iptal et. + Kayıp aygıtı bul + Aygıtı Kalibre Et FM Frekansını Değiştir LED Rengini Değiştir Bağlan… - Cihazı Sil - Bu Cihaz ve ilişkili veriler silinecek! + Aygıtı Sil + Aygıt ve tüm ilişkili verileri silinecek! %1$s Sil Bağlantıyı Kes - Senkronize Et - Kayıp Cihazı Bul - Gezinti Çizimini Kapat - Gezinti Çizimini Aç + Eşzamanla + Kayıp aygıtı bul + Gezinme çekmecesini kapat + Gezinme çekmecesini aç Bağlanıyor… Bağlantı Kesiliyor - Bağlantıyı kesmek için Cihazın İsmine uzunca basın - Cihazın Ekran Görüntüsü Alma - Aktivite İzlemeleriniz - Aktiviteniz (ALPHA) - Alarmları Yapılandır + Bağlantıyı kesmek için karta uzun basın + Aygıtın ekran görüntüsü alınıyor + Etkinlik izlemeleriniz + Etkinliğiniz (ALPHA) + Alarmları yapılandır Ekran Görüntüsü Al - Zaman & Tarih - Zaman - "'%1$s' İşleme Hatası" - Veri Silindi. - VeriTabanı Silmede Hata. - Aktivite Verisi Silinsin mi? - Eski Aktiviteler VeriTabanı Silinsin mi? - Eski Aktiviteler VeriTabanı Gerçekten Silinsin mi? İçe aktarılmayan aktivite verileri kaybolacak. - VeriTabanı DışaAktarma Hatası: %1$s - Tercihi DışaAktarma Hatası: %1$s - VeriTabanı İçe Aktarma Hatası: %1$s - Tercihi İçeAktarmada Hata: %1$s - Dışa Aktarıldı: %1$s - VeriTabanı İçe Aktarılsın mı? - İçe Aktarıldı. - Eski Aktiviteler VeriTabanı Silmede Hata. - Eski Aktiviteler VeriTabanı Silindi. + Saat ve tarih + Saat + \'%1$s\' çalıştırılırken hata oluştu + Veriler silindi. + Veri tabanını silme başarısız oldu. + Etkinlik Verileri Silinsin mi\? + Eski Etkinlik Veri Tabanı Silinsin mi\? + Eski etkinlik veri tabanı gerçekten silinsin mi\? İçe aktarılmayan etkinlik verileri kaybolacak. + Veri tabanı dışa aktarılırken hata oluştu: %1$s + Tercih dışa aktarılırken hata oluştu: %1$s + Veri tabanı içe aktarılırken hata oluştu: %1$s + Tercih içe aktarılırken hata oluştu: %1$s + Dışa aktarıldı: %1$s + Veriler İçe Aktarılsın mı\? + İçe aktarıldı. + Eski etkinlik veri tabanının silinmesi başarısız oldu. + Eski etkinlik verileri silindi. Üzerine Yaz - Gerçekten Mevcut VeriTabanı Üzerine Yazılsın mı? Mevcut Aktivite Verisi Kaybedilecek. - Gerçekten Mevcut VeriTabanı Silinsin mi? Mevcut Aktivite Veriniz ve Cihaz Bilgileriniz Silinecek. - Dışa Aktarma Yoluna Erişilemiyor. Lütfen Geliştiricilere Başvurun. - Fabrika ayarlarına sıfırlama yapılması bağlı cihazdaki tüm verileri silecektir. Xiaomi / Huami cihazları da Bluetooth MAC adresini değiştirir, bu nedenle GadgetBridge yeni bir cihaz olarak görür. - Gerçekten Fabrika Ayarlarına Sıfırlansın mı? - Firmware Sürümü: %1$s - Donanım Revizyonu: %1$s + Geçerli veri tabanının gerçekten üzerine yazılsın mı\? Tüm etkinlik verileriniz (varsa) kaybolacak. + Veri tabanının tamamı gerçekten silinsin mi\? Tüm etkinlik verileriniz ve aygıtlarınızla ilgili bilgileriniz kaybolacak. + Dışa aktarma yoluna erişilemiyor. Lütfen geliştiricilerle iletişime geçin. + Fabrika ayarlarına sıfırlama yapılması bağlı aygıttaki tüm verileri (destekleniyorsa) silecektir. Xiaomi/Huami aygıtları ayrıca Bluetooth MAC adresini değiştirir, bu nedenle Gadgetbridge tarafından yeni bir aygıt olarak görülürler. + Gerçekten fabrika ayarlarına sıfırlansın mı\? + Ürün yazılımı sürümü: %1$s + Donanım revizyonu: %1$s Bağlanmadı. %1$s (%2$s) AmazFit Bip @@ -205,319 +205,319 @@ Roidmi Roidmi 3 Teclast H30 - Test Cihazı - Bilinmeyen Cihaz + Test Aygıtı + Bilinmeyen Aygıt Vibratissimo Watch 9 XWatch Y5 %1$s ile eşleşmeye çalışılıyor %1$s ile etkileşim anında başarısız oldu. - Cihazınızı Bluetooth araması için Görünür Yapın. Halen Eşleşmiş Cihazlar Muhtemelen Bulunmayabilir. Bu durumda Bluetooth üzerindeki eşleşmeyi kaldırıp yeniden deneyin. Android 6 ve sonraki sürümlerinde konumu (ör. GPS) etkinleştirin. GadgetBridge için Privacy Guardı devre dışı bırakın, çünkü telefonunuz çökebilir ve yeniden başlatılabilir. Birkaç dakika sonra hiçbir cihaz bulunmazsa, mobil cihazınızı yeniden başlattıktan sonra tekrar deneyin. - "Eşlenmesin" - Cihazları bulmak için Bluetoothu aktifleştirin. - Bu cihaz için bir Gizli Kimlik Doğrulama Anahtarı gerekiyor, Girmek için uzunca basın. Bilgi için ilgili wiki maddesini okuyun. + Aygıtınızı bulunabilir hale getirin. Şu anda bağlı aygıtlar muhtemelen keşfedilmeyecek. Android 6+ için konumu (örn. GPS) etkinleştirin. Gadgetbridge için Gizlilik Korumasını (Privacy Guard) devre dışı bırakın, çünkü çökebilir ve telefonunuzu yeniden başlatabilir. Birkaç dakika sonra hiçbir aygıt bulunmazsa, mobil aygıtınızı yeniden başlattıktan sonra tekrar deneyin. + Eşleştirme + Aygıtları bulmak için Bluetooth\'u etkinleştir. + Bu aygıt için gizli bir kimlik doğrulama anahtarı gerekiyor, girmek için aygıta uzun basın. Wiki sayfasını okuyun. Not: - Cihazlarınızı eşleştirmek için Eşleştiri seçin. Bu başarısız olursa, Eşleştirmeyi tekrar deneyin. + Aygıtlarınızı eşleştirmek için Eşleştir seçeneğini seçin. Bu başarısız olursa, eşleştirme olmadan tekrar deneyin. %1$s ile eşleştirilsin mi? - Taramayı Başlat - Taramayı Durdur + Taramayı başlat + Taramayı durdur Bağlanacak: %1$s. - Bağlanmaya Çalışıyor: %1$s + Bağlanmaya çalışıyor: %1$s Eşleştir Mesafe - Dutch - Her bir kelimeyi yeni bir satıra girin - English + Flemenkçe + Her bir sözcüğü yeni bir satıra girin + İngilizce Günlük dosyaları oluşturmada hata: %1$s - Taramanın düzgün çalışması için Konum Erişimi verilmeli ve etkinleştirilmelidir - Bayan + Taramanın düzgün çalışması için konum erişimi verilmeli ve etkinleştirilmelidir + Kadın Filtreleme Modu - Kelimeler bulunduğunda engelle - Filtreleme Yapılmasın - Kelimeler bulunduğunda göster - Kelimelerin Hepsi - Kelimelerin EnAz Birisi + Sözcükler bulunduğunda engelle + Filtreleme yapılmasın + Sözcükler bulunduğunda göster + Sözcüklerin tümü + Sözcüklerden en az biri Buldunuz! - Telefonu Bul! - Telefon Bulundu - "Riski size ait olan bir FIRMWARE yüklüyorsunuz. - - - Bu Firmware şu HW revizyonu içindir: %s" - French - Mevcut Mi Band cihazınıza %1$s ve %2$s için Firmware yüklemek üzeresiniz. - %s yüklemek üzeresiniz. - AmazFit Bip cihazınıza %s ürün yazılımını yüklemek üzeresiniz. + Buldunuz! + Telefonumu bul + BİR ÜRÜN YAZILIMI KURMAYA ÇALIŞIYORSUNUZ, RİSKİ GÖZE ALARAK İLERLEYİN. \n -\nLütfen .fw dosyasını, ardından .res dosyasını ve son olarak .gps dosyasını yüklediğinizden emin olun. Saatiniz .fw dosyasını yükledikten sonra yeniden başlatılacak. \n -\nNot: Bu dosyalar, daha önce yüklenenler ile aynıysa, .res ve .gps yüklemeniz gerekmez. +\n Bu ürün yazılımı şu donanım revizyonu içindir: %s + Fransızca + Şu anda Mi Band\'ınızda bulunanların yerine %1$s ve %2$s ürün yazılımını kurmak üzeresiniz. + %s kurmak üzeresiniz. + AmazFit Bip aygıtınıza %s ürün yazılımını kurmak üzeresiniz. +\n +\nLütfen .fw dosyasını, ardından .res dosyasını ve son olarak .gps dosyasını kurduğunuzdan emin olun. Saatiniz .fw dosyasını kurduktan sonra yeniden başlatılacak. +\n +\nNot: Bu dosyalar, daha önce kurulanlarla tamamen aynıysa, .res ve .gps kurmanız gerekmez. \n \nİLERLEMENİZ DURUMUNDA RİSK SİZE AİTTİR! - AmazFit Bip Lite cihazınıza %s ürün yazılımını yüklemek üzeresiniz. + AmazFit Bip Lite aygıtınıza %s ürün yazılımını kurmak üzeresiniz. \n -\nLütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını yükleyin. .fw dosyasını yükledikten sonra saatiniz yeniden başlatılacak. +\nLütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını kurun. .fw dosyasını kurduktan sonra saatiniz yeniden başlatılacak. \n -\nNote: Bu dosyalar önceden yüklenmiş olanlarla tamamen aynıysa .res ve .gps dosyalarını yüklemeniz gerekmez. +\nNote: Bu dosyalar önceden kurulanlarla tamamen aynıysa .res ve .gps dosyalarını kurmanız gerekmez. \n \nİLERLEMENİZ DURUMUNDA RİSK SİZE AİTTİR! - AmazFit Core cihazınıza %s ürün yazılımını yüklemek üzeresiniz. + AmazFit Core aygıtınıza %s ürün yazılımını kurmak üzeresiniz. \n -\nLütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını yükleyin. .fw dosyasını yükledikten sonra saatiniz yeniden başlatılacak. +\nLütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını kurun. .fw dosyasını kurduktan sonra bilekliğiniz yeniden başlatılacak. \n -\nNot: Bu dosyalar önceden yüklenmiş olanlarla tamamen aynıysa .res ve .gps dosyalarını yüklemeniz gerekmez. +\nNot: Bu dosyalar önceden kurulanlarla tamamen aynıysa .res ve .gps dosyalarını kurmanız gerekmez. \n \nİLERLEMENİZ DURUMUNDA RİSK SİZE AİTTİR! - AmazFit Core 2 cihazınıza %s ürün yazılımını yüklemek üzeresiniz. + AmazFit Core 2 aygıtınıza %s ürün yazılımını kurmak üzeresiniz. \n -\nLütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını yükleyin. .fw dosyasını yükledikten sonra saatiniz yeniden başlatılacak. +\nLütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını kurun. .fw dosyasını kurduktan sonra bilekliğiniz yeniden başlatılacak. \n -\nNot: Bu dosyalar önceden yüklenmiş olanlarla tamamen aynıysa .res ve .gps dosyalarını yüklemeniz gerekmez. +\nNot: Bu dosyalar önceden kurulanlarla tamamen aynıysa .res ve .gps dosyalarını kurmanız gerekmez. \n \nİLERLEMENİZ DURUMUNDA RİSK SİZE AİTTİR! \n -\n TAM OLARAK TEST EDİLMEMİŞTİR, CİHAZINIZIN İSMİ \"Amazfit Band 2\" İSE BEATS_W ÜRÜN YAZILIMINI YÜKLEMENİZ GEREKEBİLİR - AmazFit GTR cihazınıza %s ürün yazılımını yüklemek üzeresiniz. +\n TAM OLARAK TEST EDİLMEMİŞTİR, AYGITINIZIN İSMİ \"Amazfit Band 2\" İSE BEATS_W ÜRÜN YAZILIMINI KURMANIZ GEREKEBİLİR + AmazFit GTR aygıtınıza %s ürün yazılımını kurmak üzeresiniz. \n -\nLütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını yükleyin. .fw dosyasını yükledikten sonra saatiniz yeniden başlatılacak. +\nLütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını kurun. .fw dosyasını kurduktan sonra saatiniz yeniden başlatılacak. \n -\nNote: Bu dosyalar önceden yüklenmiş olanlarla tamamen aynıysa .res ve .gps dosyalarını yüklemeniz gerekmez. +\nNote: Bu dosyalar önceden kurulanlarla tamamen aynıysa .res ve .gps dosyalarını kurmanız gerekmez. \n \nİLERLEMENİZ DURUMUNDA RİSK SİZE AİTTİR! - AmazFit GTS cihazınıza %s ürün yazılımını yüklemek üzeresiniz. + AmazFit GTS aygıtınıza %s ürün yazılımını kurmak üzeresiniz. \n -\nLütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını yükleyin. .fw dosyasını yükledikten sonra saatiniz yeniden başlatılacak. +\nLütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını kurun. .fw dosyasını kurduktan sonra saatiniz yeniden başlatılacak. \n -\nNote: Bu dosyalar önceden yüklenmiş olanlarla tamamen aynıysa .res ve .gps dosyalarını yüklemeniz gerekmez. +\nNote: Bu dosyalar önceden kurulanlarla tamamen aynıysa .res ve .gps dosyalarını kurmanız gerekmez. \n \nİLERLEMENİZ DURUMUNDA RİSK SİZE AİTTİR! - "Mi Band 3 üzerine %s Firmware yüklemek üzeresiniz. - -Lütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını yükleyin. .fw dosyasını yükledikten sonra Saatiniz yeniden başlatılacak. - -Note: Bu dosyalar önceden yüklenmiş olanlarla tamamen aynıysa .res ve .gps dosyalarını yüklemeniz gerekmez. - -YÜKLEME RİSKİ RİZE AİTTİR!" - "Mi Band 4 üzerine %s Firmware yüklemek üzeresiniz. - -Lütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını yükleyin. .fw dosyasını yükledikten sonra Saatiniz yeniden başlatılacak. - -Note: Bu dosyalar önceden yüklenmiş olanlarla tamamen aynıysa .res ve .gps dosyalarını yüklemeniz gerekmez. - -YÜKLEME RİSKİ RİZE AİTTİR!" - Dosya kurulamadı, Cihaz hazır değil. - Bağlanacak Cihaz: %1$s - Bu Firmware bu cihaza uyumlu değil - GadgetBridge Çalışıyor - German - Kalp Ritmi + Mi Band 3 aygıtınıza %s ürün yazılımını kurmak üzeresiniz. +\n +\nLütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını kurun. .fw dosyasını kurduktan sonra bilekliğiniz yeniden başlatılacak. +\n +\nNote: Bu dosyalar önceden kurulanlarla tamamen aynıysa .res ve .gps dosyalarını kurmanız gerekmez. +\n +\nİLERLEMENİZ DURUMUNDA RİSK SİZE AİTTİR! + Mi Band 4 aygıtınıza %s ürün yazılımını kurmak üzeresiniz. +\n +\nLütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını kurun. .fw dosyasını kurduktan sonra bilekliğiniz yeniden başlatılacak. +\n +\nNote: Bu dosyalar önceden kurulanlarla tamamen aynıysa .res ve .gps dosyalarını kurmanız gerekmez. +\n +\nİLERLEMENİZ DURUMUNDA RİSK SİZE AİTTİR! + Dosya kurulamıyor, aygıt hazır değil. + Aygıta bağlantı: %1$s + Bu ürün yazılımı bu aygıtla uyumlu değil + Gadgetbridge çalışıyor + Almanca + Kalp ritmi Yatay - Çevirme + İşe Gidip Gelme Kronometre Sağlık Egzersiz - Aktif Dakikalar - Batarya + Etkin dakikalar + Pil Kalori Tarih - Kalp Ritmi - HiçbirŞey + Kalp ritmi + Hiçbir Şey Adım Hava Durumu Simge - Indonesian - Başlatılmış + Endonezce + başlatıldı Başlatılıyor - Yükleme Hatası - Yüklendi - Bu dosyayı yüklemek için bir İşleyici bulunamıyor. - Yükleme durumundayken bekleyiniz… + Kurulum başarısız oldu + Kuruldu + Bu dosyayı kurmak için bir işleyici bulunamıyor. + Kurulum durumu belirlenirken lütfen bekleyin… %1$s: %2$s %3$s - %1$d/%2$d Yükleniyor - Her 15 Dakikada - Her 5 Dakikada - Her 45 Dakikada - Saatte 1 Kez - Dakikada 1 Kez - Her 10 Dakikada - Her 30 Dakikada - Italian - Japanese - Firmware - YazıTipi - GPS Firmware + %1$d/%2$d kuruluyor + her 15 dakikada bir + her 5 dakikada bir + her 45 dakikada bir + saatte 1 defa + dakikada 1 defa + her 10 dakikada bir + her 30 dakikada bir + İtalyanca + Japonca + Ürün yazılımı + Yazı tipi + GPS Ürün Yazılımı GPS Almanak GPS Hata Düzeltme - Geçersiz Veri + Geçersiz veri Kaynaklar - Saat AraYüzü - Korean - Eksik Uyku: %1$s - Eksik Adım: %1$d - Dil ve Bölge Ayarları + Saat Arayüzü + Korece + Eksik uyku: %1$s + Eksik adım sayısı: %1$d + Dil ve bölge ayarları Sol - Mevcut Adımlar/Dakika - Kalp Ritmi - Mevcut / EnYüksek Kalp Ritmi: %1$d / %2$d - Etkinliğinizi Başlatın - Geçmişte Adımlar - Geçmişte Dakikada Adımlar - Toplam Adımlar - Canlı Aktivite + Şu anki adım sayısı/dakika + Kalp ritmi + Şu anki / En yüksek kalp ritmi: %1$d / %2$d + Etkinliğinizi başlatın + Adım sayısı geçmişi + Dakikada adım sayısı geçmişi + Toplam adım sayısı + Canlı etkinlik Erkek Süre Etkinlik Alarm - AliPay + Alipay Pusula - DahaFazla + Daha Fazla Müzik NFC Bildirimler Ayarlar - AliPay (KısaYol) - HavaDurumu (KısaYol) + Alipay (Kısayol) + Hava Durumu (Kısayol) Durum Zamanlama Hava Durumu - MAC Adresi geçilemedi, Eşlenilemez. - Otomatik (Uyku Algılama) + MAC adresi verilmedi, eşleştirilemiyor. + Otomatik (uyku algılama) Kapalı - Zamanlanmış (Zaman Aralığı) - Metin Bildirimleri - Firmware gerekiyor >= 1.0.1.28 ve Mili_pro.ft* yüklendi. - Bu Firmware yüklenmeden önce %1$s Sürümü yüklenmelidir! - Kolumu Kaldırınca Ekranı Aç - Düğme Basma Eylemini Etkinleştir - Belirtilen sayıda basıldığında eylemi etkinleştir - Band Titreşimini Etkinleştir - Tetiklenen Basma eyleminde bant titreşimini etkinleştir - Basma Eylemleri - Basma Eylemlerini Belirleme - Gönderilecek Mesajı Yayınla + Planlandı (zaman aralığı) + Metin bildirimleri + 1.0.1.28 veya üstü bir ürün yazılımı ve Mili_pro.ft* kurulu olması gerekiyor. + Bu ürün yazılımını kurmadan önce %1$s sürümünü kurmalısınız! + Kaldırma anında ekranı etkinleştir + Düğme eylemini etkinleştir + Belirtilen sayıda düğmeye basıldığında eylemi etkinleştir + Bileklik titreşimini etkinleştir + Tetiklenen düğme eyleminde bileklik titreşimini etkinleştir + Düğme eylemleri + Düğme basma eylemlerini belirtin + Gönderilecek yayınlama mesajı nodomain.freeyourgadget.gadgetbridge.ButtonPressed - Aktiviteyle gönderilip Yayınlanan Mesaj. `button_id` Değeri otomatik olarak her mesaja eklenecek. - Basma Eylemi Sayısı + Etkinlikle birlikte gönderilen yayınlama mesajı. `button_id` parametresi otomatik olarak her mesaja eklenir. + Düğme basma sayısı Basmalar arasındaki azami gecikme Milisaniye cinsinden basmalar arasındaki azami gecikme - Etkinliği tetiklemek için Basma Sayısı. Aynı miktarda Basma ikinci etkinliği tetikler. - Görüntülenecek Eylemler - Band Ekranında Görüntülenecek Eylemleri Belirleyin - Rahatsız Etmeyin - Bitiş Zamanı - Başlama Zamanı - "Aktifken Band Bildirim Almaz" - Hedefe Ulaşma Bildirimi - Günlük Adım hedefine ulaşıldığında Band titreyecek - Hareketsizlik Uyarıları + Etkinlik 1\'i tetiklemek için gerekli düğme basma sayısı. Sonraki aynı miktarda basmalar Etkinlik 2\'yi tetikler ve bu böyle devam eder. + Görüntülenecek ögeler + Bileklik ekranında görüntülenen ögeleri seçin + Rahatsız Etme + Bitiş zamanı + Başlama zamanı + Etkinken bileklik bildirim almayacak + Hedefe ulaşma bildirimi + Günlük adım sayısı hedefine ulaşıldığında bileklik titreyecek + Hareketsizlik uyarıları Belirli zaman arasında Hareketsizlik Uyarısı vermesin - Belirli süre Hareketsiz kaldığınızda Band titreyecek - Hareketsizlik Dakika Sınırı - Ekran bilgisini değiştirmek için Bileği döndür - Gün Batımında - Band Ekran Kilidini Açma - "Band Ekran Kilidi açmak için Yukarı kaydır" - Gece Modu - Geceleri Otomatik olarak DahaAz Ekran Parlaklığı - Tarih Formatı - Mi2: Zaman Formatı - Bu ürün yazılımı test edilmiştir ve GadgetBridge ile uyumlu olduğu bilinmektedir. - Her şey düzgün çalışıyorsa, lütfen GadgetBridge geliştiricilerine %s ürün yazılımı sürümünü beyaz listeye eklemelerini söyleyin. - Bu Firmware test edilmemiştir ve GadgetBridge ile uyumlu olmayabilir. -\n -\nFormat atmanız gerekebilir! - Uyumlu Sürüm - Uyumsuz Firmware - Test edilmemiş Sürüm! - Bandınız titreşip yanıp söndüğünde, arka arkaya birkaç kez dokunun. - Geçerli kullanıcı verisi alınmadı, şimdilik model kullanıcı verisi kullanıyor. - İsim/TakmaAd - Vardiyalı Çalışanlar (Uyku Zamanı) için Saat Farklılığı - Günlük Adım Hedefi - Uyku algılamasını iyileştirmek için Kalp Ritmi sensörünü kullan - Gelecek etkinlikler için ayrılacak Alarmlar + Belirli süre hareketsiz kaldığınızda bileklik titreyecek + Hareketsizlik sınırı (dakika cinsinden) + Bilgileri değiştirmek için bileği döndür + Gün batımında + Bileklik ekran kilidini açma + Bileklik ekran kilidini açmak için yukarı kaydır + Gece modu + Geceleri bilekliğin ekran parlaklığını otomatik olarak azalt + Tarih biçimi + Mi2: Zaman biçimi + Bu ürün yazılımı test edilmiştir ve Gadgetbridge ile uyumlu olduğu bilinmektedir. + Her şey düzgün çalışıyorsa, lütfen Gadgetbridge geliştiricilerine %s ürün yazılımı sürümünü beyaz listeye eklemelerini söyleyin. + Bu ürün yazılımı test edilmemiştir ve Gadgetbridge ile uyumlu olmayabilir. +\n +\nKurulması TAVSİYE EDİLMEZ! + Uyumlu sürüm + Uyumsuz ürün yazılımı + Test edilmemiş sürüm! + Bilekliğiniz titreşip yanıp söndüğünde, arka arkaya birkaç kez dokunun. + Geçerli kullanıcı verisi verilmedi, şimdilik model kullanıcı verisi kullanılıyor. + İsim/Takma ad + Saat cinsinden aygıt zamanı farklılığı (vardiyalı çalışanların uykusunu algılamak için) + Günlük adım sayısı hedefi + Uyku algılamasını iyileştirmek için kalp ritmi sensörünü kullan + Gelecek etkinlikler için ayrılacak alarmlar Titreşim - 1 Dakika - 10 Dakika - 30 Dakika - 5 Dakika + 1 dakika + 10 dakika + 30 dakika + 5 dakika Mod Yapılandırması YOK - Bildirim Yapılmasın - VeriYok - Limitsiz - Norwegian Bokmål - Bağlı Değil - %1$s Batarya Düşük + Asla + Veri yok + Sınırsız + Bokmål Norveççe + Bağlı değil + %1$s pil düşük Son şarj: %s \n - Sarj Oranı: %s - %1$s Batarya Az: %2$s - %1$s Batarya kaldı: %2$s%% - Cihaz Bataryası Az! - VeriTabanı DışaAktarma Hata verdi. Ayarları kontrol edin. - GadgetBridge Bildirimlerine Yüksek Öncelik - GadgetBridge Bildirimleri + Şarj sayısı: %s + %1$s pil düşük: %2$s + %1$s pil kaldı: %2$s%% + Aygıt pili düşük! + Veri tabanını dışa aktarma başarısız oldu! Lütfen ayarlarınızı gözden geçirin. + Gadgetbridge bildirimlerine yüksek öncelik + Gadgetbridge bildirimleri Kapalı Tamam Açık Diğer - Uyku Süresi: %1$s - Uyku Aşımı: %1$d - %s ile Eşleşiyor… + Fazla uyku: %1$s + Fazla adım: %1$d + %s ile eşleştiriliyor… %1$s (%2$s) ile zaten etkileşimde, bağlanıyor… %1$s (%2$s) ile etkileşim başlıyor Etkileşim sürüyor: %1$s (%2$s) - %1$s (%2$s) ile Eşleşme geçersiz - "Belirtilen Firmware yüklenemiyor: Pebble donanım revizyonuyla eşleşmiyor." - Dosya Yükleme Hatası: %1$s + %1$s (%2$s) ile eşleştirilemiyor + Verilen ürün yazılımı kurulamıyor: Pebble aygıtınızın donanım revizyonuyla eşleşmiyor. + Verilen dosya kurulamıyor: %1$s Pebble Sürümü %1$s %1$s (%2$s) - Donanım Revizyonu Doğru - Donanım Revizyonu Benzemiyor! - Android cihazınızda bir eşleşme iletişim kutusu açılır. Değilse, bildirim kutusuna bakın ve eşleştirme isteğini kabul edin. Daha sonra Pebble üzerinde onaylayın. - Polish - Portuguese + Donanım revizyonu doğru + Donanım revizyonu eşleşmiyor! + Android aygıtınızda bir eşleşme iletişim kutusu açılacak. Açılmazsa, bildirim çekmecesine bakın ve eşleştirme isteğini kabul edin. Daha sonra Pebble üzerinde kabul edin. + Lehçe + Portekizce Etkinlik verilerini otomatik getir Getirmeler arasındaki asgari süre Her %d dakikada bir getirir - Getirme, ekran kilidi açıldığında gerçekleşir. Sadece bir kilit mekanizması ayarlanmışsa çalışır! - KaraListe Uygulamaları - KaraListe Takvimleri + Getirme, ekran kilidi açıldığında gerçekleşir. Yalnızca bir kilit mekanizması ayarlanmışsa çalışır! + Kara Liste Uygulamaları + Kara Liste Takvimleri Engellenmiş Bilinmeyen - İsim ve Numarayı Gizle - İsim Gizle Numara Göster - Numara Gizle İsim Göster - İsim ve Numara Göster - Portakal + İsim ve numarayı gizle + İsmi gizle ama numarayı göster + Numarayı gizle ama ismi göster + İsim ve numara göster + Turuncu Kırmızı - Öğleden Öğleye - Son 24 Saat - Haftalık Ortalama Çizelgesi - Aylık Ortalama Çizelgesi - Varsayılan - Yeni BLE (BlueTooth Düşük Enerji) Tarama Yapılmasın - Yeni Cihaz Bağlama Simgesi - Sadece hiçbir Cihaz eklenmediğinde görünür - Daima Göster - Aktivite İzleyiciler - Otomatik DışaAktar + Öğleden öğleye + Son 24 saat + Haftalık ortalama çizelgesi + Aylık ortalama çizelgesi + Öntanımlı + Yeni BLE (BlueTooth Düşük Enerji) taramasını devre dışı bırak + Yeni aygıt bağla düğmesi + Yalnızca hiçbir aygıt eklenmediğinde görünür + Her zaman göster + Etkinlik izleyiciler + Otomatik dışa aktar Otomatik getir - Hazır Mesajlar - Gösterge Ayarları - Tarih ve Zaman + Mesaj şablonları + Çizelge Ayarları + Tarih ve Saat Gelişmiş Seçenekler Genel Ayarlar Konum Bildirimler - Pebble ZamanÇizgisi + Pebble zaman çizelgesi Gizlilik - Titreme Sayısı - Titreme Ayarları - Lütfen 87.5 ve 108.0 arası bir Frekans girin - Geçersiz Frekans + Titreşim sayısı + Titreme ayarları + Lütfen 87.5 ve 108.0 arasında bir frekans girin + Geçersiz frekans Sonrakine Geç SONRAKİ Sonraki Medya @@ -536,276 +536,276 @@ YÜKLEME RİSKİ RİZE AİTTİR!" SESDÜŞÜR Sesi Yükselt SESYÜKSELT - Sadece Bildirim Simgesini Göster + Yalnızca bildirim simgesini göster Bildirim metnini ekran dışına kaydır - Normal Bildirimler - Sağdan Sola EnFazla Satır Uzunluğu - Sağdan Sola Metin Satırlarını Uzatma veya Kısaltma - Alarm Saati - Kayıp Cihaz Uyarısı - Takvim Bildirimi - E-Posta Bildirimi - Genel Bildirim + Normal bildirimler + Sağdan Sola En Fazla Satır Uzunluğu + Sağdan sola metinlerin ayrıldığı satırları uzatır veya kısaltır + Alarm saati + Kayıp önleme uyarısı + Takvim bildirimi + E-Posta bildirimi + Genel bildirim Sohbet Navigasyon - Sosyal Ağ - Hareketsizlik Bildirimi - GelenÇağrı Bildirimi - Düşük Batarya Uyarısı - Cevapsız Çağrı Bildirimi - SMS Bildirimi - Titreşim Kurgusu - Aktarım hızını artırır, ancak bazı Android cihazlarda çalışmayabilir. - Kimlik doğrulama anahtarını, bağlanmak istediğiniz tüm Android cihazlarınızda ortak bir anahtar olarak değiştirin. Tüm cihazlar için önceki varsayılan anahtar: 0123456789 @ ABCDE - Her %d saatte dışa aktar - Android cihazdan silinen bildirimleri Pebble cihazından da otomatik olarak kaldır - Arabça içeriği desteklemek için bunu Etkinleştirin - Bağlandığında GadgetBridge simgesi yerine cihaza özel bir Android bildirim simgesi göster - Android cihazda Saat ve Saat Dilimi değiştiğinde, GadgetBridge bağlanırken Zamanı senkronize et - Tarama sırasında cihazınız bulunmaz ise bu seçeneği işaretleyin - Banda onay gönderilmezse, aktivite verileri temizlenmez. GB diğer uygulamalarla birlikte kullanılıyorsa kullanışlıdır. - Takvim etkinliklerini Zaman Çizgisine gönder - Bunun Pasifleştirilmesi, Pebble 2/LE cihazındaki giden aramalarda titremeyi durduracaktır + Sosyal ağ + Hareketsizlik bildirimi + Gelen çağrı bildirimi + Düşük pil uyarısı + Cevapsız çağrı bildirimi + SMS bildirimi + Titreşim profili + Aktarım hızını artırır, ancak bazı Android aygıtlarda çalışmayabilir. + Kimlik doğrulama anahtarını, bağlanmak istediğiniz tüm Android aygıtlarınızda ortak bir anahtar olarak değiştirin. Tüm aygıtlar için önceki öntanımlı anahtar: 0123456789@ABCDE + Her %d saatte bir dışa aktar + Bildirimler Android aygıtından silindiğinde otomatik olarak Pebble üzerinden kaldırılır + Arapça içeriği desteklemek için bunu etkinleştirin + Bağlandığında Gadgetbridge simgesi yerine aygıta özel bir Android bildirim simgesi göster + Bağlanırken ve Android aygıtında saat ve saat dilimi değiştiğinde saati Gadgetbridge aygıtına eşzamanla + Tarama sırasında aygıtınız bulunamıyorsa bu seçeneği işaretleyin + Bilekliğe onay (ACK) gönderilmezse, etkinlik verileri temizlenmez. GB diğer uygulamalarla birlikte kullanılıyorsa kullanışlıdır. + Takvim etkinliklerini zaman çizelgesine gönder + Bunun devre dışı bırakılması, Pebble 2/LE aygıtının giden aramalarda titremesini durduracaktır PebbleKit kullanırken Android uygulamaları için deneysel desteği etkinleştirin - GadgetBridge bağlıyken diğer uygulamaların verilere gerçek zamanlı erişmesine izin ver + Gadgetbridge bağlıyken diğer uygulamaların kalp ritmi verilerine gerçek zamanlı erişmesine izin verir Saatinizin karanlık görünümü varsa kullanışlıdır - Senkronizasyondan sonra bile Aktivite verilerini Mi Bandında tutar. GB diğer uygulamalarla birlikte kullanılıyorsa kullanışlıdır. + Eşzamanlamadan sonra bile etkinlik verilerini Mi Band üzerinde tutar. GB diğer uygulamalarla birlikte kullanılıyorsa kullanışlıdır. Geçerli konumu çalışma zamanında almaya çalış, depolanan konumu yedek olarak kullan - Bu, Ürün Firmware yüklemesinin başarısız olduğu cihazlarda yardımcı olabilir. - Durum Çubuğunda Simge ve Kilit Ekranında Bildirim gösterilir - Durum Çubuğunda Simge ve Kilit Ekranında Bildirim gizlenir - İstenmeyen Bildirimler, bu modda durdurulmuştur - PebbleKit üzerinden Pebble cihazına bildirim gönderen uygulamalar için destek. - Harici 3.Taraf uygulamalarına gönderilen iletilerin her zaman ve hemen onaylanmasına neden olur - Saat uygulamalarındaki günlüklerin GadgetBridge tarafından günlüğe kaydedilmesine neden olur (yeniden bağlanma gerekir) - Etkinleştirildiği zaman, Hava Durumu, Batarya ve benzeri bilgileri Bileklik arayüzüne gönderir. - Klasik BT yerine tüm Pebbleler için deneysel Pebble LE desteği kullanın. Bu önce LE olmayan ile ve daha sonra Pebble LE ile eşleştirme gerektirir + Bu, ürün yazılımı kurulmasının başarısız olduğu aygıtlarda yardımcı olabilir. + Durum çubuğunda simge ve kilit ekranında bildirim gösterilir + Durum çubuğunda simge ve kilit ekranında bildirim gizlenir + İstenmeyen bildirimler, bu modda durdurulmuştur + PebbleKit üzerinden Pebble aygıtına bildirim gönderen uygulamalar için destek. + Harici 3. taraf uygulamalarına gönderilen mesajların her zaman ve hemen onaylanmasına neden olur + Saat uygulamalarındaki günlüklerin Gadgetbridge tarafından günlüğe kaydedilmesine neden olur (yeniden bağlanma gerekir) + Etkinleştirildiğinde, saat arayüzlerinin hava durumu, pil bilgisi vs. göstermesine izin verir. + Klasik BT yerine tüm Pebble\'ler için deneysel Pebble LE desteği kullanın. Bu önce LE olmayan ile ve daha sonra Pebble LE ile eşleştirme gerektirir Bu seçenek, ürün yazılımı sürümüne bağlı olarak en son bildirim protokolünü kullanmaya zorlar. NE YAPTIĞINIZI BİLİN! Test edilmemiş özellikleri etkinleştir. NE YAPTIĞINIZI BİLİN! - Bu sadece Pebble 2 ve Deneysel içindir, bağlantı sorunlarınız varsa bunu deneyin - Verileri \"olduğu gibi\" saklar, daha sonra yorumlanabilmesi için veritabanı kullanımını artırır. + Bu yalnızca Pebble 2 içindir ve deneyseldir, bağlantı sorunlarınız varsa bunu deneyin + Verileri \"olduğu gibi\" saklar, daha sonra yorumlanabilmesi için veri tabanı kullanımını artırır. Eğer Pebble 2/Pebble LE beklendiği gibi çalışmazsa, MTU sınırlaması için bu ayarı deneyin (geçerli aralık 20–512) - Firmware kontrollerini gevşet - Cihazınız sağdan sola dilleri gösteremiyorsa bunu etkinleştirin + Ürün yazılımı denetimlerini gevşet + Aygıtınız sağdan sola dilleri gösteremiyorsa bunu etkinleştirin Bağlanma konusunda sorun yaşıyorsanız bunu devre dışı bırakın - Konum üzerindeki GünDoğumu ve GünBatımı zamanlarını, Pebble Zaman Çizgisine gönder - Bağlantı kesildiğinde bile Takvim Alarmlarını etkinleştirir - Cihazınızda, Dilinizin YazıTipi için destek yoksa bunu etkinleştirin - Cihazınız, Emoji desteği için özel bir YazıTipi yazılımına sahipse bunu etkinleştirin - Karanlık - Aydınlık + Pebble zaman çizelgesine konuma göre gün doğumu ve gün batımı saatlerini gönder + Bağlantı kesildiğinde bile takvim uyarılarını etkinleştirir + Aygıtınızda dilinize ait yazı tipi desteği yoksa bunu etkinleştirin + Aygıtınız, Emoji desteği için özel bir yazı tipi yazılımına sahipse bunu etkinleştirin + Koyu + Açık karanlık aydınlık Yüksek MTU\'ya izin ver Tercih Edilen Ses Oynatıcı Kimlik Doğrulama Anahtarı - Otomatik DışaAktarma Açık - Dışa Aktarma Aralığı - Dışa Aktarma Konumu - Yoksayılan Bildirimleri Otomatik Kaldır - Çağrı Gizlilik Modu - Çağrı YokSayımı + Otomatik dışa aktarma etkinleştirildi + Dışa aktarma aralığı + Dışa aktarma konumu + Yok sayılan bildirimleri otomatik kaldır + Çağrı gizlilik modu + Çağrı Yok Sayma Pebble Güncelleme Cevaplar - Ortak SonEk - Kalp Ritmi Rengi - Uyku Aralığı - Çizelgelerdeki Ortalamaları Göster + Ortak son ek + Kalp ritmi rengi + Uyku aralığı + Çizelgelerde ortalamaları göster Çizelge Aralığı - Aktivite Çizelgelerini sola / sağa hızlıca kaydırmayı etkinleştir - Arabça İçerik - Cihaz Özel Bildirim Simgesini Göster - Senkronizasyon Zamanı - Mi Band Bluetooth Adresi - ACK Aktivite veri aktarımı yapma - Takvimi Senkronize Et - Giden Çağrıları Destekle - 3.Taraf Android Uygulama erişimine izin ver - 3.Taraf gerçek zamanlı KalpRitmi erişimi - Beyaz renk şemasında siyahı zorla - Bluetooth açıkken GadgetBridge cihazına Bağlan - Otomatik Yeniden Bağlan - Otomatik Başlat - Verileri Cihazda Sakla - Lisan - Konumu Al - Konumu Güncel Tut + Etkinlik çizelgelerinde sola/sağa kaydırmayı etkinleştir + Arapça İçerik + Aygıta özel bildirim simgesini göster + Saati eşzamanla + Mi Band adresi + Etkinlik veri aktarımını ACK (onaylama) yapma + Takvimi eşzamanla + Giden çağrıları destekle + 3. taraf Android Uygulama erişimine izin ver + 3. taraf gerçek zamanlı kalp ritmi erişimi + Beyaz üzerinde siyah renk şemasını zorla + Bluetooth açıkken Gadgetbridge aygıtına bağlan + Otomatik yeniden bağlan + Otomatik başlat + Etkinlik verilerini aygıtta sakla + Dil + Konumu al + Konumu güncel tut Enlem Boylam - Firmware yüklerken Düşük-Gecikme Modu kullan - Azaltma Butonu - Orta Butonu - GadgetBridge Bildirimini Gizle + Ürün yazılımı kurarken düşük gecikme modu kullan + Alt Düğme + Orta Düğme + Gadgetbridge bildirimini gizle Rahatsız Etme Telefon Çağrıları - Genel Bildirim Desteği + Genel bildirim desteği Pebble Mesajları Tekrarlar SMS Bildirimler arasındaki asgari süre - Tercih Edilen Aktivite İzleyici + Tercih edilen etkinlik izleyici Erken ACK PebbleKit İzleme uygulaması kaydetmeyi etkinleştir - ArkaPlanda JS Etkinleştir - Daima BLE (Bluetooth Düşük Enerji) TercihEt - Bildirim Protokolünü Zorla + Arka plan JS\'sini etkinleştir + Her zaman BLE (Bluetooth Düşük Enerji) tercih et + Bildirim protokolünü zorla Test edilmemiş özellikleri etkinleştir - Sadece GATT İstemcisi - Ham kaydı veritabanında depola - Pebble 2/LE GATT MTU Limiti - Gizlilik Modu - Yeniden Bağlanma Denemeleri - Pebble Ayarları - Pebble Health Senkronizasyonu - Misfit Senkronizasyonu - Morpheuz Sentronizasyonu - Cihazınız için tasarlanmamış bir ürün yazılımı yüklemek istiyorsanız bunu etkinleştirin (sorumluluk size aittir) + Yalnızca GATT istemcisi + Ham kaydı veri tabanında depola + Pebble 2/LE GATT MTU sınırı + Gizlilik modu + Yeniden bağlanma denemeleri + Pebble ayarları + Pebble Health Eşzamanlaması + Misfit Eşzamanlaması + Morpheuz Eşzamanlaması + Aygıtınız için tasarlanmamış bir ürün yazılımı kurmak istiyorsanız bunu etkinleştirin (sorumluluk size aittir) Sağdan Sola - Zaman Gösterimi Ekranı - Bluetooth Paylaşımını Etkinleştir - GünDoğumu ve GünBatımı - VoIP Çağrılarını Etkinleştir - Takvim Etkinliklerini Senkronize Et - Görünüm - Zaman Formatı - KarakterÇevirisi + Ekran açık süresi + Bluetooth eşleştirmeyi etkinleştir + Gün doğumu ve gün batımı + VoIP çağrılarını etkinleştir + Takvim etkinliklerini eşzamanla + Tema + Zaman biçimi + Karakter çevirisi Birimler - Yukarı Butonu - Özel Font Kullan - Titreşim Gücü + Üst Düğme + Özel yazı tipi kullan + Titreşim gücü Hava Durumu Hava durumu konumu (LineageOS hava durumu sağlayıcısı için) - Ekran Açıkken de Genel Bildirim - Günlük Dosyalarını Yaz - Cihaz Özel Ayarları + …ayrıca ekran açıkken + Günlük dosyalarını yaz + Aygıta özel ayarlar FM Frekansı - HPlus/Makibes Ayarları + HPlus/Makibes ayarları LED Rengi - Makibes HR3 Ayarları - Mi Band / AmazFit Ayarları + Makibes HR3 ayarları + Mi Band / AmazFit ayarları Q Hybrid Ayarları Sağdan Sola Desteği - Aktivite 2 Eylemi - Uzun Basma Eylemi - Aktivite 1 Eylemi - Aktivite 3 Eylemi - Ayrıntılı Basma Eylemi Ayarları - Bağlantı Kesilme Bildirimi - \\\'Telefonu Bul\\\' Etkinleştir - Telefonu Bul - Zil Süresi Saniye + Etkinlik 2 eylemi + Uzun basma eylemi + Etkinlik 1 eylemi + Etkinlik 3 eylemi + Ayrıntılı basma eylemi ayarları + Bağlantı kesilme bildirimi + \\\'Telefonu bul\\\' özelliğini aç + Telefonu bul + Saniye cinsinden zil süresi Telefonunuzun zilini çalmak için bilekliğinizi kullanın. - Spor etkinliği sırasında Kalp Ritmi alarmı - EnYüksek Limit - EnDüşük Limit - Ekran Yönü - Tüm gün Kalp Ritmi ölçümü - Tüm gün KalpRitmi ölçümü - Saatiniz Sağda mı Solda mı? - Üzerine Yazma Butonları Hatası - Üzerine Yazma Butonları + Spor etkinliği sırasında kalp ritmi alarmı + Üst sınır + Alt sınır + Ekran yönü + Tüm gün kalp ritmi ölçümü + Tüm gün kalp ritmi ölçümü + Sola mı sağa mı giyiyorsunuz\? + Düğmelerin üzerine yazılırken hata oluştu + Düğmelerin üzerine yazıldı değişiklik birkaç saniye sürebilir… - Adım Hedefi - Zaman Dengeleyici - SaatDilimi Dengeleyici - Üzerine yazma butonları + Adım sayısı hedefi + saati kaydır + saat dilimini kaydır + düğmelerin üzerine yaz Etkinleştirmek için lütfen adım sayısını bir milyona ayarlayın. UTC olarak ikinci saat dilimi farkı - Zaman Değişimi - Aktivite elini bildirim sayacı olarak kullan - Titreşim Gücü: + zaman değişimi + etkinlik elini bildirim sayacı olarak kullan + titreşim gücü: Getirme tarihini sıfırla Sağ - Russian + Rusça Yapılandırmayı Kaydet - 10 Saniye - 20 Saniye - 30 Saniye - 5 Dakika - Hepsini Seç + 10 saniye + 20 saniye + 30 saniye + 5 saniye + Tümünü seç Paylaş - Günlüğü Paylaş - "Genel Sorun Raporuna ait bir günlük dosyasını göndermeden önce Lütfen Şunu Unutmayın: Gadgetbridge, sağlık verileri, benzersiz tanımlayıcılar (bir cihazın MAC adresi gibi), müzik tercihleri vb. bilgiler yanında çok sayıda kişisel bilgi içerebilecek konuları da günlüğe kaydeder." - Simplified Chinese + Günlüğü paylaş + Gadgetbridge\'in sağlık verileri, benzersiz tanımlayıcılar (bir aygıtın MAC adresi gibi), müzik tercihleri vb. dahil ancak bunlarla sınırlı olmamak üzere birçok kişisel bilgiyi günlük dosyalarına kaydettiğini lütfen unutmayın. Dosyayı herkese açık sorun bildirimi olarak göndermeden önce bu dosyayı düzenlemeyi ve bu bilgileri kaldırmayı göz önünde bulundurun. + Basitleştirilmiş Çince Saat %1$s ile %2$s arası Uyku - Spanish - Hız Dilimleri - Toplam Dakika - Dakikada Adım - Bağlanmak için cihaza dokunun - Aktivite için bağlı cihaza hafifçe dokunun - Uygulama yöneticisi için bağlı cihaza hafifçe dokunun - Titreşim için bağlı cihaza hafifçe dokunun + İspanyolca + Hız bölgeleri + Toplam dakika + Dakikada adım sayısı + Bağlanmak için bir aygıta dokunun + Etkinlik için bağlı aygıta hafifçe dokunun + Uygulama yöneticisi için bağlı aygıta hafifçe dokunun + Titreşim için bağlı aygıta hafifçe dokunun Test - Test Bildirimi - Thai - Bu GadgetBridge üzerinden bir test bildirimidir - 24Saat + Test bildirimi + Tayca + Bu Gadgetbridge üzerinden bir test bildirimidir + 24 saat ÖÖ/ÖS - Alarm Detayları - Cihaz Eşleştir - Bildirim KaraListesi + Alarm ayrıntıları + Aygıt eşleştir + Bildirim kara listesi Uygulama Yöneticisi - KaraListedeki Takvimler - Aktivite ve Uyku Grafiği - GadgetBridge - VeriTabanı Yönetimi + Kara Listeye Alınan Takvimler + Etkinlik ve Uyku + Gadgetbridge + Veri tabanı yönetimi Hata Ayıklama - Cihaz Özel Ayarları - Cihaz Arama - FW/App Yükleme - Mi Bandını Eşleştir + Aygıta özel ayarlar + Aygıt arama + Ürün yazılımı/uygulama kurucu + Mi Band\'ınızı eşleştirin Bildirim Filtresi Pebble Eşleştirme - Alarmları Yapılandır + Alarmları yapılandır Ayarlar - Uyku Göstergesi + Uyku izleme Titreşim - Watch 9 Ayarı - Watch 9 Eşleştirme - Yapılandırılacak Uygulama KaraListeye alınmamalıdır - Konum Alındı - Lütfen Ağ Konumlamayı Etkinleştirin - Bildirim Filtresi Kaydedildi - Lütfen EnAz 1 Kelime Girin - Traditional Chinese + Watch 9 kalibrasyonu + Watch 9 eşleştirme + Uygulamanın yapılandırılması için kara listeye alınmaması gerekir + konum alındı + Lütfen ağ konumunu etkinleştirin + Bildirim filtresi kaydedildi + Lütfen en az bir sözcük girin + Geleneksel Çince Türkçe - Ukrainian - Imperyal + Ukraynaca + İngiliz birimleri Metrik - Bilinmeyen Durum - Firmware Gönderilmedi - Firmware Meta Veri Aktarımı Sorunu - Firmware Yükleme Tamam - Firmware Yükleme Tamamlandı, Cihaz Yeniden Başlatılıyor… - Firmware Yazılıyor - Firmware Aktarımı Sorunu. Mi Bandınızı YENİDEN BAŞLATMAYIN! - Firmware Yazımı Başarısız - Firmware Yazılıyor… - Bütün Alarmlar Etkisizleştirildi - Veri Aktarımı Durumu: %1$s / %2$s + Bilinmeyen durum + Ürün yazılımı gönderilmedi + Ürün yazılımı üst veri aktarımıyla ilgili sorun + Ürün yazılımı kurulumu tamamlandı + Ürün yazılımı kurulumu tamamlandı, aygıt yeniden başlatılıyor… + Ürün yazılımı kuruluyor + Ürün yazılımı aktarımıyla ilgili sorun. Mi Band\'ınızı YENİDEN BAŞLATMAYIN! + Ürün yazılımı kurulması başarısız oldu + Ürün yazılımı kuruluyor… + Bütün alarmlar devre dışı + Veri aktarımı durumu: %1$s / %2$s Alarmlar ayarlanırken bir hata oluştu, lütfen tekrar deneyin. - Alarmlar Cihaza Gönderildi. + Alarmlar aygıta gönderildi. Dikey - Alarm Saati + Alarm saati Uzun Orta - ZilSesi + Zil sesi Kısa - KesikKesik - SuDamlası - DenemeYap - Vietnamese - Bağlanmak için bekliyor + Kesik kesik + Su damlası + Dene + Vietnamca + Yeniden bağlanmak için bekliyor Dikkat! Kalibrasyon - Cihazınızın şu anda size göstereceği zamanı ayarlayın. - Saatiniz titreştiğinde cihazı sallayın veya düğmesine basın. + Aygıtınızın şu anda size gösterdiği saati ayarlayın. + Saatiniz titreştiğinde aygıtı sallayın veya düğmesine basın. Saat: Dakika: Saniye: - Saat Bağlanmadı + Saat bağlanmadı Pebble üzerinde hava durumu bilgisi almak için bu dış görünümün Hava Durumu Bildirimi uygulamasında etkinleştirildiğinden emin olun. \n \nBurada herhangi bir yapılandırma gerekmez. @@ -813,127 +813,127 @@ YÜKLEME RİSKİ RİZE AİTTİR!" \nPebble sistem hava durumu uygulamasını uygulama yönetiminden etkinleştirebilirsiniz. \n \nDesteklenen saat yüzleri hava durumunu otomatik olarak gösterir. - Aylık Uykular - Haftalık Uykular - Bugünkü Uyku, Hedef: %1$s - Bugünkü Adımlar, Hedef: %1$s - Aylık Adımlar - Haftalık Adımlar - Ekran Kapalı Olduğu Zaman - Bildirimler için Hepsini BeyazListeye al - 10 Dakika - 1 Saat - 20 Dakika - 5 Dakika + Aylık uyku + Haftalık uyku + Bugünkü uyku, hedef: %1$s + Bugünkü adım sayısı, hedef: %1$s + Aylık adım sayısı + Haftalık adım sayısı + Ekran kapalı olduğunda + Bildirimler için hepsini beyaz listeye al + 10 dakika + 1 saat + 20 dakika + 5 dakika Durum ve Alarmlar - Alarm Ayarla: + Alarm kur: Uyku: %1$s Adım: %1$02d Uyumadınız Saat %1$s ile %2$s arası uyudunuz - Aktivite İzleme - Aktivite İzlemenin Açılması, adımları sayacaktır. - Analog Modu - Sadece Ellerle - Eller ve Adımlar - Kalori Cinsi - Sadece Aktif Harcanan Kalori - Aktif ve Pasif Harcama Kalorisi - Tarih Formatı - YL/AY/GN - GN/AY/YL - AY/GN/YL - El Hareketi - Ekranı Açmak veya Kapamak için bileğinizi döndürün. - Kalp Ritmi Alarmını Etkinleştir + Etkinlik izleme + Etkinlik izlemenin açılması adımlarınızı sayacaktır. + Analog modu + Yalnızca eller + Eller ve adımlar + Kalori türü + Yalnızca etkin harcanan kaloriler + Etkin ve etkin değilken harcanan kaloriler + Tarih biçimi + YY/AA/GG + GG/AA/YY + AA/GG/YY + El hareketi + Ekranı açmak veya kapatmak için bileğinizi döndürün. + Kalp ritmi alarmını etkinleştir Cuma Pazartesi - Tekrarlama Sayısı + Tekrarlar Cumartesi Pazar Perşembe Salı Çarşamba - Sürekli Bip Sesi - 1 Kez Bip Sesi - 2 Kez Bip Sesi - Sessizlik - Sürekli Titreşim - Sürekli Titreşim ve Bipleme - 1 Kez Titreşim ve Bipleme - 1 Kez Titreşim - 2 Kez Titreşim - Alarm için Sinyal Türü Ayarla - Kalp Ritmi Alarmı - Kalp Ritminiz sınırı aşarsa, Saat sizi uyaracaktır. - Kalp Ritmi Ayarları - Zaman Durumu Ekranında Saniyeler - ZeTime Ayarları + Sürekli bip sesi + 1 defa bip sesi + 2 defa bip sesi + Sessiz + Sürekli titreşim + Sürekli titreşim ve bip sesi + 1 defa titreşim ve bip sesi + 1 defa titreşim + 2 defa titreşim + Alarm için sinyal türünü ayarla + Kalp ritmi alarmı + Kalp ritminiz sınırı aşarsa, saat sizi uyaracaktır. + Kalp ritmi ayarları + Saniye cinsinden ekran açık süresi + ZeTime ayarları - %d Saat - %d Saat + %d saat + %d saat - AnaSayfa Göstergesi (Widget) Çevresi Çiz - Watch X Plus Kalibrasyonu - Ham aktivite dosyalarını kaydet + Widget çemberleri çiz + Watch X Plus kalibrasyonu + Ham etkinlik dosyalarını kaydet Egzersiz Etkinlik Hatırlatıcı Kalp Ritmi PAI Watch X Plus Watch X - Sadece Saat - Güç Tasarrufu + Yalnızca saat + Güç tasarrufu Normal - Saat Güç Modu + Saat güç modu Kalibrasyona başlamak için buraya basın Kalibrasyon Sistolik Kan Basıncı (Yüksek Tansiyon) Diyastolik Kan Basıncı (Düşük Tansiyon) Kan Basıncı Kalibrasyonu - Yükseklik (irtifa) Kalibrasyonu + Yükseklik (irtifa) kalibrasyonu Sensörler Kalibrasyonu - Aktivite grafiğinde RAW verilerini göster - Yeniden bağlandığında Otomatik Senkronizasyonu zorla. Elle müdahalede yanlış zaman gösterebilir! - Senkronizasyon Zamanını Zorla - Cihaz Ayarları - İzleme Butonu Eylemini Çoğaltma - Bileği Sallayarak Çağrıyı YokSayma/Reddetme - Çağrıyı YokSayma/Reddetme Butonu - YokSayma Kapalı, Reddetme Açık + Etkinlik grafiğinde RAW verilerini göster + Yeniden bağlandığında saati eşzamanlamayı zorla. Analog saatler yanlış zaman gösterebilir! + Saati eşzamanlamayı zorla + Aygıt ayarları + Saat düğmesi eylemini kopyalar + Bileği sallayarak çağrıyı yok say/reddet + Düğmeyle çağrıyı yok say/reddet + Kapalı - yok say, Açık - reddet Çağrı Yönetimi - X Dakikada Tekrarla - Dakika Başı Tekrarlar - Cevapsız Çağrı Bildirimi - Telefon Çalarken Bildirim - Çağrı Bildirimini Tekrarla + X dakikada tekrarla + Her dakikada tekrarlar + Cevapsız çağrı bildirimi + Telefon çalarken bildirim + Çağrı bildirimini tekrarla Bildirimler ve Çağrılar Sistem - Czech - Swedish - Hebrew - Greek - Hungarian - Romanian + Çekçe + İsveççe + İbranice + Yunanca + Macarca + Rumence Bilinmeyen - Band Ekranı üzerindeki KısaYolları seçin - KısaYollar + Bileklik ekranındaki kısayolları seçin + Kısa yollar Son bildirim Takma Ad Ayarla - Amazfit T-Rex cihazınıza %s ürün yazılımını yüklemek üzeresiniz. + Amazfit T-Rex aygıtınıza %s ürün yazılımını kurmak üzeresiniz. \n -\nLütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını yükleyin. .fw dosyasını yükledikten sonra saatiniz yeniden başlatılacak. +\nLütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını kurun. .fw dosyasını kurduktan sonra saatiniz yeniden başlatılacak. \n -\nNote: Bu dosyalar önceden yüklenmiş olanlarla tamamen aynıysa .res ve .gps dosyalarını yüklemeniz gerekmez. +\nNote: Bu dosyalar önceden kurulanlarla tamamen aynıysa .res ve .gps dosyalarını kurmanız gerekmez. \n \nİLERLEMENİZ DURUMUNDA RİSK SİZE AİTTİR! Amazfit T-Rex Listelenmeyen herkese kod, çeviri, destek, fikirler, motivasyon, hata raporları, para… ile katkıda bulundukları için çok teşekkürler ✊ - Ek cihaz desteği + Ek aygıt desteği Katkıda bulunanlar Çekirdek Ekip (ilk kod katkıda bulunma sırasına göre) Aygıt sağlayıcılarınızın kapalı kaynaklı Android uygulamalarının yerine özgür ve bulut gerektirmeyen bir alternatif. - Gadgetbridge Hakkında + Gadgetbridge hakkında Hakkında LineageOS hava durumu sağlayıcısı için kullanılır, diğer Android sürümleri \"Weather notification\" gibi bir uygulama kullanmalıdır. Gadgetbridge wiki sayfasında daha fazla bilgi bulabilirsiniz. Dünya Saati @@ -946,39 +946,39 @@ YÜKLEME RİSKİ RİZE AİTTİR!" Döngüler Nefes Alma Mi Band 5 - Mi Band 5 cihazınıza %s ürün yazılımını yüklemek üzeresiniz. + Mi Band 5 aygıtınıza %s ürün yazılımını kurmak üzeresiniz. \n -\nLütfen önce .fw dosyasını, sonra .res dosyasını yükleyin. .fw dosyasını yükledikten sonra saatiniz yeniden başlatılacak. +\nLütfen önce .fw dosyasını, sonra .res dosyasın kurun. .fw dosyasını kurduktan sonra bilekliğiniz yeniden başlatılacak. \n -\nNot: Önceden yüklenmiş olanla tamamen aynıysa .res dosyasını yüklemeniz gerekmez. +\nNote: Bu dosyalar önceden kurulanlarla tamamen aynıysa .res dosyasını kurmanız gerekmez. \n \nİLERLEMENİZ DURUMUNDA RİSK SİZE AİTTİR! TLW64 - Hizmetin arka planda yeniden başlatılması gerekiyorsa güvenilirliği artıracak yeni CompanionDevice API desteğini etkinleştirir (sadece Android 8 veya üzerinde bir etkisi vardır), etkili olması için Gadgetbridge kullanarak yeniden eşleştirme gerektirir - Bu seçeneğin etkinleştirilmesi, tarama sırasında önceden etkileşimde/eşleştirilmiş olan cihazları yok sayar - Etkileşimde olan cihazları yok say + Hizmetin arka planda yeniden başlatılması gerekiyorsa güvenilirliği artıracak yeni CompanionDevice API desteğini etkinleştirir (yalnızca Android 8 veya üzerinde bir etkisi vardır), etkili olması için Gadgetbridge kullanarak yeniden eşleştirme gerektirir + Bu seçeneğin etkinleştirilmesi, tarama sırasında önceden etkileşimde/eşleştirilmiş olan aygıtları yok sayar + Etkileşimde olan aygıtları yok say ZATEN ETKİLEŞİMDE ANAHTAR GEREKLİ - Cihazları taramak için konumun açık olması gerekmektedir - Veri tabanından cihazlar alınırken hata oluştu + Aygıtları taramak için konumun açık olması gerekmektedir + Veri tabanından aygıtlar alınırken hata oluştu Takma ad ayarlanırken hata oluştu: - Cihaza özgü tercihler dışa aktarılırken hata oluştu - Anında gerekmediklerinde bile eksik izinleri denetleyin ve isteyin. Bunu sadece cihazlarınız bu özelliklerin hiçbirini desteklemiyorsa devre dışı bırakın. İzin verilmemesi sorunlara neden olabilir! + Aygıta özel tercihler dışa aktarılırken hata oluştu + Anında gerekmediklerinde bile eksik izinleri denetleyin ve isteyin. Bunu yalnızca aygıtlarınız bu özelliklerin hiçbirini desteklemiyorsa devre dışı bırakın. İzin verilmemesi sorunlara neden olabilir! İzin durumuna göz at CompanionDevice Eşleştirme Konum etkinleştirilmelidir PineTime (JF Ürün Yazılımı) Artan Azalma - GPS Takibini Göster + GPS İzlemesini Göster Yükseklik Kulaç bpm kulaç kulaç/s Düz - yüzme tarzı - swolf indeksi + Yüzme Tarzı + Swolf İndeksi Ortalama Kulaç Ortalama Kulaç Uzunluğu Etkinlik @@ -1004,7 +1004,7 @@ YÜKLEME RİSKİ RİZE AİTTİR!" Ortalama Tur Hızı Ortalama Adım Hız - Nabız + Kalp ritmi Toplam adım En Yüksek Hız Azami @@ -1023,8 +1023,8 @@ YÜKLEME RİSKİ RİZE AİTTİR!" Giyilmediğinde Uyandığında Uykuya Dalarken - Cihaz eylemleri - Eylemleri ve Android yayınlarını tetiklemek için cihaz olaylarını kullan + Aygıt eylemleri + Eylemleri ve Android yayınlarını tetiklemek için aygıt olaylarını kullan Yoga İp Atlama Yüzme (Açık su) @@ -1053,7 +1053,7 @@ YÜKLEME RİSKİ RİZE AİTTİR!" Bugün bugün uzak geçmiş - Tüm cihazlar + Tüm aygıtlar Zaman dilimi 30 gün 7 gün @@ -1074,4 +1074,11 @@ YÜKLEME RİSKİ RİZE AİTTİR!" Kriket Kürek Makinesi Futbol + En düşük etkinlik uzunluğu (dakika) + Etkinlikleri ayırmak için duraklama uzunluğu (dakika) + Etkinlik algılamak için dakikada en az adım sayısı + Çalışmayı algılamak için dakikada en az adım sayısı + Etkinlik listesi + Biraz etkinlik yapın ve aygıtı eşzamanlayın. + Etkinlik algılanmadı. \ No newline at end of file From 2b7be916aa362a2b315f5262af512d65f696d373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B0=91=E4=B8=BE?= Date: Sat, 10 Oct 2020 05:38:24 +0000 Subject: [PATCH 50/86] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (984 of 984 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 7794b1102..2d793db37 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1055,4 +1055,11 @@ 今天 遥远的过去 连接 + 最小活动长度(分钟) + 以暂停长度来分割活动(分钟) + 每分钟检测到的最小活动步数 + 每分钟检测跑步的最小步数 + 活动列表 + 做一些运动来同步设备。 + 未检测到活动。 \ No newline at end of file From e80f66e758614762e881fecae8b30973571ad99e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vinc=C3=A8n=20PUJOL?= Date: Sat, 10 Oct 2020 20:11:53 +0000 Subject: [PATCH 51/86] Translated using Weblate (French) Currently translated at 100.0% (991 of 991 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/fr/ --- app/src/main/res/values-fr/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5f02d6939..1e2022a45 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1050,4 +1050,11 @@ Temps de sommeil préféré en heures Liste d\'activités Avoir une activité et synchroniser l\'appareil. Aucune activité détectée. + Lefun + langue de l\'interface + Le bracelet vibrera si votre téléphone se déconnecte du bracelet + Anti-perte + Intervalle de rappel d\'hydratation (en minutes) + Le bracelet vibrera pour vous rappeler de boire de l\'eau + Rappel d\'hydratation \ No newline at end of file From b2701d676d6119e32ce1dc010e568085ba8ab4d9 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Sun, 11 Oct 2020 05:45:33 +0000 Subject: [PATCH 52/86] Translated using Weblate (Hebrew) Currently translated at 99.8% (990 of 991 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/he/ --- app/src/main/res/values-he/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index b7bcaad7f..7e47f278e 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -1051,4 +1051,11 @@ רשימת פעילויות יש לעשות פעילות כלשהי ולסנכרן את ההתקן. לא זוהו פעילויות. + Lefun + שפת המנשק + הצמיד ירוטו אם הטלפון שלך מתנתק מהצמיד + מונע איבוד + הפרש תזכורת שתייה (בדקות) + הצמיד ירטוט כדי להזכיר לך לשתות מים + תזכורת שתייה \ No newline at end of file From 1b972cde2289e5b36b38eb5704af3fbcb97504ca Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Sat, 10 Oct 2020 21:41:47 +0000 Subject: [PATCH 53/86] Translated using Weblate (Russian) Currently translated at 100.0% (991 of 991 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/ru/ --- app/src/main/res/values-ru/strings.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1712e1f2a..5014ccd76 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -763,7 +763,7 @@ Makibes HR3 Amazfit Bip Lite Найти телефон - Включить \\\'Найти телефон\\\' + Включить \'Найти телефон\' Использовать ваш браслет для проигрывания рингтонов. Продолжительность звонка в секундах Продолжительность @@ -1057,4 +1057,11 @@ Список активностей Сделайте некоторую активность и синхронизируйте устройство. Никакой активности не обнаружено. + Интервал напоминания о гидратации (в минутах) + Напоминание о гидратации + Браслет будет вибрировать, если ваш телефон отключится от браслета + Браслет будет вибрировать, напоминая вам о питье воды + Lefun + Язык интерфейса + Защита от потери \ No newline at end of file From 5ea83266f69efdabf66f7081b5cfd4ebe3857430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Sat, 10 Oct 2020 20:23:21 +0000 Subject: [PATCH 54/86] Translated using Weblate (Turkish) Currently translated at 100.0% (991 of 991 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/tr/ --- app/src/main/res/values-tr/strings.xml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index efcf310bc..83f1c5db1 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -413,7 +413,7 @@ Hareketsizlik uyarıları Belirli zaman arasında Hareketsizlik Uyarısı vermesin Belirli süre hareketsiz kaldığınızda bileklik titreyecek - Hareketsizlik sınırı (dakika cinsinden) + Hareketsizlik sınırı (dakika olarak) Bilgileri değiştirmek için bileği döndür Gün batımında Bileklik ekran kilidini açma @@ -691,7 +691,7 @@ Etkinlik 3 eylemi Ayrıntılı basma eylemi ayarları Bağlantı kesilme bildirimi - \\\'Telefonu bul\\\' özelliğini aç + \'Telefonu bul\' özelliğini aç Telefonu bul Saniye cinsinden zil süresi Telefonunuzun zilini çalmak için bilekliğinizi kullanın. @@ -1081,4 +1081,11 @@ Etkinlik listesi Biraz etkinlik yapın ve aygıtı eşzamanlayın. Etkinlik algılanmadı. + Telefonunuzun bileklikle bağlantısı kesilirse bileklik titreyecek + Lefun + Arayüz dili + Kayıp önleme + Sıvı alma hatırlatma aralığı (dakika olarak) + Bileklik su içmenizi hatırlatmak için titreyecek + Sıvı alma hatırlatıcı \ No newline at end of file From f16c11f6884e36f131525cef12840a0518ec25cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=B0=91=E4=B8=BE?= Date: Sun, 11 Oct 2020 04:10:08 +0000 Subject: [PATCH 55/86] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (991 of 991 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 2d793db37..ab178e865 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -759,7 +759,7 @@ Makibes HR3 华米手表青春版 Lite 查找手机 - 启用查找手机 + 启用“查找手机” 使用您的手环以在手机上播放铃声。 铃声将持续数秒 持续 @@ -1062,4 +1062,11 @@ 活动列表 做一些运动来同步设备。 未检测到活动。 + Lefun + 界面语言 + 如果您的手机断开与手环的连接,将会震动 + 防丢失 + 水合提醒间隔(分钟) + 当需要提醒喝水时,手环会震动 + 水合提醒 \ No newline at end of file From 2f03b6939fb236371d6a0da3a361d77bd79c0cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Sat, 10 Oct 2020 21:25:54 +0000 Subject: [PATCH 56/86] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 90.7% (899 of 991 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/nb_NO/ --- app/src/main/res/values-nb-rNO/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 6a28afc1c..097393a81 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -1013,4 +1013,7 @@ Alle enheter i dag Lenker + Drikkepåminnelseintervall (i minutter) + Grensesnittspråk + Brukt av LineageOS-værtilbyderen, andre Android-versjoner må bruke et program som «Ditt lokale vær». Mer info er å finne på Gadgetbridge-wiki-en. \ No newline at end of file From 7f8d4ac92a635439b153cd7bb4a2f449bfd4b2b4 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Sun, 11 Oct 2020 15:48:25 +0200 Subject: [PATCH 57/86] Bump version and add draft changelog --- CHANGELOG.md | 7 +++++++ app/build.gradle | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4db2c15d..115abb6c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ### Changelog +#### 0.48.0 (WIP) +* Initial support for Lefun Smart Bands +* InfiniTime: Improved music support for latest firmware +* Add sport activity list tab in charts +* Weather: Fix wind speed and direction not being passed properly +* Fix find your phone feature on Android 10 (need companion device pairing) + #### 0.47.2 * Amazfit Bip S: Send sunrise and sunset on latest firmware if enabled * Huami: Support new firmware update protocol (fixes firmware flashing with firmware 2.1.1.50/4.1.5.55 on Amazfit Bip S) diff --git a/app/build.gradle b/app/build.gradle index e29b19f49..7ca9d5544 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,8 +25,8 @@ android { targetSdkVersion 29 // Note: always bump BOTH versionCode and versionName! - versionName "0.47.2" - versionCode 181 + versionName "0.48.0" + versionCode 182 vectorDrawables.useSupportLibrary = true } buildTypes { From 8394928b94f1dc3a56450a9c28d96009144db605 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Sun, 11 Oct 2020 16:10:35 +0200 Subject: [PATCH 58/86] enable minifiy for release, but don't obfuscate (shrinks apk size from 6.1 to 5.1MB) --- app/build.gradle | 2 +- app/proguard-rules.pro | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 7ca9d5544..c9df48cff 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,7 +31,7 @@ android { } buildTypes { release { - minifyEnabled false + minifyEnabled true proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" } } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index befd643ca..2c0d6dd0c 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -9,6 +9,8 @@ # Add any project specific keep options here: +-dontobfuscate + # Pebble BG-JS -keepclassmembers class * { @android.webkit.JavascriptInterface ; From 06a12300a1c22e1bad9922a10f536aaf2055c766 Mon Sep 17 00:00:00 2001 From: opavlov Date: Tue, 1 Sep 2020 23:15:20 +0300 Subject: [PATCH 59/86] added support for sony swr12 --- .../gadgetbridge/daogen/GBDaoGenerator.java | 12 + app/src/main/AndroidManifest.xml | 3 + .../activities/SettingsActivity.java | 10 + .../sonyswr12/SonySWR12DeviceCoordinator.java | 122 ++++++ .../sonyswr12/SonySWR12PrefActivity.java | 98 +++++ .../sonyswr12/SonySWR12SampleProvider.java | 83 +++++ .../gadgetbridge/model/DeviceType.java | 1 + .../service/DeviceSupportFactory.java | 4 + .../devices/sonyswr12/SonySWR12Constants.java | 22 ++ .../sonyswr12/SonySWR12DeviceSupport.java | 351 ++++++++++++++++++ .../sonyswr12/SonySWR12HandlerThread.java | 135 +++++++ .../devices/sonyswr12/SonySWR12Util.java | 22 ++ .../entities/activity/ActivityBase.java | 35 ++ .../entities/activity/ActivityHeartRate.java | 22 ++ .../entities/activity/ActivitySleep.java | 22 ++ .../entities/activity/ActivityType.java | 23 ++ .../entities/activity/ActivityWithData.java | 22 ++ .../entities/activity/EventBase.java | 21 ++ .../entities/activity/EventCode.java | 21 ++ .../entities/activity/EventFactory.java | 26 ++ .../entities/activity/EventWithActivity.java | 62 ++++ .../entities/activity/EventWithValue.java | 18 + .../entities/activity/SleepLevel.java | 21 ++ .../sonyswr12/entities/alarm/AlarmRepeat.java | 53 +++ .../sonyswr12/entities/alarm/AlarmState.java | 13 + .../sonyswr12/entities/alarm/BandAlarm.java | 60 +++ .../sonyswr12/entities/alarm/BandAlarms.java | 35 ++ .../entities/control/CommandCode.java | 15 + .../entities/control/ControlPoint.java | 17 + .../control/ControlPointLowVibration.java | 28 ++ .../control/ControlPointWithValue.java | 21 ++ .../entities/time/BandDaylightSavingTime.java | 24 ++ .../sonyswr12/entities/time/BandTime.java | 62 ++++ .../sonyswr12/entities/time/BandTimeZone.java | 61 +++ .../sonyswr12/util/ByteArrayReader.java | 53 +++ .../sonyswr12/util/ByteArrayWriter.java | 61 +++ .../devices/sonyswr12/util/IntFormat.java | 35 ++ .../devices/sonyswr12/util/UIntBitReader.java | 27 ++ .../devices/sonyswr12/util/UIntBitWriter.java | 37 ++ .../gadgetbridge/util/DeviceHelper.java | 2 + .../layout/activity_sonyswr12_settings.xml | 55 +++ app/src/main/res/values-bg/strings.xml | 5 + app/src/main/res/values-ca/strings.xml | 5 + app/src/main/res/values-cs/strings.xml | 6 + app/src/main/res/values-de/strings.xml | 6 + app/src/main/res/values-el/strings.xml | 6 + app/src/main/res/values-en-rGB/strings.xml | 6 + app/src/main/res/values-es/strings.xml | 6 + app/src/main/res/values-et/strings.xml | 6 + app/src/main/res/values-fa/strings.xml | 1 + app/src/main/res/values-fi/strings.xml | 5 + app/src/main/res/values-fr-rCA/strings.xml | 6 + app/src/main/res/values-fr/strings.xml | 6 + app/src/main/res/values-gl/strings.xml | 5 + app/src/main/res/values-he/strings.xml | 6 + app/src/main/res/values-hi/strings.xml | 2 + app/src/main/res/values-hr/strings.xml | 3 + app/src/main/res/values-hu/strings.xml | 5 + app/src/main/res/values-id/strings.xml | 3 + app/src/main/res/values-it/strings.xml | 6 + app/src/main/res/values-ja/strings.xml | 6 + app/src/main/res/values-ka/strings.xml | 3 + app/src/main/res/values-ko/strings.xml | 1 + app/src/main/res/values-lt/strings.xml | 6 + app/src/main/res/values-my/strings.xml | 2 + app/src/main/res/values-nb-rNO/strings.xml | 6 + app/src/main/res/values-nl/strings.xml | 6 + app/src/main/res/values-pl/strings.xml | 6 + app/src/main/res/values-pt-rBR/strings.xml | 6 + app/src/main/res/values-pt/strings.xml | 6 + app/src/main/res/values-ro/strings.xml | 4 + app/src/main/res/values-ru/strings.xml | 6 + app/src/main/res/values-sk/strings.xml | 6 + app/src/main/res/values-sv/strings.xml | 5 + app/src/main/res/values-tr/strings.xml | 9 +- app/src/main/res/values-uk/strings.xml | 6 + app/src/main/res/values-vi/strings.xml | 4 + app/src/main/res/values-zh-rCN/strings.xml | 6 + app/src/main/res/values-zh-rTW/strings.xml | 2 + app/src/main/res/values/arrays.xml | 9 + app/src/main/res/values/strings.xml | 6 + app/src/main/res/xml/preferences.xml | 8 +- 82 files changed, 2024 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12PrefActivity.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12SampleProvider.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Constants.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12HandlerThread.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Util.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityType.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventBase.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventCode.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventFactory.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithValue.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/SleepLevel.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmRepeat.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmState.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarm.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarms.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/CommandCode.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPoint.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointLowVibration.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointWithValue.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandDaylightSavingTime.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTime.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTimeZone.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayReader.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayWriter.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/IntFormat.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitReader.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitWriter.java create mode 100644 app/src/main/res/layout/activity_sonyswr12_settings.xml diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index 1b7c08465..5184aecaa 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -77,6 +77,7 @@ public class GBDaoGenerator { addLefunActivitySample(schema, user, device); addLefunBiometricSample(schema,user,device); addLefunSleepSample(schema, user, device); + addSonySWR12Sample(schema, user, device); addHybridHRActivitySample(schema, user, device); addCalendarSyncState(schema, device); @@ -407,6 +408,17 @@ public class GBDaoGenerator { return activitySample; } + private static Entity addSonySWR12Sample(Schema schema, Entity user, Entity device) { + Entity activitySample = addEntity(schema, "SonySWR12Sample"); + activitySample.implementsSerializable(); + addCommonActivitySampleProperties("AbstractActivitySample", activitySample, user, device); + addHeartRateProperties(activitySample); + activitySample.addIntProperty(SAMPLE_STEPS).notNull().codeBeforeGetterAndSetter(OVERRIDE); + activitySample.addIntProperty(SAMPLE_RAW_KIND).notNull().codeBeforeGetterAndSetter(OVERRIDE); + activitySample.addIntProperty(SAMPLE_RAW_INTENSITY).notNull().codeBeforeGetterAndSetter(OVERRIDE); + return activitySample; + } + private static Entity addLefunActivitySample(Schema schema, Entity user, Entity device) { Entity activitySample = addEntity(schema, "LefunActivitySample"); activitySample.implementsSerializable(); diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5e61bf86c..f0479d146 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -567,5 +567,8 @@ + \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java index 277404ee8..39f3c99ff 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java @@ -59,6 +59,7 @@ import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity; import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.ConfigActivity; +import nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12.SonySWR12PrefActivity; import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; @@ -137,6 +138,15 @@ public class SettingsActivity extends AbstractSettingsActivity { } }); + pref = findPreference("pref_key_sonyswr12"); + pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(SettingsActivity.this, SonySWR12PrefActivity.class); + startActivity(intent); + return true; + } + }); + pref = findPreference("pref_key_blacklist"); pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { public boolean onPreferenceClick(Preference preference) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java new file mode 100644 index 000000000..67e936d7a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java @@ -0,0 +1,122 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12; + +import android.app.Activity; +import android.content.Context; +import android.net.Uri; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +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 SonySWR12DeviceCoordinator extends AbstractDeviceCoordinator { + + @Override + public DeviceType getDeviceType() { + return DeviceType.SONY_SWR12; + } + + @NonNull + @Override + public DeviceType getSupportedType(GBDeviceCandidate candidate) { + String name = candidate.getDevice().getName(); + if (!name.isEmpty() && name.toLowerCase().contains("swr12")) + return getDeviceType(); + return DeviceType.UNKNOWN; + } + + @Override + public String getManufacturer() { + return "Sony"; + } + + @Override + protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { + + } + + @Nullable + @Override + public Class getPairingActivity() { + return null; + } + + @Override + public boolean supportsActivityDataFetching() { + return true; + } + + @Override + public boolean supportsActivityTracking() { + return true; + } + + @Override + public SampleProvider getSampleProvider(GBDevice device, DaoSession session) { + return new SonySWR12SampleProvider(device, session); + } + + @Override + public InstallHandler findInstallHandler(Uri uri, Context context) { + return null; + } + + @Override + public boolean supportsScreenshots() { + return false; + } + + @Override + public int getAlarmSlotCount() { + return 5; + } + + @Override + public boolean supportsSmartWakeup(GBDevice device) { + return true; + } + + @Override + public boolean supportsHeartRateMeasurement(GBDevice device) { + return true; + } + + @Override + public boolean supportsAppsManagement() { + return false; + } + + @Override + public Class getAppsManagementActivity() { + return null; + } + + @Override + public boolean supportsCalendarEvents() { + return false; + } + + @Override + public boolean supportsRealtimeData() { + return true; + } + + @Override + public boolean supportsWeather() { + return false; + } + + @Override + public boolean supportsFindDevice() { + return false; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12PrefActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12PrefActivity.java new file mode 100644 index 000000000..4611a88ed --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12PrefActivity.java @@ -0,0 +1,98 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12; + +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; +import android.widget.CompoundButton; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.widget.AppCompatSpinner; +import androidx.appcompat.widget.SwitchCompat; + +import java.util.Arrays; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12Constants; + +public class SonySWR12PrefActivity extends AbstractGBActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_sonyswr12_settings); + setTitle(getString(R.string.sonyswr12_settings_title)); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayShowHomeEnabled(true); + } + setVibrationSetting(); + setStaminaSetting(); + setAlarmIntervalSetting(); + GBDevice device = GBApplication.app().getDeviceManager().getSelectedDevice(); + int disablerVisibility = (device != null + && device.isConnected() + && device.getType() == DeviceType.SONY_SWR12) ? View.GONE : View.VISIBLE; + findViewById(R.id.settingsDisabler).setVisibility(disablerVisibility); + } + + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; + } + + private void setVibrationSetting() { + boolean isLow = GBApplication.getPrefs().getBoolean(SonySWR12Constants.VIBRATION_PREFERENCE, false); + SwitchCompat switchVibration = ((SwitchCompat) findViewById(R.id.lowVibration)); + switchVibration.setChecked(isLow); + switchVibration.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + GBApplication.getPrefs().getPreferences().edit() + .putBoolean(SonySWR12Constants.VIBRATION_PREFERENCE, isChecked).apply(); + GBApplication.deviceService().onSendConfiguration(SonySWR12Constants.VIBRATION_PREFERENCE); + } + }); + } + + private void setStaminaSetting() { + boolean isOn = GBApplication.getPrefs().getBoolean(SonySWR12Constants.STAMINA_PREFERENCE, false); + SwitchCompat switchStamina = ((SwitchCompat) findViewById(R.id.staminaOn)); + switchStamina.setChecked(isOn); + switchStamina.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + GBApplication.getPrefs().getPreferences().edit() + .putBoolean(SonySWR12Constants.STAMINA_PREFERENCE, isChecked).apply(); + GBApplication.deviceService().onSendConfiguration(SonySWR12Constants.STAMINA_PREFERENCE); + } + }); + } + + private void setAlarmIntervalSetting() { + String interval = GBApplication.getPrefs().getString(SonySWR12Constants.SMART_ALARM_INTERVAL_PREFERENCE, "0"); + List intervalsArray = Arrays.asList(GBApplication.getContext().getResources().getStringArray(R.array.sonyswr12_smart_alarm_intervals)); + int position = intervalsArray.indexOf(interval); + final AppCompatSpinner spinner = ((AppCompatSpinner) findViewById(R.id.smartAlarmSpinner)); + spinner.setSelection(position); + spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + String interval = (String) spinner.getItemAtPosition(position); + GBApplication.getPrefs().getPreferences().edit() + .putString(SonySWR12Constants.SMART_ALARM_INTERVAL_PREFERENCE, interval).apply(); + GBApplication.deviceService().onSendConfiguration(SonySWR12Constants.SMART_ALARM_INTERVAL_PREFERENCE); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12SampleProvider.java new file mode 100644 index 000000000..1f0bacf62 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12SampleProvider.java @@ -0,0 +1,83 @@ +package nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12; + +import androidx.annotation.NonNull; +import androidx.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.SonySWR12Sample; +import nodomain.freeyourgadget.gadgetbridge.entities.SonySWR12SampleDao; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12Constants; + +public class SonySWR12SampleProvider extends AbstractSampleProvider { + public SonySWR12SampleProvider(GBDevice device, DaoSession session) { + super(device, session); + } + + @Override + public AbstractDao getSampleDao() { + return getSession().getSonySWR12SampleDao(); + } + + @Nullable + @Override + protected Property getRawKindSampleProperty() { + return SonySWR12SampleDao.Properties.RawKind; + } + + @NonNull + @Override + protected Property getTimestampSampleProperty() { + return SonySWR12SampleDao.Properties.Timestamp; + } + + @NonNull + @Override + protected Property getDeviceIdentifierSampleProperty() { + return SonySWR12SampleDao.Properties.DeviceId; + } + + @Override + public int normalizeType(int rawType) { + switch (rawType) { + case SonySWR12Constants.TYPE_ACTIVITY: + return ActivityKind.TYPE_ACTIVITY; + case SonySWR12Constants.TYPE_LIGHT: + return ActivityKind.TYPE_LIGHT_SLEEP; + case SonySWR12Constants.TYPE_DEEP: + return ActivityKind.TYPE_DEEP_SLEEP; + case SonySWR12Constants.TYPE_NOT_WORN: + return ActivityKind.TYPE_NOT_WORN; + } + return ActivityKind.TYPE_UNKNOWN; + } + + @Override + public int toRawActivityKind(int activityKind) { + switch (activityKind) { + case ActivityKind.TYPE_ACTIVITY: + return SonySWR12Constants.TYPE_ACTIVITY; + case ActivityKind.TYPE_LIGHT_SLEEP: + return SonySWR12Constants.TYPE_LIGHT; + case ActivityKind.TYPE_DEEP_SLEEP: + return SonySWR12Constants.TYPE_DEEP; + case ActivityKind.TYPE_NOT_WORN: + return SonySWR12Constants.TYPE_NOT_WORN; + } + return SonySWR12Constants.TYPE_ACTIVITY; + } + + @Override + public float normalizeIntensity(int rawIntensity) { + return rawIntensity; + } + + @Override + public SonySWR12Sample createActivitySample() { + return new SonySWR12Sample(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java index 02607887f..ef48af631 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -76,6 +76,7 @@ public enum DeviceType { LEFUN(210, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled, R.string.devicetype_lefun), ITAG(250, R.drawable.ic_device_itag, R.drawable.ic_device_itag_disabled, R.string.devicetype_itag), VIBRATISSIMO(300, R.drawable.ic_device_lovetoy, R.drawable.ic_device_lovetoy_disabled, R.string.devicetype_vibratissimo), + SONY_SWR12(310, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_sonyswr12), TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test); private final int key; @DrawableRes diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java index 44b1b3ef4..e39f5fdcf 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java @@ -62,6 +62,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport import nodomain.freeyourgadget.gadgetbridge.service.devices.pinetime.PineTimeJFSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi.RoidmiSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.tlw64.TLW64Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport; @@ -264,6 +265,9 @@ public class DeviceSupportFactory { case LEFUN: deviceSupport = new ServiceDeviceSupport(new LefunDeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); break; + case SONY_SWR12: + deviceSupport = new ServiceDeviceSupport(new SonySWR12DeviceSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); + break; } if (deviceSupport != null) { deviceSupport.setContext(gbDevice, mBtAdapter, mContext); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Constants.java new file mode 100644 index 000000000..1c01e4811 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Constants.java @@ -0,0 +1,22 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12; + +import java.util.UUID; + +public class SonySWR12Constants { + //accessory host service + public static final String BASE_UUID_AHS = "0000%s-37CB-11E3-8682-0002A5D5C51B"; + public static final UUID UUID_SERVICE_AHS = UUID.fromString(String.format(BASE_UUID_AHS, "0200")); + public static final UUID UUID_CHARACTERISTIC_ALARM = UUID.fromString(String.format(BASE_UUID_AHS, "0204")); + public static final UUID UUID_CHARACTERISTIC_EVENT = UUID.fromString(String.format(BASE_UUID_AHS, "0205")); + public static final UUID UUID_CHARACTERISTIC_TIME = UUID.fromString(String.format(BASE_UUID_AHS, "020B")); + public static final UUID UUID_CHARACTERISTIC_CONTROL_POINT = UUID.fromString(String.format(BASE_UUID_AHS, "0208")); + + public static final String VIBRATION_PREFERENCE = "vibration_preference"; + public static final String STAMINA_PREFERENCE = "stamina_preference"; + public static final String SMART_ALARM_INTERVAL_PREFERENCE = "smart_alarm_interval_preference"; + + public static final int TYPE_ACTIVITY = 0; + public static final int TYPE_LIGHT = 1; + public static final int TYPE_DEEP = 2; + public static final int TYPE_NOT_WORN = 3; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java new file mode 100644 index 000000000..6e47e6ba1 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java @@ -0,0 +1,351 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.content.Intent; +import android.net.Uri; +import android.widget.Toast; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; +import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.IntentListener; +import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo; +import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfoProfile; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventBase; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventFactory; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm.BandAlarm; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm.BandAlarms; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control.CommandCode; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control.ControlPointLowVibration; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control.ControlPointWithValue; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.time.BandTime; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +// done: +// - time sync +// - alarms (also smart) +// - fetching activity(walking, sleep) +// - stamina mode +// - vibration intensity +// - realtime heart rate +// todo options: +// - "get moving" +// - get notified: -call, -notification, -notification from, -do not disturb +// - media control: media/find phone(tap once for play pause, tap twice for next, tap triple for previous) + +public class SonySWR12DeviceSupport extends AbstractBTLEDeviceSupport { + private static final Logger LOG = LoggerFactory.getLogger(SonySWR12DeviceSupport.class); + private SonySWR12HandlerThread processor = null; + + private final BatteryInfoProfile batteryInfoProfile; + private final IntentListener mListener = new IntentListener() { + @Override + public void notify(Intent intent) { + if (intent.getAction().equals(BatteryInfoProfile.ACTION_BATTERY_INFO)) { + BatteryInfo info = intent.getParcelableExtra(BatteryInfoProfile.EXTRA_BATTERY_INFO); + GBDeviceEventBatteryInfo gbInfo = new GBDeviceEventBatteryInfo(); + gbInfo.level = (short) info.getPercentCharged(); + handleGBDeviceEvent(gbInfo); + } + } + }; + + public SonySWR12DeviceSupport() { + super(LOG); + addSupportedService(GattService.UUID_SERVICE_BATTERY_SERVICE); + addSupportedService(SonySWR12Constants.UUID_SERVICE_AHS); + batteryInfoProfile = new BatteryInfoProfile<>(this); + batteryInfoProfile.addListener(mListener); + addSupportedProfile(batteryInfoProfile); + } + + @Override + protected TransactionBuilder initializeDevice(TransactionBuilder builder) { + initialize(); + setTime(builder); + batteryInfoProfile.requestBatteryInfo(builder); + return builder; + } + + private SonySWR12HandlerThread getProcessor() { + if (processor == null) { + processor = new SonySWR12HandlerThread(getDevice(), getContext()); + processor.start(); + } + return processor; + } + + private void initialize() { + if (gbDevice.getState() != GBDevice.State.INITIALIZED) { + gbDevice.setFirmwareVersion("N/A"); + gbDevice.setFirmwareVersion2("N/A"); + gbDevice.setState(GBDevice.State.INITIALIZED); + gbDevice.sendDeviceUpdateIntent(getContext()); + } + } + + @Override + public boolean useAutoConnect() { + return false; + } + + @Override + public void onNotification(NotificationSpec notificationSpec) { + + } + + @Override + public void onDeleteNotification(int id) { + + } + + @Override + public void onSetTime() { + try { + TransactionBuilder builder = performInitialized("setTime"); + setTime(builder); + builder.queue(getQueue()); + } catch (Exception e) { + GB.toast(getContext(), "Error setting time: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + } + } + + private void setTime(TransactionBuilder builder) { + BluetoothGattCharacteristic timeCharacteristic = getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_TIME); + builder.write(timeCharacteristic, new BandTime(Calendar.getInstance()).toByteArray()); + } + + @Override + public void onSetAlarms(ArrayList alarms) { + try { + BluetoothGattCharacteristic alarmCharacteristic = getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_ALARM); + TransactionBuilder builder = performInitialized("alarm"); + int prefInterval = Integer.valueOf(GBApplication.getPrefs().getString(SonySWR12Constants.SMART_ALARM_INTERVAL_PREFERENCE, "0")); + ArrayList bandAlarmList = new ArrayList<>(); + for (Alarm alarm : alarms) { + BandAlarm bandAlarm = BandAlarm.fromAppAlarm(alarm, bandAlarmList.size(), alarm.getSmartWakeup() ? prefInterval : 0); + if (bandAlarm != null) + bandAlarmList.add(bandAlarm); + } + builder.write(alarmCharacteristic, new BandAlarms(bandAlarmList).toByteArray()); + builder.queue(getQueue()); + } catch (Exception e) { + GB.toast(getContext(), "Error setting alarms: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR); + } + } + + @Override + public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + return super.onCharacteristicRead(gatt, characteristic, status); + } + + @Override + public boolean onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + if (super.onCharacteristicChanged(gatt, characteristic)) + return true; + UUID uuid = characteristic.getUuid(); + if (uuid.equals(SonySWR12Constants.UUID_CHARACTERISTIC_EVENT)) { + try { + EventBase event = EventFactory.readEventFromByteArray(characteristic.getValue()); + getProcessor().process(event); + } catch (Exception e) { + return false; + } + return true; + } + return false; + } + + @Override + public void onSetCallState(CallSpec callSpec) { + + } + + @Override + public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) { + + } + + @Override + public void onSetMusicState(MusicStateSpec stateSpec) { + + } + + @Override + public void onSetMusicInfo(MusicSpec musicSpec) { + + } + + @Override + public void onEnableRealtimeSteps(boolean enable) { + //doesn't support realtime steps + //supports only realtime heart rate + } + + @Override + public void onInstallApp(Uri uri) { + + } + + @Override + public void onAppInfoReq() { + + } + + @Override + public void onAppStart(UUID uuid, boolean start) { + + } + + @Override + public void onAppDelete(UUID uuid) { + + } + + @Override + public void onAppConfiguration(UUID appUuid, String config, Integer id) { + + } + + @Override + public void onAppReorder(UUID[] uuids) { + + } + + @Override + public void onFetchRecordedData(int dataTypes) { + try { + TransactionBuilder builder = performInitialized("fetchActivity"); + builder.notify(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_EVENT), true); + ControlPointWithValue flushControl = new ControlPointWithValue(CommandCode.FLUSH_ACTIVITY, 0); + builder.write(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_CONTROL_POINT), flushControl.toByteArray()); + builder.queue(getQueue()); + } catch (Exception e) { + LOG.error("failed to fetch activity data", e); + } + } + + @Override + public void onReset(int flags) { + + } + + @Override + public void onHeartRateTest() { + } + + @Override + public void onEnableRealtimeHeartRateMeasurement(boolean enable) { + try { + TransactionBuilder builder = performInitialized("HeartRateTest"); + builder.notify(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_EVENT), enable); + ControlPointWithValue controlPointHeart = new ControlPointWithValue(CommandCode.HEARTRATE_REALTIME, enable ? 1 : 0); + builder.write(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_CONTROL_POINT), controlPointHeart.toByteArray()); + builder.queue(getQueue()); + } catch (IOException ex) { + LOG.error("Unable to read heart rate from Sony device", ex); + } + } + + @Override + public void onFindDevice(boolean start) { + + } + + @Override + public void onSetConstantVibration(int integer) { + + } + + @Override + public void onScreenshotReq() { + + } + + @Override + public void onEnableHeartRateSleepSupport(boolean enable) { + + } + + @Override + public void onSetHeartRateMeasurementInterval(int seconds) { + + } + + @Override + public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) { + + } + + @Override + public void onDeleteCalendarEvent(byte type, long id) { + + } + + @Override + public void onSendConfiguration(String config) { + try { + switch (config) { + case SonySWR12Constants.STAMINA_PREFERENCE: { + //stamina can be: + //disabled = 0, enabled = 1 or todo auto on low battery = 2 + int status = GBApplication.getPrefs().getBoolean(config, false) ? 1 : 0; + TransactionBuilder builder = performInitialized(config); + ControlPointWithValue vibrationControl = new ControlPointWithValue(CommandCode.STAMINA_MODE, status); + builder.write(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_CONTROL_POINT), vibrationControl.toByteArray()); + builder.queue(getQueue()); + break; + } + case SonySWR12Constants.VIBRATION_PREFERENCE: { + boolean isEnabled = GBApplication.getPrefs().getBoolean(config, false); + TransactionBuilder builder = performInitialized(config); + ControlPointLowVibration vibrationControl = new ControlPointLowVibration(isEnabled); + builder.write(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_CONTROL_POINT), vibrationControl.toByteArray()); + builder.queue(getQueue()); + break; + } + case SonySWR12Constants.SMART_ALARM_INTERVAL_PREFERENCE: { + onSetAlarms(new ArrayList(DBHelper.getAlarms(gbDevice))); + } + } + } catch (Exception exc) { + LOG.error("failed to send config " + config, exc); + } + } + + @Override + public void onReadConfiguration(String config) { + + } + + @Override + public void onTestNewFunction() { + + } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12HandlerThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12HandlerThread.java new file mode 100644 index 000000000..2ce1816bd --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12HandlerThread.java @@ -0,0 +1,135 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12; + +import android.content.Context; +import android.content.Intent; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; +import nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12.SonySWR12SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.SonySWR12Sample; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.ActivityBase; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.ActivitySleep; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.ActivityWithData; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventBase; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventCode; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventWithActivity; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity.EventWithValue; +import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread; + +public class SonySWR12HandlerThread extends GBDeviceIoThread { + private static final Logger LOG = LoggerFactory.getLogger(SonySWR12HandlerThread.class); + + public SonySWR12HandlerThread(GBDevice gbDevice, Context context) { + super(gbDevice, context); + } + + public void process(EventBase event) { + if (event instanceof EventWithValue) { + if (event.getCode() == EventCode.HEART_RATE) { + processRealTimeHeartRate((EventWithValue) event); + } + } else if (event instanceof EventWithActivity) { + processWithActivity((EventWithActivity) event); + } + } + + private void processRealTimeHeartRate(EventWithValue event) { + try { + DBHandler dbHandler = GBApplication.acquireDB(); + Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId(); + Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId(); + SonySWR12SampleProvider provider = new SonySWR12SampleProvider(getDevice(), dbHandler.getDaoSession()); + int timestamp = getTimestamp(); + SonySWR12Sample sample = new SonySWR12Sample(timestamp, deviceId, userId, (int) event.value, ActivitySample.NOT_MEASURED, 0, 1); + provider.addGBActivitySample(sample); + GBApplication.releaseDB(); + Intent intent = new Intent(DeviceService.ACTION_REALTIME_SAMPLES) + .putExtra(DeviceService.EXTRA_REALTIME_SAMPLE, sample) + .putExtra(DeviceService.EXTRA_TIMESTAMP, timestamp); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + } + } + + private int getTimestamp() { + return (int) (System.currentTimeMillis() / 1000); + } + + private void processWithActivity(EventWithActivity event) { + List payloadList = event.activityList; + for (ActivityBase activity : payloadList) { + switch (activity.getType()) { + case WALK: + case RUN: + addActivity((ActivityWithData) activity); + break; + case SLEEP: + addSleep((ActivitySleep) activity); + break; + } + } + } + + private void addActivity(ActivityWithData activity) { + try { + DBHandler dbHandler = GBApplication.acquireDB(); + Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId(); + Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId(); + SonySWR12SampleProvider provider = new SonySWR12SampleProvider(getDevice(), dbHandler.getDaoSession()); + int kind = SonySWR12Constants.TYPE_ACTIVITY; + SonySWR12Sample sample = new SonySWR12Sample(activity.getTimeStampSec(), deviceId, userId, ActivitySample.NOT_MEASURED, activity.data, kind, 1); + provider.addGBActivitySample(sample); + GBApplication.releaseDB(); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + } + } + + private void addSleep(ActivitySleep activity) { + try { + DBHandler dbHandler = GBApplication.acquireDB(); + Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId(); + Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId(); + SonySWR12SampleProvider provider = new SonySWR12SampleProvider(getDevice(), dbHandler.getDaoSession()); + int kind; + switch (activity.sleepLevel) { + case LIGHT: + kind = SonySWR12Constants.TYPE_LIGHT; + break; + case DEEP: + kind = SonySWR12Constants.TYPE_DEEP; + break; + default: + kind = SonySWR12Constants.TYPE_ACTIVITY; + break; + } + if (kind == SonySWR12Constants.TYPE_LIGHT || kind == SonySWR12Constants.TYPE_DEEP) { + //need so much samples because sleep has exact duration + //so empty samples are for right representation of sleep on activity charts + SonySWR12Sample sample = new SonySWR12Sample(activity.getTimeStampSec(), deviceId, userId, ActivitySample.NOT_MEASURED, 0, SonySWR12Constants.TYPE_NOT_WORN, 1); + provider.addGBActivitySample(sample); + sample = new SonySWR12Sample(activity.getTimeStampSec() + 2, deviceId, userId, ActivitySample.NOT_MEASURED, 0, kind, 1); + provider.addGBActivitySample(sample); + sample = new SonySWR12Sample(activity.getTimeStampSec() + activity.durationMin * 60 - 2, deviceId, userId, ActivitySample.NOT_MEASURED, 0, kind, 1); + provider.addGBActivitySample(sample); + sample = new SonySWR12Sample(activity.getTimeStampSec() + activity.durationMin * 60, deviceId, userId, ActivitySample.NOT_MEASURED, 0, SonySWR12Constants.TYPE_NOT_WORN, 1); + provider.addGBActivitySample(sample); + } + GBApplication.releaseDB(); + } catch (Exception e) { + LOG.error(e.getMessage(), e); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Util.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Util.java new file mode 100644 index 000000000..7f2497c4b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Util.java @@ -0,0 +1,22 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +public class SonySWR12Util { + + public static long secSince2013() { + //sony uses time on band since 2013 for some reason + final Calendar instance = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + instance.set(2013, 0, 1, 0, 0, 0); + instance.set(14, 0); + return instance.getTimeInMillis()/1000; + } + + public static String timeToString(long sec) { + SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss"); + return format.format(new Date(sec * 1000)); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java new file mode 100644 index 000000000..1cb102fb6 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java @@ -0,0 +1,35 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitWriter; + +public abstract class ActivityBase { + protected final ActivityType type; + protected final int timeOffsetMin; + private final long timeStampSec; + + public ActivityBase(ActivityType type, int timeOffsetMin, long timeStampSec) { + if (timeOffsetMin < 0 || timeOffsetMin > 1440) { + throw new IllegalArgumentException("activity time offset out of range: " + timeOffsetMin); + } + this.type = type; + this.timeOffsetMin = timeOffsetMin; + this.timeStampSec = timeStampSec + this.timeOffsetMin * 60; + } + + public final int getTimeStampSec() { + return (int) (timeStampSec); + } + + public final ActivityType getType() { + return this.type; + } + + protected final UIntBitWriter getWriterWithTypeAndOffset() { + UIntBitWriter uIntBitWriter = new UIntBitWriter(32); + uIntBitWriter.append(4, this.type.value); + uIntBitWriter.append(12, this.timeOffsetMin); + return uIntBitWriter; + } + + public abstract long toLong(); +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java new file mode 100644 index 000000000..611c64c33 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java @@ -0,0 +1,22 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitWriter; + +public class ActivityHeartRate extends ActivityBase { + public final int bpm; + + public ActivityHeartRate(int timeOffsetMin, int bpm, Long timeStampSec) { + super(ActivityType.HEART_RATE, timeOffsetMin, timeStampSec); + if (bpm < 0 || bpm > 65535) { + throw new IllegalArgumentException("bpm out of range: " + bpm); + } + this.bpm = bpm; + } + + @Override + public long toLong() { + UIntBitWriter writerWithTypeAndOffset = this.getWriterWithTypeAndOffset(); + writerWithTypeAndOffset.append(16, this.bpm); + return writerWithTypeAndOffset.getValue(); + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java new file mode 100644 index 000000000..f8afb9703 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java @@ -0,0 +1,22 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitWriter; + +public class ActivitySleep extends ActivityBase { + public final SleepLevel sleepLevel; + public final int durationMin; + + public ActivitySleep(int timeOffsetMin, int durationMin, SleepLevel sleepLevel, Long timeStampSec) { + super(ActivityType.SLEEP, timeOffsetMin, timeStampSec); + this.durationMin = durationMin; + this.sleepLevel = sleepLevel; + } + + @Override + public long toLong() { + UIntBitWriter writerWithTypeAndOffset = this.getWriterWithTypeAndOffset(); + writerWithTypeAndOffset.append(14, this.durationMin); + writerWithTypeAndOffset.append(2, this.sleepLevel.value); + return writerWithTypeAndOffset.getValue(); + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityType.java new file mode 100644 index 000000000..e9c6d92e3 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityType.java @@ -0,0 +1,23 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; + +public enum ActivityType { + WALK(1), + RUN(2), + SLEEP(3), + HEART_RATE(10), + END(14); + + final int value; + + ActivityType(int value) { + this.value = value; + } + + public static ActivityType fromInt(int i) { + for (ActivityType type : values()){ + if (type.value == i) + return type; + } + throw new IllegalArgumentException("wrong activity type: " + i); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java new file mode 100644 index 000000000..37348177b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java @@ -0,0 +1,22 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitWriter; + +public class ActivityWithData extends ActivityBase { + public final int data; + + public ActivityWithData(ActivityType activityType, int timeOffsetMin, int data, Long timeStampSec) { + super(activityType, timeOffsetMin, timeStampSec); + if (data < 0 || data > 65535) { + throw new IllegalArgumentException("data out of range: " + data); + } + this.data = data; + } + + @Override + public long toLong() { + UIntBitWriter writerWithTypeAndOffset = this.getWriterWithTypeAndOffset(); + writerWithTypeAndOffset.append(16, this.data); + return writerWithTypeAndOffset.getValue(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventBase.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventBase.java new file mode 100644 index 000000000..1b1d31161 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventBase.java @@ -0,0 +1,21 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter; + +public abstract class EventBase { + protected final EventCode eventCode; + + protected EventBase(EventCode eventCode) { + this.eventCode = eventCode; + } + + public EventCode getCode() { + return this.eventCode; + } + + protected ByteArrayWriter getValueWriter() { + ByteArrayWriter byteArrayWriter = new ByteArrayWriter(); + byteArrayWriter.appendUint8(this.eventCode.value); + return byteArrayWriter; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventCode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventCode.java new file mode 100644 index 000000000..777d0dba6 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventCode.java @@ -0,0 +1,21 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; + +public enum EventCode { + STEPS(3), + ACTIVITY_DATA(5), + HEART_RATE(9); + + final int value; + + EventCode(int value) { + this.value = value; + } + + static EventCode fromInt(int i) { + for (EventCode code : values()){ + if (code.value == i) + return code; + } + throw new RuntimeException("wrong event code: " + i); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventFactory.java new file mode 100644 index 000000000..03e09e61a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventFactory.java @@ -0,0 +1,26 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.IntFormat; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayReader; + +public class EventFactory { + + public static EventBase readEventFromByteArray(byte[] array) { + try { + ByteArrayReader byteArrayReader = new ByteArrayReader(array); + EventCode eventCode = EventCode.fromInt(byteArrayReader.readUint8()); + switch (eventCode) { + case HEART_RATE: { + long value = byteArrayReader.readInt(IntFormat.UINT32); + return new EventWithValue(eventCode, value); + } + case ACTIVITY_DATA: { + return EventWithActivity.fromByteArray(byteArrayReader); + } + default: return null; + } + } catch (Exception ex) { + return null; + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java new file mode 100644 index 000000000..e7c804ddd --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java @@ -0,0 +1,62 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; + +import java.util.ArrayList; +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12Util; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.IntFormat; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitReader; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayReader; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter; + +public class EventWithActivity extends EventBase { + public final long timeStampSec; + public final List activityList; + + private EventWithActivity(long timeStampSec, List activityList) { + super(EventCode.ACTIVITY_DATA); + this.timeStampSec = timeStampSec; + this.activityList = activityList; + } + + public static EventWithActivity fromByteArray(ByteArrayReader byteArrayReader) { + long timeOffset = byteArrayReader.readInt(IntFormat.UINT32); + long timeStampSec = SonySWR12Util.secSince2013() + timeOffset; + ArrayList activities = new ArrayList<>(); + while (byteArrayReader.getBytesLeft() > 0) { + UIntBitReader uIntBitReader = new UIntBitReader(byteArrayReader.readInt(IntFormat.UINT32), 32); + ActivityType activityType = ActivityType.fromInt(uIntBitReader.read(4)); + int offsetMin = uIntBitReader.read(12); + ActivityBase activityPayload; + switch (activityType) { + case SLEEP: { + SleepLevel sleepLevel = SleepLevel.fromInt(uIntBitReader.read(2)); + int duration = uIntBitReader.read(14); + activityPayload = new ActivitySleep(offsetMin, duration, sleepLevel, timeStampSec); + break; + } + case HEART_RATE: { + int bpm = uIntBitReader.read(16); + activityPayload = new ActivityHeartRate(offsetMin, bpm, timeStampSec); + break; + } + default: { + int data = uIntBitReader.read(16); + activityPayload = new ActivityWithData(activityType, offsetMin, data, timeStampSec); + break; + } + } + activities.add(activityPayload); + } + return new EventWithActivity(timeStampSec, activities); + } + + public byte[] toByteArray() { + ByteArrayWriter byteArrayWriter = this.getValueWriter(); + byteArrayWriter.appendUint32(this.timeStampSec); + for (ActivityBase activity : activityList){ + byteArrayWriter.appendUint32(activity.toLong()); + } + return byteArrayWriter.getByteArray(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithValue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithValue.java new file mode 100644 index 000000000..cf81e5eb0 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithValue.java @@ -0,0 +1,18 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter; + +public class EventWithValue extends EventBase { + public final long value; + + public EventWithValue(EventCode eventCode, long value) { + super(eventCode); + this.value = value; + } + + public byte[] toByteArray() { + ByteArrayWriter byteArrayWriter = this.getValueWriter(); + byteArrayWriter.appendUint32(this.value); + return byteArrayWriter.getByteArray(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/SleepLevel.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/SleepLevel.java new file mode 100644 index 000000000..c8565467f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/SleepLevel.java @@ -0,0 +1,21 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; + +public enum SleepLevel { + AWAKE(0), + LIGHT(1), + DEEP(2); + + final int value; + + SleepLevel(int value){ + this.value = value; + } + + public static SleepLevel fromInt(int i) { + for (SleepLevel level : values()){ + if (level.value == i) + return level; + } + throw new RuntimeException("wrong sleep level: " + i); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmRepeat.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmRepeat.java new file mode 100644 index 000000000..28ef12656 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmRepeat.java @@ -0,0 +1,53 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm; + +import java.util.Arrays; + +import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitWriter; + +public class AlarmRepeat { + private final boolean[] repeat = new boolean[7]; + + public AlarmRepeat(Alarm alarm) { + super(); + setRepeatOnDay(0, alarm.getRepetition(Alarm.ALARM_MON)); + setRepeatOnDay(1, alarm.getRepetition(Alarm.ALARM_TUE)); + setRepeatOnDay(2, alarm.getRepetition(Alarm.ALARM_WED)); + setRepeatOnDay(3, alarm.getRepetition(Alarm.ALARM_THU)); + setRepeatOnDay(4, alarm.getRepetition(Alarm.ALARM_FRI)); + setRepeatOnDay(5, alarm.getRepetition(Alarm.ALARM_SAT)); + setRepeatOnDay(6, alarm.getRepetition(Alarm.ALARM_SUN)); + } + + @Override + public boolean equals(Object o) { + if (this != o) { + if (o == null || this.getClass() != o.getClass()) { + return false; + } + return Arrays.equals(this.repeat, ((AlarmRepeat) o).repeat); + } + return true; + } + + @Override + public int hashCode() { + return Arrays.hashCode(this.repeat); + } + + public void setRepeatOnDay(int i, boolean b) { + this.repeat[i] = b; + } + + public int toInt() { + UIntBitWriter uIntBitWriter = new UIntBitWriter(7); + uIntBitWriter.appendBoolean(this.repeat[6]); + uIntBitWriter.appendBoolean(this.repeat[5]); + uIntBitWriter.appendBoolean(this.repeat[4]); + uIntBitWriter.appendBoolean(this.repeat[3]); + uIntBitWriter.appendBoolean(this.repeat[2]); + uIntBitWriter.appendBoolean(this.repeat[1]); + uIntBitWriter.appendBoolean(this.repeat[0]); + return (int) uIntBitWriter.getValue(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmState.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmState.java new file mode 100644 index 000000000..1564207f6 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmState.java @@ -0,0 +1,13 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm; + +public enum AlarmState { + TRIGGERED( 0), + SNOOZED(1), + IDLE(2); + + final int value; + + AlarmState(int value) { + this.value = value; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarm.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarm.java new file mode 100644 index 000000000..306ba6d94 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarm.java @@ -0,0 +1,60 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm; + +import nodomain.freeyourgadget.gadgetbridge.model.Alarm; + +public class BandAlarm { + public static BandAlarm fromAppAlarm(Alarm alarm, int index, int interval) { + if (!alarm.getEnabled()) return null; + //smart wakeup = (0,10..60 min)/5 + int ahsInterval = interval / 5; + return new BandAlarm(AlarmState.IDLE, index, ahsInterval, alarm.getHour(), alarm.getMinute(), new AlarmRepeat(alarm)); + } + + public AlarmState state; + public int index; + public int interval; + public int hour; + public int minute; + public AlarmRepeat repeat; + + public BandAlarm(AlarmState state, int index, int interval, int hour, int minute, AlarmRepeat repeat) { + this.state = state; + this.index = index; + this.interval = interval; + this.hour = hour; + this.minute = minute; + this.repeat = repeat; + } + + @Override + public boolean equals(Object o) { + if (this != o) { + if (o == null || this.getClass() != o.getClass()) { + return false; + } + BandAlarm bandAlarm = (BandAlarm) o; + if (this.index != bandAlarm.index) { + return false; + } + if (this.hour != bandAlarm.hour) { + return false; + } + if (this.interval != bandAlarm.interval) { + return false; + } + if (this.minute != bandAlarm.minute) { + return false; + } + if (!this.repeat.equals(bandAlarm.repeat)) { + return false; + } + return this.state == bandAlarm.state; + } + return true; + } + + @Override + public int hashCode() { + return ((((this.state.hashCode() * 31 + this.index) * 31 + this.interval) * 31 + this.hour) * 31 + this.minute) * 31 + this.repeat.hashCode(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarms.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarms.java new file mode 100644 index 000000000..7cd7676dc --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarms.java @@ -0,0 +1,35 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm; + +import java.util.List; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitWriter; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter; + +public class BandAlarms { + public final List alarms; + + public BandAlarms(List alarms) { + this.alarms = alarms; + } + + public byte[] toByteArray() { + ByteArrayWriter byteArrayWriter = new ByteArrayWriter(); + if (this.alarms.size() == 0) { + byteArrayWriter.appendUint32(1073741824L); + } else { + for (BandAlarm bandAlarm : this.alarms) { + UIntBitWriter uIntBitWriter = new UIntBitWriter(32); + uIntBitWriter.append(2, 0); + uIntBitWriter.append(4, bandAlarm.index); + uIntBitWriter.append(2, bandAlarm.state.value); + uIntBitWriter.append(4, bandAlarm.interval); + uIntBitWriter.append(6, bandAlarm.hour); + uIntBitWriter.append(6, bandAlarm.minute); + uIntBitWriter.append(1, 0); + uIntBitWriter.append(7, bandAlarm.repeat.toInt()); + byteArrayWriter.appendUint32(uIntBitWriter.getValue()); + } + } + return byteArrayWriter.getByteArray(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/CommandCode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/CommandCode.java new file mode 100644 index 000000000..fdd5979c3 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/CommandCode.java @@ -0,0 +1,15 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control; + +public enum CommandCode { + FLUSH_ACTIVITY(7), + HEARTRATE_REALTIME(11), + STAMINA_MODE(17), + MANUAL_ALARM(19), + LOW_VIBRATION(25); + + public final int value; + + CommandCode(int value) { + this.value = value; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPoint.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPoint.java new file mode 100644 index 000000000..038a3dace --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPoint.java @@ -0,0 +1,17 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter; + +public abstract class ControlPoint { + protected final CommandCode code; + + public ControlPoint(CommandCode code) { + this.code = code; + } + + protected final ByteArrayWriter getValueWriter() { + final ByteArrayWriter byteArrayWriter = new ByteArrayWriter(); + byteArrayWriter.appendUint8(this.code.value); + return byteArrayWriter; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointLowVibration.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointLowVibration.java new file mode 100644 index 000000000..7addaf1ed --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointLowVibration.java @@ -0,0 +1,28 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitWriter; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter; + +public final class ControlPointLowVibration extends ControlPoint { + public boolean smartWakeUp; + public boolean incomingCall; + public boolean notification; + + public ControlPointLowVibration(boolean isEnabled){ + super(CommandCode.LOW_VIBRATION); + this.smartWakeUp = isEnabled; + this.incomingCall = isEnabled; + this.notification = isEnabled; + } + + public final byte[] toByteArray() { + final UIntBitWriter uIntBitWriter = new UIntBitWriter(16); + uIntBitWriter.append(13, 0); + uIntBitWriter.appendBoolean(this.smartWakeUp); + uIntBitWriter.appendBoolean(this.incomingCall); + uIntBitWriter.appendBoolean(this.notification); + final ByteArrayWriter byteArrayWriter = this.getValueWriter(); + byteArrayWriter.appendUint16((int) uIntBitWriter.getValue()); + return byteArrayWriter.getByteArray(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointWithValue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointWithValue.java new file mode 100644 index 000000000..ed4536924 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointWithValue.java @@ -0,0 +1,21 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter; + +public class ControlPointWithValue extends ControlPoint { + protected final int value; + + public ControlPointWithValue(final CommandCode commandCode, final int value) { + super(commandCode); + if (value < 0 || value > 65535) { + throw new IllegalArgumentException("command value out of range " + value); + } + this.value = value; + } + + public final byte[] toByteArray() { + final ByteArrayWriter byteArrayWriter = this.getValueWriter(); + byteArrayWriter.appendUint16(this.value); + return byteArrayWriter.getByteArray(); + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandDaylightSavingTime.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandDaylightSavingTime.java new file mode 100644 index 000000000..39ef3605f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandDaylightSavingTime.java @@ -0,0 +1,24 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.time; + +public enum BandDaylightSavingTime { + STANDARD_TIME(0, 0), + HALF_AN_HOUR_DST(2, 30), + DST(4, 60), + DOUBLE_DST( 8, 120); + + final int key; + private final long saving; + + BandDaylightSavingTime(int key, int min) { + this.key = key; + this.saving = 60000L * min; + } + + public static BandDaylightSavingTime fromOffset(final int dstSaving) { + for (BandDaylightSavingTime dst: values()){ + if (dst.saving == dstSaving) + return dst; + } + throw new RuntimeException("wrong dst saving: " + dstSaving); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTime.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTime.java new file mode 100644 index 000000000..315b48726 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTime.java @@ -0,0 +1,62 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.time; + +import java.util.Calendar; +import java.util.TimeZone; + +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.IntFormat; +import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter; + +public class BandTime { + private final int year; + private final int month; + private final int dayOfMonth; + private final int hour; + private final int min; + private final int sec; + private final int dayOfWeek; + private final BandTimeZone timeZone; + private final BandDaylightSavingTime dst; + + public BandTime(Calendar calendar) { + int dayOfWeek = 7; + if (calendar == null) { + throw new IllegalArgumentException("Calendar cant be null"); + } + this.year = calendar.get(1); + if (this.year > 2099 || this.year < 2013) { + throw new RuntimeException("out of 2013-2099"); + } + this.month = calendar.get(2) + 1; + this.dayOfMonth = calendar.get(5); + int value = calendar.get(7); + if (value != 1) { + dayOfWeek = value - 1; + } + this.dayOfWeek = dayOfWeek; + this.hour = calendar.get(11); + this.min = calendar.get(12); + this.sec = calendar.get(13); + TimeZone timeZone = calendar.getTimeZone(); + this.timeZone = BandTimeZone.fromOffset(timeZone.getRawOffset()); + if (timeZone.inDaylightTime(calendar.getTime())) { + this.dst = BandDaylightSavingTime.fromOffset(timeZone.getDSTSavings()); + return; + } + this.dst = BandDaylightSavingTime.STANDARD_TIME; + } + + public byte[] toByteArray() { + ByteArrayWriter byteArrayWriter = new ByteArrayWriter(); + byteArrayWriter.appendUint16(this.year); + byteArrayWriter.appendUint8(this.month); + byteArrayWriter.appendUint8(this.dayOfMonth); + byteArrayWriter.appendUint8(this.hour); + byteArrayWriter.appendUint8(this.min); + byteArrayWriter.appendUint8(this.sec); + byteArrayWriter.appendUint8(this.dayOfWeek); + byteArrayWriter.appendValue(this.timeZone.key, IntFormat.SINT8); + byteArrayWriter.appendUint8(this.dst.key); + return byteArrayWriter.getByteArray(); + } +} + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTimeZone.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTimeZone.java new file mode 100644 index 000000000..1040f5af4 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTimeZone.java @@ -0,0 +1,61 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.time; + +public enum BandTimeZone { + UTC_PLUS_06_30(26, 6, 30), + UTC_PLUS_07_00(28, 7, 0), + UTC_PLUS_08_00(32, 8, 0), + UTC_PLUS_08_45(35, 8, 45), + UTC_PLUS_09_00(36, 9, 0), + UTC_PLUS_09_30(38, 9, 30), + UTC_PLUS_10_00(40, 10, 0), + UTC_PLUS_10_30(42, 10, 30), + UTC_PLUS_11_00(44, 11, 0), + UTC_PLUS_11_30(46, 11, 30), + UTC_PLUS_12_00(48, 12, 0), + UTC_PLUS_12_45(51, 12, 45), + UTC_PLUS_13_00( 52, 13, 0), + UTC_PLUS_14_00(56, 14, 0), + UTC_MINUS_12_00(-48, -12, 0), + UTC_MINUS_11_00(-44, -11, 0), + UTC_MINUS_10_00(-40, -10, 0), + UTC_MINUS_09_30(-38, -9, -30), + UTC_MINUS_09_00(-36, -9, 0), + UTC_MINUS_08_00(-32, -8, 0), + UTC_MINUS_07_00(-28, -7, 0), + UTC_MINUS_06_00(-24, -6, 0), + UTC_MINUS_05_00(-20, -5, 0), + UTC_MINUS_04_30(-18, -4, -30), + UTC_MINUS_04_00(-16, -4, 0), + UTC_MINUS_03_30(-14, -3, -30), + UTC_MINUS_03_00(-12, -3, 0), + UTC_MINUS_02_00(-8, -2, 0), + UTC_MINUS_01_00(-4, -1, 0), + UTC_PLUS_00_00(0, 0, 0), + UTC_PLUS_01_00(4, 1, 0), + UTC_PLUS_02_00(8, 2, 0), + UTC_PLUS_03_00(12, 3, 0), + UTC_PLUS_03_30(14, 3, 30), + UTC_PLUS_04_00(16, 4, 0), + UTC_PLUS_04_30(18, 4, 30), + UTC_PLUS_05_00(20, 5, 0), + UTC_PLUS_05_30(22, 5, 30), + UTC_PLUS_05_45(23, 5, 45), + UTC_PLUS_06_00(24, 6, 0); + + final int key; + private final long rawOffset; + + BandTimeZone(int key, int hourOffset, int minOffset) { + this.key = key; + this.rawOffset = 3600000L * hourOffset + 60000L * minOffset; + } + + public static BandTimeZone fromOffset(long rawOffset) { + for (BandTimeZone zone : values()){ + if (zone.rawOffset == rawOffset) + return zone; + } + throw new RuntimeException("wrong raw offset: " + rawOffset); + } +} + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayReader.java new file mode 100644 index 000000000..edf1edb45 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayReader.java @@ -0,0 +1,53 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util; + +public class ByteArrayReader { + public final byte[] byteArray; + public int bytesRead; + + public ByteArrayReader(byte[] array) { + this.bytesRead = 0; + if (array == null || array.length <= 0) { + throw new IllegalArgumentException("wrong byte array"); + } + this.byteArray = array.clone(); + } + + public int getBytesLeft() { + return this.byteArray.length - this.bytesRead; + } + + public long readInt(IntFormat intFormat) { + if (intFormat == null) { + throw new IllegalArgumentException("wrong intFormat"); + } + int i = 0; + long n = 0L; + try { + while (i < intFormat.bytesCount) { + long n2 = this.byteArray[this.bytesRead++] & 0xFF; + int n3 = i + 1; + n += n2 << i * 8; + i = n3; + } + long n4 = n; + if (intFormat.isSigned) { + int n5 = intFormat.bytesCount * 8; + n4 = n; + if (((long) (1 << n5 - 1) & n) != 0x0L) { + n4 = ((1 << n5 - 1) - (n & (long) ((1 << n5 - 1) - 1))) * -1L; + } + } + return n4; + } catch (ArrayIndexOutOfBoundsException ex) { + throw new RuntimeException("reading outside of byte array", ex.getCause()); + } + } + + public int readUint16() { + return (int) this.readInt(IntFormat.UINT16); + } + + public int readUint8() { + return (int) this.readInt(IntFormat.UINT8); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayWriter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayWriter.java new file mode 100644 index 000000000..762f41136 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayWriter.java @@ -0,0 +1,61 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util; + +import java.util.Arrays; + +public class ByteArrayWriter { + public byte[] byteArray; + private int bytesWritten; + + public ByteArrayWriter() { + this.bytesWritten = 0; + } + + private void addIntToValue(long n, IntFormat intFormat) { + for (int i = 0; i < intFormat.bytesCount; ++i) { + this.byteArray[this.bytesWritten++] = (byte) (n >> i * 8 & 0xFFL); + } + } + + public void appendUint16(int n) { + this.appendValue(n, IntFormat.UINT16); + } + + public void appendUint32(long n) { + this.appendValue(n, IntFormat.UINT32); + } + + public void appendUint8(int n) { + this.appendValue(n, IntFormat.UINT8); + } + + public void appendValue(long lng, IntFormat intFormat) { + if (intFormat == null) { + throw new IllegalArgumentException("wrong int format"); + } + if (lng > intFormat.max || lng < intFormat.min) { + throw new IllegalArgumentException("wrong value for intFormat. max: " + intFormat.max + " min: " + intFormat.min + " value: " + lng); + } + this.increaseByteArray(intFormat.bytesCount); + long n = lng; + if (intFormat.isSigned) { + int n2 = intFormat.bytesCount * 8; + n = lng; + if (lng < 0L) { + n = (1 << n2 - 1) + ((long) ((1 << n2 - 1) - 1) & lng); + } + } + this.addIntToValue(n, intFormat); + } + + public void increaseByteArray(int n) { + if (this.byteArray == null) { + this.byteArray = new byte[n]; + return; + } + this.byteArray = Arrays.copyOf(this.byteArray, this.byteArray.length + n); + } + + public byte[] getByteArray() { + return this.byteArray.clone(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/IntFormat.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/IntFormat.java new file mode 100644 index 000000000..284238753 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/IntFormat.java @@ -0,0 +1,35 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util; + +public enum IntFormat { + UINT8(1, false), + SINT8(1, true), + UINT16( 2, false), + SINT16( 2, true), + UINT32(4, false), + SINT32(4, true); + + final int bytesCount; + final boolean isSigned; + final long max; + final long min; + + IntFormat(int bytesCount, boolean isSigned) { + this.bytesCount = bytesCount; + this.isSigned = isSigned; + int bitsCount = bytesCount * 8; + long max; + if (isSigned) { + max = (long) Math.pow(2.0, bitsCount - 1) - 1L; + } else { + max = (long) (Math.pow(2.0, bitsCount) - 1.0); + } + this.max = max; + long min; + if (isSigned) { + min = (long) (-1.0 * Math.pow(2.0, bitsCount - 1)); + } else { + min = 0L; + } + this.min = min; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitReader.java new file mode 100644 index 000000000..645d24599 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitReader.java @@ -0,0 +1,27 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util; + +public class UIntBitReader { + private final long value; + private int offset; + + public UIntBitReader(long value, int offset) { + this.value = value; + this.offset = offset; + } + + public int read(int offset) { + this.offset -= offset; + if (this.offset < 0) { + throw new IllegalArgumentException("Read out of range"); + } + return (int) ((long) ((1 << offset) - 1) & this.value >>> this.offset); + } + + public boolean readBoolean() { + boolean b = true; + if (this.read(1) == 0) { + b = false; + } + return b; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitWriter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitWriter.java new file mode 100644 index 000000000..21ae241f4 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitWriter.java @@ -0,0 +1,37 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util; + +public class UIntBitWriter { + private long value; + private long offset; + + public UIntBitWriter(int offset) { + this.value = 0L; + this.offset = offset; + } + + public void append(int offset, int value) { + if (value < 0 || value > (1 << offset) - 1) { + throw new IllegalArgumentException("value is out of range: " + value); + } + this.offset -= offset; + if (this.offset < 0L) { + throw new IllegalArgumentException("Write offset out of range"); + } + this.value |= (long) value << (int) this.offset; + } + + public void appendBoolean(boolean b) { + if (b) { + this.append(1, 1); + return; + } + this.append(1, 0); + } + + public long getValue() { + if (this.offset != 0L) { + throw new IllegalStateException("value is not complete yet: " + this.offset); + } + return this.value; + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java index 921702b23..a30d79823 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -82,6 +82,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeJFCoordinat import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.QHybridCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi1Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi3Coordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12.SonySWR12DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.tlw64.TLW64Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo.VibratissimoCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9DeviceCoordinator; @@ -258,6 +259,7 @@ public class DeviceHelper { result.add(new PineTimeJFCoordinator()); result.add(new SG2Coordinator()); result.add(new LefunDeviceCoordinator()); + result.add(new SonySWR12DeviceCoordinator()); return result; } diff --git a/app/src/main/res/layout/activity_sonyswr12_settings.xml b/app/src/main/res/layout/activity_sonyswr12_settings.xml new file mode 100644 index 000000000..2c1ab8994 --- /dev/null +++ b/app/src/main/res/layout/activity_sonyswr12_settings.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 151c838f7..757eeaea4 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -595,4 +595,9 @@ Изкл. Вкл. Няма данни + Настройки Sony SWR12 + Активирана ниска вибрация + Режимът за пестене на енергия е включен + Интелигентен алармен интервал в минути + За да промените настройките, първо трябва да се свържете с устройството! \ No newline at end of file diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index a1d51c956..b1e63f5a3 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -874,4 +874,9 @@ Repeteix la notificació de trucada Notificacions i trucades Calibració del Watch X Plus + Sony SWR12 + Configuració de Sony SWR12 + Vibració baixa activada + Interval d\'alarma intel·ligent en minuts + Per canviar la configuració, primer us heu de connectar al dispositiu. \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index cd11fa671..792bbc5c4 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -1047,4 +1047,10 @@ Kriket Veslovací trenažér Fotbal + Sony SWR12 + Sony SWR12 nastavení + Nízké vibrace povoleny + Režim úspory energie je zapnutý + Interval inteligentního alarmu v minutách + Chcete-li změnit nastavení, měli byste se nejprve připojit k zařízení! \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 65c43c032..2e0632114 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1059,4 +1059,10 @@ Heute Vergangenheit Keine Aktivitäten gefunden. + Sony SWR12 + Sony SWR12 Einstellungen + Geringe Vibration aktiviert + Der Energiesparmodus ist aktiviert + Intelligentes Alarmintervall in Minuten + Um Einstellungen zu ändern, sollten Sie zuerst eine Verbindung zum Gerät herstellen! \ No newline at end of file diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 5227c67c3..c4e91c4ba 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -988,4 +988,10 @@ Ελλειπτικό μηχάνημα Ποδηλασία εσωτερικού χώρου Κολύμβηση (ανοιχτό νερό) + Sony SWR12 + Ρυθμίσεις Sony SWR12 + Ενεργοποιήθηκε χαμηλή δόνηση + Η λειτουργία εξοικονόμησης ενέργειας είναι ενεργοποιημένη + Έξυπνο διάστημα συναγερμού σε λίγα λεπτά + Για να αλλάξετε τις ρυθμίσεις πρέπει πρώτα να συνδεθείτε στη συσκευή! \ No newline at end of file diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index fbd4a9a98..7b15cc575 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -896,4 +896,10 @@ Choose the shortcuts on the band screen Shortcuts Set Alias + Sony SWR12 + Sony SWR12 settings + Low vibration enabled + Power saving mode on + Smart alarm interval in minutes + To change settings you should first connect to device! \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 49416171d..061fd5f73 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -851,4 +851,10 @@ Llamadas y Notificaciones Calibracion Watch X Plus Establecer Alias + Sony SWR12 + Configuración de Sony SWR12 + Baja vibración habilitada + El modo de ahorro de energía está activado + Intervalo de alarma inteligente en minutos + Para cambiar la configuración, primero debe conectarse al dispositivo. \ No newline at end of file diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 0c49d70ed..c9347cca4 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -855,4 +855,10 @@ Nimi Filter Statistika + Sony SWR12 + Sony SWR12 sätted + Madal vibratsioon on lubatud + Energiasäästurežiim on sisse lülitatud + Nutika häire intervall minutites + Seadete muutmiseks peaksite kõigepealt seadmega ühenduse looma! \ No newline at end of file diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 0da36995b..a47dcba1c 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -125,4 +125,5 @@ مکان تعیین شده برای هواشناسی (CM/LOS) اضافه کردن برنامه‌ها به لیست سیاه nodomain.freeyourgadget.gadgetbridge.ButtonPressed + برای تغییر تنظیمات ابتدا باید به دستگاه متصل شوید! \ No newline at end of file diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 1c8f36875..344ed368d 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -36,4 +36,9 @@ Määritä Siirrä ylös nodomain.freeyourgadget.gadgetbridge.ButtonPressed + Sony SWR12 asetukset + Matala tärinä käytössä + Virransäästötila on päällä + Älykäs hälytysväli minuutteina + Muuta asetuksia muodostamalla ensin yhteys laitteeseen! \ No newline at end of file diff --git a/app/src/main/res/values-fr-rCA/strings.xml b/app/src/main/res/values-fr-rCA/strings.xml index ab07b88ba..049f76618 100644 --- a/app/src/main/res/values-fr-rCA/strings.xml +++ b/app/src/main/res/values-fr-rCA/strings.xml @@ -885,4 +885,10 @@ Notifications et appels Calibrage de Watch X Plus Système + Sony SWR12 + Paramètres Sony SWR12 + Faible vibration activée + Le mode d\'économie d\'énergie est activé + Intervalle d\'alarme intelligente en minutes + Pour modifier les paramètres, vous devez d\'abord vous connecter à l\'appareil! \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 1e2022a45..601893395 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1057,4 +1057,10 @@ Temps de sommeil préféré en heures Intervalle de rappel d\'hydratation (en minutes) Le bracelet vibrera pour vous rappeler de boire de l\'eau Rappel d\'hydratation + Sony SWR12 + Paramètres Sony SWR12 + Faible vibration activée + Le mode d\'économie d\'énergie est activé + Intervalle d\'alarme intelligente en minutes + Pour modifier les paramètres, vous devez d\'abord vous connecter à l\'appareil! \ No newline at end of file diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index d5d321704..007d460e2 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -515,4 +515,9 @@ Axustes Alipay nodomain.freeyourgadget.gadgetbridge.ButtonPressed + Sony SWR12 + Axustes Sony SWR12 + Vibración baixa habilitada + Intervalo de alarma intelixente en minutos + Para cambiar a configuración, primeiro debes conectarte ao dispositivo. \ No newline at end of file diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 7e47f278e..cbc089e4c 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -1058,4 +1058,10 @@ הפרש תזכורת שתייה (בדקות) הצמיד ירטוט כדי להזכיר לך לשתות מים תזכורת שתייה + Sony SWR12 + Sony SWR12 הגדרות + רטט נמוך מופעל + מצב חיסכון בחשמל פועל + מרווח אזעקה חכם בדקות + כדי לשנות הגדרות כדאי להתחבר תחילה למכשיר! \ No newline at end of file diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 7506e24bd..d83731f29 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -152,4 +152,6 @@ अपनी गतिविधि को युक्ति में रखें सभी अलार्म बंद है कनेक्ट नहीं. + मिनटों में स्मार्ट अलार्म अंतराल + सेटिंग्स बदलने के लिए आपको पहले डिवाइस से कनेक्ट करना चाहिए! \ No newline at end of file diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 1220c86fd..a0bb62fe7 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -61,4 +61,7 @@ Sinkroniziraj Doniraj Postavke + Uključen je način uštede energije + Pametni interval alarma u minutama + Da biste promijenili postavke, prvo se povežite s uređajem! \ No newline at end of file diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index c099464d0..e50fbbb41 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -495,4 +495,9 @@ Értesítések közötti legrövidebb idő Rendszer Feketelistás Naptárak + Beállítások Sony SWR12 + Alacsony rezgés engedélyezve + Az energiatakarékos mód be van kapcsolva + Intelligens riasztási intervallum percekben + A beállítások módosításához először csatlakoznia kell az eszközhöz! \ No newline at end of file diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 424b97f2f..8576b444f 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -34,4 +34,7 @@ Manajer App Melakukan factory reset akan menghapus seluruh data dari perangkat terkoneksi (jika didukung). Perangkat Xiaomi/Huami juga mengganti MAC address Bluetooth, sehingga akan muncul sebagai aplikasi baru di GadgetBridge. Debug + Pengaturan Sony SWR12 + Interval alarm pintar dalam beberapa menit + Untuk mengubah pengaturan, Anda harus menghubungkan ke perangkat terlebih dahulu! \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 0c26bc3dd..f6a784e94 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1041,4 +1041,10 @@ Nuoto (all\'aperto) No km + Sony SWR12 + Impostazioni Sony SWR12 + Bassa vibrazione abilitata + La modalità di risparmio energetico è attiva + Intervallo di allarme intelligente in pochi minuti + Per modificare le impostazioni devi prima connetterti al dispositivo! \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 9d8bc111f..c536cd4da 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -755,4 +755,10 @@ インドネシア語 切断通知 距離 + Sony SWR12 + Sony SWR12 設定 + 低振動対応 + 省電力モードがオンになっている + 分単位のスマートアラーム間隔 + 設定を変更するには、最初にデバイスに接続する必要があります! \ No newline at end of file diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index 6d833162d..ebc6e7d9f 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -30,4 +30,7 @@ გააქტიურება გამორტვა კონფიგურაცია + Sony SWR12 პარამეტრები + სიგნალის ჭკვიანი ინტერვალი წუთებში + პარამეტრების შესაცვლელად ჯერ უნდა დაუკავშირდეთ მოწყობილობას! \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 099eedb83..4f98f62a7 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -609,4 +609,5 @@ 올바르지 않은 주파수 87.5와 108.0 사이의 주파수를 입력하세요 nodomain.freeyourgadget.gadgetbridge.ButtonPressed + Sony SWR12 \ No newline at end of file diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 1ba57eb79..fd2051d31 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -308,4 +308,10 @@ Snūstelti Leisti didelį MTU Padidinti perkėlimo greitį, bet gali ir neveikti kai kuriuose android įrenginiuose.. + Sony SWR12 + Sony SWR12 nustatymai + Įjungta maža vibracija + Įjungtas energijos taupymo režimas + Išmaniojo žadintuvo intervalas minutėmis + Norėdami pakeisti nustatymus, pirmiausia turite prisijungti prie įrenginio! \ No newline at end of file diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml index fa089bd6e..e106ebc8b 100644 --- a/app/src/main/res/values-my/strings.xml +++ b/app/src/main/res/values-my/strings.xml @@ -37,4 +37,6 @@ အခ်က္အလက္မ်ားကိုသိမ္းဆည္းမည္ မခ်ိတ္ဆက္ထားျခင္းမရွိပါ , သတိေပးခ်က္မထားရေသးပါ nodomain.freeyourgadget.gadgetbridge.ButtonPressed + မိနစ်အတွင်းစမတ်နှိုးဆော်သံကြားကာလ + ချိန်ညှိချက်များကိုပြောင်းလဲရန်သင်ပထမ ဦး ဆုံးကိရိယာနှင့်ချိတ်ဆက်သင့်သည်! \ No newline at end of file diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 097393a81..06f0bbb12 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -1016,4 +1016,10 @@ Drikkepåminnelseintervall (i minutter) Grensesnittspråk Brukt av LineageOS-værtilbyderen, andre Android-versjoner må bruke et program som «Ditt lokale vær». Mer info er å finne på Gadgetbridge-wiki-en. + Sony SWR12 + Sony SWR12 innstillinger + Lav vibrasjon aktivert + Strømsparingsmodus er på + Smart alarmintervall på få minutter + For å endre innstillinger, bør du først koble til enheten! \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 1127072a1..8599ece1c 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -956,4 +956,10 @@ Elliptische Trainer Binnenshuis fietsen Zwemmen (Open water) + Sony SWR12 + Sony SWR12 instellingen + Laag trillingsniveau ingeschakeld + Energiebesparende modus is ingeschakeld + Slimme alarminterval in minuten + Om instellingen te wijzigen, moet u eerst verbinding maken met het apparaat! \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 625208a31..f7f2d0fb1 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1029,4 +1029,10 @@ Krykiet Wiosłująca maszyna Trener eliptyczny + Sony SWR12 + Ustawienia Sony SWR12 + Włączono niski poziom wibracji + Tryb oszczędzania energii jest włączony + Inteligentny interwał alarmu w minutach + Aby zmienić ustawienia, należy najpierw połączyć się z urządzeniem! \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index eedb5f51e..99fcc675a 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1056,4 +1056,10 @@ hoje passado distante Links + Sony SWR12 + Configurações de Sony SWR12 + Baixa vibração habilitada + O modo de economia de energia está ativado + Intervalo de alarme inteligente em minutos + Para alterar as configurações, você deve primeiro se conectar ao dispositivo! \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 1e7a82d73..42cbd1ae8 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1046,4 +1046,10 @@ Ritmo médio de voltas Média de braçadas Distância média das braçadas + Sony SWR12 + Configurações de Sony SWR12 + Baixa vibração habilitada + O modo de economia de energia está ativado + Intervalo de alarme inteligente em minutos + Para alterar as configurações, você deve primeiro se conectar ao dispositivo! \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 2edb5b68d..edeba4e2c 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -129,4 +129,8 @@ Salvare Configurație Deconectat(ă), alarma nu este setată. nodomain.freeyourgadget.gadgetbridge.ButtonPressed + Setari Sony SWR12 + Modul de economisire a energiei este activat + Interval de alarmă inteligentă în minute + Pentru a modifica setările, ar trebui să vă conectați mai întâi la dispozitiv! \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 5014ccd76..1637bb9f9 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1064,4 +1064,10 @@ Lefun Язык интерфейса Защита от потери + Sony SWR12 + Настройки Sony SWR12 + Слабая вибрация + Режима энергосбережения включен + Интервал умного будильника в минутах + Для изменения настроек необходимо сначала подключиться к устройству! \ No newline at end of file diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index c052e3fdf..aee8c2d55 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -491,4 +491,10 @@ Pripojiť Zap. nodomain.freeyourgadget.gadgetbridge.ButtonPressed + Sony SWR12 + Nastavenia Sony SWR12 + Nízke vibrácie sú povolené + Režim úspory energie je zapnutý + Interval inteligentného alarmu v minútach + Ak chcete zmeniť nastavenie, mali by ste sa najskôr pripojiť k zariadeniu! \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 61daafb9c..0df2fd4ba 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -48,4 +48,9 @@ Aktivera pulsmätare Avaktivera pulsmätare Konfigurera + Inställningar Sony SWR12 + Låg vibration aktiverad + Energisparläge är på + Smart larmintervall på några minuter + För att ändra inställningar bör du först ansluta till enheten! \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 83f1c5db1..fcab38fe3 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -296,7 +296,7 @@ \n \nLütfen önce .fw dosyasını, sonra .res dosyasını, ve son olarak .gps dosyasını kurun. .fw dosyasını kurduktan sonra bilekliğiniz yeniden başlatılacak. \n -\nNote: Bu dosyalar önceden kurulanlarla tamamen aynıysa .res ve .gps dosyalarını kurmanız gerekmez. +\nNote: Bu dosyalar önceden kurulanlarla tamamen aynıysa .res ve .gps dosyalarını kurmanız gerekmez. \n \nİLERLEMENİZ DURUMUNDA RİSK SİZE AİTTİR! Mi Band 4 aygıtınıza %s ürün yazılımını kurmak üzeresiniz. @@ -950,7 +950,7 @@ \n \nLütfen önce .fw dosyasını, sonra .res dosyasın kurun. .fw dosyasını kurduktan sonra bilekliğiniz yeniden başlatılacak. \n -\nNote: Bu dosyalar önceden kurulanlarla tamamen aynıysa .res dosyasını kurmanız gerekmez. +\nNote: Bu dosyalar önceden kurulanlarla tamamen aynıysa .res dosyasını kurmanız gerekmez. \n \nİLERLEMENİZ DURUMUNDA RİSK SİZE AİTTİR! TLW64 @@ -1088,4 +1088,9 @@ Sıvı alma hatırlatma aralığı (dakika olarak) Bileklik su içmenizi hatırlatmak için titreyecek Sıvı alma hatırlatıcı + Sony SWR12 + Sony SWR12 Ayarları + Düşük titreşim etkin + Dakikalar içinde akıllı alarm aralığı + Ayarları değiştirmek için önce cihaza bağlanmalısınız! \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 8ebf505b5..42b697c2a 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1052,4 +1052,10 @@ \nПримітка: вам не потрібно встановлювати .res файл, якщо він такий самий був встановлений раніше. \n \nДІЄТЕ НА ВЛАСНИЙ РИЗИК! + Sony SWR12 + Sony SWR12 налаштування + Cлабка вібрація + Режим енергозбереження ввімкнено + Інтелектуальний інтервал тривоги в хвилинах + Щоб змінити налаштування, спочатку слід підключитися до пристрою! \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index a5c98d6f0..72fe6b58b 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -190,4 +190,8 @@ Giờ Giờ và ngày nodomain.freeyourgadget.gadgetbridge.ButtonPressed + Cài đặt Sony SWR12 + Chế độ tiết kiệm pin đang bật + Khoảng thời gian báo thức thông minh trong vài phút + Để thay đổi cài đặt, trước tiên bạn nên kết nối với thiết bị! \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ab178e865..ae2662fd1 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1069,4 +1069,10 @@ 水合提醒间隔(分钟) 当需要提醒喝水时,手环会震动 水合提醒 + Sony SWR12 + Sony SWR12 设置 + 低振动启用 + 省电模式已开启 + 智能警报间隔(以分钟为单位) + 要更改设置,您应该首先连接设备! \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 1bba745d1..49edd533e 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -585,4 +585,6 @@ 游泳 這是一個來自 Gadgetbridge 的測試通知 測試通知 + Sony SWR12 + Sony SWR12 設定 \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index fb85f5c2e..1afcadb36 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -986,4 +986,13 @@ 0 1 + + 0 + 10 + 20 + 30 + 40 + 50 + 60 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 24cd7e73c..56881c8f8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -785,6 +785,7 @@ Bangle.js TLW64 PineTime (JF Firmware) + Sony SWR12 Choose export location Gadgetbridge notifications Gadgetbridge notifications high priority @@ -934,6 +935,11 @@ Ignore bonded devices Enabling this option will ignore devices that have been bonded/paired already when scanning Location must be turned on to scan for devices + Sony SWR12 Settings + Low vibration enabled + Power saving mode on + Smart alarm interval in minutes + To change settings you should first connect to device! Distance Uphill diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 7573f8eec..dfbe19375 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -581,10 +581,14 @@ - + android:title="@string/zetime_title_settings" /> + Date: Mon, 28 Sep 2020 20:39:53 +0300 Subject: [PATCH 60/86] fixed issue with sleep data(wrong order of reading bits). removed rest useless methods from band classes. --- .../sonyswr12/SonySWR12DeviceCoordinator.java | 8 +++++--- .../sonyswr12/entities/activity/ActivityBase.java | 15 +-------------- .../entities/activity/ActivityHeartRate.java | 9 --------- .../entities/activity/ActivitySleep.java | 10 ---------- .../entities/activity/ActivityWithData.java | 9 --------- .../entities/activity/EventWithActivity.java | 12 +----------- 6 files changed, 7 insertions(+), 56 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java index 67e936d7a..d3e8ada6d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java @@ -28,9 +28,11 @@ public class SonySWR12DeviceCoordinator extends AbstractDeviceCoordinator { @NonNull @Override public DeviceType getSupportedType(GBDeviceCandidate candidate) { - String name = candidate.getDevice().getName(); - if (!name.isEmpty() && name.toLowerCase().contains("swr12")) - return getDeviceType(); + try { + String name = candidate.getDevice().getName(); + if (name != null && !name.isEmpty() && name.toLowerCase().contains("swr12")) + return getDeviceType(); + } catch (Exception exc){} return DeviceType.UNKNOWN; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java index 1cb102fb6..a168620fe 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java @@ -1,10 +1,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; -import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitWriter; - public abstract class ActivityBase { protected final ActivityType type; - protected final int timeOffsetMin; private final long timeStampSec; public ActivityBase(ActivityType type, int timeOffsetMin, long timeStampSec) { @@ -12,8 +9,7 @@ public abstract class ActivityBase { throw new IllegalArgumentException("activity time offset out of range: " + timeOffsetMin); } this.type = type; - this.timeOffsetMin = timeOffsetMin; - this.timeStampSec = timeStampSec + this.timeOffsetMin * 60; + this.timeStampSec = timeStampSec + timeOffsetMin * 60; } public final int getTimeStampSec() { @@ -23,13 +19,4 @@ public abstract class ActivityBase { public final ActivityType getType() { return this.type; } - - protected final UIntBitWriter getWriterWithTypeAndOffset() { - UIntBitWriter uIntBitWriter = new UIntBitWriter(32); - uIntBitWriter.append(4, this.type.value); - uIntBitWriter.append(12, this.timeOffsetMin); - return uIntBitWriter; - } - - public abstract long toLong(); } \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java index 611c64c33..e722f6439 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java @@ -1,7 +1,5 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; -import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitWriter; - public class ActivityHeartRate extends ActivityBase { public final int bpm; @@ -12,11 +10,4 @@ public class ActivityHeartRate extends ActivityBase { } this.bpm = bpm; } - - @Override - public long toLong() { - UIntBitWriter writerWithTypeAndOffset = this.getWriterWithTypeAndOffset(); - writerWithTypeAndOffset.append(16, this.bpm); - return writerWithTypeAndOffset.getValue(); - } } \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java index f8afb9703..4f8842673 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java @@ -1,7 +1,5 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; -import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitWriter; - public class ActivitySleep extends ActivityBase { public final SleepLevel sleepLevel; public final int durationMin; @@ -11,12 +9,4 @@ public class ActivitySleep extends ActivityBase { this.durationMin = durationMin; this.sleepLevel = sleepLevel; } - - @Override - public long toLong() { - UIntBitWriter writerWithTypeAndOffset = this.getWriterWithTypeAndOffset(); - writerWithTypeAndOffset.append(14, this.durationMin); - writerWithTypeAndOffset.append(2, this.sleepLevel.value); - return writerWithTypeAndOffset.getValue(); - } } \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java index 37348177b..1c1773b88 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java @@ -1,7 +1,5 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; -import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitWriter; - public class ActivityWithData extends ActivityBase { public final int data; @@ -12,11 +10,4 @@ public class ActivityWithData extends ActivityBase { } this.data = data; } - - @Override - public long toLong() { - UIntBitWriter writerWithTypeAndOffset = this.getWriterWithTypeAndOffset(); - writerWithTypeAndOffset.append(16, this.data); - return writerWithTypeAndOffset.getValue(); - } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java index e7c804ddd..a1f866e6c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java @@ -7,7 +7,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12U import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.IntFormat; import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitReader; import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayReader; -import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter; public class EventWithActivity extends EventBase { public final long timeStampSec; @@ -30,8 +29,8 @@ public class EventWithActivity extends EventBase { ActivityBase activityPayload; switch (activityType) { case SLEEP: { - SleepLevel sleepLevel = SleepLevel.fromInt(uIntBitReader.read(2)); int duration = uIntBitReader.read(14); + SleepLevel sleepLevel = SleepLevel.fromInt(uIntBitReader.read(2)); activityPayload = new ActivitySleep(offsetMin, duration, sleepLevel, timeStampSec); break; } @@ -50,13 +49,4 @@ public class EventWithActivity extends EventBase { } return new EventWithActivity(timeStampSec, activities); } - - public byte[] toByteArray() { - ByteArrayWriter byteArrayWriter = this.getValueWriter(); - byteArrayWriter.appendUint32(this.timeStampSec); - for (ActivityBase activity : activityList){ - byteArrayWriter.appendUint32(activity.toLong()); - } - return byteArrayWriter.getByteArray(); - } } From 1ba454a89b347f57eba5e88cb81465cddabc56e4 Mon Sep 17 00:00:00 2001 From: opavlov Date: Fri, 2 Oct 2020 22:58:48 +0300 Subject: [PATCH 61/86] refactored settings to "per-device" according to wiki --- app/src/main/AndroidManifest.xml | 3 - .../activities/SettingsActivity.java | 10 -- .../DeviceSettingsPreferenceConst.java | 4 + .../DeviceSpecificSettingsFragment.java | 6 ++ .../sonyswr12/SonySWR12DeviceCoordinator.java | 6 ++ .../sonyswr12/SonySWR12PrefActivity.java | 98 ------------------- .../devices/sonyswr12/SonySWR12Constants.java | 4 - .../sonyswr12/SonySWR12DeviceSupport.java | 14 +-- .../layout/activity_sonyswr12_settings.xml | 55 ----------- app/src/main/res/values-bg/strings.xml | 4 +- app/src/main/res/values-ca/strings.xml | 4 +- app/src/main/res/values-cs/strings.xml | 4 +- app/src/main/res/values-de/strings.xml | 4 +- app/src/main/res/values-el/strings.xml | 4 +- app/src/main/res/values-en-rGB/strings.xml | 4 +- app/src/main/res/values-es/strings.xml | 4 +- app/src/main/res/values-et/strings.xml | 4 +- app/src/main/res/values-fa/strings.xml | 4 +- app/src/main/res/values-fi/strings.xml | 4 +- app/src/main/res/values-fr-rCA/strings.xml | 4 +- app/src/main/res/values-fr/strings.xml | 4 +- app/src/main/res/values-gl/strings.xml | 4 +- app/src/main/res/values-he/strings.xml | 4 +- app/src/main/res/values-hi/strings.xml | 4 +- app/src/main/res/values-hr/strings.xml | 4 +- app/src/main/res/values-hu/strings.xml | 4 +- app/src/main/res/values-id/strings.xml | 4 +- app/src/main/res/values-it/strings.xml | 4 +- app/src/main/res/values-ja/strings.xml | 4 +- app/src/main/res/values-ka/strings.xml | 4 +- app/src/main/res/values-lt/strings.xml | 4 +- app/src/main/res/values-lv/strings.xml | 3 + app/src/main/res/values-ml/strings.xml | 3 + app/src/main/res/values-my/strings.xml | 4 +- app/src/main/res/values-nb-rNO/strings.xml | 4 +- app/src/main/res/values-nl/strings.xml | 4 +- app/src/main/res/values-pl/strings.xml | 4 +- app/src/main/res/values-pt-rBR/strings.xml | 4 +- app/src/main/res/values-pt/strings.xml | 4 +- app/src/main/res/values-ro/strings.xml | 4 +- app/src/main/res/values-ru/strings.xml | 4 +- app/src/main/res/values-sk/strings.xml | 4 +- app/src/main/res/values-sv/strings.xml | 4 +- app/src/main/res/values-tr/strings.xml | 4 +- app/src/main/res/values-uk/strings.xml | 4 +- app/src/main/res/values-vi/strings.xml | 4 +- app/src/main/res/values-zh-rCN/strings.xml | 4 +- app/src/main/res/values/arrays.xml | 9 ++ app/src/main/res/values/strings.xml | 4 +- .../main/res/xml/devicesettings_sonyswr12.xml | 21 ++++ 50 files changed, 171 insertions(+), 213 deletions(-) delete mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12PrefActivity.java delete mode 100644 app/src/main/res/layout/activity_sonyswr12_settings.xml create mode 100644 app/src/main/res/xml/devicesettings_sonyswr12.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f0479d146..5e61bf86c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -567,8 +567,5 @@ - \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java index 39f3c99ff..277404ee8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java @@ -59,7 +59,6 @@ import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity; import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.ConfigActivity; -import nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12.SonySWR12PrefActivity; import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; @@ -138,15 +137,6 @@ public class SettingsActivity extends AbstractSettingsActivity { } }); - pref = findPreference("pref_key_sonyswr12"); - pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - public boolean onPreferenceClick(Preference preference) { - Intent intent = new Intent(SettingsActivity.this, SonySWR12PrefActivity.class); - startActivity(intent); - return true; - } - }); - pref = findPreference("pref_key_blacklist"); pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { public boolean onPreferenceClick(Preference preference) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java index 2e574f94b..1d8d2a98b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java @@ -52,4 +52,8 @@ public class DeviceSettingsPreferenceConst { public static final String PREF_HYDRATION_PERIOD = "pref_hydration_period"; public static final String PREF_AMPM_ENABLED = "pref_ampm_enabled"; public static final String PREF_LEFUN_INTERFACE_LANGUAGE = "pref_lefun_interface_language"; + + public static final String PREF_SONYSWR12_LOW_VIBRATION = "vibration_preference"; + public static final String PREF_SONYSWR12_STAMINA = "stamina_preference"; + public static final String PREF_SONYSWR12_SMART_INTERVAL = "smart_alarm_interval_preference"; } \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java index 0dd9bf6ce..fafb12b71 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java @@ -63,6 +63,9 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LONGSIT_SWITCH; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_POWER_MODE; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SCREEN_ORIENTATION; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONYSWR12_LOW_VIBRATION; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONYSWR12_SMART_INTERVAL; +import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SONYSWR12_STAMINA; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_VIBRATION_STRENGH_PERCENTAGE; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_WEARLOCATION; @@ -368,6 +371,9 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat { addPreferenceHandlerFor(PREF_HYBRID_HR_FORCE_WHITE_COLOR); addPreferenceHandlerFor(PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES); + addPreferenceHandlerFor(PREF_SONYSWR12_STAMINA); + addPreferenceHandlerFor(PREF_SONYSWR12_LOW_VIBRATION); + addPreferenceHandlerFor(PREF_SONYSWR12_SMART_INTERVAL); String displayOnLiftState = prefs.getString(PREF_ACTIVATE_DISPLAY_ON_LIFT, PREF_DO_NOT_DISTURB_OFF); boolean displayOnLiftScheduled = displayOnLiftState.equals(PREF_DO_NOT_DISTURB_SCHEDULED); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java index d3e8ada6d..78d3854e8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java @@ -8,6 +8,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import nodomain.freeyourgadget.gadgetbridge.GBException; +import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; @@ -121,4 +122,9 @@ public class SonySWR12DeviceCoordinator extends AbstractDeviceCoordinator { public boolean supportsFindDevice() { return false; } + + @Override + public int[] getSupportedDeviceSpecificSettings(GBDevice device) { + return new int[]{R.xml.devicesettings_sonyswr12}; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12PrefActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12PrefActivity.java deleted file mode 100644 index 4611a88ed..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12PrefActivity.java +++ /dev/null @@ -1,98 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12; - -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; -import android.widget.CompoundButton; - -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.widget.AppCompatSpinner; -import androidx.appcompat.widget.SwitchCompat; - -import java.util.Arrays; -import java.util.List; - -import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.R; -import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; -import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12Constants; - -public class SonySWR12PrefActivity extends AbstractGBActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_sonyswr12_settings); - setTitle(getString(R.string.sonyswr12_settings_title)); - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setDisplayShowHomeEnabled(true); - } - setVibrationSetting(); - setStaminaSetting(); - setAlarmIntervalSetting(); - GBDevice device = GBApplication.app().getDeviceManager().getSelectedDevice(); - int disablerVisibility = (device != null - && device.isConnected() - && device.getType() == DeviceType.SONY_SWR12) ? View.GONE : View.VISIBLE; - findViewById(R.id.settingsDisabler).setVisibility(disablerVisibility); - } - - @Override - public boolean onSupportNavigateUp() { - onBackPressed(); - return true; - } - - private void setVibrationSetting() { - boolean isLow = GBApplication.getPrefs().getBoolean(SonySWR12Constants.VIBRATION_PREFERENCE, false); - SwitchCompat switchVibration = ((SwitchCompat) findViewById(R.id.lowVibration)); - switchVibration.setChecked(isLow); - switchVibration.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - GBApplication.getPrefs().getPreferences().edit() - .putBoolean(SonySWR12Constants.VIBRATION_PREFERENCE, isChecked).apply(); - GBApplication.deviceService().onSendConfiguration(SonySWR12Constants.VIBRATION_PREFERENCE); - } - }); - } - - private void setStaminaSetting() { - boolean isOn = GBApplication.getPrefs().getBoolean(SonySWR12Constants.STAMINA_PREFERENCE, false); - SwitchCompat switchStamina = ((SwitchCompat) findViewById(R.id.staminaOn)); - switchStamina.setChecked(isOn); - switchStamina.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - GBApplication.getPrefs().getPreferences().edit() - .putBoolean(SonySWR12Constants.STAMINA_PREFERENCE, isChecked).apply(); - GBApplication.deviceService().onSendConfiguration(SonySWR12Constants.STAMINA_PREFERENCE); - } - }); - } - - private void setAlarmIntervalSetting() { - String interval = GBApplication.getPrefs().getString(SonySWR12Constants.SMART_ALARM_INTERVAL_PREFERENCE, "0"); - List intervalsArray = Arrays.asList(GBApplication.getContext().getResources().getStringArray(R.array.sonyswr12_smart_alarm_intervals)); - int position = intervalsArray.indexOf(interval); - final AppCompatSpinner spinner = ((AppCompatSpinner) findViewById(R.id.smartAlarmSpinner)); - spinner.setSelection(position); - spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - String interval = (String) spinner.getItemAtPosition(position); - GBApplication.getPrefs().getPreferences().edit() - .putString(SonySWR12Constants.SMART_ALARM_INTERVAL_PREFERENCE, interval).apply(); - GBApplication.deviceService().onSendConfiguration(SonySWR12Constants.SMART_ALARM_INTERVAL_PREFERENCE); - } - - @Override - public void onNothingSelected(AdapterView parent) { - } - }); - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Constants.java index 1c01e4811..f629713f4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Constants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Constants.java @@ -11,10 +11,6 @@ public class SonySWR12Constants { public static final UUID UUID_CHARACTERISTIC_TIME = UUID.fromString(String.format(BASE_UUID_AHS, "020B")); public static final UUID UUID_CHARACTERISTIC_CONTROL_POINT = UUID.fromString(String.format(BASE_UUID_AHS, "0208")); - public static final String VIBRATION_PREFERENCE = "vibration_preference"; - public static final String STAMINA_PREFERENCE = "stamina_preference"; - public static final String SMART_ALARM_INTERVAL_PREFERENCE = "smart_alarm_interval_preference"; - public static final int TYPE_ACTIVITY = 0; public static final int TYPE_LIGHT = 1; public static final int TYPE_DEEP = 2; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java index 6e47e6ba1..834d229e6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java @@ -15,6 +15,7 @@ import java.util.Calendar; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -141,7 +142,8 @@ public class SonySWR12DeviceSupport extends AbstractBTLEDeviceSupport { try { BluetoothGattCharacteristic alarmCharacteristic = getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_ALARM); TransactionBuilder builder = performInitialized("alarm"); - int prefInterval = Integer.valueOf(GBApplication.getPrefs().getString(SonySWR12Constants.SMART_ALARM_INTERVAL_PREFERENCE, "0")); + int prefInterval = Integer.valueOf(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()) + .getString(DeviceSettingsPreferenceConst.PREF_SONYSWR12_SMART_INTERVAL, "0")); ArrayList bandAlarmList = new ArrayList<>(); for (Alarm alarm : alarms) { BandAlarm bandAlarm = BandAlarm.fromAppAlarm(alarm, bandAlarmList.size(), alarm.getSmartWakeup() ? prefInterval : 0); @@ -307,25 +309,25 @@ public class SonySWR12DeviceSupport extends AbstractBTLEDeviceSupport { public void onSendConfiguration(String config) { try { switch (config) { - case SonySWR12Constants.STAMINA_PREFERENCE: { + case DeviceSettingsPreferenceConst.PREF_SONYSWR12_STAMINA: { //stamina can be: //disabled = 0, enabled = 1 or todo auto on low battery = 2 - int status = GBApplication.getPrefs().getBoolean(config, false) ? 1 : 0; + int status = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(config, false) ? 1 : 0; TransactionBuilder builder = performInitialized(config); ControlPointWithValue vibrationControl = new ControlPointWithValue(CommandCode.STAMINA_MODE, status); builder.write(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_CONTROL_POINT), vibrationControl.toByteArray()); builder.queue(getQueue()); break; } - case SonySWR12Constants.VIBRATION_PREFERENCE: { - boolean isEnabled = GBApplication.getPrefs().getBoolean(config, false); + case DeviceSettingsPreferenceConst.PREF_SONYSWR12_LOW_VIBRATION: { + boolean isEnabled = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(config, false); TransactionBuilder builder = performInitialized(config); ControlPointLowVibration vibrationControl = new ControlPointLowVibration(isEnabled); builder.write(getCharacteristic(SonySWR12Constants.UUID_CHARACTERISTIC_CONTROL_POINT), vibrationControl.toByteArray()); builder.queue(getQueue()); break; } - case SonySWR12Constants.SMART_ALARM_INTERVAL_PREFERENCE: { + case DeviceSettingsPreferenceConst.PREF_SONYSWR12_SMART_INTERVAL: { onSetAlarms(new ArrayList(DBHelper.getAlarms(gbDevice))); } } diff --git a/app/src/main/res/layout/activity_sonyswr12_settings.xml b/app/src/main/res/layout/activity_sonyswr12_settings.xml deleted file mode 100644 index 2c1ab8994..000000000 --- a/app/src/main/res/layout/activity_sonyswr12_settings.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 757eeaea4..e74f26fca 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -599,5 +599,7 @@ Активирана ниска вибрация Режимът за пестене на енергия е включен Интелигентен алармен интервал в минути - За да промените настройките, първо трябва да се свържете с устройството! + Активирайте ниската интензивност на вибрациите на маншета + Режимът за пестене на енергия изключва периодичното автоматично измерване на сърдечната честота, като по този начин увеличава работното време + Интелигентният алармен интервал е интервал преди инсталираната аларма. В този интервал устройството се опитва да открие най-леката фаза на сън за събуждане на потребителя \ No newline at end of file diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index b1e63f5a3..e57e963a5 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -878,5 +878,7 @@ Configuració de Sony SWR12 Vibració baixa activada Interval d\'alarma intel·ligent en minuts - Per canviar la configuració, primer us heu de connectar al dispositiu. + Activa la baixa intensitat de vibració a la banda + El mode d’estalvi d’energia desactiva la mesura automàtica periòdica de la freqüència cardíaca i, per tant, augmenta el temps de treball + L\'interval d\'alarma intel·ligent és l\'interval abans de l\'alarma instal·lada. En aquest interval, el dispositiu intenta detectar la fase més lleugera del son per despertar l\'usuari \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 792bbc5c4..2b5db0d56 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -1052,5 +1052,7 @@ Nízké vibrace povoleny Režim úspory energie je zapnutý Interval inteligentního alarmu v minutách - Chcete-li změnit nastavení, měli byste se nejprve připojit k zařízení! + Povolit nízkou intenzitu vibrací na pásmu + Režim úspory energie vypíná periodické automatické měření srdeční frekvence a tím prodlužuje pracovní dobu + Interval inteligentního alarmu je interval před nainstalovaným alarmem. V tomto intervalu se zařízení pokouší detekovat nejlehčí fázi spánku, aby probudil uživatele \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 2e0632114..bd765ab59 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1064,5 +1064,7 @@ Geringe Vibration aktiviert Der Energiesparmodus ist aktiviert Intelligentes Alarmintervall in Minuten - Um Einstellungen zu ändern, sollten Sie zuerst eine Verbindung zum Gerät herstellen! + Aktivieren Sie eine geringe Vibrationsintensität auf dem Band + Der Energiesparmodus schaltet die regelmäßige automatische Messung der Herzfrequenz aus und verlängert so die Arbeitszeit + Das intelligente Alarmintervall ist das Intervall vor dem installierten Alarm. In diesem Intervall versucht das Gerät, die leichteste Schlafphase zu erkennen, um den Benutzer zu wecken \ No newline at end of file diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index c4e91c4ba..baddfa5b8 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -993,5 +993,7 @@ Ενεργοποιήθηκε χαμηλή δόνηση Η λειτουργία εξοικονόμησης ενέργειας είναι ενεργοποιημένη Έξυπνο διάστημα συναγερμού σε λίγα λεπτά - Για να αλλάξετε τις ρυθμίσεις πρέπει πρώτα να συνδεθείτε στη συσκευή! + Ενεργοποιήστε τη χαμηλή ένταση των κραδασμών στη ζώνη + Η λειτουργία εξοικονόμησης ενέργειας απενεργοποιεί την περιοδική αυτόματη μέτρηση του καρδιακού ρυθμού αυξάνοντας έτσι το χρόνο εργασίας + Το διάστημα έξυπνου συναγερμού είναι διάστημα πριν από το εγκατεστημένο συναγερμό Σε αυτό το διάστημα η συσκευή προσπαθεί να ανιχνεύσει την ελαφρύτερη φάση ύπνου για να ξυπνήσει ο χρήστης \ No newline at end of file diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index 7b15cc575..d7913da7a 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -901,5 +901,7 @@ Low vibration enabled Power saving mode on Smart alarm interval in minutes - To change settings you should first connect to device! + Enable low intensity of vibration on wristband + Power saving mode turns off periodic auto measuring of heart rate thus increases working time + Smart alarm interval is interval before of installed alarm. In this interval device is trying to detect lightest phase of sleep to awake user \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 061fd5f73..975894350 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -856,5 +856,7 @@ Baja vibración habilitada El modo de ahorro de energía está activado Intervalo de alarma inteligente en minutos - Para cambiar la configuración, primero debe conectarse al dispositivo. + Activar baja intensidad de vibración en la pulsera + El modo de ahorro de energía desactiva la medición automática periódica de la frecuencia cardíaca, lo que aumenta el tiempo de trabajo + El intervalo de alarma inteligente es el intervalo anterior a la alarma instalada. En este intervalo, el dispositivo está tratando de detectar la fase más ligera del sueño para despertar al usuario. \ No newline at end of file diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index c9347cca4..77da1866d 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -860,5 +860,7 @@ Madal vibratsioon on lubatud Energiasäästurežiim on sisse lülitatud Nutika häire intervall minutites - Seadete muutmiseks peaksite kõigepealt seadmega ühenduse looma! + Luba madala vibratsiooni intensiivsus ribal + Energiasäästurežiim lülitab südame löögisageduse perioodilise automaatse mõõtmise välja, pikendades seega tööaega + Nutika häire intervall on intervall enne installitud alarmi. Selles intervallis üritab seade kasutaja ärkamiseks tuvastada une kergemat faasi \ No newline at end of file diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index a47dcba1c..b651f5f2e 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -125,5 +125,7 @@ مکان تعیین شده برای هواشناسی (CM/LOS) اضافه کردن برنامه‌ها به لیست سیاه nodomain.freeyourgadget.gadgetbridge.ButtonPressed - برای تغییر تنظیمات ابتدا باید به دستگاه متصل شوید! + لرزش کم روی باند را فعال کنید + حالت صرفه جویی در مصرف انرژی اندازه گیری دوره ای ضربان قلب را خاموش می کند و در نتیجه زمان کار افزایش می یابد + فاصله هشدار هوشمند فاصله قبل از زنگ هشدار نصب شده است. در این فاصله دستگاه در تلاش است تا کمترین مرحله خواب را برای کاربر بیدار تشخیص دهد \ No newline at end of file diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 344ed368d..fa1e5bccd 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -40,5 +40,7 @@ Matala tärinä käytössä Virransäästötila on päällä Älykäs hälytysväli minuutteina - Muuta asetuksia muodostamalla ensin yhteys laitteeseen! + Ota käyttöön matala tärinän voimakkuus kaistalla + Virransäästötila poistaa säännöllisen automaattisen sykemittauksen käytöstä, mikä pidentää työaikaa + Älykäs hälytysväli on aikaväli ennen asennettua hälytystä. Tässä välissä laite yrittää tunnistaa käyttäjän herättämisen unen kevyimmän vaiheen \ No newline at end of file diff --git a/app/src/main/res/values-fr-rCA/strings.xml b/app/src/main/res/values-fr-rCA/strings.xml index 049f76618..f6b59a238 100644 --- a/app/src/main/res/values-fr-rCA/strings.xml +++ b/app/src/main/res/values-fr-rCA/strings.xml @@ -890,5 +890,7 @@ Faible vibration activée Le mode d\'économie d\'énergie est activé Intervalle d\'alarme intelligente en minutes - Pour modifier les paramètres, vous devez d\'abord vous connecter à l\'appareil! + Permettre une faible intensité de vibration sur la bande + Le mode d\'économie d\'énergie désactive la mesure automatique périodique de la fréquence cardiaque augmente ainsi le temps de travail + L\'intervalle d\'alarme intelligente est l\'intervalle avant l\'alarme installée. Dans cet intervalle, l\'appareil tente de détecter la phase de sommeil la plus légère pour réveiller l\'utilisateur \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 601893395..3aa007795 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1062,5 +1062,7 @@ Temps de sommeil préféré en heures Faible vibration activée Le mode d\'économie d\'énergie est activé Intervalle d\'alarme intelligente en minutes - Pour modifier les paramètres, vous devez d\'abord vous connecter à l\'appareil! + Permettre une faible intensité de vibration sur la bande + Le mode d\'économie d\'énergie désactive la mesure automatique périodique de la fréquence cardiaque augmente ainsi le temps de travail + L\'intervalle d\'alarme intelligente est l\'intervalle avant l\'alarme installée. Dans cet intervalle, l\'appareil tente de détecter la phase de sommeil la plus légère pour réveiller l\'utilisateur \ No newline at end of file diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 007d460e2..2b1a6b91a 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -519,5 +519,7 @@ Axustes Sony SWR12 Vibración baixa habilitada Intervalo de alarma intelixente en minutos - Para cambiar a configuración, primeiro debes conectarte ao dispositivo. + Activa a baixa intensidade de vibración na banda + O modo de aforro de enerxía desactiva a medición automática periódica da frecuencia cardíaca, polo que aumenta o tempo de traballo + O intervalo de alarma intelixente é o intervalo antes da alarma instalada. Neste intervalo o dispositivo está intentando detectar a fase máis lixeira do sono para espertar ao usuario \ No newline at end of file diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index cbc089e4c..62936fe32 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -1063,5 +1063,7 @@ רטט נמוך מופעל מצב חיסכון בחשמל פועל מרווח אזעקה חכם בדקות - כדי לשנות הגדרות כדאי להתחבר תחילה למכשיר! + אפשר עוצמת רטט נמוכה בלהקה + מצב חיסכון בחשמל מכבה מדידה אוטומטית תקופתית של דופק ובכך מגדיל את זמן העבודה + מרווח אזעקה חכם הוא מרווח לפני ההתראה המותקנת. במרווח זה מכשיר מנסה לאתר את שלב השינה הקל ביותר למשתמש ער \ No newline at end of file diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index d83731f29..8096dd100 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -153,5 +153,7 @@ सभी अलार्म बंद है कनेक्ट नहीं. मिनटों में स्मार्ट अलार्म अंतराल - सेटिंग्स बदलने के लिए आपको पहले डिवाइस से कनेक्ट करना चाहिए! + बैंड पर कंपन की कम तीव्रता को सक्षम करें + पावर सेविंग मोड हृदय गति की आवधिक ऑटो माप को बंद कर देता है जिससे काम का समय बढ़ जाता है + स्थापित अलार्म से पहले स्मार्ट अलार्म अंतराल है। इस अंतराल डिवाइस में उपयोगकर्ता को जगाने के लिए नींद के सबसे हल्के चरण का पता लगाने की कोशिश की जा रही है \ No newline at end of file diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index a0bb62fe7..07a36f43d 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -63,5 +63,7 @@ Postavke Uključen je način uštede energije Pametni interval alarma u minutama - Da biste promijenili postavke, prvo se povežite s uređajem! + Omogućite niski intenzitet vibracija na pojasu + Način uštede energije isključuje povremeno automatsko mjerenje brzine otkucaja srca čime se povećava radno vrijeme + Interval pametnog alarma je interval prije instaliranog alarma. U ovom intervalu uređaj pokušava otkriti najlakšu fazu spavanja za budnog korisnika \ No newline at end of file diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index e50fbbb41..85ef51f71 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -499,5 +499,7 @@ Alacsony rezgés engedélyezve Az energiatakarékos mód be van kapcsolva Intelligens riasztási intervallum percekben - A beállítások módosításához először csatlakoznia kell az eszközhöz! + Engedélyezze a vibráció alacsony intenzitását a sávon + Az energiatakarékos üzemmód kikapcsolja a pulzus időszakos automatikus mérését, így megnő a munkaidő + Az intelligens riasztási intervallum a telepített riasztás előtti intervallum. Ebben az intervallumban az eszköz megpróbálja észlelni az alvás legkönnyebb fázisát a felhasználó felébresztésére \ No newline at end of file diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 8576b444f..1788a641a 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -36,5 +36,7 @@ Debug Pengaturan Sony SWR12 Interval alarm pintar dalam beberapa menit - Untuk mengubah pengaturan, Anda harus menghubungkan ke perangkat terlebih dahulu! + Aktifkan getaran intensitas rendah pada pita + Mode hemat daya menonaktifkan pengukuran detak jantung otomatis berkala sehingga meningkatkan waktu kerja + Interval alarm pintar adalah interval sebelum alarm dipasang. Dalam interval ini perangkat mencoba mendeteksi fase tidur paling ringan untuk membangunkan pengguna \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index f6a784e94..d0918bc0d 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1046,5 +1046,7 @@ Bassa vibrazione abilitata La modalità di risparmio energetico è attiva Intervallo di allarme intelligente in pochi minuti - Per modificare le impostazioni devi prima connetterti al dispositivo! + Abilita una bassa intensità di vibrazione sulla banda + La modalità di risparmio energetico disattiva la misurazione automatica periodica della frequenza cardiaca, quindi aumenta il tempo di lavoro + L\'intervallo di allarme intelligente è l\'intervallo prima dell\'allarme installato. In questo intervallo il dispositivo sta cercando di rilevare la fase più leggera del sonno per svegliare l\'utente \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index c536cd4da..f6fd1f1cb 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -760,5 +760,7 @@ 低振動対応 省電力モードがオンになっている 分単位のスマートアラーム間隔 - 設定を変更するには、最初にデバイスに接続する必要があります! + バンドで低強度の振動を有効にする + 省電力モードでは、心拍数の定期的な自動測定がオフになり、作業時間が長くなります + スマートアラーム間隔は、インストールされたアラームの前の間隔です。 この間隔では、デバイスはユーザーを目覚めさせるために睡眠の最も軽い段階を検出しようとしています \ No newline at end of file diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index ebc6e7d9f..748231cad 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -32,5 +32,7 @@ კონფიგურაცია Sony SWR12 პარამეტრები სიგნალის ჭკვიანი ინტერვალი წუთებში - პარამეტრების შესაცვლელად ჯერ უნდა დაუკავშირდეთ მოწყობილობას! + ჩართეთ ვიბრაციის დაბალი ინტენსივობა დიაპაზონში + ენერგიის დაზოგვის რეჟიმი გამორთავს გულისცემის პერიოდულ ავტომატურ გაზომვას და ამით ზრდის სამუშაო დროს + ჭკვიანი განგაშის ინტერვალი არის დაინსტალირებული მაღვიძარის ინტერვალი. ამ ინტერვალში მოწყობილობა ცდილობს გამოავლინოს ძილის ყველაზე მსუბუქი ფაზა, რომ გაიღვიძოს მომხმარებელი \ No newline at end of file diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index fd2051d31..14e69d6b0 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -313,5 +313,7 @@ Įjungta maža vibracija Įjungtas energijos taupymo režimas Išmaniojo žadintuvo intervalas minutėmis - Norėdami pakeisti nustatymus, pirmiausia turite prisijungti prie įrenginio! + Įjungti mažą juostos vibracijos intensyvumą + Energijos taupymo režimas išjungia periodinį automatinį širdies ritmo matavimą ir taip pailgina darbo laiką + Išmaniojo aliarmo intervalas yra intervalas prieš įdiegtą signalizaciją. Šiuo intervalu prietaisas bando nustatyti lengviausią miego fazę, kad pažadintų vartotoją \ No newline at end of file diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index c0fe6d32d..8f06a037c 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -12,4 +12,7 @@ Pašreizējie soļi / min Soļu vēsture Atrast tālruni + Iespējot zemu vibrācijas intensitāti joslā + Enerģijas taupīšanas režīms izslēdz periodisku automātisku sirdsdarbības mērīšanu, tādējādi palielinot darba laiku + Viedā trauksmes intervāls ir intervāls pirms instalētās trauksmes. Šajā intervālā ierīce mēģina noteikt vieglāko miega fāzi, lai pamodinātu lietotāju \ No newline at end of file diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 1c1a27943..8666d9d78 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -80,4 +80,7 @@ ക്രമീകരണങ്ങൾ ഗാഡ്ജക്‌റ്റ് ബ്രിഡ്ജ് ഗാഡ്ജക്‌റ്റ് ബ്രിഡ്ജ് + ബാൻഡിൽ വൈബ്രേഷന്റെ കുറഞ്ഞ തീവ്രത പ്രവർത്തനക്ഷമമാക്കുക + പവർ സേവിംഗ് മോഡ് ഹൃദയമിടിപ്പിന്റെ ആനുകാലിക യാന്ത്രിക അളവ് ഓഫുചെയ്യുന്നത് പ്രവർത്തന സമയം വർദ്ധിപ്പിക്കുന്നു + ഇൻസ്റ്റാൾ ചെയ്ത അലാറത്തിന് മുമ്പുള്ള ഇടവേളയാണ് സ്മാർട്ട് അലാറം ഇടവേള. ഈ ഇടവേള ഉപകരണം ഉപയോക്താവിനെ ഉണർത്താൻ ഉറക്കത്തിന്റെ ഭാരം കുറഞ്ഞ ഘട്ടം കണ്ടെത്താൻ ശ്രമിക്കുന്നു \ No newline at end of file diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml index e106ebc8b..54a0f189a 100644 --- a/app/src/main/res/values-my/strings.xml +++ b/app/src/main/res/values-my/strings.xml @@ -38,5 +38,7 @@ မခ်ိတ္ဆက္ထားျခင္းမရွိပါ , သတိေပးခ်က္မထားရေသးပါ nodomain.freeyourgadget.gadgetbridge.ButtonPressed မိနစ်အတွင်းစမတ်နှိုးဆော်သံကြားကာလ - ချိန်ညှိချက်များကိုပြောင်းလဲရန်သင်ပထမ ဦး ဆုံးကိရိယာနှင့်ချိတ်ဆက်သင့်သည်! + တီးဝိုင်းပေါ်တုန်ခါမှု၏နိမ့်ကျသောပြင်းအားကိုခွင့်ပြုပါ + ပါဝါချွေတာသည့်အနေဖြင့်နှလုံးခုန်နှုန်းကိုအလိုအလျောက်တိုင်းတာခြင်းကိုပိတ်ထားသဖြင့်အလုပ်ချိန်တိုးလာသည် + စမတ်နှိုးစက်ကြားကာလသည်တပ်ဆင်ထားသည့်နှိုးစက်မတိုင်မီကြားကာလဖြစ်သည်။ ဤကြားကာလတွင်အသုံးပြုသူသည်အိပ်ပျော်ရန်အလျင်မြန်ဆုံးအဆင့်ကိုအသုံးပြုရန်အသုံးပြုသည် \ No newline at end of file diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 06f0bbb12..9de9ac8c0 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -1021,5 +1021,7 @@ Lav vibrasjon aktivert Strømsparingsmodus er på Smart alarmintervall på få minutter - For å endre innstillinger, bør du først koble til enheten! + Aktiver lav vibrasjonsintensitet på båndet + Strømsparingsmodus slår av periodisk automatisk måling av hjertefrekvensen og øker dermed arbeidstiden + Smart alarmintervall er intervall før installert alarm. I dette intervallet prøver enheten å oppdage den letteste søvnfasen for å våkne brukeren \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 8599ece1c..a04df55b1 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -961,5 +961,7 @@ Laag trillingsniveau ingeschakeld Energiebesparende modus is ingeschakeld Slimme alarminterval in minuten - Om instellingen te wijzigen, moet u eerst verbinding maken met het apparaat! + Schakel lage trillingsintensiteit op de band in + De energiebesparende modus schakelt de periodieke automatische hartslagmeting uit, waardoor de werktijd toeneemt + Slimme alarminterval is het interval vóór het geïnstalleerde alarm. In dit interval probeert het apparaat de lichtste slaapfase te detecteren om de gebruiker wakker te maken \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index f7f2d0fb1..68a1065c3 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1034,5 +1034,7 @@ Włączono niski poziom wibracji Tryb oszczędzania energii jest włączony Inteligentny interwał alarmu w minutach - Aby zmienić ustawienia, należy najpierw połączyć się z urządzeniem! + Włącz niską intensywność wibracji na paśmie + Tryb oszczędzania energii wyłącza okresowe automatyczne pomiary tętna, co wydłuża czas pracy + Interwał inteligentnego alarmu to interwał przed zainstalowanym alarmem. W tym interwale urządzenie próbuje wykryć najlżejszą fazę snu, aby obudzić użytkownika \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 99fcc675a..4f307c1e4 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1061,5 +1061,7 @@ Baixa vibração habilitada O modo de economia de energia está ativado Intervalo de alarme inteligente em minutos - Para alterar as configurações, você deve primeiro se conectar ao dispositivo! + Ativar baixa intensidade de vibração na banda + O modo de economia de energia desativa a medição automática periódica da frequência cardíaca, aumentando assim o tempo de trabalho + O intervalo de alarme inteligente é o intervalo antes do alarme instalado. Neste intervalo, o dispositivo está tentando detectar a fase mais leve do sono para acordar o usuário \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 42cbd1ae8..6eaf8f1f7 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1051,5 +1051,7 @@ Baixa vibração habilitada O modo de economia de energia está ativado Intervalo de alarme inteligente em minutos - Para alterar as configurações, você deve primeiro se conectar ao dispositivo! + Ativar baixa intensidade de vibração na banda + O modo de economia de energia desativa a medição automática periódica da frequência cardíaca, aumentando assim o tempo de trabalho + O intervalo de alarme inteligente é o intervalo antes do alarme instalado. Neste intervalo, o dispositivo está tentando detectar a fase mais leve do sono para acordar o usuário \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index edeba4e2c..e114f959d 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -132,5 +132,7 @@ Setari Sony SWR12 Modul de economisire a energiei este activat Interval de alarmă inteligentă în minute - Pentru a modifica setările, ar trebui să vă conectați mai întâi la dispozitiv! + Activați intensitatea redusă a vibrațiilor pe bandă + Modul de economisire a energiei dezactivează măsurarea automată periodică a ritmului cardiac, crescând astfel timpul de lucru + Intervalul de alarmă inteligentă este intervalul înainte de alarma instalată. În acest interval, dispozitivul încearcă să detecteze cea mai ușoară fază de somn pentru a trezi utilizatorul \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1637bb9f9..02024c6e2 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1069,5 +1069,7 @@ Слабая вибрация Режима энергосбережения включен Интервал умного будильника в минутах - Для изменения настроек необходимо сначала подключиться к устройству! + Включить слабую вибрации на браслете + Режим энергосбережения отключает периодическое автоматическое измерение пульса, что увеличивает время работы + Интервал умного будильника - это интервал до установленного будильника. В этом интервале устройство пытается определить самую легкую фазу сна, чтобы разбудить пользователя. \ No newline at end of file diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index aee8c2d55..f2cf84daa 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -496,5 +496,7 @@ Nízke vibrácie sú povolené Režim úspory energie je zapnutý Interval inteligentného alarmu v minútach - Ak chcete zmeniť nastavenie, mali by ste sa najskôr pripojiť k zariadeniu! + Povoľte nízku intenzitu vibrácií na náramku + Režim úspory energie vypne pravidelné automatické meranie srdcovej frekvencie, čím sa predĺži pracovná doba + Interval inteligentného alarmu je interval pred nainštalovaným alarmom. V tomto intervale sa zariadenie pokúša zistiť najľahšiu fázu spánku, aby sa používateľ zobudil \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 0df2fd4ba..6a8f73ae5 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -52,5 +52,7 @@ Låg vibration aktiverad Energisparläge är på Smart larmintervall på några minuter - För att ändra inställningar bör du först ansluta till enheten! + Aktivera låg vibrationsintensitet på armbandet + Energisparläget stänger av periodisk automatisk mätning av hjärtfrekvensen, vilket ökar arbetstiden + Smart alarmintervall är intervall före installerat larm. I detta intervall försöker enheten att upptäcka den lättaste fasen av sömn för att vakna användare \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index fcab38fe3..dd17485e9 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1092,5 +1092,7 @@ Sony SWR12 Ayarları Düşük titreşim etkin Dakikalar içinde akıllı alarm aralığı - Ayarları değiştirmek için önce cihaza bağlanmalısınız! + Bileklik üzerinde düşük titreşim yoğunluğu sağlayın + Güç tasarrufu modu, kalp atış hızının periyodik otomatik ölçümünü kapatır ve böylece çalışma süresini uzatır + Akıllı alarm aralığı, kurulu alarmdan önceki aralıktır. Bu aralıkta cihaz, kullanıcıyı uyandırmak için uykunun en hafif aşamasını tespit etmeye çalışıyor \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 42b697c2a..91c81138d 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1057,5 +1057,7 @@ Cлабка вібрація Режим енергозбереження ввімкнено Інтелектуальний інтервал тривоги в хвилинах - Щоб змінити налаштування, спочатку слід підключитися до пристрою! + Увімкніть низьку інтенсивність вібрації на браслеті + Режим енергозбереження вимикає періодичне автоматичне вимірювання частоти серцевих скорочень, таким чином збільшуючи час роботи + Інтервал інтелектуальної тривоги - це інтервал до встановленої сигналізації. У цей інтервал пристрій намагається виявити найлегшу фазу сну для пробудження користувача \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 72fe6b58b..986eeb798 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -193,5 +193,7 @@ Cài đặt Sony SWR12 Chế độ tiết kiệm pin đang bật Khoảng thời gian báo thức thông minh trong vài phút - Để thay đổi cài đặt, trước tiên bạn nên kết nối với thiết bị! + Bật cường độ rung thấp trên thiết bị đeo tay + Chế độ tiết kiệm năng lượng tắt tính năng đo nhịp tim tự động định kỳ do đó tăng thời gian làm việc + Khoảng thời gian báo động thông minh là khoảng thời gian trước khi báo động được cài đặt. Trong khoảng thời gian này, thiết bị đang cố gắng phát hiện giai đoạn ngủ nhẹ nhất để người dùng tỉnh táo \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ae2662fd1..ef7bdd9ad 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1074,5 +1074,7 @@ 低振动启用 省电模式已开启 智能警报间隔(以分钟为单位) - 要更改设置,您应该首先连接设备! + 降低频带上的振动强度 + 省电模式会关闭定期自动测量心率,从而增加工作时间 + 智能警报间隔是安装警报之前的间隔。 在此间隔内,设备尝试检测最轻的睡眠阶段以唤醒用户 \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 1afcadb36..1619bac8f 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -987,6 +987,15 @@ 1 + 0 minutes + 10 minutes + 20 minutes + 30 minutes + 40 minutes + 50 minutes + 60 minutes + + 0 10 20 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 56881c8f8..5beb07d8d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -937,9 +937,11 @@ Location must be turned on to scan for devices Sony SWR12 Settings Low vibration enabled + Enable low intensity of vibration on wristband Power saving mode on + Power saving mode turns off periodic auto measuring of heart rate thus increases working time Smart alarm interval in minutes - To change settings you should first connect to device! + Smart alarm interval is interval before of installed alarm. In this interval device is trying to detect lightest phase of sleep to awake user Distance Uphill diff --git a/app/src/main/res/xml/devicesettings_sonyswr12.xml b/app/src/main/res/xml/devicesettings_sonyswr12.xml new file mode 100644 index 000000000..e67a0ba1d --- /dev/null +++ b/app/src/main/res/xml/devicesettings_sonyswr12.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file From a45795efc7271c5ee5bdb9f8874409d69b0ac286 Mon Sep 17 00:00:00 2001 From: opavlov Date: Fri, 2 Oct 2020 23:23:40 +0300 Subject: [PATCH 62/86] fixed all strings --- app/src/main/res/values-ca/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 1 - app/src/main/res/values-el/strings.xml | 1 - app/src/main/res/values-en-rGB/strings.xml | 1 - app/src/main/res/values-es/strings.xml | 1 - app/src/main/res/values-et/strings.xml | 1 - app/src/main/res/values-fa/strings.xml | 4 ++++ app/src/main/res/values-fr-rCA/strings.xml | 1 - app/src/main/res/values-gl/strings.xml | 2 +- app/src/main/res/values-hi/strings.xml | 3 +++ app/src/main/res/values-hr/strings.xml | 2 ++ app/src/main/res/values-id/strings.xml | 2 ++ app/src/main/res/values-ja/strings.xml | 1 - app/src/main/res/values-ka/strings.xml | 2 ++ app/src/main/res/values-ko/strings.xml | 8 +++++++- app/src/main/res/values-lt/strings.xml | 1 - app/src/main/res/values-lv/strings.xml | 4 ++++ app/src/main/res/values-ml/strings.xml | 4 ++++ app/src/main/res/values-my/strings.xml | 3 +++ app/src/main/res/values-nl/strings.xml | 1 - app/src/main/res/values-pl/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values-pt/strings.xml | 1 - app/src/main/res/values-ro/strings.xml | 1 + app/src/main/res/values-sk/strings.xml | 1 - app/src/main/res/values-tr/strings.xml | 1 + app/src/main/res/values-uk/strings.xml | 1 - app/src/main/res/values-vi/strings.xml | 1 + app/src/main/res/values-zh-rTW/strings.xml | 9 +++++++-- app/src/main/res/values/strings.xml | 2 +- app/src/main/res/xml/preferences.xml | 8 ++------ 31 files changed, 46 insertions(+), 26 deletions(-) diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index e57e963a5..b898c344c 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -874,11 +874,11 @@ Repeteix la notificació de trucada Notificacions i trucades Calibració del Watch X Plus - Sony SWR12 Configuració de Sony SWR12 Vibració baixa activada Interval d\'alarma intel·ligent en minuts Activa la baixa intensitat de vibració a la banda El mode d’estalvi d’energia desactiva la mesura automàtica periòdica de la freqüència cardíaca i, per tant, augmenta el temps de treball L\'interval d\'alarma intel·ligent és l\'interval abans de l\'alarma instal·lada. En aquest interval, el dispositiu intenta detectar la fase més lleugera del son per despertar l\'usuari + Mode d\'estalvi d\'energia activat \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 2b5db0d56..cfc32aebf 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -1047,7 +1047,6 @@ Kriket Veslovací trenažér Fotbal - Sony SWR12 Sony SWR12 nastavení Nízké vibrace povoleny Režim úspory energie je zapnutý diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index baddfa5b8..1c7b0e974 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -988,7 +988,6 @@ Ελλειπτικό μηχάνημα Ποδηλασία εσωτερικού χώρου Κολύμβηση (ανοιχτό νερό) - Sony SWR12 Ρυθμίσεις Sony SWR12 Ενεργοποιήθηκε χαμηλή δόνηση Η λειτουργία εξοικονόμησης ενέργειας είναι ενεργοποιημένη diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index d7913da7a..7a8ec7db0 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -896,7 +896,6 @@ Choose the shortcuts on the band screen Shortcuts Set Alias - Sony SWR12 Sony SWR12 settings Low vibration enabled Power saving mode on diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 975894350..76cbe14d6 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -851,7 +851,6 @@ Llamadas y Notificaciones Calibracion Watch X Plus Establecer Alias - Sony SWR12 Configuración de Sony SWR12 Baja vibración habilitada El modo de ahorro de energía está activado diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 77da1866d..c1dcfc880 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -855,7 +855,6 @@ Nimi Filter Statistika - Sony SWR12 Sony SWR12 sätted Madal vibratsioon on lubatud Energiasäästurežiim on sisse lülitatud diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index b651f5f2e..3113b6652 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -128,4 +128,8 @@ لرزش کم روی باند را فعال کنید حالت صرفه جویی در مصرف انرژی اندازه گیری دوره ای ضربان قلب را خاموش می کند و در نتیجه زمان کار افزایش می یابد فاصله هشدار هوشمند فاصله قبل از زنگ هشدار نصب شده است. در این فاصله دستگاه در تلاش است تا کمترین مرحله خواب را برای کاربر بیدار تشخیص دهد + تنظیمات Sony SWR12 + لرزش کم فعال است + حالت صرفه جویی در مصرف برق روشن است + فاصله زنگ هوشمند در چند دقیقه \ No newline at end of file diff --git a/app/src/main/res/values-fr-rCA/strings.xml b/app/src/main/res/values-fr-rCA/strings.xml index f6b59a238..ef3e2da22 100644 --- a/app/src/main/res/values-fr-rCA/strings.xml +++ b/app/src/main/res/values-fr-rCA/strings.xml @@ -885,7 +885,6 @@ Notifications et appels Calibrage de Watch X Plus Système - Sony SWR12 Paramètres Sony SWR12 Faible vibration activée Le mode d\'économie d\'énergie est activé diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 2b1a6b91a..5041210be 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -515,11 +515,11 @@ Axustes Alipay nodomain.freeyourgadget.gadgetbridge.ButtonPressed - Sony SWR12 Axustes Sony SWR12 Vibración baixa habilitada Intervalo de alarma intelixente en minutos Activa a baixa intensidade de vibración na banda O modo de aforro de enerxía desactiva a medición automática periódica da frecuencia cardíaca, polo que aumenta o tempo de traballo O intervalo de alarma intelixente é o intervalo antes da alarma instalada. Neste intervalo o dispositivo está intentando detectar a fase máis lixeira do sono para espertar ao usuario + Modo de aforro de enerxía activado \ No newline at end of file diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 8096dd100..cd38f934a 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -156,4 +156,7 @@ बैंड पर कंपन की कम तीव्रता को सक्षम करें पावर सेविंग मोड हृदय गति की आवधिक ऑटो माप को बंद कर देता है जिससे काम का समय बढ़ जाता है स्थापित अलार्म से पहले स्मार्ट अलार्म अंतराल है। इस अंतराल डिवाइस में उपयोगकर्ता को जगाने के लिए नींद के सबसे हल्के चरण का पता लगाने की कोशिश की जा रही है + Sony SWR12 समायोजन + कम कंपन सक्षम + पर बिजली की बचत मोड \ No newline at end of file diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 07a36f43d..e17fd32d4 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -66,4 +66,6 @@ Omogućite niski intenzitet vibracija na pojasu Način uštede energije isključuje povremeno automatsko mjerenje brzine otkucaja srca čime se povećava radno vrijeme Interval pametnog alarma je interval prije instaliranog alarma. U ovom intervalu uređaj pokušava otkriti najlakšu fazu spavanja za budnog korisnika + Sony SWR12 postavke + Omogućena slaba vibracija \ No newline at end of file diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 1788a641a..31403b8a6 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -39,4 +39,6 @@ Aktifkan getaran intensitas rendah pada pita Mode hemat daya menonaktifkan pengukuran detak jantung otomatis berkala sehingga meningkatkan waktu kerja Interval alarm pintar adalah interval sebelum alarm dipasang. Dalam interval ini perangkat mencoba mendeteksi fase tidur paling ringan untuk membangunkan pengguna + Getaran rendah diaktifkan + Mode hemat daya aktif \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index f6fd1f1cb..90f5245de 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -755,7 +755,6 @@ インドネシア語 切断通知 距離 - Sony SWR12 Sony SWR12 設定 低振動対応 省電力モードがオンになっている diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml index 748231cad..ae5e463fc 100644 --- a/app/src/main/res/values-ka/strings.xml +++ b/app/src/main/res/values-ka/strings.xml @@ -35,4 +35,6 @@ ჩართეთ ვიბრაციის დაბალი ინტენსივობა დიაპაზონში ენერგიის დაზოგვის რეჟიმი გამორთავს გულისცემის პერიოდულ ავტომატურ გაზომვას და ამით ზრდის სამუშაო დროს ჭკვიანი განგაშის ინტერვალი არის დაინსტალირებული მაღვიძარის ინტერვალი. ამ ინტერვალში მოწყობილობა ცდილობს გამოავლინოს ძილის ყველაზე მსუბუქი ფაზა, რომ გაიღვიძოს მომხმარებელი + ჩართულია დაბალი ვიბრაცია + ენერგიის დაზოგვის რეჟიმი ჩართულია \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 4f98f62a7..35e2abd47 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -609,5 +609,11 @@ 올바르지 않은 주파수 87.5와 108.0 사이의 주파수를 입력하세요 nodomain.freeyourgadget.gadgetbridge.ButtonPressed - Sony SWR12 + Sony SWR12 설정 + 저진동 가능 + 손목 밴드에서 낮은 강도의 진동 활성화 + 절전 모드 켜기 + 절전 모드는주기적인 심박수 자동 측정을 해제하여 작업 시간을 늘립니다. + 분 단위의 스마트 알람 간격 + 스마트 알람 간격은 설치된 알람 이전의 간격입니다. 이 간격에서 장치는 사용자를 깨우기 위해 가장 가벼운 수면 단계를 감지하려고합니다. \ No newline at end of file diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 14e69d6b0..f466c9a57 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -308,7 +308,6 @@ Snūstelti Leisti didelį MTU Padidinti perkėlimo greitį, bet gali ir neveikti kai kuriuose android įrenginiuose.. - Sony SWR12 Sony SWR12 nustatymai Įjungta maža vibracija Įjungtas energijos taupymo režimas diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 8f06a037c..a0124b426 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -15,4 +15,8 @@ Iespējot zemu vibrācijas intensitāti joslā Enerģijas taupīšanas režīms izslēdz periodisku automātisku sirdsdarbības mērīšanu, tādējādi palielinot darba laiku Viedā trauksmes intervāls ir intervāls pirms instalētās trauksmes. Šajā intervālā ierīce mēģina noteikt vieglāko miega fāzi, lai pamodinātu lietotāju + Sony SWR12 iestatījumi + Iespējota zema vibrācija + Ieslēgts enerģijas taupīšanas režīms + Viedā trauksmes intervāls minūtēs \ No newline at end of file diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 8666d9d78..692c26523 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -83,4 +83,8 @@ ബാൻഡിൽ വൈബ്രേഷന്റെ കുറഞ്ഞ തീവ്രത പ്രവർത്തനക്ഷമമാക്കുക പവർ സേവിംഗ് മോഡ് ഹൃദയമിടിപ്പിന്റെ ആനുകാലിക യാന്ത്രിക അളവ് ഓഫുചെയ്യുന്നത് പ്രവർത്തന സമയം വർദ്ധിപ്പിക്കുന്നു ഇൻസ്റ്റാൾ ചെയ്ത അലാറത്തിന് മുമ്പുള്ള ഇടവേളയാണ് സ്മാർട്ട് അലാറം ഇടവേള. ഈ ഇടവേള ഉപകരണം ഉപയോക്താവിനെ ഉണർത്താൻ ഉറക്കത്തിന്റെ ഭാരം കുറഞ്ഞ ഘട്ടം കണ്ടെത്താൻ ശ്രമിക്കുന്നു + Sony SWR12 ക്രമീകരണങ്ങൾ + കുറഞ്ഞ വൈബ്രേഷൻ പ്രവർത്തനക്ഷമമാക്കി + പവർ സേവിംഗ് മോഡ് ഓണാണ് + മിനിറ്റുകൾക്കുള്ളിൽ സ്മാർട്ട് അലാറം ഇടവേള \ No newline at end of file diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml index 54a0f189a..4caf4bc91 100644 --- a/app/src/main/res/values-my/strings.xml +++ b/app/src/main/res/values-my/strings.xml @@ -41,4 +41,7 @@ တီးဝိုင်းပေါ်တုန်ခါမှု၏နိမ့်ကျသောပြင်းအားကိုခွင့်ပြုပါ ပါဝါချွေတာသည့်အနေဖြင့်နှလုံးခုန်နှုန်းကိုအလိုအလျောက်တိုင်းတာခြင်းကိုပိတ်ထားသဖြင့်အလုပ်ချိန်တိုးလာသည် စမတ်နှိုးစက်ကြားကာလသည်တပ်ဆင်ထားသည့်နှိုးစက်မတိုင်မီကြားကာလဖြစ်သည်။ ဤကြားကာလတွင်အသုံးပြုသူသည်အိပ်ပျော်ရန်အလျင်မြန်ဆုံးအဆင့်ကိုအသုံးပြုရန်အသုံးပြုသည် + "Sony SWR12 ချိန်ညှိချက်များ " + တုန်ခါမှုနိမ့်သည် + ပါဝါချွေတာခြင်းမုဒ်ကိုဖွင့်ပါ \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index a04df55b1..15ade29b0 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -956,7 +956,6 @@ Elliptische Trainer Binnenshuis fietsen Zwemmen (Open water) - Sony SWR12 Sony SWR12 instellingen Laag trillingsniveau ingeschakeld Energiebesparende modus is ingeschakeld diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 68a1065c3..562037b43 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1029,7 +1029,6 @@ Krykiet Wiosłująca maszyna Trener eliptyczny - Sony SWR12 Ustawienia Sony SWR12 Włączono niski poziom wibracji Tryb oszczędzania energii jest włączony diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 4f307c1e4..497c9b3d8 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1056,7 +1056,6 @@ hoje passado distante Links - Sony SWR12 Configurações de Sony SWR12 Baixa vibração habilitada O modo de economia de energia está ativado diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 6eaf8f1f7..715e68dcb 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1046,7 +1046,6 @@ Ritmo médio de voltas Média de braçadas Distância média das braçadas - Sony SWR12 Configurações de Sony SWR12 Baixa vibração habilitada O modo de economia de energia está ativado diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index e114f959d..a05411b86 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -135,4 +135,5 @@ Activați intensitatea redusă a vibrațiilor pe bandă Modul de economisire a energiei dezactivează măsurarea automată periodică a ritmului cardiac, crescând astfel timpul de lucru Intervalul de alarmă inteligentă este intervalul înainte de alarma instalată. În acest interval, dispozitivul încearcă să detecteze cea mai ușoară fază de somn pentru a trezi utilizatorul + Vibrație scăzută activată \ No newline at end of file diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index f2cf84daa..8cff0a970 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -491,7 +491,6 @@ Pripojiť Zap. nodomain.freeyourgadget.gadgetbridge.ButtonPressed - Sony SWR12 Nastavenia Sony SWR12 Nízke vibrácie sú povolené Režim úspory energie je zapnutý diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index dd17485e9..e7d5f2fd5 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1095,4 +1095,5 @@ Bileklik üzerinde düşük titreşim yoğunluğu sağlayın Güç tasarrufu modu, kalp atış hızının periyodik otomatik ölçümünü kapatır ve böylece çalışma süresini uzatır Akıllı alarm aralığı, kurulu alarmdan önceki aralıktır. Bu aralıkta cihaz, kullanıcıyı uyandırmak için uykunun en hafif aşamasını tespit etmeye çalışıyor + Güç tasarrufu modu açık \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 91c81138d..394ab5c4e 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1052,7 +1052,6 @@ \nПримітка: вам не потрібно встановлювати .res файл, якщо він такий самий був встановлений раніше. \n \nДІЄТЕ НА ВЛАСНИЙ РИЗИК! - Sony SWR12 Sony SWR12 налаштування Cлабка вібрація Режим енергозбереження ввімкнено diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 986eeb798..7bea7ae23 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -196,4 +196,5 @@ Bật cường độ rung thấp trên thiết bị đeo tay Chế độ tiết kiệm năng lượng tắt tính năng đo nhịp tim tự động định kỳ do đó tăng thời gian làm việc Khoảng thời gian báo động thông minh là khoảng thời gian trước khi báo động được cài đặt. Trong khoảng thời gian này, thiết bị đang cố gắng phát hiện giai đoạn ngủ nhẹ nhất để người dùng tỉnh táo + Độ rung thấp được kích hoạt \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 49edd533e..e56bc856d 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -585,6 +585,11 @@ 游泳 這是一個來自 Gadgetbridge 的測試通知 測試通知 - Sony SWR12 - Sony SWR12 設定 + Sony SWR12 设置 + 低振动启用 + 降低频带上的振动强度 + 省电模式已开启 + 省电模式会关闭定期自动测量心率,从而增加工作时间 + 智能警报间隔(以分钟为单位) + 智能警报间隔是安装警报之前的间隔。 在此间隔内,设备尝试检测最轻的睡眠阶段以唤醒用户 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5beb07d8d..1eb70b126 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -785,7 +785,7 @@ Bangle.js TLW64 PineTime (JF Firmware) - Sony SWR12 + Sony SWR12 Choose export location Gadgetbridge notifications Gadgetbridge notifications high priority diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index dfbe19375..7573f8eec 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -581,14 +581,10 @@ - - + android:title="@string/zetime_title_settings"/> Date: Thu, 8 Oct 2020 15:30:50 +0300 Subject: [PATCH 63/86] fixed copyright info --- .../sonyswr12/SonySWR12DeviceCoordinator.java | 17 +++++++++++++++++ .../sonyswr12/SonySWR12SampleProvider.java | 17 +++++++++++++++++ .../devices/sonyswr12/SonySWR12Constants.java | 16 ++++++++++++++++ .../sonyswr12/SonySWR12DeviceSupport.java | 18 ++++++++++++++++++ .../sonyswr12/SonySWR12HandlerThread.java | 16 ++++++++++++++++ .../devices/sonyswr12/SonySWR12Util.java | 16 ++++++++++++++++ .../entities/activity/ActivityBase.java | 16 ++++++++++++++++ .../entities/activity/ActivityHeartRate.java | 16 ++++++++++++++++ .../entities/activity/ActivitySleep.java | 16 ++++++++++++++++ .../entities/activity/ActivityType.java | 16 ++++++++++++++++ .../entities/activity/ActivityWithData.java | 16 ++++++++++++++++ .../sonyswr12/entities/activity/EventBase.java | 16 ++++++++++++++++ .../sonyswr12/entities/activity/EventCode.java | 16 ++++++++++++++++ .../entities/activity/EventFactory.java | 16 ++++++++++++++++ .../entities/activity/EventWithActivity.java | 16 ++++++++++++++++ .../entities/activity/EventWithValue.java | 16 ++++++++++++++++ .../entities/activity/SleepLevel.java | 16 ++++++++++++++++ .../sonyswr12/entities/alarm/AlarmRepeat.java | 16 ++++++++++++++++ .../sonyswr12/entities/alarm/AlarmState.java | 16 ++++++++++++++++ .../sonyswr12/entities/alarm/BandAlarm.java | 16 ++++++++++++++++ .../sonyswr12/entities/alarm/BandAlarms.java | 16 ++++++++++++++++ .../entities/control/CommandCode.java | 16 ++++++++++++++++ .../entities/control/ControlPoint.java | 16 ++++++++++++++++ .../control/ControlPointLowVibration.java | 16 ++++++++++++++++ .../control/ControlPointWithValue.java | 16 ++++++++++++++++ .../entities/time/BandDaylightSavingTime.java | 16 ++++++++++++++++ .../sonyswr12/entities/time/BandTime.java | 16 ++++++++++++++++ .../sonyswr12/entities/time/BandTimeZone.java | 16 ++++++++++++++++ .../sonyswr12/util/ByteArrayReader.java | 16 ++++++++++++++++ .../sonyswr12/util/ByteArrayWriter.java | 16 ++++++++++++++++ .../devices/sonyswr12/util/IntFormat.java | 16 ++++++++++++++++ .../devices/sonyswr12/util/UIntBitReader.java | 16 ++++++++++++++++ .../devices/sonyswr12/util/UIntBitWriter.java | 16 ++++++++++++++++ 33 files changed, 532 insertions(+) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java index 78d3854e8..1a2df6cfc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12DeviceCoordinator.java @@ -1,3 +1,20 @@ +/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti, José Rebelo, Matthieu Baerts, Nephiel, vanous, opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12; import android.app.Activity; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12SampleProvider.java index 1f0bacf62..c2247998d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12SampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/sonyswr12/SonySWR12SampleProvider.java @@ -1,3 +1,20 @@ +/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele + Gobbetti, opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12; import androidx.annotation.NonNull; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Constants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Constants.java index f629713f4..0c4b1d6c0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Constants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Constants.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12; import java.util.UUID; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java index 834d229e6..637373b04 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12DeviceSupport.java @@ -1,3 +1,21 @@ +/* Copyright (C) 2015-2020 Andreas Böhler, Andreas Shimokawa, Carsten + Pfeiffer, Daniel Dakhno, Daniele Gobbetti, JohnnySun, José Rebelo, + opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12; import android.bluetooth.BluetoothGatt; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12HandlerThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12HandlerThread.java index 2ce1816bd..a66024cc4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12HandlerThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12HandlerThread.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2015-2020 Andreas Shimokawa, Carsten Pfeiffer, opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12; import android.content.Context; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Util.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Util.java index 7f2497c4b..fa9066ead 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Util.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/SonySWR12Util.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12; import java.text.SimpleDateFormat; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java index a168620fe..5f8d96247 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityBase.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; public abstract class ActivityBase { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java index e722f6439..b9a36f86c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityHeartRate.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; public class ActivityHeartRate extends ActivityBase { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java index 4f8842673..f2ad773a2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivitySleep.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; public class ActivitySleep extends ActivityBase { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityType.java index e9c6d92e3..06c798f57 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityType.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; public enum ActivityType { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java index 1c1773b88..34f22c123 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/ActivityWithData.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; public class ActivityWithData extends ActivityBase { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventBase.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventBase.java index 1b1d31161..c6b21a08e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventBase.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventBase.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventCode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventCode.java index 777d0dba6..e3f25b1f1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventCode.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventCode.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; public enum EventCode { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventFactory.java index 03e09e61a..feab44396 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventFactory.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.IntFormat; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java index a1f866e6c..5a36b66a7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithActivity.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; import java.util.ArrayList; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithValue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithValue.java index cf81e5eb0..29c59fda6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithValue.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/EventWithValue.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/SleepLevel.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/SleepLevel.java index c8565467f..521d631c7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/SleepLevel.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/activity/SleepLevel.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.activity; public enum SleepLevel { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmRepeat.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmRepeat.java index 28ef12656..26ecaf1cb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmRepeat.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmRepeat.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm; import java.util.Arrays; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmState.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmState.java index 1564207f6..7d2e7c0c4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmState.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/AlarmState.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm; public enum AlarmState { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarm.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarm.java index 306ba6d94..cde517714 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarm.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarm.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarms.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarms.java index 7cd7676dc..47a14c499 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarms.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/alarm/BandAlarms.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.alarm; import java.util.List; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/CommandCode.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/CommandCode.java index fdd5979c3..1be44a199 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/CommandCode.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/CommandCode.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control; public enum CommandCode { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPoint.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPoint.java index 038a3dace..9e19ce32e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPoint.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPoint.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control; import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointLowVibration.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointLowVibration.java index 7addaf1ed..408bc4f3f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointLowVibration.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointLowVibration.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control; import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.UIntBitWriter; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointWithValue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointWithValue.java index ed4536924..0bc42878b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointWithValue.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/control/ControlPointWithValue.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.control; import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util.ByteArrayWriter; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandDaylightSavingTime.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandDaylightSavingTime.java index 39ef3605f..f1e8d1ef0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandDaylightSavingTime.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandDaylightSavingTime.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.time; public enum BandDaylightSavingTime { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTime.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTime.java index 315b48726..60da07556 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTime.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTime.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.time; import java.util.Calendar; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTimeZone.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTimeZone.java index 1040f5af4..f5dc0fbb1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTimeZone.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/entities/time/BandTimeZone.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.entities.time; public enum BandTimeZone { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayReader.java index edf1edb45..a87182a29 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayReader.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayReader.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util; public class ByteArrayReader { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayWriter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayWriter.java index 762f41136..eb7ae6ce1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayWriter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/ByteArrayWriter.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util; import java.util.Arrays; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/IntFormat.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/IntFormat.java index 284238753..8ad493497 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/IntFormat.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/IntFormat.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util; public enum IntFormat { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitReader.java index 645d24599..573b1fb2c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitReader.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitReader.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util; public class UIntBitReader { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitWriter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitWriter.java index 21ae241f4..3e1301cb3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitWriter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sonyswr12/util/UIntBitWriter.java @@ -1,3 +1,19 @@ +/* Copyright (C) 2019-2020 opavlov + + This file is part of Gadgetbridge. + + Gadgetbridge is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Gadgetbridge is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.util; public class UIntBitWriter { From f421d4869c45082bd977ac3ef621f49533d8ce38 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Sun, 11 Oct 2020 13:43:10 +0000 Subject: [PATCH 64/86] Translated using Weblate (Hebrew) Currently translated at 100.0% (991 of 991 strings) Translation: Freeyourgadget/Gadgetbridge Translate-URL: https://hosted.weblate.org/projects/freeyourgadget/gadgetbridge/he/ --- app/src/main/res/values-he/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 7e47f278e..3f7f2f3a1 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -1058,4 +1058,5 @@ הפרש תזכורת שתייה (בדקות) הצמיד ירטוט כדי להזכיר לך לשתות מים תזכורת שתייה + בסיס לחישוב רום \ No newline at end of file From 98fd8c413bee679756691d1d5ccacad5a74fd744 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Sun, 11 Oct 2020 16:38:42 +0200 Subject: [PATCH 65/86] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 115abb6c2..8c7075e08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ### Changelog #### 0.48.0 (WIP) +* Initial support for Sony SWR12 * Initial support for Lefun Smart Bands * InfiniTime: Improved music support for latest firmware * Add sport activity list tab in charts From 46a1d2908a68fd9de514df4983672ae1074c59a6 Mon Sep 17 00:00:00 2001 From: vanous Date: Sun, 11 Oct 2020 18:05:49 +0200 Subject: [PATCH 66/86] Sharing Sports Activity improvements --- .../activities/ActivitySummaryDetail.java | 150 ++++++++++++++---- .../charts/ActivityListingChartFragment.java | 9 ++ app/src/main/res/drawable/ic_map.xml | 9 ++ .../res/layout/activity_summary_details.xml | 6 - app/src/main/res/menu/activity_list_menu.xml | 25 ++- .../menu/activity_take_screenshot_menu.xml | 29 ++++ app/src/main/res/values/strings.xml | 1 + 7 files changed, 181 insertions(+), 48 deletions(-) create mode 100644 app/src/main/res/drawable/ic_map.xml create mode 100644 app/src/main/res/menu/activity_take_screenshot_menu.xml diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java index 60c6a350e..f719d52d7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummaryDetail.java @@ -19,21 +19,26 @@ package nodomain.freeyourgadget.gadgetbridge.activities; import android.annotation.SuppressLint; import android.app.AlertDialog; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Typeface; +import android.net.Uri; import android.os.Bundle; import android.text.InputType; import android.util.TypedValue; import android.view.Gravity; +import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; -import android.widget.Button; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; @@ -43,12 +48,16 @@ import android.widget.TableRow; import android.widget.TextView; import android.widget.Toast; +import androidx.core.content.FileProvider; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.text.DecimalFormat; import java.util.Date; @@ -65,28 +74,49 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryItems; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryJsonSummary; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; +import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.SwipeEvents; //import nodomain.freeyourgadget.gadgetbridge.util.OnSwipeTouchListener; public class ActivitySummaryDetail extends AbstractGBActivity { private static final Logger LOG = LoggerFactory.getLogger(ActivitySummaryDetail.class); - private GBDevice gbDevice; - - private boolean show_raw_data = false; BaseActivitySummary currentItem = null; + private GBDevice gbDevice; + private boolean show_raw_data = false; private int alternateColor; - //private Object BottomSheetBehavior; + private Menu mOptionsMenu; + + public static int getAlternateColor(Context context) { + TypedValue typedValue = new TypedValue(); + Resources.Theme theme = context.getTheme(); + theme.resolveAttribute(R.attr.alternate_row_background, typedValue, true); + return typedValue.data; + } + + public static Bitmap getScreenShot(View view, int height, int width, Context context) { + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + if (GBApplication.isDarkThemeEnabled()) { + canvas.drawColor(GBApplication.getBackgroundColor(context)); + } else { + canvas.drawColor(Color.WHITE); + } + view.draw(canvas); + return bitmap; + } @SuppressLint("ClickableViewAccessibility") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + final Context appContext = this.getApplicationContext(); if (appContext instanceof GBApplication) { setContentView(R.layout.activity_summary_details); } + Intent intent = getIntent(); Bundle bundle = intent.getExtras(); @@ -137,9 +167,9 @@ public class ActivitySummaryDetail extends AbstractGBActivity { currentItem = newItem; makeSummaryHeader(newItem); makeSummaryContent(newItem); - activitySummariesChartFragment.setDateAndGetData(gbDevice, currentItem.getStartTime().getTime()/1000, currentItem.getEndTime().getTime()/1000); + activitySummariesChartFragment.setDateAndGetData(gbDevice, currentItem.getStartTime().getTime() / 1000, currentItem.getEndTime().getTime() / 1000); layout.startAnimation(animFadeRight); - + show_hide_gpx_menu(); } else { layout.startAnimation(animBounceRight); } @@ -152,8 +182,9 @@ public class ActivitySummaryDetail extends AbstractGBActivity { currentItem = newItem; makeSummaryHeader(newItem); makeSummaryContent(newItem); - activitySummariesChartFragment.setDateAndGetData(gbDevice, currentItem.getStartTime().getTime()/1000, currentItem.getEndTime().getTime()/1000); + activitySummariesChartFragment.setDateAndGetData(gbDevice, currentItem.getStartTime().getTime() / 1000, currentItem.getEndTime().getTime() / 1000); layout.startAnimation(animFadeLeft); + show_hide_gpx_menu(); } else { layout.startAnimation(animBounceLeft); } @@ -164,7 +195,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity { if (currentItem != null) { makeSummaryHeader(currentItem); makeSummaryContent(currentItem); - activitySummariesChartFragment.setDateAndGetData(gbDevice, currentItem.getStartTime().getTime()/1000, currentItem.getEndTime().getTime()/1000); + activitySummariesChartFragment.setDateAndGetData(gbDevice, currentItem.getStartTime().getTime() / 1000, currentItem.getEndTime().getTime() / 1000); } @@ -190,7 +221,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity { String name = currentItem.getName(); input.setText((name != null) ? name : ""); FrameLayout container = new FrameLayout(ActivitySummaryDetail.this); - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); params.leftMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin); params.rightMargin = getResources().getDimensionPixelSize(R.dimen.dialog_margin); input.setLayoutParams(params); @@ -224,22 +255,6 @@ public class ActivitySummaryDetail extends AbstractGBActivity { private void makeSummaryHeader(BaseActivitySummary item) { //make view of data from main part of item - final String gpxTrack = item.getGpxTrack(); - Button show_track_btn = findViewById(R.id.showTrack); - show_track_btn.setVisibility(View.GONE); - - if (gpxTrack != null) { - show_track_btn.setVisibility(View.VISIBLE); - show_track_btn.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - try { - AndroidUtils.viewFile(gpxTrack, Intent.ACTION_VIEW, ActivitySummaryDetail.this); - } catch (IOException e) { - GB.toast(getApplicationContext(), "Unable to display GPX track: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e); - } - } - }); - } String activitykindname = ActivityKind.asString(item.getActivityKind(), getApplicationContext()); String activityname = item.getName(); Date starttime = item.getStartTime(); @@ -292,7 +307,7 @@ public class ActivitySummaryDetail extends AbstractGBActivity { TableRow label_row = new TableRow(ActivitySummaryDetail.this); TextView label_field = new TextView(ActivitySummaryDetail.this); label_field.setTextSize(16); - label_field.setPadding(0,10,0,0); + label_field.setPadding(0, 10, 0, 0); label_field.setTypeface(null, Typeface.BOLD); label_field.setText(String.format("%s", getStringResourceByName(key))); label_row.addView(label_field); @@ -361,14 +376,6 @@ public class ActivitySummaryDetail extends AbstractGBActivity { } } - - public static int getAlternateColor(Context context) { - TypedValue typedValue = new TypedValue(); - Resources.Theme theme = context.getTheme(); - theme.resolveAttribute(R.attr.alternate_row_background, typedValue, true); - return typedValue.data; - } - private String getStringResourceByName(String aString) { String packageName = getPackageName(); int resId = getResources().getIdentifier(aString, "string", packageName); @@ -387,9 +394,82 @@ public class ActivitySummaryDetail extends AbstractGBActivity { // back button finish(); return true; + case R.id.activity_action_take_screenshot: + take_share_screenshot(ActivitySummaryDetail.this); + return true; + case R.id.activity_action_share_gpx: + share_gpx_track(ActivitySummaryDetail.this); + return true; } return super.onOptionsItemSelected(item); } + private void take_share_screenshot(Context context) { + final ScrollView layout = findViewById(R.id.activity_summary_detail_scroll_layout); + int width = layout.getChildAt(0).getHeight(); + int height = layout.getChildAt(0).getWidth(); + Bitmap screenShot = getScreenShot(layout, width, height, context); + String fileName = FileUtils.makeValidFileName("Screenshot-" + ActivityKind.asString(currentItem.getActivityKind(), context).toLowerCase() + "-" + DateTimeUtils.formatIso8601(currentItem.getStartTime()) + ".png"); + try { + File targetFile = new File(FileUtils.getExternalFilesDir(), fileName); + FileOutputStream fOut = new FileOutputStream(targetFile); + screenShot.compress(Bitmap.CompressFormat.PNG, 85, fOut); + fOut.flush(); + fOut.close(); + shareScreenshot(targetFile, context); + GB.toast(getApplicationContext(), "Screenshot saved", Toast.LENGTH_LONG, GB.INFO); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void shareScreenshot(File targetFile, Context context) { + Uri contentUri = FileProvider.getUriForFile(context, + context.getApplicationContext().getPackageName() + ".screenshot_provider", targetFile); + Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); + sharingIntent.setType("image/*"); + String shareBody = "Sports Activity"; + sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Sports Activity"); + sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody); + sharingIntent.putExtra(Intent.EXTRA_STREAM, contentUri); + + try { + startActivity(Intent.createChooser(sharingIntent, "Share via")); + } catch (ActivityNotFoundException e) { + Toast.makeText(context, R.string.activity_error_no_app_for_png, Toast.LENGTH_LONG).show(); + } + } + + private void share_gpx_track(Context context) { + final String gpxTrack = currentItem.getGpxTrack(); + + if (gpxTrack != null) { + try { + AndroidUtils.viewFile(gpxTrack, Intent.ACTION_VIEW, context); + } catch (IOException e) { + GB.toast(getApplicationContext(), "Unable to display GPX track: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e); + } + } else { + GB.toast(getApplicationContext(), "No GPX track in this activity", Toast.LENGTH_LONG, GB.INFO); + } + } + + private void show_hide_gpx_menu() { + final String gpxTrack = currentItem.getGpxTrack(); + if (gpxTrack == null) { + mOptionsMenu.findItem(R.id.activity_detail_overflowMenu).getSubMenu().findItem(R.id.activity_action_share_gpx).setVisible(false); + } else { + mOptionsMenu.findItem(R.id.activity_detail_overflowMenu).getSubMenu().findItem(R.id.activity_action_share_gpx).setVisible(true); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + mOptionsMenu = menu; + getMenuInflater().inflate(R.menu.activity_take_screenshot_menu, menu); + show_hide_gpx_menu(); + return true; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java index 593fe811a..0a598dfa5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityListingChartFragment.java @@ -31,6 +31,7 @@ import com.github.mikephil.charting.charts.Chart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; @@ -61,6 +62,14 @@ public class ActivityListingChartFragment extends AbstractChartFragment { stepListAdapter = new ActivityListingAdapter(getContext()); stepsList.setAdapter(stepListAdapter); stepsDateView = rootView.findViewById(R.id.stepsDateView); + LOG.debug("PETR " + getHost()); + try { + getChartsHost().getClass().getMethod("enableSwipeRefresh").invoke(false); + } catch (Exception e) { + LOG.debug("dsda", e); + } + //device.getClass().getMethod("cancelBondProcess").invoke(device); + //refresh(); return rootView; } diff --git a/app/src/main/res/drawable/ic_map.xml b/app/src/main/res/drawable/ic_map.xml new file mode 100644 index 000000000..b17843c50 --- /dev/null +++ b/app/src/main/res/drawable/ic_map.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_summary_details.xml b/app/src/main/res/layout/activity_summary_details.xml index d2fd8a22e..005681a5d 100644 --- a/app/src/main/res/layout/activity_summary_details.xml +++ b/app/src/main/res/layout/activity_summary_details.xml @@ -172,12 +172,6 @@ -