From c02835d1dcbad0b0bc0285ccf48a79558663e0f8 Mon Sep 17 00:00:00 2001 From: pachi81 Date: Thu, 14 Mar 2024 09:38:49 +0100 Subject: [PATCH 01/37] Upgrade Gradle and Android version --- .idea/gradle.xml | 5 ++--- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 1f8f648a..d181ac75 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,10 +4,8 @@ diff --git a/build.gradle b/build.gradle index 237f9244..d26d01cc 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.1.2' apply false - id 'com.android.library' version '8.1.2' apply false + id 'com.android.application' version '8.3.0' apply false + id 'com.android.library' version '8.3.0' apply false id 'org.jetbrains.kotlin.android' version '1.8.20' apply false } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 403669e7..132b9f03 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Dec 14 15:11:00 CET 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME From eae60b016d98c64595bdcc1a25fe92af9d358450 Mon Sep 17 00:00:00 2001 From: pachi81 Date: Sun, 17 Mar 2024 19:08:06 +0100 Subject: [PATCH 02/37] Libre support for multiple patients --- build.gradle | 2 +- .../glucodatahandler/common/Constants.kt | 1 + .../common/GlucoDataService.kt | 5 ++- .../common/notifier/NotifySource.kt | 3 +- .../common/tasks/LibreViewSourceTask.kt | 45 ++++++++++++++++++- common/src/main/res/values-de/strings.xml | 2 + common/src/main/res/values-pl/strings.xml | 2 + common/src/main/res/values/strings.xml | 2 + .../preferences/SourceFragment.kt | 31 ++++++++++++- mobile/src/main/res/xml/sources.xml | 5 +++ 10 files changed, 92 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index d26d01cc..0ce23333 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } project.ext.set("versionCode", 26) -project.ext.set("versionName", "0.9.11") +project.ext.set("versionName", "0.9.11.1") project.ext.set("compileSdk", 34) project.ext.set("targetSdk", 33) project.ext.set("minSdk", 26) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt index 2fb9a266..b65d169a 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt @@ -105,6 +105,7 @@ object Constants { const val SHARED_PREF_LIBRE_TOKEN="source_libre_token" const val SHARED_PREF_LIBRE_TOKEN_EXPIRE="source_libre_token_expire" const val SHARED_PREF_LIBRE_REGION="source_libre_region" + const val SHARED_PREF_LIBRE_PATIENT_ID="source_libre_patient_id" const val SHARED_PREF_NIGHTSCOUT_ENABLED="src_ns_enabled" const val SHARED_PREF_NIGHTSCOUT_URL="src_ns_url" diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt index 523b1faf..93a4c78a 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt @@ -64,6 +64,7 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService() { fun start(source: AppSource, context: Context, cls: Class<*>, force: Boolean = false) { if (!running || force) { + Log.v(LOG_ID, "start called") try { appSource = source val serviceIntent = Intent( @@ -80,7 +81,7 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService() { // default on wear and phone true//sharedPref.getBoolean(Constants.SHARED_PREF_FOREGROUND_SERVICE, true) ) - context.startService(serviceIntent) + context.startForegroundService(serviceIntent) } catch (exc: Exception) { Log.e( LOG_ID, @@ -112,7 +113,7 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService() { @RequiresApi(Build.VERSION_CODES.Q) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { try { - Log.d(LOG_ID, "onStartCommand called") + Log.v(LOG_ID, "onStartCommand called") super.onStartCommand(intent, flags, startId) val isForeground = true // intent?.getBooleanExtra(Constants.SHARED_PREF_FOREGROUND_SERVICE, true) --> always use foreground!!! if (isForeground && !isForegroundService && Utils.checkPermission(this, android.Manifest.permission.POST_NOTIFICATIONS, Build.VERSION_CODES.TIRAMISU)) { diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt index 79f4e731..2fff5a11 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt @@ -14,5 +14,6 @@ enum class NotifySource { SOURCE_STATE_CHANGE, NOTIFIER_CHANGE, IOB_COB_CHANGE, - LOGCAT_REQUEST; + LOGCAT_REQUEST, + PATIENT_DATA_CHANGED; } \ No newline at end of file diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt index cb0987ec..7755cb63 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt @@ -3,6 +3,7 @@ package de.michelinside.glucodatahandler.common.tasks import android.content.Context import android.content.SharedPreferences import android.os.Bundle +import android.os.Handler import android.util.Log import de.michelinside.glucodatahandler.common.BuildConfig import de.michelinside.glucodatahandler.common.Constants @@ -13,6 +14,7 @@ import de.michelinside.glucodatahandler.common.SourceState import de.michelinside.glucodatahandler.common.notifier.DataSource import de.michelinside.glucodatahandler.common.notifier.InternalNotifier import de.michelinside.glucodatahandler.common.notifier.NotifySource +import org.json.JSONArray import org.json.JSONObject import java.text.DateFormat import java.text.SimpleDateFormat @@ -32,6 +34,8 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, private var token = "" private var tokenExpire = 0L private var region = "" + private var patientId = "" + val patientData = mutableMapOf() const val server = "https://api.libreview.io" const val region_server = "https://api-%s.libreview.io" const val LOGIN_ENDPOINT = "/llu/auth/login" @@ -243,7 +247,7 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, setLastError(GlucoDataService.context!!.getString(R.string.src_libre_setup_librelink)) return } - val data = array.optJSONObject(0) + val data = getPatientData(array) if(data.has("glucoseMeasurement")) { val glucoseData = data.optJSONObject("glucoseMeasurement") if (glucoseData != null) { @@ -278,6 +282,35 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, } } + private fun getPatientData(dataArray: JSONArray): JSONObject { + if(dataArray.length() != patientData.size) { + // create patientData map + patientData.clear() + for (i in 0 until dataArray.length()) { + val data = dataArray.getJSONObject(i) + if(data.has("patientId") && data.has("firstName") && data.has("lastName")) { + val id = data.getString("patientId") + val name = data.getString("firstName") + " " + data.getString("lastName") + Log.d(LOG_ID, "New patient found: $name with ID: $id") + patientData[id] = name + } + } + Handler(GlucoDataService.context!!.mainLooper).post { + InternalNotifier.notify(GlucoDataService.context!!, NotifySource.PATIENT_DATA_CHANGED, null) + } + } + if(dataArray.length() > 1 && patientId.isNotEmpty()) { + for (i in 0 until dataArray.length()) { + val data = dataArray.getJSONObject(i) + if (data.has("patientId") && data.getString("patientId") == patientId) { + return data + } + } + } + // default: use first one + return dataArray.optJSONObject(0) + } + private fun getConnection(firstCall: Boolean = true) { if (login()) { handleGlucoseResponse(httpGet(getUrl(CONNECTION_ENDPOINT), getHeader())) @@ -287,6 +320,7 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, } override fun checkPreferenceChanged(sharedPreferences: SharedPreferences, key: String?, context: Context): Boolean { + Log.v(LOG_ID, "checkPreferenceChanged called for $key") var trigger = false if (key == null) { user = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_USER, "")!!.trim() @@ -294,6 +328,7 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, token = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_TOKEN, "")!! tokenExpire = sharedPreferences.getLong(Constants.SHARED_PREF_LIBRE_TOKEN_EXPIRE, 0L) region = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_REGION, "")!! + patientId = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_PATIENT_ID, "")!! InternalNotifier.notify(GlucoDataService.context!!, NotifySource.SOURCE_STATE_CHANGE, null) trigger = true } else { @@ -317,6 +352,14 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, Constants.SHARED_PREF_LIBRE_RECONNECT -> { if (reconnect != sharedPreferences.getBoolean(Constants.SHARED_PREF_LIBRE_RECONNECT, false)) { reconnect = sharedPreferences.getBoolean(Constants.SHARED_PREF_LIBRE_RECONNECT, false) + Log.d(LOG_ID, "Reconnect triggered") + trigger = true + } + } + Constants.SHARED_PREF_LIBRE_PATIENT_ID -> { + if (patientId != sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_PATIENT_ID, "")) { + patientId = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_PATIENT_ID, "")!! + Log.d(LOG_ID, "PatientID changed to $patientId") trigger = true } } diff --git a/common/src/main/res/values-de/strings.xml b/common/src/main/res/values-de/strings.xml index 4a04ffc4..3b6e526a 100644 --- a/common/src/main/res/values-de/strings.xml +++ b/common/src/main/res/values-de/strings.xml @@ -310,5 +310,7 @@ Für alle Zeit und Intervall spezifischen Aufgaben, benötigt die App die Berechtigung für Wecker und Erinnerungen.\nDie App verändert keine vom Benutzer eingerichteten Wecker oder Erinnerung. Sie benötigt die Berechtigung nur für interne Trigger.\nNach dem Drücken von OK leitet die App Sie zur entsprechenden Berechtigungseinstellung weiter. Bitte aktivieren Sie diese Berechtigung für GlucoDataHandler. Berechtigung für Wecker und Erinnerungen Genaue Bearbeitung von Intervallen deaktiviert!!!\nKorrekte Funktionalität von GlucoDataHandler ist nicht gewährleistet!!!\nBitte hier drücken, um zu der entsprechenden Berechtigungseinstellung zu gelangen. + Patient + Wenn der Libre Accout für mehr als einen Patienten verwendet wird, bitte wählen sie den entsprechenden Patienten aus, für den die Daten empfangen werden sollen. diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index a497d0cd..ce86de94 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -312,5 +312,7 @@ W przypadku wszystkich zadań związanych z czasem i odstępami czasu aplikacja wymaga udzielenia uprawnień do ich obsługi.\nAplikacja nie dodaje ani nie zmienia żadnych alarmów i przypomnień, które ustawił użytkownik. Wymaga jedynie pozwolenia na uruchamianie wewnętrznych wyzwalaczy.\nJeśli naciśniesz OK, otwarte zostaną ustawienia uprawnień, gdzie można je nadać dla GlucoDataHandler. Zezwalaj na ustawianie alarmów i przypomnień Uprawnienie do ustawiania alarmów i przypomnień jest wyłączone!!!\nGlucoDataHandler może nie działać poprawnie!!!\nNaciśnij tutaj, aby przejść bezpośrednio do ustawień. + Patient + If there is more then one patient connected to the libre account, please select the patient receiving the data for. diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 8d1b13de..2b44fd72 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -325,4 +325,6 @@ For all time/interval related work, this app requires the permission for alarms & reminders.\nIt will not add or change any user reminders, it is only for internal scheduling.\nIf you press OK, you will forward to the permission setting to enable it for GlucoDataHandler. Alarms & reminders permission Schedule exact alarm is disabled!!!\nGlucoDataHandler may not work correct!!!\nPress here to go direct to the permission setting. + Patient + If there is more then one patient connected to the libre account, please select the patient receiving the data for. diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SourceFragment.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SourceFragment.kt index b09fe855..0f1acd7d 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SourceFragment.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SourceFragment.kt @@ -1,5 +1,6 @@ package de.michelinside.glucodatahandler.preferences +import android.content.Context import android.content.SharedPreferences import android.os.Bundle import android.text.InputType @@ -8,11 +9,13 @@ import androidx.preference.* import de.michelinside.glucodatahandler.R import de.michelinside.glucodatahandler.common.Constants import de.michelinside.glucodatahandler.common.notifier.InternalNotifier +import de.michelinside.glucodatahandler.common.notifier.NotifierInterface import de.michelinside.glucodatahandler.common.notifier.NotifySource import de.michelinside.glucodatahandler.common.tasks.DataSourceTask +import de.michelinside.glucodatahandler.common.tasks.LibreViewSourceTask -class SourceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener { +class SourceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener, NotifierInterface { private val LOG_ID = "GDH.SourceFragment" private var settingsChanged = false @@ -33,6 +36,7 @@ class SourceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPre editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD } + setupLibrePatientData() } catch (exc: Exception) { Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString()) } @@ -56,6 +60,7 @@ class SourceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPre try { preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this) updateEnableStates(preferenceManager.sharedPreferences!!) + InternalNotifier.addNotifier(requireContext(), this, mutableSetOf(NotifySource.PATIENT_DATA_CHANGED)) super.onResume() } catch (exc: Exception) { Log.e(LOG_ID, "onResume exception: " + exc.toString()) @@ -66,6 +71,7 @@ class SourceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPre Log.d(LOG_ID, "onPause called") try { preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this) + InternalNotifier.remNotifier(requireContext(), this) super.onPause() } catch (exc: Exception) { Log.e(LOG_ID, "onPause exception: " + exc.toString()) @@ -128,4 +134,27 @@ class SourceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPre Log.e(LOG_ID, "updateEnableStates exception: " + exc.toString()) } } + + private fun setupLibrePatientData() { + try { + val listPreference = findPreference(Constants.SHARED_PREF_LIBRE_PATIENT_ID) + // force "global broadcast" to be the first entry + listPreference!!.entries = LibreViewSourceTask.patientData.values.toTypedArray() + listPreference.entryValues = LibreViewSourceTask.patientData.keys.toTypedArray() + listPreference.isVisible = LibreViewSourceTask.patientData.size > 1 + } catch (exc: Exception) { + Log.e(LOG_ID, "setupLibrePatientData exception: $exc") + } + } + + override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) { + try { + Log.v(LOG_ID, "OnNotifyData called for source $dataSource") + if (dataSource == NotifySource.PATIENT_DATA_CHANGED) + setupLibrePatientData() + } catch (exc: Exception) { + Log.e(LOG_ID, "OnNotifyData exception for source $dataSource: $exc") + } + } + } \ No newline at end of file diff --git a/mobile/src/main/res/xml/sources.xml b/mobile/src/main/res/xml/sources.xml index 8f783812..c47fdb5e 100644 --- a/mobile/src/main/res/xml/sources.xml +++ b/mobile/src/main/res/xml/sources.xml @@ -74,6 +74,11 @@ android:summary="@string/src_libre_password_summary" android:inputType="textPassword" app:iconSpaceReserved="false" /> + Date: Wed, 20 Mar 2024 08:53:29 +0100 Subject: [PATCH 03/37] Reset multiple patients --- build.gradle | 2 +- .../common/GlucoDataService.kt | 2 +- .../common/tasks/LibreViewSourceTask.kt | 20 ++++++++++++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 0ce23333..8815dff6 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } project.ext.set("versionCode", 26) -project.ext.set("versionName", "0.9.11.1") +project.ext.set("versionName", "0.9.11.3") project.ext.set("compileSdk", 34) project.ext.set("targetSdk", 33) project.ext.set("minSdk", 26) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt index 93a4c78a..35ce73be 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt @@ -81,7 +81,7 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService() { // default on wear and phone true//sharedPref.getBoolean(Constants.SHARED_PREF_FOREGROUND_SERVICE, true) ) - context.startForegroundService(serviceIntent) + context.startService(serviceIntent) } catch (exc: Exception) { Log.e( LOG_ID, diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt index 7755cb63..0d8fc799 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt @@ -71,6 +71,7 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, Log.i(LOG_ID, "reset called") token = "" region = "" + patientData.clear() } private fun checkResponse(body: String?): JSONObject? { @@ -248,6 +249,10 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, return } val data = getPatientData(array) + if (data == null) { + setState(SourceState.NO_NEW_VALUE) + return + } if(data.has("glucoseMeasurement")) { val glucoseData = data.optJSONObject("glucoseMeasurement") if (glucoseData != null) { @@ -282,9 +287,10 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, } } - private fun getPatientData(dataArray: JSONArray): JSONObject { - if(dataArray.length() != patientData.size) { + private fun getPatientData(dataArray: JSONArray): JSONObject? { + if(dataArray.length() > patientData.size) { // create patientData map + val checkPatienId = patientData.isEmpty() && patientId.isEmpty() patientData.clear() for (i in 0 until dataArray.length()) { val data = dataArray.getJSONObject(i) @@ -295,17 +301,25 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, patientData[id] = name } } + if (checkPatienId && !patientData.keys.contains(patientId)) { + patientId = "" + with (GlucoDataService.sharedPref!!.edit()) { + putString(Constants.SHARED_PREF_LIBRE_PATIENT_ID, "") + apply() + } + } Handler(GlucoDataService.context!!.mainLooper).post { InternalNotifier.notify(GlucoDataService.context!!, NotifySource.PATIENT_DATA_CHANGED, null) } } - if(dataArray.length() > 1 && patientId.isNotEmpty()) { + if(patientId.isNotEmpty()) { for (i in 0 until dataArray.length()) { val data = dataArray.getJSONObject(i) if (data.has("patientId") && data.getString("patientId") == patientId) { return data } } + return null } // default: use first one return dataArray.optJSONObject(0) From d4cf6b467084da0eb85abdfbcb351f9ca493a6b8 Mon Sep 17 00:00:00 2001 From: pachi81 Date: Wed, 20 Mar 2024 13:13:31 +0100 Subject: [PATCH 04/37] Lockscreen Wallpaper --- build.gradle | 2 +- .../glucodatahandler/common/Constants.kt | 3 + .../common/utils/BitmapUtils.kt | 16 ++ common/src/main/res/values-de/strings.xml | 5 + common/src/main/res/values-pl/strings.xml | 5 + common/src/main/res/values/strings.xml | 5 + images/LockscreenWallpaper.png | Bin 0 -> 74794 bytes mobile/src/main/AndroidManifest.xml | 1 + .../GlucoDataServiceMobile.kt | 3 + .../widget/LockScreenWallpaper.kt | 158 ++++++++++++++++++ mobile/src/main/res/layout/wallpaper.xml | 121 ++++++++++++++ mobile/src/main/res/xml/preferences.xml | 21 +++ 12 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 images/LockscreenWallpaper.png create mode 100644 mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt create mode 100644 mobile/src/main/res/layout/wallpaper.xml diff --git a/build.gradle b/build.gradle index 8815dff6..21bf2934 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } project.ext.set("versionCode", 26) -project.ext.set("versionName", "0.9.11.3") +project.ext.set("versionName", "0.9.11.4") project.ext.set("compileSdk", 34) project.ext.set("targetSdk", 33) project.ext.set("minSdk", 26) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt index b65d169a..7de56e9d 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt @@ -115,6 +115,9 @@ object Constants { const val SHARED_PREF_DUMMY_VALUES = "dummy_values" + const val SHARED_PREF_LOCKSCREEN_WP_ENABLED = "lockscreen_enabled" + const val SHARED_PREF_LOCKSCREEN_WP_Y_POS = "lockscreen_y_pos" + // Android Auto const val AA_MEDIA_ICON_STYLE_TREND = "trend" const val AA_MEDIA_ICON_STYLE_GLUCOSE_TREND = "glucose_trend" diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/utils/BitmapUtils.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/utils/BitmapUtils.kt index d8f0207d..1e38fc60 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/utils/BitmapUtils.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/utils/BitmapUtils.kt @@ -1,5 +1,6 @@ package de.michelinside.glucodatahandler.common.utils +import android.content.res.Resources import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color @@ -15,9 +16,24 @@ import de.michelinside.glucodatahandler.common.GlucoDataService import de.michelinside.glucodatahandler.common.ReceiveData import kotlin.math.abs + object BitmapUtils { private val LOG_ID = "GDH.Utils.Bitmap" + + fun getScreenWidth(): Int { + return Resources.getSystem().displayMetrics.widthPixels + } + + fun getScreenHeight(): Int { + return Resources.getSystem().displayMetrics.heightPixels + } + + fun getScreenDpi(): Int { + return Resources.getSystem().displayMetrics.densityDpi + } + + private fun isShortText(text: String): Boolean = text.length <= (if (text.contains(".")) 3 else 2) private fun calcMaxTextSizeForBitmap(bitmap: Bitmap, text: String, roundTarget: Boolean, maxTextSize: Float, top: Boolean, bold: Boolean): Float { diff --git a/common/src/main/res/values-de/strings.xml b/common/src/main/res/values-de/strings.xml index 3b6e526a..bebaa9dc 100644 --- a/common/src/main/res/values-de/strings.xml +++ b/common/src/main/res/values-de/strings.xml @@ -312,5 +312,10 @@ Genaue Bearbeitung von Intervallen deaktiviert!!!\nKorrekte Funktionalität von GlucoDataHandler ist nicht gewährleistet!!!\nBitte hier drücken, um zu der entsprechenden Berechtigungseinstellung zu gelangen. Patient Wenn der Libre Accout für mehr als einen Patienten verwendet wird, bitte wählen sie den entsprechenden Patienten aus, für den die Daten empfangen werden sollen. + Lockscreen Hintergrund + Aktivieren + Wenn sie den Hintergrund für den Lockscreen aktivieren, erstellt die App einen Hintergrund auf dem Lockscreen, welche den Glucosewert und den Trendpfeil enthält. + Vertikale Position + Vertikale position auf dem Lockscreen:\n0: am oberen Rand des Bildschirms\n100: am unteren Rand des Bildschirms diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index ce86de94..fa6b68ae 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -314,5 +314,10 @@ Uprawnienie do ustawiania alarmów i przypomnień jest wyłączone!!!\nGlucoDataHandler może nie działać poprawnie!!!\nNaciśnij tutaj, aby przejść bezpośrednio do ustawień. Patient If there is more then one patient connected to the libre account, please select the patient receiving the data for. + Lockscreen Wallpaper + Enable + If you enable lockscreen wallpaper, the app replaces the wallpaper on lockscreen with an Image containing the value and trend. + Vertical position + Vertical position of the glucose-trend image on lockscreen:\n0 is the top of the display\n100 is the botton of the display diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 2b44fd72..e7231e66 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -327,4 +327,9 @@ Schedule exact alarm is disabled!!!\nGlucoDataHandler may not work correct!!!\nPress here to go direct to the permission setting. Patient If there is more then one patient connected to the libre account, please select the patient receiving the data for. + Lockscreen Wallpaper + Enable + If you enable lockscreen wallpaper, the app replaces the wallpaper on lockscreen with an Image containing the value and trend. + Vertical position + Vertical position of the glucose-trend image on lockscreen:\n0 is the top of the display\n100 is the botton of the display diff --git a/images/LockscreenWallpaper.png b/images/LockscreenWallpaper.png new file mode 100644 index 0000000000000000000000000000000000000000..d7bba8b70d359d35bf2d18b3f3f5de9b321edd3b GIT binary patch literal 74794 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 literal 0 HcmV?d00001 diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 4b237cef..162b3105 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt index 46bd666b..f96f8c14 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt @@ -15,6 +15,7 @@ import de.michelinside.glucodatahandler.tasker.setWearConnectionState import de.michelinside.glucodatahandler.watch.WatchDrip import de.michelinside.glucodatahandler.widget.FloatingWidget import de.michelinside.glucodatahandler.widget.GlucoseBaseWidget +import de.michelinside.glucodatahandler.widget.LockScreenWallpaper class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInterface { @@ -57,6 +58,7 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt GlucoseBaseWidget.updateWidgets(applicationContext) WatchDrip.init(applicationContext) floatingWidget.create() + LockScreenWallpaper.create(this) } catch (exc: Exception) { Log.e(LOG_ID, "onCreate exception: " + exc.message.toString() ) } @@ -76,6 +78,7 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt CarModeReceiver.cleanup(applicationContext) WatchDrip.close(applicationContext) floatingWidget.destroy() + LockScreenWallpaper.destroy(this) super.onDestroy() } catch (exc: Exception) { Log.e(LOG_ID, "onDestroy exception: " + exc.message.toString() ) diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt new file mode 100644 index 00000000..e3b35b64 --- /dev/null +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt @@ -0,0 +1,158 @@ +package de.michelinside.glucodatahandler.widget + +import android.app.WallpaperManager +import android.content.Context +import android.content.SharedPreferences +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.drawable.BitmapDrawable +import android.os.Bundle +import android.util.Log +import de.michelinside.glucodatahandler.common.Constants +import de.michelinside.glucodatahandler.common.GlucoDataService +import de.michelinside.glucodatahandler.common.notifier.InternalNotifier +import de.michelinside.glucodatahandler.common.notifier.NotifierInterface +import de.michelinside.glucodatahandler.common.notifier.NotifySource +import de.michelinside.glucodatahandler.common.utils.BitmapUtils +import kotlin.math.max + + +object LockScreenWallpaper : NotifierInterface, SharedPreferences.OnSharedPreferenceChangeListener { + private val LOG_ID = "GDH.LockScreenWallpaper" + private var enabled = false + private var yPos = 75 + + + fun create(context: Context) { + try { + Log.d(LOG_ID, "create called") + val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) + sharedPref.registerOnSharedPreferenceChangeListener(this) + onSharedPreferenceChanged(sharedPref, null) + } catch (exc: Exception) { + Log.e(LOG_ID, "create exception: " + exc.message.toString() ) + } + } + + fun destroy(context: Context) { + try { + Log.d(LOG_ID, "destroy called") + val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE) + sharedPref.unregisterOnSharedPreferenceChangeListener(this) + disable(context) + } catch (exc: Exception) { + Log.e(LOG_ID, "destroy exception: " + exc.message.toString() ) + } + } + + private fun enable(context: Context) { + if (!enabled) { + Log.d(LOG_ID, "enable called") + enabled = true + val filter = mutableSetOf( + NotifySource.BROADCAST, + NotifySource.MESSAGECLIENT, + NotifySource.OBSOLETE_VALUE, + NotifySource.SETTINGS + ) + InternalNotifier.addNotifier(context, this, filter) + updateLockScreen(context) + } + } + + private fun disable(context: Context) { + if (enabled) { + Log.d(LOG_ID, "disable called") + enabled = false + InternalNotifier.remNotifier(context, this) + setWallpaper(null, context) + } + } + + fun updateLockScreen(context: Context) { + try { + Log.v(LOG_ID, "updateLockScreen called - enabled=$enabled") + if (enabled) { + setWallpaper(getBitmapForWallpaper(context), context) + } + } catch (exc: Exception) { + Log.e(LOG_ID, "updateLockScreen exception: " + exc.message.toString() ) + } + } + + private fun setWallpaper(bitmap: Bitmap?, context: Context) { + try { + Log.v(LOG_ID, "updateLockScreen called for bitmap $bitmap") + val wallpaperManager = WallpaperManager.getInstance(context) + if (bitmap != null) { + val wallpaper = createWallpaper(bitmap, context) + wallpaperManager.setBitmap(wallpaper, null, false, WallpaperManager.FLAG_LOCK) + wallpaper?.recycle() + } else { + // remove wallpaper + wallpaperManager.clear() + } + } catch (exc: Exception) { + Log.e(LOG_ID, "updateLockScreen exception: " + exc.message.toString() ) + } + + } + + private fun createWallpaper(bitmap: Bitmap, context: Context): Bitmap? { + try { + Log.v(LOG_ID, "creatWallpaper called") + val screenWidth = BitmapUtils.getScreenWidth() + val screenHeigth = BitmapUtils.getScreenHeight() + val screenDPI = BitmapUtils.getScreenDpi().toFloat() + val wallpaper = Bitmap.createBitmap(screenWidth, screenHeigth, Bitmap.Config.ARGB_8888) + val canvas = Canvas(wallpaper) + val drawable = BitmapDrawable(context.resources, bitmap) + drawable.setBounds(0, 0, screenWidth, screenHeigth) + val xOffset = ((screenWidth-bitmap.width)/2F) //*1.2F-(screenDPI*0.3F) + val yOffset = max(0F, ((screenHeigth-bitmap.height)*yPos/100F)) //-(screenDPI*0.3F)) + Log.d(LOG_ID, "Create wallpaper at x=$xOffset/$screenWidth and y=$yOffset/$screenHeigth DPI=$screenDPI") + canvas.drawBitmap(bitmap, xOffset, yOffset, Paint(Paint.ANTI_ALIAS_FLAG)) + bitmap.recycle() + return wallpaper + } catch (exc: Exception) { + Log.e(LOG_ID, "updateLockScreen exception: " + exc.message.toString() ) + } + return null + } + + private fun getBitmapForWallpaper(context: Context): Bitmap? { + return BitmapUtils.getGlucoseTrendBitmap(width = 400, height = 400) + } + + override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) { + try { + Log.v(LOG_ID, "OnNotifyData called for source $dataSource") + updateLockScreen(context) + } catch (exc: Exception) { + Log.e(LOG_ID, "OnNotifyData exception: " + exc.message.toString() ) + } + } + + override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { + try { + Log.v(LOG_ID, "onSharedPreferenceChanged called for key $key") + var changed = false + if (yPos != sharedPreferences.getInt(Constants.SHARED_PREF_LOCKSCREEN_WP_Y_POS, 75)) { + yPos = sharedPreferences.getInt(Constants.SHARED_PREF_LOCKSCREEN_WP_Y_POS, 75) + Log.d(LOG_ID, "New Y pos: $yPos") + changed = true + } + if (enabled != sharedPreferences.getBoolean(Constants.SHARED_PREF_LOCKSCREEN_WP_ENABLED, false)) { + if (sharedPreferences.getBoolean(Constants.SHARED_PREF_LOCKSCREEN_WP_ENABLED, false)) + enable(GlucoDataService.context!!) + else + disable(GlucoDataService.context!!) + } else if (changed) { + updateLockScreen(GlucoDataService.context!!) + } + } catch (exc: Exception) { + Log.e(LOG_ID, "onSharedPreferenceChanged exception: " + exc.message.toString() ) + } + } +} \ No newline at end of file diff --git a/mobile/src/main/res/layout/wallpaper.xml b/mobile/src/main/res/layout/wallpaper.xml new file mode 100644 index 00000000..18606d36 --- /dev/null +++ b/mobile/src/main/res/layout/wallpaper.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mobile/src/main/res/xml/preferences.xml b/mobile/src/main/res/xml/preferences.xml index 606ec5c0..e9399f98 100644 --- a/mobile/src/main/res/xml/preferences.xml +++ b/mobile/src/main/res/xml/preferences.xml @@ -191,6 +191,27 @@ android:title="@string/pref_notification_big_icon" app:iconSpaceReserved="false" /> + + + + Date: Wed, 20 Mar 2024 19:26:09 +0100 Subject: [PATCH 05/37] Refactor startForegroundService --- build.gradle | 2 +- .../glucodatahandler/common/GlucoDataService.kt | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 21bf2934..8659b2fb 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } project.ext.set("versionCode", 26) -project.ext.set("versionName", "0.9.11.4") +project.ext.set("versionName", "0.9.11.5") project.ext.set("compileSdk", 34) project.ext.set("targetSdk", 33) project.ext.set("minSdk", 26) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt index 35ce73be..06abb270 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt @@ -63,9 +63,10 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService() { } fun start(source: AppSource, context: Context, cls: Class<*>, force: Boolean = false) { - if (!running || force) { - Log.v(LOG_ID, "start called") + if (!running) { + Log.v(LOG_ID, "start called (running: $running - foreground: $foreground)") try { + isRunning = true appSource = source val serviceIntent = Intent( context, @@ -81,12 +82,16 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService() { // default on wear and phone true//sharedPref.getBoolean(Constants.SHARED_PREF_FOREGROUND_SERVICE, true) ) - context.startService(serviceIntent) + if (foreground) + context.startService(serviceIntent) + else + context.startForegroundService(serviceIntent) } catch (exc: Exception) { Log.e( LOG_ID, "start exception: " + exc.message.toString() ) + isRunning = false } } } @@ -200,6 +205,7 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService() { super.onDestroy() service = null isRunning = false + isForegroundService = false } catch (exc: Exception) { Log.e(LOG_ID, "onDestroy exception: " + exc.toString()) } From f323aa64f9b475fbf7bc83579602ab6fdc0d8d61 Mon Sep 17 00:00:00 2001 From: pachi81 Date: Thu, 4 Apr 2024 10:11:35 +0200 Subject: [PATCH 06/37] Optimize IOB/COB change notification --- auto/build.gradle | 2 ++ .../glucodataauto/MainActivity.kt | 1 + build.gradle | 2 +- common/build.gradle | 2 ++ common/proguard-dev-rules.pro | 32 +++++++++++++++++++ .../glucodatahandler/common/ReceiveData.kt | 19 ++++++++--- .../common/WearPhoneConnection.kt | 3 +- .../common/notifier/InternalNotifier.kt | 4 +-- .../common/notifier/NotifySource.kt | 1 + .../glucodatahandler/common/utils/Utils.kt | 3 +- mobile/build.gradle | 2 ++ mobile/proguard-dev-rules.pro | 32 +++++++++++++++++++ wear/build.gradle | 2 ++ wear/proguard-dev-rules.pro | 32 +++++++++++++++++++ .../glucodatahandler/WaerActivity.kt | 1 + 15 files changed, 127 insertions(+), 11 deletions(-) create mode 100644 common/proguard-dev-rules.pro create mode 100644 mobile/proguard-dev-rules.pro create mode 100644 wear/proguard-dev-rules.pro diff --git a/auto/build.gradle b/auto/build.gradle index c811b7d6..884fcb75 100644 --- a/auto/build.gradle +++ b/auto/build.gradle @@ -25,6 +25,8 @@ android { resValue "string", "app_name", "GlucoDataAuto" } dev_release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-dev-rules.pro' versionNameSuffix '-dev' resValue "string", "app_name", "GlucoDataAuto" } diff --git a/auto/src/main/java/de/michelinside/glucodataauto/MainActivity.kt b/auto/src/main/java/de/michelinside/glucodataauto/MainActivity.kt index f7cfb66f..3a41f489 100644 --- a/auto/src/main/java/de/michelinside/glucodataauto/MainActivity.kt +++ b/auto/src/main/java/de/michelinside/glucodataauto/MainActivity.kt @@ -110,6 +110,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface { InternalNotifier.addNotifier( this, this, mutableSetOf( NotifySource.BROADCAST, NotifySource.IOB_COB_CHANGE, + NotifySource.IOB_COB_TIME, NotifySource.MESSAGECLIENT, NotifySource.CAPILITY_INFO, NotifySource.NODE_BATTERY_LEVEL, diff --git a/build.gradle b/build.gradle index 8659b2fb..3e5067be 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } project.ext.set("versionCode", 26) -project.ext.set("versionName", "0.9.11.5") +project.ext.set("versionName", "0.9.11.12") project.ext.set("compileSdk", 34) project.ext.set("targetSdk", 33) project.ext.set("minSdk", 26) diff --git a/common/build.gradle b/common/build.gradle index e606c365..a665af1c 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -25,6 +25,8 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } dev_release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-dev-rules.pro' } } compileOptions { diff --git a/common/proguard-dev-rules.pro b/common/proguard-dev-rules.pro new file mode 100644 index 00000000..9f50960e --- /dev/null +++ b/common/proguard-dev-rules.pro @@ -0,0 +1,32 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile +-dontwarn ** +-keep class ** +-keepclassmembers class *{*;} +-keepattributes * + +# -------------------------------------------------------------------- +# REMOVE all debug log messages +# -------------------------------------------------------------------- +-assumenosideeffects class android.util.Log { + public static *** v(...); +} \ No newline at end of file diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/ReceiveData.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/ReceiveData.kt index 322a0a95..9a0d21a3 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/ReceiveData.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/ReceiveData.kt @@ -475,14 +475,23 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { System.currentTimeMillis() if(!isIobCob() || (newTime > iobCobTime && (newTime-iobCobTime) > 30000)) { - Log.i(LOG_ID, "Only IOB/COB changed: " + extras.getFloat(IOB, Float.NaN) + "/" + extras.getFloat(COB, Float.NaN)) - iob = extras.getFloat(IOB, Float.NaN) - cob = extras.getFloat(COB, Float.NaN) + var iobCobChange = false + if(iob != extras.getFloat(IOB, Float.NaN) || cob != extras.getFloat(COB, Float.NaN)) { + Log.i(LOG_ID, "Only IOB/COB changed: " + extras.getFloat(IOB, Float.NaN) + "/" + extras.getFloat(COB, Float.NaN)) + iob = extras.getFloat(IOB, Float.NaN) + cob = extras.getFloat(COB, Float.NaN) + iobCobChange = true + } else { + Log.d(LOG_ID, "Only IOB/COB time changed") + } iobCobTime = newTime // do not forward extras as interApp to prevent sending back to source... val bundle: Bundle? = if(interApp) null else createExtras() // re-create extras to have all changed value inside for sending to receiver - InternalNotifier.notify(context, NotifySource.IOB_COB_CHANGE, bundle) + if(iobCobChange) + InternalNotifier.notify(context, NotifySource.IOB_COB_CHANGE, bundle) + else + InternalNotifier.notify(context, NotifySource.IOB_COB_TIME, bundle) saveExtras(context) } } @@ -622,7 +631,7 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener { private fun saveExtras(context: Context) { try { - Log.d(LOG_ID, "Saving extras") + Log.v(LOG_ID, "Saving extras") // use own tag to prevent trigger onChange event at every time! val sharedGlucosePref = context.getSharedPreferences(Constants.GLUCODATA_BROADCAST_ACTION, Context.MODE_PRIVATE) with(sharedGlucosePref.edit()) { diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/WearPhoneConnection.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/WearPhoneConnection.kt index 45b4a7ce..bb3e42bc 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/WearPhoneConnection.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/WearPhoneConnection.kt @@ -73,6 +73,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC val filter = mutableSetOf( NotifySource.BROADCAST, NotifySource.IOB_COB_CHANGE, + NotifySource.IOB_COB_TIME, NotifySource.BATTERY_LEVEL) // to trigger re-start for the case of stopped by the system if (sendSettings) { filter.add(NotifySource.SETTINGS) // only send setting changes from phone to wear! @@ -391,7 +392,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) { try { - if (dataSource != NotifySource.IOB_COB_CHANGE || extras != null) { // do not send IOB change without extras + if ((dataSource != NotifySource.IOB_COB_CHANGE && dataSource != NotifySource.IOB_COB_TIME) || extras != null) { // do not send IOB change without extras Log.d(LOG_ID, "OnNotifyData for source " + dataSource.toString() + " and extras " + extras.toString()) sendMessage(dataSource, extras) } diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt index 7fec0c65..72cef671 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt @@ -28,11 +28,11 @@ object InternalNotifier { fun notify(context: Context, notifySource: NotifySource, extras: Bundle?) { - Log.d(LOG_ID, "Sending new data to " + notifiers.size.toString() + " notifier(s).") + Log.d(LOG_ID, "Sending new data from " + notifySource.toString() + " to " + getNotifierCount(notifySource) + " notifier(s).") notifiers.forEach{ try { if (it.value == null || it.value!!.contains(notifySource)) { - Log.d(LOG_ID, "Sending new data from " + notifySource.toString() + " to " + it.toString()) + Log.v(LOG_ID, "Sending new data from " + notifySource.toString() + " to " + it.toString()) it.key.OnNotifyData(context, notifySource, extras) } } catch (exc: Exception) { diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt index 2fff5a11..5d381e6c 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt @@ -14,6 +14,7 @@ enum class NotifySource { SOURCE_STATE_CHANGE, NOTIFIER_CHANGE, IOB_COB_CHANGE, + IOB_COB_TIME, LOGCAT_REQUEST, PATIENT_DATA_CHANGED; } \ No newline at end of file diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/utils/Utils.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/utils/Utils.kt index 4a1eafb0..c7784606 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/utils/Utils.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/utils/Utils.kt @@ -5,7 +5,6 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.graphics.* import android.net.Uri import android.os.Build import android.os.Bundle @@ -200,7 +199,7 @@ object Utils { fun saveLogs(outputStream: OutputStream) { try { - val cmd = "logcat -t 3000" + val cmd = "logcat -t 4000" Log.i(LOG_ID, "Getting logcat with command: $cmd") val process = Runtime.getRuntime().exec(cmd) val thread = Thread { diff --git a/mobile/build.gradle b/mobile/build.gradle index 1c480f8a..395685a7 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -25,6 +25,8 @@ android { resValue "string", "app_name", "GlucoDataHandler" } dev_release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-dev-rules.pro' versionNameSuffix '-dev' resValue "string", "app_name", "GlucoDataHandler" } diff --git a/mobile/proguard-dev-rules.pro b/mobile/proguard-dev-rules.pro new file mode 100644 index 00000000..9f50960e --- /dev/null +++ b/mobile/proguard-dev-rules.pro @@ -0,0 +1,32 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile +-dontwarn ** +-keep class ** +-keepclassmembers class *{*;} +-keepattributes * + +# -------------------------------------------------------------------- +# REMOVE all debug log messages +# -------------------------------------------------------------------- +-assumenosideeffects class android.util.Log { + public static *** v(...); +} \ No newline at end of file diff --git a/wear/build.gradle b/wear/build.gradle index 60da876b..c4c92ca2 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -23,6 +23,8 @@ android { resValue "string", "app_name", "GlucoDataHandler" } dev_release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-dev-rules.pro' versionNameSuffix '-dev' resValue "string", "app_name", "GlucoDataHandler" } diff --git a/wear/proguard-dev-rules.pro b/wear/proguard-dev-rules.pro new file mode 100644 index 00000000..9f50960e --- /dev/null +++ b/wear/proguard-dev-rules.pro @@ -0,0 +1,32 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile +-dontwarn ** +-keep class ** +-keepclassmembers class *{*;} +-keepattributes * + +# -------------------------------------------------------------------- +# REMOVE all debug log messages +# -------------------------------------------------------------------- +-assumenosideeffects class android.util.Log { + public static *** v(...); +} \ No newline at end of file diff --git a/wear/src/main/java/de/michelinside/glucodatahandler/WaerActivity.kt b/wear/src/main/java/de/michelinside/glucodatahandler/WaerActivity.kt index 6eff97af..fe22f6a5 100644 --- a/wear/src/main/java/de/michelinside/glucodatahandler/WaerActivity.kt +++ b/wear/src/main/java/de/michelinside/glucodatahandler/WaerActivity.kt @@ -207,6 +207,7 @@ class WaerActivity : AppCompatActivity(), NotifierInterface { InternalNotifier.addNotifier(this, this, mutableSetOf( NotifySource.BROADCAST, NotifySource.IOB_COB_CHANGE, + NotifySource.IOB_COB_TIME, NotifySource.MESSAGECLIENT, NotifySource.CAPILITY_INFO, NotifySource.NODE_BATTERY_LEVEL, From f7f56f95656401e55772e801a05df733f6699f4e Mon Sep 17 00:00:00 2001 From: pachi81 Date: Thu, 4 Apr 2024 22:16:33 +0200 Subject: [PATCH 07/37] Refactor time notifier --- .../common/notifier/InternalNotifier.kt | 45 ++++++++++++++++++- .../common/notifier/NotifySource.kt | 2 +- .../common/tasks/ElapsedTimeTask.kt | 7 ++- .../common/tasks/ObsoleteTask.kt | 2 +- .../common/tasks/TimeTaskService.kt | 4 +- 5 files changed, 52 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt index 72cef671..9e0e0ef6 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt @@ -7,19 +7,31 @@ import android.util.Log object InternalNotifier { private const val LOG_ID = "GDH.InternalNotifier" private var notifiers = mutableMapOf?>() + private var timeNotifierCount = 0 + private var obsoleteNotifierCount = 0 + val hasTimeNotifier: Boolean get() = timeNotifierCount>0 + val hasObsoleteNotifier: Boolean get() = obsoleteNotifierCount>0 + fun addNotifier(context: Context, notifier: NotifierInterface, sourceFilter: MutableSet) { + val timeChanged = hasSource(notifier, NotifySource.TIME_VALUE) != sourceFilter.contains(NotifySource.TIME_VALUE) || + hasSource(notifier, NotifySource.OBSOLETE_VALUE) != sourceFilter.contains(NotifySource.OBSOLETE_VALUE) Log.i(LOG_ID, "add notifier " + notifier.toString() + " - filter: " + sourceFilter.toString() ) notifiers[notifier] = sourceFilter Log.d(LOG_ID, "notifier size: " + notifiers.size.toString() ) - notify(context, NotifySource.NOTIFIER_CHANGE, null) + if(timeChanged) + checkTimeNotifierChanged(context) + } fun remNotifier(context: Context, notifier: NotifierInterface) { Log.i(LOG_ID, "rem notifier " + notifier.toString() ) + val timeChanged = hasSource(notifier, NotifySource.TIME_VALUE) || + hasSource(notifier, NotifySource.OBSOLETE_VALUE) notifiers.remove(notifier) Log.d(LOG_ID, "notifier size: " + notifiers.size.toString() ) - notify(context, NotifySource.NOTIFIER_CHANGE, null) + if(timeChanged) + checkTimeNotifierChanged(context) } fun hasNotifier(notifier: NotifierInterface): Boolean { @@ -50,4 +62,33 @@ object InternalNotifier { } return count } + + private fun checkTimeNotifierChanged(context: Context) { + var trigger = false + val newTimeCount = getNotifierCount(NotifySource.TIME_VALUE) + if(timeNotifierCount != newTimeCount) { + Log.d(LOG_ID, "Time notifier have changed from $timeNotifierCount to $newTimeCount") + val curCount = timeNotifierCount + timeNotifierCount = newTimeCount + if(curCount == 0 || newTimeCount == 0) + trigger = true + } + val newObsoleteCount = getNotifierCount(NotifySource.TIME_VALUE) + if(obsoleteNotifierCount != newObsoleteCount) { + Log.d(LOG_ID, "Obsolete notifier have changed from $obsoleteNotifierCount to $newObsoleteCount") + val curCount = obsoleteNotifierCount + obsoleteNotifierCount = newObsoleteCount + if(curCount == 0 || newObsoleteCount == 0) + trigger = true + } + if(trigger) + notify(context, NotifySource.TIME_NOTIFIER_CHANGE, null) + } + + private fun hasSource(notifier: NotifierInterface, notifySource: NotifySource): Boolean { + if(hasNotifier(notifier)) { + return notifiers[notifier]!!.contains(notifySource) + } + return false + } } \ No newline at end of file diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt index 5d381e6c..04867907 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/NotifySource.kt @@ -12,7 +12,7 @@ enum class NotifySource { TIME_VALUE, SOURCE_SETTINGS, SOURCE_STATE_CHANGE, - NOTIFIER_CHANGE, + TIME_NOTIFIER_CHANGE, IOB_COB_CHANGE, IOB_COB_TIME, LOGCAT_REQUEST, diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt index 11fbd9a2..6a360fb6 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt @@ -22,7 +22,10 @@ class ElapsedTimeTask : BackgroundTask() { } override fun getIntervalMinute(): Long { - return if (relativeTimeValue) 1L else interval + return if (relativeTimeValue) 1L + else if(interval > 0) interval + else if(InternalNotifier.hasTimeNotifier) 1L + else 0 } override fun execute(context: Context) { @@ -33,7 +36,7 @@ class ElapsedTimeTask : BackgroundTask() { } override fun active(elapsetTimeMinute: Long): Boolean { - return (relativeTimeValue || interval > 0) && elapsetTimeMinute <= 60 && InternalNotifier.getNotifierCount(NotifySource.TIME_VALUE) > 0 + return (relativeTimeValue || interval > 0 || InternalNotifier.hasTimeNotifier) && elapsetTimeMinute <= 60 } override fun checkPreferenceChanged(sharedPreferences: SharedPreferences, key: String?, context: Context): Boolean { diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ObsoleteTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ObsoleteTask.kt index 132fa27b..941e9f7f 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ObsoleteTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ObsoleteTask.kt @@ -25,6 +25,6 @@ class ObsoleteTask : BackgroundTask() { } override fun active(elapsetTimeMinute: Long): Boolean { - return elapsetTimeMinute <= 10 && (InternalNotifier.getNotifierCount(NotifySource.OBSOLETE_VALUE) > 0 || (!ElapsedTimeTask.relativeTime && InternalNotifier.getNotifierCount(NotifySource.TIME_VALUE) > 0) ) + return elapsetTimeMinute <= 10 && (InternalNotifier.hasObsoleteNotifier || (!ElapsedTimeTask.relativeTime && InternalNotifier.hasTimeNotifier) ) } } \ No newline at end of file diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/TimeTaskService.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/TimeTaskService.kt index 4068264d..13cc6cfd 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/TimeTaskService.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/TimeTaskService.kt @@ -11,8 +11,8 @@ import de.michelinside.glucodatahandler.common.notifier.NotifySource object TimeTaskService: BackgroundTaskService(42, "GDH.Task.Time.TaskService") { override fun getAlarmReceiver() : Class<*> = TimeAlarmReceiver::class.java - // also notify for NOTIFIER_CHANGE as if there is no receiver, the alarm manager is not needed - override fun getNotifySourceFilter() : MutableSet = mutableSetOf(NotifySource.NOTIFIER_CHANGE) + // also notify for TIME_NOTIFIER_CHANGE as if there is no receiver, the alarm manager is not needed + override fun getNotifySourceFilter() : MutableSet = mutableSetOf(NotifySource.TIME_NOTIFIER_CHANGE) override fun getBackgroundTasks(): MutableList = mutableListOf(ElapsedTimeTask(), ObsoleteTask()) From 80d34150f0e873e850063b94a9e4415edbbf93e7 Mon Sep 17 00:00:00 2001 From: pachi81 Date: Fri, 5 Apr 2024 14:10:47 +0200 Subject: [PATCH 08/37] Upgrade to gradle 8.3.1 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 3e5067be..c8a574ac 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.3.0' apply false - id 'com.android.library' version '8.3.0' apply false + id 'com.android.application' version '8.3.1' apply false + id 'com.android.library' version '8.3.1' apply false id 'org.jetbrains.kotlin.android' version '1.8.20' apply false } From bd8a9c2da699cdd50b9f7041b80c737d57b2d94e Mon Sep 17 00:00:00 2001 From: pachi81 Date: Fri, 5 Apr 2024 14:11:04 +0200 Subject: [PATCH 09/37] Reset LibreLink on error --- .../michelinside/glucodatahandler/common/SourceState.kt | 6 ++++++ .../glucodatahandler/common/tasks/DataSourceTask.kt | 6 +++++- .../glucodatahandler/common/tasks/LibreViewSourceTask.kt | 8 ++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/SourceState.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/SourceState.kt index 9ac9f19f..112bfbb9 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/SourceState.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/SourceState.kt @@ -2,6 +2,7 @@ package de.michelinside.glucodatahandler.common import android.content.Context import android.os.Handler +import android.util.Log import de.michelinside.glucodatahandler.common.notifier.DataSource import de.michelinside.glucodatahandler.common.notifier.InternalNotifier import de.michelinside.glucodatahandler.common.notifier.NotifySource @@ -13,6 +14,7 @@ enum class SourceState(val resId: Int) { ERROR(R.string.source_state_error); } object SourceStateData { + private val LOG_ID = "GDH.SourceStateData" var lastSource: DataSource = DataSource.NONE var lastState: SourceState = SourceState.NONE @@ -27,6 +29,10 @@ object SourceStateData { lastSource = source lastState = state + if(state == SourceState.ERROR) { + Log.e(LOG_ID, "Error state for source $source: $error" ) + } + if (GlucoDataService.context != null) { Handler(GlucoDataService.context!!.mainLooper).post { InternalNotifier.notify(GlucoDataService.context!!, NotifySource.SOURCE_STATE_CHANGE, null) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/DataSourceTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/DataSourceTask.kt index 390bf6c7..8dcbfb63 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/DataSourceTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/DataSourceTask.kt @@ -110,7 +110,11 @@ abstract class DataSourceTask(private val enabledKey: String, protected val sour } fun setState(state: SourceState, error: String = "", code: Int = -1) { - Log.v(LOG_ID,"Set state for source " + source + ": " + state + " - " + error + " (" + code + ")") + if(state == SourceState.NONE) { + Log.v(LOG_ID,"Set state for source " + source + ": " + state + " - " + error + " (" + code + ")") + } else { + Log.w(LOG_ID,"Set state for source " + source + ": " + state + " - " + error + " (" + code + ")") + } lastErrorCode = code lastState = state diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt index 0d8fc799..eba32a29 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt @@ -100,6 +100,7 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, return jsonObj } setLastError("Missing data in response!", 500) + reset() return null } @@ -245,7 +246,9 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, val array = jsonObject.optJSONArray("data") if (array != null) { if (array.length() == 0) { + Log.w(LOG_ID, "Empty data array in response: $jsonObject") setLastError(GlucoDataService.context!!.getString(R.string.src_libre_setup_librelink)) + reset() return } val data = getPatientData(array) @@ -283,6 +286,11 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, handleResult(glucoExtras) } } + } else { + Log.e(LOG_ID, "No data array found in response: $jsonObject") + setLastError("Invalid response! Please send logs to developer.") + reset() + return } } } From 116ad14641d2a6b25f01ede8003d14b5f02deaf7 Mon Sep 17 00:00:00 2001 From: pachi81 Date: Sun, 7 Apr 2024 13:35:04 +0200 Subject: [PATCH 10/37] Update kotlin to 1.9.23 --- .idea/kotlinc.xml | 2 +- build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 69e86158..fe63bb67 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/build.gradle b/build.gradle index c8a574ac..06bfb3a6 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'com.android.application' version '8.3.1' apply false id 'com.android.library' version '8.3.1' apply false - id 'org.jetbrains.kotlin.android' version '1.8.20' apply false + id 'org.jetbrains.kotlin.android' version '1.9.23' apply false } project.ext.set("versionCode", 26) From d95f9353f7d8e9e7d7d896dce8ff3bbab10fe7c6 Mon Sep 17 00:00:00 2001 From: pachi81 Date: Fri, 5 Apr 2024 15:07:02 +0200 Subject: [PATCH 11/37] Add portuguese strings --- common/src/main/res/values-pl/strings.xml | 3 + common/src/main/res/values-pt/strings.xml | 330 ++++++++++++++++++++++ common/src/main/res/values/strings.xml | 3 + 3 files changed, 336 insertions(+) create mode 100644 common/src/main/res/values-pt/strings.xml diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index fa6b68ae..46fbacc4 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -312,6 +312,9 @@ W przypadku wszystkich zadań związanych z czasem i odstępami czasu aplikacja wymaga udzielenia uprawnień do ich obsługi.\nAplikacja nie dodaje ani nie zmienia żadnych alarmów i przypomnień, które ustawił użytkownik. Wymaga jedynie pozwolenia na uruchamianie wewnętrznych wyzwalaczy.\nJeśli naciśniesz OK, otwarte zostaną ustawienia uprawnień, gdzie można je nadać dla GlucoDataHandler. Zezwalaj na ustawianie alarmów i przypomnień Uprawnienie do ustawiania alarmów i przypomnień jest wyłączone!!!\nGlucoDataHandler może nie działać poprawnie!!!\nNaciśnij tutaj, aby przejść bezpośrednio do ustawień. + + + Patient If there is more then one patient connected to the libre account, please select the patient receiving the data for. Lockscreen Wallpaper diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml new file mode 100644 index 00000000..99520b4e --- /dev/null +++ b/common/src/main/res/values-pt/strings.xml @@ -0,0 +1,330 @@ + + + Sensor + Valor + Raw + Delta + Taxa + Leitura + Leitura IOB/COB + Timediff + Alarme + Fonte + por minuto + Nenhum dado recebido ainda!\nConfigure uma fonte primeiro. + + %1$d min + \> 1h + + subindo muito rapidamente + subindo rapidamente + subindo + estável + descendo + descendo rapidamente + descendo muito rapidamente + + OK + Cancelar + + - + muito baixa + baixa + alta + muito alta + + + + Otimização da bateria está habilitada. + Fonte: não ativa + Wear: conectado (%1$s) + Wear: disconectado + Android Auto: conectado + Android Auto: disconectado + A acessibilidade de alto contraste está ativa!\nPressione aqui para acessar as configurações. + Sem novos valores há %1$d minutos! + + Configurações + Fontes + GitHub + Atualiações + Suporte + Ajuda + + https://github.com/pachi81/GlucoDataHandler/blob/master/README.md + + + + Novo valor de glicose + Valor obsoleto + Alarme de glicose + Wear conexão + Android Auto conexão + Glucose range change + + + Telefone + Wear + + Não ativo + Em progresso + OK + Nenhum novo valor + Sem conexão com a Internet + Erro + + Intervalo + Intervalo de solicitação de dados da fonte. + Atraso + Atraso em segundos antes de solicitar os dados da nuvem, pois demora algum tempo até que os dados estejam presentes lá. + + LibreLink Seguidor + Habilita + Habilite o seguidor do LibreLink, se o usuário e a senha do LibreLink estiverem definidos. + E-mail + E-mail para acesso LibreLink + Senha + Senha para acesso LibreLink + Reconectar + No próximo intervalo um novo login será requerido. + Configure a conexão LibreLinkUp primeiro! + + Seguidor Nightscout + Habilita + Habilita Nightscout seguidor. + Nightscout URL + URL do servidor Nightscout. + API Secret + API Secret para acesso aos dados (opcional). + Access Token + Adicione seu token de acesso, se preciso for (opcional). + + Sempre + 1 minuto + 5 minutos + 10 minutos + 15 minutos + 20 minutos + 30 minutos + Alarme somente + + + + desabilitado + não conectado + conectado + erro: %s + + + + + Geral + Notificação + Widgets + Android Auto + Forward valores de glicose + Faixa do alvo + Coress + + Valores fictícios + Glicose alta + Limite para valores altos de glicose.\nSe você ativou o alarme alto no Juggluco, não é necessário definir esse valor. + Meta de glicose superior + Valor superior para o intervalo desejado. + Meta de glicose superior inferior + Valor inferior para o intervalo desejado. + Glicose baixa + Limite para valores baixos de glicose.\nSe você ativou o alarme baixo no Juggluco, não é necessário definir esse valor. + Android Auto notificações + Notificação para Android Auto. + Intervalo de notificação + Intervalo mínimo para exibição de notificações de um novo valor.\nOs alarmes sempre mostrarão uma notificação. + Apenas alarme + Acionará notificações de alarmes apenas em intervalos específicos.\nValores muito baixos sempre mostrarão uma notificação. + Leitor de mídia fictício + Mostre o valor da glicose como música atual para Android Auto. + +Enviar transmissão de glucodata + SEnvie transmissão de glucodata para novos valores recebidos de qualquer fonte. + Identificar receptores de glucodata + Escolha os receptores para os quais enviar a transmissão de glucodata. Uma transmissão global será enviada para todos os aplicativos registrados dinamicamente na transmissão. + Enviar transmissão Libre (pached) + Encaminhe os valores de glicose para xDrip+. Selecione \'Libre (patched app)\' como fonte no xDrip+. + Identifique receptores Libre (pached) + Escolha os receptores para os quais enviar a transmissão Libre (patched). Uma transmissão global será enviada para todos os aplicativos registrados dinamicamente na transmissão + Enviar transmissão xDrip+ + Envie a transmissão como o xDrip+ está enviando. Por exemplo, para AAPS. + +Identifique receptores de transmissão xDrip+ + Escolha os receptores para os quais enviar a transmissão xDrip+. Uma transmissão global será enviada para todos os aplicativos registrados dinamicamente na transmissão. + Use mmol/l + Use mmol/l em vez de mg/dl. + 5 minutos delta + SAtive esta opção para usar valores delta para intervalos de 5 minutos; caso contrário, será usado intervalo de 1 minuto. + Cores Alto/Baixo + Cor para valores altos e baixos de glicose. + Cor fora do intervalo + Cor para valores de glicose fora da faixa alvo. + Cor alvo + Cor para valores de glicose na faixa do alvo. + Notificação + Mostra uma notificação permanente com a seta de tendência atual, valor de glicose e delta na barra de status.\nEsta notificação impede que o Android interrompa este app. + Ícone da barra de status + Estilo pequeno para o ícone da barra de status da notificação. + Ícone grande na barra de status + Ícone maior na barra de status. Você tem que verificar se o ícone está cortado, se fica muito grande. + Ocultar conteúdo + Remover o conteúdo da notificação, para ter apenas uma pequena notificação com o ícone relacionado. + 2. notificação + +Mostre uma segunda notificação sem conteúdo para ter um ícone adicional na barra de status. + 2. ícone da barra de status + Estilo do pequeno para o ícone da barra de status da segunda notificação. + Widget flutuante + Mostre glicose, tendência, delta, tempo e IOB/COB como widget flutuante.\nSe você segurar e não mover o widget por mais de 5 segundos, ele desaparecerá. + Tamanho + Alterar o tamanho do widget flutuante + Estilo + Estilo do widget flutuante. + Transparência + Transparência do fundo do widget flutuante. + Transparência do Widget + Transparência do fundo do widget. + Tempo relativo + UUse o tempo relativo em vez do valor recebido de data/hora da glicose para widgets.\nO tempo relativo depende das configurações da bateria do seu dispositivo e pode não funcionar corretamente. + + ïcone do App + Valor da Glicose + Seta de tendência + Delta + + Wear: vibração + + Global Transmissão + + Mostrar tudo + Nenhum receptor encontrado! + + Glicose + Glicose e tendência + Glicose, tendência e delta + Glicose, tendência delta e timestamp + Glicose, tendência, delta, tempo e IOB/COB + + Suporte BangleJS + Encaminhar valores para o widget widbgjs para BangleJS. + + + + Recebendo dados para complicações + + Telefone: conectado (%1$s) + Telefone: disconectado + + Primeiro plano + Seta de tendência grande + Vibração + Colorido AOD + + + + Glicose + Glicose (grande e colorida) + Glicose (colorida) + Glicose (grande) + Glicose com ícone + Glicose e tendência + Glicose e tendência (grande e colorida) + Glicose e tendência (colorida) + Glicose and faixa de tendência (se suportado) + Glicose, delta e faixa de tendência (se suportado) + Delta, seta de tendência e faixa de tendência (se suportado) + Glicose e tendência + Glicose, delta e tendência + Glicose, delta, tendência e timestamp + Glicose e valor de tendência + Delta (grande) + Delta + Delta and tendência + Delta com ícone + Delta e timestamp + Tendência (grande, colorido) + Tendência (colorido) + Ícone de tendência + Complicação do intervalo de teste com valores negativos (para suporte ao intervalo de tendência) + Valor de tendência + Valor de tendênciae seta + Valor de tendência e ícone + Glicose timestamp + Nível da bateria (wear e Telefone) + IOB/COB + + + + https://github.com/pachi81/GlucoDataAuto/blob/main/README.md + Para usar este aplicativo no Android Auto, você deve ativar as configurações do desenvolvedor no Android Auto clicando várias vezes na versão. Então você deve ativar \"Fontes desconhecidas\" no menu de configurações do desenvolvedor.\n\nSe você estiver usando GlucoDataHandler como fonte, os valores serão enviados se o telefone estiver conectado ao Android Auto. + Informação + Se você estiver usando GlucoDataHandler como fonte, essas configurações serão sobrescritas no GlucoDataHandler, portanto você não precisa alterá-las aqui. + GlucoDataAuto ausente + TPara usar o GlucoDataHandler para Android Auto, você deve instalar o GlucoDataAuto do GitHub.\nClique aqui para obter mais informações e fazer o download do GlucoDataAuto. + Encaminhar para GlucoDataAuto + Envie valores e configurações de glicose para GlucoDataAuto, se o telefone estiver conectado ao Android Auto. + + + + + + Notificação em primeiro plano + Notificação em primeiro plano que impede a interrupção do aplicativo durante a execução em segundo plano. + Segunda notificação + Notificação adicional para mostrar um ícone extra na barra de status. + Worker notification + Worker notification shown while running tasks in the background. + Notificação em primeiro plano + Notificação em primeiro plano que impede a interrupção do aplicativo durante a execução em segundo plano. + Notificação para Android Auto + Notification which is shown in Android Auto + IOB + COB + NNoise-Block ativo nas configurações entre aplicativos do xDrip+!\nPor favor, altere para \"Extremamente barulhento\"! + Seguidor + Juggluco + Para receber valores do Juggluco:\n- abra o Juggluco\n- acesse as configurações\n- ative a transmissão do Glucodata\n- ative \"de.michelinside.glucodatahandler\" + xDrip+ + Para receber valores do xDrip+:\n- abra o xDrip+\n- vá para configurações\n- vá para configurações entre aplicativos\n- ative \"Transmissão local\"\n- defina \"Bloqueio de ruído\" como \"Enviar até mesmo sinais extremamente barulhentos\"\n- habilite \"Transmissão Compatível\"\n- marque \"Identificar receptores\" para estar vazio ou adicione uma nova linha com \"de.michelinside.glucodatahandler\" + Configurar LibreLinkUp + IMPORTANTE: esta não é a conta LibreView!\nPara ativar o LibreLinkUp:\n- abra seu aplicativo FreeStyle Libre e selecione no menu Compartilhar ou Aplicativos conectados\n- ative a conexão LibreLinkUp\n- instale o LibreLinkUp da PlayStore\n- configure seu conta e aguarde o convite + Contato + IOB/COB support + RReceba também valores IOB e COB do endpoint de seixo nightscout. + A notificação é obrigatória para o funcionamento correto do aplicativo.\nA partir do Android 13, você pode remover esta notificação. Se você não quiser que a notificação seja de volta, defina o \"Ícone da barra de status\" como \"Ícone do aplicativo\" e ative \"Ocultar conteúdo\". + AAPS + AndroidAPS + Para receber valores do AAPS:\n- abra o aplicativo AAPS\n- vá para \"Config Builder\"\n- ative \"Samsung Tizen\" + WatchDrip+ + "Ative a comunicação WatchDrip+ (sem gráfico).\nIMPORTANTE: ative \"Ativar serviço\" no WatchDrip+ depois de ativar esta configuração aqui!" + Reaparecer + ntervalo em que a notificação reaparecerá caso não haja novo valor. (0 para nunca). + Estilo do ícone/imagem fictício do media player. + Estilo do ïcone/imagem + Mobile + Wear + Salvar logs + Logs salvos com sucesso + Falha salvando logs! + Logs do wear salvos com sucesso + Falha ao savar logs do wear! + Para todos os trabalhos relacionados a tempo/intervalo, este aplicativo requer permissão para alarmes e alarmes. lembretes.\nEle não irá adicionar ou alterar nenhum lembrete do usuário, é apenas para agendamento interno.\nSe você pressionar OK, você irá encaminhar para a configuração de permissão para habilitá-lo para GlucoDataHandler. + Alarmes & permissão de lembretes + O alarme de agendamento exato está desabilitado!!!\nGlucoDataHandler pode não funcionar corretamente!!!\nPressione aqui para ir direto para a configuração de permissão. + + + + Patient + If there is more then one patient connected to the libre account, please select the patient receiving the data for. + Lockscreen Wallpaper + Enable + If you enable lockscreen wallpaper, the app replaces the wallpaper on lockscreen with an Image containing the value and trend. + Vertical position + Vertical position of the glucose-trend image on lockscreen:\n0 is the top of the display\n100 is the botton of the display + \ No newline at end of file diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index e7231e66..a8f59272 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -325,6 +325,9 @@ For all time/interval related work, this app requires the permission for alarms & reminders.\nIt will not add or change any user reminders, it is only for internal scheduling.\nIf you press OK, you will forward to the permission setting to enable it for GlucoDataHandler. Alarms & reminders permission Schedule exact alarm is disabled!!!\nGlucoDataHandler may not work correct!!!\nPress here to go direct to the permission setting. + + + Patient If there is more then one patient connected to the libre account, please select the patient receiving the data for. Lockscreen Wallpaper From 52e5f5b11b794e3443cacc689643f6c8af0b64fa Mon Sep 17 00:00:00 2001 From: pachi81 Date: Sun, 7 Apr 2024 14:27:19 +0200 Subject: [PATCH 12/37] Fix notification permission and foreground service --- .../glucodataauto/GlucoDataServiceAuto.kt | 2 +- .../glucodatahandler/common/GlucoDataService.kt | 11 ++++++----- common/src/main/res/values-de/strings.xml | 6 +++--- common/src/main/res/values-pl/strings.xml | 5 +++-- common/src/main/res/values-pt/strings.xml | 5 +++-- common/src/main/res/values/strings.xml | 5 +++-- .../glucodatahandler/MainActivity.kt | 17 ++++++++++++++--- mobile/src/main/res/layout/activity_main.xml | 16 ++++++++++++++-- 8 files changed, 47 insertions(+), 20 deletions(-) diff --git a/auto/src/main/java/de/michelinside/glucodataauto/GlucoDataServiceAuto.kt b/auto/src/main/java/de/michelinside/glucodataauto/GlucoDataServiceAuto.kt index bd45ffd1..7dcf8aa6 100644 --- a/auto/src/main/java/de/michelinside/glucodataauto/GlucoDataServiceAuto.kt +++ b/auto/src/main/java/de/michelinside/glucodataauto/GlucoDataServiceAuto.kt @@ -163,7 +163,7 @@ class GlucoDataServiceAuto: Service() { Log.d(LOG_ID, "onStartCommand called") super.onStartCommand(intent, flags, startId) val isForeground = intent.getBooleanExtra(Constants.SHARED_PREF_FOREGROUND_SERVICE, false) - if (isForeground && !isForegroundService && Utils.checkPermission(this, android.Manifest.permission.POST_NOTIFICATIONS, Build.VERSION_CODES.TIRAMISU)) { + if (isForeground && !isForegroundService) { Log.i(LOG_ID, "Starting service in foreground!") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) startForeground(NOTIFICATION_ID, getNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt index 06abb270..d10eed4f 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt @@ -18,7 +18,6 @@ import de.michelinside.glucodatahandler.common.tasks.TimeTaskService import de.michelinside.glucodatahandler.common.utils.GlucoDataUtils import de.michelinside.glucodatahandler.common.notification.ChannelType import de.michelinside.glucodatahandler.common.notification.Channels -import de.michelinside.glucodatahandler.common.utils.Utils enum class AppSource { NOT_SET, @@ -63,7 +62,7 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService() { } fun start(source: AppSource, context: Context, cls: Class<*>, force: Boolean = false) { - if (!running) { + if (!running || !foreground) { Log.v(LOG_ID, "start called (running: $running - foreground: $foreground)") try { isRunning = true @@ -82,10 +81,12 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService() { // default on wear and phone true//sharedPref.getBoolean(Constants.SHARED_PREF_FOREGROUND_SERVICE, true) ) - if (foreground) + if (foreground) { context.startService(serviceIntent) - else + } else { + Log.v(LOG_ID, "start foreground service") context.startForegroundService(serviceIntent) + } } catch (exc: Exception) { Log.e( LOG_ID, @@ -121,7 +122,7 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService() { Log.v(LOG_ID, "onStartCommand called") super.onStartCommand(intent, flags, startId) val isForeground = true // intent?.getBooleanExtra(Constants.SHARED_PREF_FOREGROUND_SERVICE, true) --> always use foreground!!! - if (isForeground && !isForegroundService && Utils.checkPermission(this, android.Manifest.permission.POST_NOTIFICATIONS, Build.VERSION_CODES.TIRAMISU)) { + if (isForeground && !isForegroundService) { Log.i(LOG_ID, "Starting service in foreground!") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) startForeground(NOTIFICATION_ID, getNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) diff --git a/common/src/main/res/values-de/strings.xml b/common/src/main/res/values-de/strings.xml index bebaa9dc..5d7a45d3 100644 --- a/common/src/main/res/values-de/strings.xml +++ b/common/src/main/res/values-de/strings.xml @@ -149,13 +149,13 @@ mmol/l Benutze mmol/l anstatt mg/dl. Hoher Glukosewert - Grenze für hohe Glukosewerte.\nWenn in Juggluco der Alarm für hohen Glukosewert aktiviert ist, muss der Wert nicht gesetzt werden. + Grenze für hohe Glukosewerte. Obere Grenze Obere Grenze für den Zielbereich. Untere Grenze Untere Grenze für den Zielbereich. Niedriger Glukosewert - Grenze für niedrige Glukosewerte.\nWenn in Juggluco der Alarm für niedrigen Glukosewert aktiviert ist, muss der Wert nicht gesetzt werden. + Grenze für niedrige Glukosewerte. 5 Minuten Delta Für Delta Werte im 5 Minuten Intervall, ansonsten wird der Delta Wert pro Minute angezeigt. Hohe/Niedrige Werte @@ -317,5 +317,5 @@ Wenn sie den Hintergrund für den Lockscreen aktivieren, erstellt die App einen Hintergrund auf dem Lockscreen, welche den Glucosewert und den Trendpfeil enthält. Vertikale Position Vertikale position auf dem Lockscreen:\n0: am oberen Rand des Bildschirms\n100: am unteren Rand des Bildschirms - + !!!Die Anzeige von Benachrichtigungen ist deaktiviert!!!\nKorrekte Funktionalität von GlucoDataHandler ist nicht gewährleistet und es können keine Alarme angezeigt werden!!!\nBitte hier drücken, um zu der entsprechenden Berechtigungseinstellung zu gelangen. diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index 46fbacc4..791ad5fd 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -129,13 +129,13 @@ Wartości fikcyjne Wysokie stęż. glukozy - Granica dla wysokich wartości stężenia glukozy.\nJeśli aktywowałeś alarm wysokiego stęż. glukozy w Juggluco, nie musisz ustawiać tej wartości. + Granica dla wysokich wartości stężenia glukozy. Górna granica zakresu docelowego Najwyższa wartość dla zakresu docelowego. Dolna granica zakresu docelowego Najniższa wartość dla zakresu docelowego. Niskie stęż. glukozy - Granica dla niskich wartości glukozy.\nJeśli aktywowałeś alarm niskiego stęż. glukozy w Juggluco, nie musisz ustawiać tej wartości. + Granica dla niskich wartości glukozy.\nJeśli aktywowałeś alarm niskiego stęż. Powiadomienia w Android Auto Powiadomienie dla Android Auto. Odstęp między powiadomieniami @@ -322,5 +322,6 @@ If you enable lockscreen wallpaper, the app replaces the wallpaper on lockscreen with an Image containing the value and trend. Vertical position Vertical position of the glucose-trend image on lockscreen:\n0 is the top of the display\n100 is the botton of the display + Showing notifications is disabled!!!\nGlucoDataHandler may not work correct and can not show alarms!!!\nPress here to go direct to the permission setting. diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index 99520b4e..b6968803 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -128,13 +128,13 @@ Valores fictícios Glicose alta - Limite para valores altos de glicose.\nSe você ativou o alarme alto no Juggluco, não é necessário definir esse valor. + Limite para valores altos de glicose. Meta de glicose superior Valor superior para o intervalo desejado. Meta de glicose superior inferior Valor inferior para o intervalo desejado. Glicose baixa - Limite para valores baixos de glicose.\nSe você ativou o alarme baixo no Juggluco, não é necessário definir esse valor. + Limite para valores baixos de glicose. Android Auto notificações Notificação para Android Auto. Intervalo de notificação @@ -327,4 +327,5 @@ Mostre uma segunda notificação sem conteúdo para ter um ícone adicional na b If you enable lockscreen wallpaper, the app replaces the wallpaper on lockscreen with an Image containing the value and trend. Vertical position Vertical position of the glucose-trend image on lockscreen:\n0 is the top of the display\n100 is the botton of the display + Showing notifications is disabled!!!\nGlucoDataHandler may not work correct and can not show alarms!!!\nPress here to go direct to the permission setting. \ No newline at end of file diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index a8f59272..2794e144 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -140,13 +140,13 @@ Dummy values High glucose - Border for high glucose values.\nIf you have activated high alarm in Juggluco, you do not have to set this value. + Border for high glucose values. Target top glucose Top value for the target range. Target bottom glucose Bottom value for the target range. Low glucose - Border for low glucose values.\nIf you have activated low alarm in Juggluco, you do not have to set this value. + Border for low glucose values. Android Auto notifications Notification for Android Auto. Notification interval @@ -335,4 +335,5 @@ If you enable lockscreen wallpaper, the app replaces the wallpaper on lockscreen with an Image containing the value and trend. Vertical position Vertical position of the glucose-trend image on lockscreen:\n0 is the top of the display\n100 is the botton of the display + Showing notifications is disabled!!!\nGlucoDataHandler may not work correct and can not show alarms!!!\nPress here to go direct to the permission setting. diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt index c73b31a0..68b6298f 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt @@ -53,6 +53,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface { private lateinit var txtBatteryOptimization: TextView private lateinit var txtHighContrastEnabled: TextView private lateinit var txtScheduleExactAlarm: TextView + private lateinit var txtNotificationPermission: TextView private lateinit var btnSources: Button private lateinit var sharedPref: SharedPreferences private lateinit var optionsMenu: Menu @@ -76,6 +77,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface { txtBatteryOptimization = findViewById(R.id.txtBatteryOptimization) txtHighContrastEnabled = findViewById(R.id.txtHighContrastEnabled) txtScheduleExactAlarm = findViewById(R.id.txtScheduleExactAlarm) + txtNotificationPermission = findViewById(R.id.txtNotificationPermission) btnSources = findViewById(R.id.btnSources) PreferenceManager.setDefaultValues(this, R.xml.preferences, false) @@ -158,6 +160,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface { if (requestNotificationPermission && Utils.checkPermission(this, android.Manifest.permission.POST_NOTIFICATIONS, Build.VERSION_CODES.TIRAMISU)) { Log.i(LOG_ID, "Notification permission granted") requestNotificationPermission = false + txtNotificationPermission.visibility = View.GONE GlucoDataServiceMobile.start(this, true) } } catch (exc: Exception) { @@ -171,8 +174,16 @@ class MainActivity : AppCompatActivity(), NotifierInterface { if (!Utils.checkPermission(this, android.Manifest.permission.POST_NOTIFICATIONS, Build.VERSION_CODES.TIRAMISU)) { Log.i(LOG_ID, "Request notification permission...") requestNotificationPermission = true - this.requestPermissions(arrayOf(android.Manifest.permission.POST_NOTIFICATIONS), 3) + txtNotificationPermission.visibility = View.VISIBLE + txtNotificationPermission.setOnClickListener { + val intent: Intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) + .putExtra(Settings.EXTRA_APP_PACKAGE, this.packageName) + startActivity(intent) + } + this.requestPermissions(arrayOf(android.Manifest.permission.FOREGROUND_SERVICE, android.Manifest.permission.POST_NOTIFICATIONS), 3) return false + } else { + txtNotificationPermission.visibility = View.GONE } } requestExactAlarmPermission() @@ -225,7 +236,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface { try { val pm = getSystemService(POWER_SERVICE) as PowerManager if (!pm.isIgnoringBatteryOptimizations(packageName)) { - Log.w(LOG_ID, "Battery optimization is inactive") + Log.w(LOG_ID, "Battery optimization is active") txtBatteryOptimization.visibility = View.VISIBLE txtBatteryOptimization.setOnClickListener { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) @@ -234,7 +245,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface { } } else { txtBatteryOptimization.visibility = View.GONE - Log.i(LOG_ID, "Battery optimization is active") + Log.i(LOG_ID, "Battery optimization is inactive") } } catch (exc: Exception) { Log.e(LOG_ID, "checkBatteryOptimization exception: " + exc.message.toString() ) diff --git a/mobile/src/main/res/layout/activity_main.xml b/mobile/src/main/res/layout/activity_main.xml index 3dd63c46..839bb815 100644 --- a/mobile/src/main/res/layout/activity_main.xml +++ b/mobile/src/main/res/layout/activity_main.xml @@ -86,13 +86,25 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:ignore="UselessParent"> + Date: Mon, 8 Apr 2024 15:36:28 +0200 Subject: [PATCH 13/37] Fix LibreLink region handling --- .../common/tasks/LibreViewSourceTask.kt | 49 ++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt index eba32a29..6116d6a6 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/LibreViewSourceTask.kt @@ -35,6 +35,7 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, private var tokenExpire = 0L private var region = "" private var patientId = "" + private var dataReceived = false // mark this endpoint as already received data val patientData = mutableMapOf() const val server = "https://api.libreview.io" const val region_server = "https://api-%s.libreview.io" @@ -71,7 +72,23 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, Log.i(LOG_ID, "reset called") token = "" region = "" + dataReceived = false patientData.clear() + saveRegion() + } + + val sensitivData = mutableSetOf("id", "patienId", "firstName", "lastName", "did", "sn", "token", "deviceId", "email", "primaryValue", "secondaryValue" ) + + private fun replaceSensitiveData(body: String): String { + var result = body + sensitivData.forEach { + val groups = Regex("\"$it\":\"(.*?)\"").find(result)?.groupValues + if(!groups.isNullOrEmpty() && groups.size > 1 && groups[1].isNotEmpty()) { + val replaceValue = groups[0].replace(groups[1], "---") + result = result.replace(groups[0], replaceValue) + } + } + return result } private fun checkResponse(body: String?): JSONObject? { @@ -81,7 +98,7 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, return null } if (BuildConfig.DEBUG) { // do not log personal data - Log.i(LOG_ID, "Handle json response: " + body) + Log.i(LOG_ID, "Handle json response: " + replaceSensitiveData(body)) } val jsonObj = JSONObject(body) if (jsonObj.has("status")) { @@ -136,6 +153,7 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, if (data.has("region")) { region = data.optString("region", "") Log.i(LOG_ID, "Handle redirect to region: " + region) + saveRegion() return login() } else { setLastError("redirect without region!!!", 500) @@ -166,6 +184,18 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, return token.isNotEmpty() } + private fun saveRegion() { + try { + Log.d(LOG_ID, "Save region $region") + with (GlucoDataService.sharedPref!!.edit()) { + putString(Constants.SHARED_PREF_LIBRE_REGION, region) + apply() + } + } catch (exc: Exception) { + Log.e(LOG_ID, "saveRegion exception: " + exc.toString() ) + } + } + private fun login(): Boolean { if (token.isNotEmpty() && (reconnect || tokenExpire <= System.currentTimeMillis())) { if (!reconnect) @@ -246,9 +276,13 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, val array = jsonObject.optJSONArray("data") if (array != null) { if (array.length() == 0) { - Log.w(LOG_ID, "Empty data array in response: $jsonObject") - setLastError(GlucoDataService.context!!.getString(R.string.src_libre_setup_librelink)) - reset() + Log.w(LOG_ID, "Empty data array in response: ${replaceSensitiveData(body!!)}") + if(dataReceived) { + setLastError("Missing data! Please send logs to developer.") + reset() + } else { + setLastError(GlucoDataService.context!!.getString(R.string.src_libre_setup_librelink)) + } return } val data = getPatientData(array) @@ -283,11 +317,12 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, if (sensor != null && sensor.has("sn")) glucoExtras.putString(ReceiveData.SERIAL, sensor.optString("sn")) } + dataReceived = true handleResult(glucoExtras) } } } else { - Log.e(LOG_ID, "No data array found in response: $jsonObject") + Log.e(LOG_ID, "No data array found in response: ${replaceSensitiveData(body!!)}") setLastError("Invalid response! Please send logs to developer.") reset() return @@ -305,7 +340,7 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, if(data.has("patientId") && data.has("firstName") && data.has("lastName")) { val id = data.getString("patientId") val name = data.getString("firstName") + " " + data.getString("lastName") - Log.d(LOG_ID, "New patient found: $name with ID: $id") + Log.v(LOG_ID, "New patient found: $name") patientData[id] = name } } @@ -348,6 +383,8 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, user = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_USER, "")!!.trim() password = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_PASSWORD, "")!!.trim() token = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_TOKEN, "")!! + if(token.isNotEmpty()) + dataReceived = true tokenExpire = sharedPreferences.getLong(Constants.SHARED_PREF_LIBRE_TOKEN_EXPIRE, 0L) region = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_REGION, "")!! patientId = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_PATIENT_ID, "")!! From 8869c40053e4ebbcc0b237ae8e9e99d71f52781a Mon Sep 17 00:00:00 2001 From: pachi81 Date: Mon, 8 Apr 2024 16:20:57 +0200 Subject: [PATCH 14/37] LibreLinkUp string updated --- common/src/main/res/values-de/strings.xml | 2 +- common/src/main/res/values-pl/strings.xml | 2 +- common/src/main/res/values-pt/strings.xml | 3 ++- common/src/main/res/values/strings.xml | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/common/src/main/res/values-de/strings.xml b/common/src/main/res/values-de/strings.xml index 5d7a45d3..21e68c30 100644 --- a/common/src/main/res/values-de/strings.xml +++ b/common/src/main/res/values-de/strings.xml @@ -88,7 +88,7 @@ Passwort für LibreLink Reconnect Bei der nächsten Ausführung wird ein erneutes Login ausgeführt. - Bitte erst LibreLinkUp Verbindung aktivieren! + Keine Daten! Bitte akzeptieren Sie zuerst die Einladung in der LibreLinkUp App! Nightscout Datenquelle Aktiv diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index 791ad5fd..19cc8789 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -89,7 +89,6 @@ Hasło do LibreLinkUp Połącz ponownie W kolejnym odstępie czasowym nastąpi nowe logowanie. - Najpierw skonfiguruj połączenie LibreLinkUp! Nightscout Follower Włącz @@ -324,4 +323,5 @@ Vertical position of the glucose-trend image on lockscreen:\n0 is the top of the display\n100 is the botton of the display Showing notifications is disabled!!!\nGlucoDataHandler may not work correct and can not show alarms!!!\nPress here to go direct to the permission setting. + Missing data! Please accept invitation in LibreLinkUp app first! diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index b6968803..2ef1da19 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -87,7 +87,6 @@ Senha para acesso LibreLink Reconectar No próximo intervalo um novo login será requerido. - Configure a conexão LibreLinkUp primeiro! Seguidor Nightscout Habilita @@ -328,4 +327,6 @@ Mostre uma segunda notificação sem conteúdo para ter um ícone adicional na b Vertical position Vertical position of the glucose-trend image on lockscreen:\n0 is the top of the display\n100 is the botton of the display Showing notifications is disabled!!!\nGlucoDataHandler may not work correct and can not show alarms!!!\nPress here to go direct to the permission setting. + + Missing data! Please accept invitation in LibreLinkUp app first! \ No newline at end of file diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 2794e144..77dd26c0 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -99,7 +99,6 @@ Password for LibreLink Reconnect On next interval a new login will be executed. - Please set up LibreLinkUp connection first! Nightscout Follower Enable @@ -336,4 +335,6 @@ Vertical position Vertical position of the glucose-trend image on lockscreen:\n0 is the top of the display\n100 is the botton of the display Showing notifications is disabled!!!\nGlucoDataHandler may not work correct and can not show alarms!!!\nPress here to go direct to the permission setting. + + Missing data! Please accept invitation in LibreLinkUp app first! From b9bffb4fcba3d129dd31232c081acacd228225af Mon Sep 17 00:00:00 2001 From: pachi81 Date: Mon, 8 Apr 2024 16:25:27 +0200 Subject: [PATCH 15/37] Check connection for remote nighscout --- .../glucodatahandler/common/tasks/NightscoutSourceTask.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/NightscoutSourceTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/NightscoutSourceTask.kt index d427bcd1..4bf02b79 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/NightscoutSourceTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/NightscoutSourceTask.kt @@ -27,7 +27,13 @@ class NightscoutSourceTask: DataSourceTask(Constants.SHARED_PREF_NIGHTSCOUT_ENAB override fun hasIobCobSupport(): Boolean = active(1L) && iob_cob_support - override fun needsInternet(): Boolean = false + override fun needsInternet(): Boolean { + if(url.contains("127.0.0.1") || url.lowercase().contains("localhost")) { + Log.v(LOG_ID, "Localhost detected!") + return false + } + return true + } override fun executeRequest(context: Context) { if (!handlePebbleResponse(httpGet(getUrl(PEBBLE_ENDPOINT), getHeader()))) { From a260ca5d1552ba351ffbdab5124a8e02db5e1e2f Mon Sep 17 00:00:00 2001 From: pachi81 Date: Thu, 4 Apr 2024 21:55:11 +0200 Subject: [PATCH 16/37] Change debug version --- .../java/de/michelinside/glucodatahandler/common/utils/Utils.kt | 2 +- mobile/build.gradle | 1 - wear/build.gradle | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/utils/Utils.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/utils/Utils.kt index c7784606..2d298973 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/utils/Utils.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/utils/Utils.kt @@ -120,7 +120,7 @@ object Utils { } fun isPackageAvailable(context: Context, packageName: String): Boolean { - return context.packageManager.getInstalledApplications(0).find { info -> info.packageName.startsWith(packageName) } != null + return context.packageManager.getLaunchIntentForPackage(packageName) != null } fun getBackgroundColor(transparancyFactor: Int) : Int { diff --git a/mobile/build.gradle b/mobile/build.gradle index 395685a7..dd21c207 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -31,7 +31,6 @@ android { resValue "string", "app_name", "GlucoDataHandler" } debug { - applicationIdSuffix '.debug' minifyEnabled false resValue "string", "app_name", "GlucoDataHandler" } diff --git a/wear/build.gradle b/wear/build.gradle index c4c92ca2..3a3f69ac 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -29,7 +29,6 @@ android { resValue "string", "app_name", "GlucoDataHandler" } debug { - applicationIdSuffix '.debug' resValue "string", "app_name", "GlucoDataHandler" } second { From 7e6a18941ab8e00ca60874c884a06bdbbabcc965 Mon Sep 17 00:00:00 2001 From: pachi81 Date: Thu, 11 Apr 2024 18:50:06 +0200 Subject: [PATCH 17/37] v0.9.12 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 06bfb3a6..8d8c0812 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { } project.ext.set("versionCode", 26) -project.ext.set("versionName", "0.9.11.12") +project.ext.set("versionName", "0.9.12") project.ext.set("compileSdk", 34) project.ext.set("targetSdk", 33) project.ext.set("minSdk", 26) From 4785343aa4818931e1f7b1b97d39ec16e3362956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20Deli=C5=9B?= <137992536+froster82@users.noreply.github.com> Date: Fri, 12 Apr 2024 11:55:22 +0200 Subject: [PATCH 18/37] Update strings.xml - PL strings translated Translated, updated and corrected some previous strings. --- common/src/main/res/values-pl/strings.xml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index 19cc8789..d589287b 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -134,7 +134,7 @@ Dolna granica zakresu docelowego Najniższa wartość dla zakresu docelowego. Niskie stęż. glukozy - Granica dla niskich wartości glukozy.\nJeśli aktywowałeś alarm niskiego stęż. + Granica dla niskich wartości stężenia glukozy. Powiadomienia w Android Auto Powiadomienie dla Android Auto. Odstęp między powiadomieniami @@ -314,14 +314,14 @@ - Patient - If there is more then one patient connected to the libre account, please select the patient receiving the data for. - Lockscreen Wallpaper - Enable - If you enable lockscreen wallpaper, the app replaces the wallpaper on lockscreen with an Image containing the value and trend. - Vertical position - Vertical position of the glucose-trend image on lockscreen:\n0 is the top of the display\n100 is the botton of the display - Showing notifications is disabled!!!\nGlucoDataHandler may not work correct and can not show alarms!!!\nPress here to go direct to the permission setting. + Pacjent + Jeśli do konta libre podłączony jest więcej niż jeden pacjent, wybierz pacjenta, dla którego odbierane są dane. + Tapeta ekranu blokady + Włącz + Jeśli włączysz tapetę ekranu blokady, aplikacja zastąpi tapetę ekranu blokady obrazem zawierającym wartość stężenia glukozy i trend. + Położenie pionowe + Pozycja pionowa obrazu wartości glukozy - trendu na ekranie blokady: \n0 to góra ekranu\n100 to dół ekranu + Wyłączono wyśietlanie powiadomień!!!\nGlucoDataHandler może nie działać poprawnie i nie może pokazywać alarmów!!!\nNaciśnij tutaj, aby przejść bezpośrednio do ustawień uprawnień. - Missing data! Please accept invitation in LibreLinkUp app first! + Brak danych! Najpierw zaakceptuj zaproszenie w aplikacji LibreLinkUp! From d0272ab3bf872db4ec8677bd18a1a79069da6541 Mon Sep 17 00:00:00 2001 From: pachi81 Date: Sun, 14 Apr 2024 14:54:50 +0200 Subject: [PATCH 19/37] Update string pt --- common/src/main/res/values-pt/strings.xml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index 2ef1da19..356a3270 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -319,14 +319,14 @@ Mostre uma segunda notificação sem conteúdo para ter um ícone adicional na b - Patient - If there is more then one patient connected to the libre account, please select the patient receiving the data for. - Lockscreen Wallpaper - Enable - If you enable lockscreen wallpaper, the app replaces the wallpaper on lockscreen with an Image containing the value and trend. - Vertical position - Vertical position of the glucose-trend image on lockscreen:\n0 is the top of the display\n100 is the botton of the display - Showing notifications is disabled!!!\nGlucoDataHandler may not work correct and can not show alarms!!!\nPress here to go direct to the permission setting. - - Missing data! Please accept invitation in LibreLinkUp app first! + Paciente + Se houver mais de um paciente conectado à conta libre, selecione o paciente para o qual receberá os dados. + Papel de parede da tela de bloqueio + Habilitado + Se você ativar o papel de parede da tela de bloqueio, o aplicativo substituirá o papel de parede da tela de bloqueio por uma imagem contendo o valor e a tendência. + Posição vertical + Posição vertical da seta de tendência na tela de bloqueio:\n0 na parte superior da tela\n100 na parte inferior da tela + Notificação está desabilitada!\nGlucoDataHandler pode não funcionar corretamente e pode não gerar alarmes!!!\n Pressione aqui para ir para a tela de configuração de permissões. + + Por favor, aceite o convite no aplicativo LibreLinkUp primeiro! \ No newline at end of file From a6be266228c21c886aea04782548c59eef717518 Mon Sep 17 00:00:00 2001 From: pachi81 Date: Sun, 14 Apr 2024 19:14:41 +0200 Subject: [PATCH 20/37] Add floating widget toggle --- .../main/res/drawable-night/icon_overlay.xml | 5 +++++ common/src/main/res/drawable/icon_overlay.xml | 5 +++++ .../main/res/drawable/icon_overlay_white.xml | 5 +++++ .../glucodatahandler/MainActivity.kt | 17 +++++++++++++++++ mobile/src/main/res/menu/menu_items.xml | 6 ++++++ mobile/src/main/res/xml/preferences.xml | 1 + 6 files changed, 39 insertions(+) create mode 100644 common/src/main/res/drawable-night/icon_overlay.xml create mode 100644 common/src/main/res/drawable/icon_overlay.xml create mode 100644 common/src/main/res/drawable/icon_overlay_white.xml diff --git a/common/src/main/res/drawable-night/icon_overlay.xml b/common/src/main/res/drawable-night/icon_overlay.xml new file mode 100644 index 00000000..5512e57e --- /dev/null +++ b/common/src/main/res/drawable-night/icon_overlay.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/common/src/main/res/drawable/icon_overlay.xml b/common/src/main/res/drawable/icon_overlay.xml new file mode 100644 index 00000000..f7c2c024 --- /dev/null +++ b/common/src/main/res/drawable/icon_overlay.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/common/src/main/res/drawable/icon_overlay_white.xml b/common/src/main/res/drawable/icon_overlay_white.xml new file mode 100644 index 00000000..5512e57e --- /dev/null +++ b/common/src/main/res/drawable/icon_overlay_white.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt index 68b6298f..30901738 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt @@ -57,6 +57,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface { private lateinit var btnSources: Button private lateinit var sharedPref: SharedPreferences private lateinit var optionsMenu: Menu + private var floatingWidgetItem: MenuItem? = null private val LOG_ID = "GDH.Main" private var requestNotificationPermission = false @@ -276,6 +277,8 @@ class MainActivity : AppCompatActivity(), NotifierInterface { inflater.inflate(R.menu.menu_items, menu) MenuCompat.setGroupDividerEnabled(menu!!, true) optionsMenu = menu + floatingWidgetItem = optionsMenu.findItem(R.id.action_floating_widget_toggle) + updateMenuItems() return true } catch (exc: Exception) { Log.e(LOG_ID, "onCreateOptionsMenu exception: " + exc.message.toString() ) @@ -283,6 +286,11 @@ class MainActivity : AppCompatActivity(), NotifierInterface { return true } + private fun updateMenuItems() { + if(floatingWidgetItem!=null) + floatingWidgetItem!!.isVisible = Settings.canDrawOverlays(this) + } + override fun onOptionsItemSelected(item: MenuItem): Boolean { try { Log.v(LOG_ID, "onOptionsItemSelected for " + item.itemId.toString()) @@ -335,6 +343,13 @@ class MainActivity : AppCompatActivity(), NotifierInterface { val menuIt: MenuItem = optionsMenu.findItem(R.id.action_save_wear_logs) menuIt.isEnabled = WearPhoneConnection.nodesConnected && !LogcatReceiver.isActive } + R.id.action_floating_widget_toggle -> { + Log.v(LOG_ID, "Floating widget toggle") + with(sharedPref.edit()) { + putBoolean(Constants.SHARED_PREF_FLOATING_WIDGET, !sharedPref.getBoolean(Constants.SHARED_PREF_FLOATING_WIDGET, false)) + apply() + } + } else -> return super.onOptionsItemSelected(item) } } catch (exc: Exception) { @@ -374,6 +389,8 @@ class MainActivity : AppCompatActivity(), NotifierInterface { } txtSourceInfo.text = SourceStateData.getState(this) + + updateMenuItems() } catch (exc: Exception) { Log.e(LOG_ID, "update exception: " + exc.message.toString() ) } diff --git a/mobile/src/main/res/menu/menu_items.xml b/mobile/src/main/res/menu/menu_items.xml index 9f014844..a4a6c322 100644 --- a/mobile/src/main/res/menu/menu_items.xml +++ b/mobile/src/main/res/menu/menu_items.xml @@ -1,6 +1,12 @@ + Date: Sun, 14 Apr 2024 19:16:06 +0200 Subject: [PATCH 21/37] v27 --- build.gradle | 2 +- wear/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 8d8c0812..920fc6d0 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'org.jetbrains.kotlin.android' version '1.9.23' apply false } -project.ext.set("versionCode", 26) +project.ext.set("versionCode", 27) project.ext.set("versionName", "0.9.12") project.ext.set("compileSdk", 34) project.ext.set("targetSdk", 33) diff --git a/wear/build.gradle b/wear/build.gradle index 3a3f69ac..481b3330 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -87,7 +87,7 @@ dependencies { implementation 'com.google.android.material:material:1.11.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.work:work-runtime:2.9.0' - implementation "androidx.core:core-splashscreen:1.1.0-alpha02" + implementation "androidx.core:core-splashscreen:1.1.0-rc01" } afterEvaluate { From b5b7eae8698f91d5c8beefa600ada19e03781ef5 Mon Sep 17 00:00:00 2001 From: dinizmauricio Date: Sun, 14 Apr 2024 17:19:02 -0300 Subject: [PATCH 22/37] Update strings.xml corrected minor typing errors --- common/src/main/res/values-pt/strings.xml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index 356a3270..ae4deb59 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -38,9 +38,9 @@ Otimização da bateria está habilitada. Fonte: não ativa Wear: conectado (%1$s) - Wear: disconectado + Wear: desconectado Android Auto: conectado - Android Auto: disconectado + Android Auto: desconectado A acessibilidade de alto contraste está ativa!\nPressione aqui para acessar as configurações. Sem novos valores há %1$d minutos! @@ -143,10 +143,10 @@ Leitor de mídia fictício Mostre o valor da glicose como música atual para Android Auto. -Enviar transmissão de glucodata - SEnvie transmissão de glucodata para novos valores recebidos de qualquer fonte. - Identificar receptores de glucodata - Escolha os receptores para os quais enviar a transmissão de glucodata. Uma transmissão global será enviada para todos os aplicativos registrados dinamicamente na transmissão. +Enviar transmissão do Glucodata + Envie transmissão do Glucodata para novos valores recebidos de qualquer fonte. + Identificar receptores do Glucodata + Escolha os receptores para os quais enviar a transmissão do Glucodata. Uma transmissão global será enviada para todos os aplicativos registrados dinamicamente na transmissão. Enviar transmissão Libre (pached) Encaminhe os valores de glicose para xDrip+. Selecione \'Libre (patched app)\' como fonte no xDrip+. Identifique receptores Libre (pached) @@ -159,7 +159,7 @@ Identifique receptores de transmissão xDrip+ Use mmol/l Use mmol/l em vez de mg/dl. 5 minutos delta - SAtive esta opção para usar valores delta para intervalos de 5 minutos; caso contrário, será usado intervalo de 1 minuto. + Ative esta opção para usar valores delta para intervalos de 5 minutos; caso contrário, será usado intervalo de 1 minuto. Cores Alto/Baixo Cor para valores altos e baixos de glicose. Cor fora do intervalo @@ -190,7 +190,7 @@ Mostre uma segunda notificação sem conteúdo para ter um ícone adicional na b Transparência do Widget Transparência do fundo do widget. Tempo relativo - UUse o tempo relativo em vez do valor recebido de data/hora da glicose para widgets.\nO tempo relativo depende das configurações da bateria do seu dispositivo e pode não funcionar corretamente. + Use o tempo relativo em vez do valor recebido de data/hora da glicose para widgets.\nO tempo relativo depende das configurações da bateria do seu dispositivo e pode não funcionar corretamente. ïcone do App Valor da Glicose @@ -218,7 +218,7 @@ Mostre uma segunda notificação sem conteúdo para ter um ícone adicional na b Recebendo dados para complicações Telefone: conectado (%1$s) - Telefone: disconectado + Telefone: desconectado Primeiro plano Seta de tendência grande @@ -295,7 +295,7 @@ Mostre uma segunda notificação sem conteúdo para ter um ícone adicional na b IMPORTANTE: esta não é a conta LibreView!\nPara ativar o LibreLinkUp:\n- abra seu aplicativo FreeStyle Libre e selecione no menu Compartilhar ou Aplicativos conectados\n- ative a conexão LibreLinkUp\n- instale o LibreLinkUp da PlayStore\n- configure seu conta e aguarde o convite Contato IOB/COB support - RReceba também valores IOB e COB do endpoint de seixo nightscout. + Receba também valores IOB e COB do endpoint de seixo Nightscout. A notificação é obrigatória para o funcionamento correto do aplicativo.\nA partir do Android 13, você pode remover esta notificação. Se você não quiser que a notificação seja de volta, defina o \"Ícone da barra de status\" como \"Ícone do aplicativo\" e ative \"Ocultar conteúdo\". AAPS AndroidAPS @@ -329,4 +329,4 @@ Mostre uma segunda notificação sem conteúdo para ter um ícone adicional na b Notificação está desabilitada!\nGlucoDataHandler pode não funcionar corretamente e pode não gerar alarmes!!!\n Pressione aqui para ir para a tela de configuração de permissões. Por favor, aceite o convite no aplicativo LibreLinkUp primeiro! - \ No newline at end of file + From 6620434db8e8b4c00f6383dbf906aedf3cd1d0b9 Mon Sep 17 00:00:00 2001 From: dinizmauricio Date: Sun, 14 Apr 2024 17:45:00 -0300 Subject: [PATCH 23/37] Update strings.xml Updated String's to be identical to what appears in Xdrip --- common/src/main/res/values-pt/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index ae4deb59..d0ab53d5 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -290,7 +290,7 @@ Mostre uma segunda notificação sem conteúdo para ter um ícone adicional na b Juggluco Para receber valores do Juggluco:\n- abra o Juggluco\n- acesse as configurações\n- ative a transmissão do Glucodata\n- ative \"de.michelinside.glucodatahandler\" xDrip+ - Para receber valores do xDrip+:\n- abra o xDrip+\n- vá para configurações\n- vá para configurações entre aplicativos\n- ative \"Transmissão local\"\n- defina \"Bloqueio de ruído\" como \"Enviar até mesmo sinais extremamente barulhentos\"\n- habilite \"Transmissão Compatível\"\n- marque \"Identificar receptores\" para estar vazio ou adicione uma nova linha com \"de.michelinside.glucodatahandler\" + Para receber valores do xDrip+:\n- abra o xDrip+\n- vá em configurações\n- vá em configurações entre aplicativos\n- ative \"Transmitir local\"\n- defina \"Bloqueio de Leituras Irregulares (Noise)\" como \"Send even Extremely noisy signals\"\n- habilite \"Broadcast Compatível\"\n- marque \"Identificar receptores\" adicione (se já tiver algo, adicione uma nova linha) \"de.michelinside.glucodatahandler\" Configurar LibreLinkUp IMPORTANTE: esta não é a conta LibreView!\nPara ativar o LibreLinkUp:\n- abra seu aplicativo FreeStyle Libre e selecione no menu Compartilhar ou Aplicativos conectados\n- ative a conexão LibreLinkUp\n- instale o LibreLinkUp da PlayStore\n- configure seu conta e aguarde o convite Contato From 1e7862606788d1b648b7104185251b3051424ca1 Mon Sep 17 00:00:00 2001 From: pachi81 Date: Mon, 15 Apr 2024 10:53:54 +0200 Subject: [PATCH 24/37] Bugfixes --- common/src/main/res/values-de/strings.xml | 3 +- common/src/main/res/values-pl/strings.xml | 3 +- common/src/main/res/values-pt/strings.xml | 3 +- common/src/main/res/values/strings.xml | 3 +- .../GlucoDataServiceMobile.kt | 1 + .../glucodatahandler/MainActivity.kt | 34 ++++++++++--------- .../glucodatahandler/PermanentNotification.kt | 7 +++- .../widget/LockScreenWallpaper.kt | 11 ++---- mobile/src/main/res/layout/activity_main.xml | 1 + mobile/src/main/res/layout/notification.xml | 3 +- mobile/src/main/res/xml/sources.xml | 8 +++-- 11 files changed, 44 insertions(+), 33 deletions(-) diff --git a/common/src/main/res/values-de/strings.xml b/common/src/main/res/values-de/strings.xml index 21e68c30..a4aa6e7a 100644 --- a/common/src/main/res/values-de/strings.xml +++ b/common/src/main/res/values-de/strings.xml @@ -284,7 +284,8 @@ Juggluco Konfiguration von Juggluco:\n- Juggluco App öffnen\n- Einstellungen öffnen\n- Glucodata broadcast aktivieren\n- \"de.michelinside.glucodatahandler\" auswählen xDrip+ - Konfiguration von xDrip+:\n- xDrip+ App öffnen\n- Einstellungen öffnen\n- Inter-App Einstellungen auswählen\n- Lokaler Broadcast aktivieren\n- in Verrauschungsunterdrückung \"Send even Extremely noisy signals\" auswählen\n- Kompatible Broadcasts aktivieren\n- Identifiziere Empfänger leer lassen oder eine neue Zeile mit \"de.michelinside.glucodatahandler\" hinzufügen + Konfiguration von xDrip+:\n- xDrip+ App öffnen\n- Einstellungen öffnen\n- Inter-App Einstellungen auswählen\n- Lokaler Broadcast aktivieren\n- in Verrauschungsunterdrückung \"Send even Extremely noisy signals\" auswählen + - Kompatible Broadcasts aktivieren\n- Identifiziere Empfänger leer lassen oder eine neue Zeile mit \"de.michelinside.glucodatahandler\" hinzufügen LibreLinkUp einrichten WICHTIG: nicht den LibreView Zugang verwenden!\nEinrichtung:\n- FreeStyle Libre App öffnen und unter Verbinden auf Teilen oder Verbundene Anwendungen klicken\n- LibreLinkUp aktivieren\n- LibreLinkUp aus dem PlayStore installieren\n- in der LibreLinkUp App einloggen und die Einladung annehmen Kontakt diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index d589287b..f8045235 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -285,7 +285,8 @@ Juggluco Aby odbierać wartości z Juggluco: \n- otwórz Juggluco \n- przejdź do ustawień \n- włącz Glucodata broadcast \n- włącz \"de.michelinside.glucodatahandler \" XDrip+ - Aby odbierać wartości z xDrip+:\n- otwórz xDrip+\n- przejdź do ustawień\n- przejdź do ustawień innych aplikacji\n- włącz \"Nadawaj lokalnie\"\n- ustaw \"Blokowanie szumów\" na \"Send even Extremely noisy signals\"\n- włącz \"Kompatybilny Broadcast\"\n- sprawdź, czy pole \"Identyfikuj odbiornik\" jest puste lub dodaj nową linię z \"de.michelinside.glucodatahandler\" + Aby odbierać wartości z xDrip+:\n- otwórz xDrip+\n- przejdź do ustawień\n- przejdź do ustawień innych aplikacji\n- włącz \"Nadawaj lokalnie\"\n- ustaw \"Blokowanie szumów\" na \"Send even Extremely noisy signals\" + - włącz \"Kompatybilny Broadcast\"\n- sprawdź, czy pole \"Identyfikuj odbiornik\" jest puste lub dodaj nową linię z \"de.michelinside.glucodatahandler\" Konfiguracja LibreLinkUp WAŻNE: to nie jest konto LibreView! Aby aktywować LibreLinkUp:\n- otwórz aplikację FreeStyle Libre i wybierz w menu Udostępnianie lub Podłączone aplikacje\n- aktywuj połączenie LibreLinkUp\n- zainstaluj LibreLinkUp ze Sklepu Play\n- skonfiguruj swoje konto i czekaj na zaproszenie Kontakt diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index d0ab53d5..2474d719 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -290,7 +290,8 @@ Mostre uma segunda notificação sem conteúdo para ter um ícone adicional na b Juggluco Para receber valores do Juggluco:\n- abra o Juggluco\n- acesse as configurações\n- ative a transmissão do Glucodata\n- ative \"de.michelinside.glucodatahandler\" xDrip+ - Para receber valores do xDrip+:\n- abra o xDrip+\n- vá em configurações\n- vá em configurações entre aplicativos\n- ative \"Transmitir local\"\n- defina \"Bloqueio de Leituras Irregulares (Noise)\" como \"Send even Extremely noisy signals\"\n- habilite \"Broadcast Compatível\"\n- marque \"Identificar receptores\" adicione (se já tiver algo, adicione uma nova linha) \"de.michelinside.glucodatahandler\" + Para receber valores do xDrip+:\n- abra o xDrip+\n- vá em configurações\n- vá em configurações entre aplicativos\n- ative \"Transmitir local\"\n- defina \"Bloqueio de Leituras Irregulares (Noise)\" como \"Send even Extremely noisy signals\" + - habilite \"Broadcast Compatível\"\n- marque \"Identificar receptores\" adicione (se já tiver algo, adicione uma nova linha) \"de.michelinside.glucodatahandler\" Configurar LibreLinkUp IMPORTANTE: esta não é a conta LibreView!\nPara ativar o LibreLinkUp:\n- abra seu aplicativo FreeStyle Libre e selecione no menu Compartilhar ou Aplicativos conectados\n- ative a conexão LibreLinkUp\n- instale o LibreLinkUp da PlayStore\n- configure seu conta e aguarde o convite Contato diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 77dd26c0..8ccb52df 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -298,7 +298,8 @@ Juggluco To receive values from Juggluco:\n- open Juggluco\n- go to settings\n- enable Glucodata broadcast\n- enable \"de.michelinside.glucodatahandler\" xDrip+ - To receive values from xDrip+:\n- open xDrip+\n- go to settings\n- go to Inter-app settings\n- enable \"Broadcast locally\"\n- set \"Noise Blocking\" to \"Send even Extremely noisy signals\"\n- enable \"Compatible Broadcast\"\n- check \"Identify receivers\" to be empty or add a new line with \"de.michelinside.glucodatahandler\" + To receive values from xDrip+:\n- open xDrip+\n- go to settings\n- go to Inter-app settings\n- enable \"Broadcast locally\"\n- set \"Noise Blocking\" to \"Send even Extremely noisy signals\" + - enable \"Compatible Broadcast\"\n- check \"Identify receivers\" to be empty or add a new line with \"de.michelinside.glucodatahandler\" Setup LibreLinkUp IMPORTANT: this is not the LibreView account!\nTo activate LibreLinkUp:\n- open your FreeStyle Libre App and select in the menu Share or Connected Apps\n- activate LibreLinkUp connection\n- install LibreLinkUp from PlayStore\n- setup your account and wait for the invitation Contact diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt index f96f8c14..359eae6e 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt @@ -65,6 +65,7 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt } override fun getNotification(): Notification { + Log.v(LOG_ID, "getNotification called") return PermanentNotification.getNotification( !sharedPref!!.getBoolean(Constants.SHARED_PREF_PERMANENT_NOTIFICATION_EMPTY, false), Constants.SHARED_PREF_PERMANENT_NOTIFICATION_ICON, true diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt index 30901738..fa2aae41 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt @@ -171,21 +171,20 @@ class MainActivity : AppCompatActivity(), NotifierInterface { fun requestPermission() : Boolean { requestNotificationPermission = false - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - if (!Utils.checkPermission(this, android.Manifest.permission.POST_NOTIFICATIONS, Build.VERSION_CODES.TIRAMISU)) { - Log.i(LOG_ID, "Request notification permission...") - requestNotificationPermission = true - txtNotificationPermission.visibility = View.VISIBLE - txtNotificationPermission.setOnClickListener { - val intent: Intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) - .putExtra(Settings.EXTRA_APP_PACKAGE, this.packageName) - startActivity(intent) - } - this.requestPermissions(arrayOf(android.Manifest.permission.FOREGROUND_SERVICE, android.Manifest.permission.POST_NOTIFICATIONS), 3) - return false - } else { - txtNotificationPermission.visibility = View.GONE - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && !Utils.checkPermission(this, android.Manifest.permission.POST_NOTIFICATIONS, Build.VERSION_CODES.TIRAMISU)) { + Log.i(LOG_ID, "Request notification permission...") + requestNotificationPermission = true + /* + txtNotificationPermission.visibility = View.VISIBLE + txtNotificationPermission.setOnClickListener { + val intent: Intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) + .putExtra(Settings.EXTRA_APP_PACKAGE, this.packageName) + startActivity(intent) + }*/ + this.requestPermissions(arrayOf(android.Manifest.permission.FOREGROUND_SERVICE, android.Manifest.permission.POST_NOTIFICATIONS), 3) + return false + } else { + txtNotificationPermission.visibility = View.GONE } requestExactAlarmPermission() return true @@ -327,7 +326,10 @@ class MainActivity : AppCompatActivity(), NotifierInterface { val mailIntent = Intent(Intent.ACTION_SENDTO, Uri.fromParts( "mailto","GlucoDataHandler@michel-inside.de", null)) mailIntent.putExtra(Intent.EXTRA_SUBJECT, "GlucoDataHander v" + BuildConfig.VERSION_NAME) - startActivity(mailIntent) + mailIntent.putExtra(Intent.EXTRA_TEXT, "") + if (mailIntent.resolveActivity(packageManager) != null) { + startActivity(mailIntent) + } return true } R.id.action_save_mobile_logs -> { diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt index 2c0f8266..7e0ee404 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt @@ -76,6 +76,9 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe private fun createNofitication(context: Context) { createNotificationChannel(context) + Channels.getNotificationManager().cancel(GlucoDataService.NOTIFICATION_ID) + Channels.getNotificationManager().cancel(SECOND_NOTIFICATION_ID) + notificationCompat = Notification.Builder(context, ChannelType.MOBILE_SECOND.channelId) .setSmallIcon(R.mipmap.ic_launcher) .setContentIntent(Utils.getAppIntent(context, MainActivity::class.java, 5, false)) @@ -84,6 +87,7 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe .setAutoCancel(false) .setShowWhen(true) .setColorized(true) + .setGroup(ChannelType.MOBILE_SECOND.channelId) .setCategory(Notification.CATEGORY_STATUS) .setVisibility(Notification.VISIBILITY_PUBLIC) @@ -95,6 +99,7 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe .setAutoCancel(false) .setShowWhen(true) .setColorized(true) + .setGroup(ChannelType.MOBILE_FOREGROUND.channelId) .setCategory(Notification.CATEGORY_STATUS) .setVisibility(Notification.VISIBILITY_PUBLIC) } @@ -155,7 +160,7 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe .setContentText(if (withContent) "Delta: " + ReceiveData.getDeltaAsString() else "") .build() - notification.visibility + notification.visibility = Notification.VISIBILITY_PUBLIC notification.flags = notification.flags or Notification.FLAG_NO_CLEAR return notification } diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt index e3b35b64..13831d24 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt @@ -85,14 +85,9 @@ object LockScreenWallpaper : NotifierInterface, SharedPreferences.OnSharedPrefer try { Log.v(LOG_ID, "updateLockScreen called for bitmap $bitmap") val wallpaperManager = WallpaperManager.getInstance(context) - if (bitmap != null) { - val wallpaper = createWallpaper(bitmap, context) - wallpaperManager.setBitmap(wallpaper, null, false, WallpaperManager.FLAG_LOCK) - wallpaper?.recycle() - } else { - // remove wallpaper - wallpaperManager.clear() - } + val wallpaper = if(bitmap != null) createWallpaper(bitmap, context) else null + wallpaperManager.setBitmap(wallpaper, null, false, WallpaperManager.FLAG_LOCK) + wallpaper?.recycle() } catch (exc: Exception) { Log.e(LOG_ID, "updateLockScreen exception: " + exc.message.toString() ) } diff --git a/mobile/src/main/res/layout/activity_main.xml b/mobile/src/main/res/layout/activity_main.xml index 839bb815..e64a4490 100644 --- a/mobile/src/main/res/layout/activity_main.xml +++ b/mobile/src/main/res/layout/activity_main.xml @@ -95,6 +95,7 @@ android:textSize="18sp" android:textStyle="bold" android:textAlignment="center" + android:visibility="gone" app:layout_constraintBottom_toTopOf="@+id/txtSourceInfo" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> diff --git a/mobile/src/main/res/layout/notification.xml b/mobile/src/main/res/layout/notification.xml index 02590d72..93bbd204 100644 --- a/mobile/src/main/res/layout/notification.xml +++ b/mobile/src/main/res/layout/notification.xml @@ -11,7 +11,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" - android:text="TextView" + android:text="No data!" android:textSize="30dp" android:textColor="@color/text_color" tools:ignore="HardcodedText,SpUsage" /> @@ -30,7 +30,6 @@ android:layout_height="match_parent" android:layout_marginStart="10dp" android:gravity="center_vertical" - android:text="Delta: +0.06" android:textSize="18dp" android:textColor="@color/text_color" tools:ignore="HardcodedText,SpUsage" /> diff --git a/mobile/src/main/res/xml/sources.xml b/mobile/src/main/res/xml/sources.xml index c47fdb5e..c55d82bd 100644 --- a/mobile/src/main/res/xml/sources.xml +++ b/mobile/src/main/res/xml/sources.xml @@ -16,8 +16,12 @@ app:iconSpaceReserved="false" app:initialExpandedChildrenCount="0"> + Date: Mon, 15 Apr 2024 11:49:35 +0200 Subject: [PATCH 25/37] v28 --- build.gradle | 2 +- .../glucodatahandler/PermanentNotification.kt | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 920fc6d0..b587f24b 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'org.jetbrains.kotlin.android' version '1.9.23' apply false } -project.ext.set("versionCode", 27) +project.ext.set("versionCode", 28) project.ext.set("versionName", "0.9.12") project.ext.set("compileSdk", 34) project.ext.set("targetSdk", 33) diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt index 7e0ee404..3df98739 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt @@ -149,7 +149,7 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe } val notificationBuilder = if(foreground) foregroundNotificationCompat else notificationCompat - val notification = notificationBuilder + notificationBuilder .setSmallIcon(getStatusBarIcon(iconKey)) .setWhen(ReceiveData.time) .setCustomContentView(remoteViews) @@ -158,7 +158,15 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe .setStyle(Notification.DecoratedCustomViewStyle()) .setContentTitle(if (withContent) ReceiveData.getClucoseAsString() else "") .setContentText(if (withContent) "Delta: " + ReceiveData.getDeltaAsString() else "") - .build() + + when(sharedPref.getString(iconKey, StatusBarIcon.APP.pref)) { + StatusBarIcon.GLUCOSE.pref, + StatusBarIcon.TREND.pref -> { + notificationBuilder.setColor(Color.TRANSPARENT) + } + } + + val notification = notificationBuilder.build() notification.visibility = Notification.VISIBILITY_PUBLIC notification.flags = notification.flags or Notification.FLAG_NO_CLEAR From b44aad165c2130dbbb2e89435edee631c44aa090 Mon Sep 17 00:00:00 2001 From: pachi81 Date: Mon, 15 Apr 2024 17:17:17 +0200 Subject: [PATCH 26/37] Use relative time as default --- .../de/michelinside/glucodatahandler/common/Constants.kt | 1 + .../glucodatahandler/common/tasks/ElapsedTimeTask.kt | 6 +++--- .../java/de/michelinside/glucodatahandler/WaerActivity.kt | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt index 7de56e9d..c66b123e 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt @@ -63,6 +63,7 @@ object Constants { const val SHARED_PREF_COLOR_OK = "color_ok" //const val SHARED_PREF_PERMANENT_NOTIFICATION = "permanent_notification" const val SHARED_PREF_PERMANENT_NOTIFICATION_ICON = "status_bar_notification_icon" + const val SHARED_PREF_PERMANENT_NOTIFICATION_COLORED_ICON = "status_bar_notification_colored_icon" const val SHARED_PREF_PERMANENT_NOTIFICATION_EMPTY = "permanent_notification_empty" const val SHARED_PREF_SECOND_PERMANENT_NOTIFICATION = "second_permanent_notification" const val SHARED_PREF_SECOND_PERMANENT_NOTIFICATION_ICON = "second_status_bar_notification_icon" diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt index 6a360fb6..6eba2fbb 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt @@ -11,7 +11,7 @@ import de.michelinside.glucodatahandler.common.notifier.NotifySource class ElapsedTimeTask : BackgroundTask() { companion object { private val LOG_ID = "GDH.Task.Time.ElapsedTask" - private var relativeTimeValue = false + private var relativeTimeValue = true private var interval = 0L val relativeTime: Boolean get() {return relativeTimeValue} fun setInterval(new_interval: Long) { @@ -41,8 +41,8 @@ class ElapsedTimeTask : BackgroundTask() { override fun checkPreferenceChanged(sharedPreferences: SharedPreferences, key: String?, context: Context): Boolean { if ((key == null || key == Constants.SHARED_PREF_RELATIVE_TIME)) { - if( relativeTimeValue != sharedPreferences.getBoolean(Constants.SHARED_PREF_RELATIVE_TIME, false) ) { - relativeTimeValue = sharedPreferences.getBoolean(Constants.SHARED_PREF_RELATIVE_TIME, false) + if( relativeTimeValue != sharedPreferences.getBoolean(Constants.SHARED_PREF_RELATIVE_TIME, true) ) { + relativeTimeValue = sharedPreferences.getBoolean(Constants.SHARED_PREF_RELATIVE_TIME, true) Log.d(LOG_ID, "relative time setting changed to " + relativeTimeValue) InternalNotifier.notify(context, NotifySource.TIME_VALUE, null) return true diff --git a/wear/src/main/java/de/michelinside/glucodatahandler/WaerActivity.kt b/wear/src/main/java/de/michelinside/glucodatahandler/WaerActivity.kt index fe22f6a5..fae4ffe7 100644 --- a/wear/src/main/java/de/michelinside/glucodatahandler/WaerActivity.kt +++ b/wear/src/main/java/de/michelinside/glucodatahandler/WaerActivity.kt @@ -108,7 +108,7 @@ class WaerActivity : AppCompatActivity(), NotifierInterface { } switchRelativeTime = findViewById(R.id.switchRelativeTime) - switchRelativeTime.isChecked = sharedPref.getBoolean(Constants.SHARED_PREF_RELATIVE_TIME, false) + switchRelativeTime.isChecked = sharedPref.getBoolean(Constants.SHARED_PREF_RELATIVE_TIME, true) switchRelativeTime.setOnCheckedChangeListener { _, isChecked -> Log.d(LOG_ID, "Relative time changed: " + isChecked.toString()) try { From 996bb952d1768ffea21d159b1932dc9123b00957 Mon Sep 17 00:00:00 2001 From: pachi81 Date: Mon, 15 Apr 2024 17:21:43 +0200 Subject: [PATCH 27/37] Setting for colored notification icon --- common/src/main/res/values-de/strings.xml | 2 ++ common/src/main/res/values-pl/strings.xml | 2 ++ common/src/main/res/values-pt/strings.xml | 2 ++ common/src/main/res/values/strings.xml | 2 ++ .../glucodatahandler/PermanentNotification.kt | 8 +++++--- mobile/src/main/res/xml/preferences.xml | 8 +++++++- 6 files changed, 20 insertions(+), 4 deletions(-) diff --git a/common/src/main/res/values-de/strings.xml b/common/src/main/res/values-de/strings.xml index a4aa6e7a..78e7e99b 100644 --- a/common/src/main/res/values-de/strings.xml +++ b/common/src/main/res/values-de/strings.xml @@ -319,4 +319,6 @@ Vertikale Position Vertikale position auf dem Lockscreen:\n0: am oberen Rand des Bildschirms\n100: am unteren Rand des Bildschirms !!!Die Anzeige von Benachrichtigungen ist deaktiviert!!!\nKorrekte Funktionalität von GlucoDataHandler ist nicht gewährleistet und es können keine Alarme angezeigt werden!!!\nBitte hier drücken, um zu der entsprechenden Berechtigungseinstellung zu gelangen. + Farbiges Statusbar Icon + Falls dein Gerät ein farbiges Statusbar Icon unterstützt, kannst du es auch deaktivieren, um ein weißes Icon zu benutzen. diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index f8045235..76f5dc94 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -325,4 +325,6 @@ Wyłączono wyśietlanie powiadomień!!!\nGlucoDataHandler może nie działać poprawnie i nie może pokazywać alarmów!!!\nNaciśnij tutaj, aby przejść bezpośrednio do ustawień uprawnień. Brak danych! Najpierw zaakceptuj zaproszenie w aplikacji LibreLinkUp! + Colored status bar icon + If your device supports colored status bar icons, you can also deactivate it to use a white item. diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index 2474d719..91bfb6fc 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -330,4 +330,6 @@ Mostre uma segunda notificação sem conteúdo para ter um ícone adicional na b Notificação está desabilitada!\nGlucoDataHandler pode não funcionar corretamente e pode não gerar alarmes!!!\n Pressione aqui para ir para a tela de configuração de permissões. Por favor, aceite o convite no aplicativo LibreLinkUp primeiro! + Colored status bar icon + If your device supports colored status bar icons, you can also deactivate it to use a white item. diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 8ccb52df..e3270c74 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -338,4 +338,6 @@ Showing notifications is disabled!!!\nGlucoDataHandler may not work correct and can not show alarms!!!\nPress here to go direct to the permission setting. Missing data! Please accept invitation in LibreLinkUp app first! + Colored status bar icon + If your device supports colored status bar icons, you can also deactivate it to use a white item. diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt index 3df98739..4d717ef5 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt @@ -113,10 +113,11 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe private fun getStatusBarIcon(iconKey: String): Icon { val bigIcon = sharedPref.getBoolean(Constants.SHARED_PREF_PERMANENT_NOTIFICATION_USE_BIG_ICON, false) + val coloredIcon = sharedPref.getBoolean(Constants.SHARED_PREF_PERMANENT_NOTIFICATION_COLORED_ICON, true) return when(sharedPref.getString(iconKey, StatusBarIcon.APP.pref)) { - StatusBarIcon.GLUCOSE.pref -> BitmapUtils.getGlucoseAsIcon(roundTarget=!bigIcon) - StatusBarIcon.TREND.pref -> BitmapUtils.getRateAsIcon(roundTarget=true, resizeFactor = if (bigIcon) 1.5F else 1F) - StatusBarIcon.DELTA.pref -> BitmapUtils.getDeltaAsIcon(roundTarget=!bigIcon) + StatusBarIcon.GLUCOSE.pref -> BitmapUtils.getGlucoseAsIcon(roundTarget=!bigIcon, color = if(coloredIcon) ReceiveData.getClucoseColor() else Color.WHITE) + StatusBarIcon.TREND.pref -> BitmapUtils.getRateAsIcon(roundTarget=true, color = if(coloredIcon) ReceiveData.getClucoseColor() else Color.WHITE, resizeFactor = if (bigIcon) 1.5F else 1F) + StatusBarIcon.DELTA.pref -> BitmapUtils.getDeltaAsIcon(roundTarget=!bigIcon, color = if(coloredIcon) ReceiveData.getClucoseColor(true) else Color.WHITE) else -> Icon.createWithResource(GlucoDataService.context, R.mipmap.ic_launcher) } } @@ -270,6 +271,7 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION, Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION_ICON, Constants.SHARED_PREF_PERMANENT_NOTIFICATION_USE_BIG_ICON, + Constants.SHARED_PREF_PERMANENT_NOTIFICATION_COLORED_ICON, Constants.SHARED_PREF_PERMANENT_NOTIFICATION_EMPTY -> { updatePreferences() } diff --git a/mobile/src/main/res/xml/preferences.xml b/mobile/src/main/res/xml/preferences.xml index fcef1bd2..5f19ea94 100644 --- a/mobile/src/main/res/xml/preferences.xml +++ b/mobile/src/main/res/xml/preferences.xml @@ -13,7 +13,7 @@ android:title="@string/pref_switch_use_mmol" app:iconSpaceReserved="false" /> + Date: Mon, 15 Apr 2024 12:32:08 -0300 Subject: [PATCH 28/37] Update strings.xml Translated two lines over colored icons --- common/src/main/res/values-pt/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index 91bfb6fc..f6f73338 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -330,6 +330,6 @@ Mostre uma segunda notificação sem conteúdo para ter um ícone adicional na b Notificação está desabilitada!\nGlucoDataHandler pode não funcionar corretamente e pode não gerar alarmes!!!\n Pressione aqui para ir para a tela de configuração de permissões. Por favor, aceite o convite no aplicativo LibreLinkUp primeiro! - Colored status bar icon - If your device supports colored status bar icons, you can also deactivate it to use a white item. + Ícone colorido da barra de status + Se o seu dispositivo suportar ícones coloridos da barra de status, você também poderá desativá-lo para usar um item branco. From db1f43832ec102395b8b359dbc26e87a31f3496c Mon Sep 17 00:00:00 2001 From: dinizmauricio Date: Mon, 15 Apr 2024 12:38:45 -0300 Subject: [PATCH 29/37] Update strings.xml Adjusted small detail in a String --- common/src/main/res/values-pt/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index f6f73338..6917d421 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -291,7 +291,7 @@ Mostre uma segunda notificação sem conteúdo para ter um ícone adicional na b Para receber valores do Juggluco:\n- abra o Juggluco\n- acesse as configurações\n- ative a transmissão do Glucodata\n- ative \"de.michelinside.glucodatahandler\" xDrip+ Para receber valores do xDrip+:\n- abra o xDrip+\n- vá em configurações\n- vá em configurações entre aplicativos\n- ative \"Transmitir local\"\n- defina \"Bloqueio de Leituras Irregulares (Noise)\" como \"Send even Extremely noisy signals\" - - habilite \"Broadcast Compatível\"\n- marque \"Identificar receptores\" adicione (se já tiver algo, adicione uma nova linha) \"de.michelinside.glucodatahandler\" + - habilite \"Broadcast Compatível\"\n- marque \"Identificar receptores\" preencha com \"de.michelinside.glucodatahandler\" (sem aspas), use espaço como separador caso já tenha algo preenchido Configurar LibreLinkUp IMPORTANTE: esta não é a conta LibreView!\nPara ativar o LibreLinkUp:\n- abra seu aplicativo FreeStyle Libre e selecione no menu Compartilhar ou Aplicativos conectados\n- ative a conexão LibreLinkUp\n- instale o LibreLinkUp da PlayStore\n- configure seu conta e aguarde o convite Contato From 4800c7e692288db31cf9e5392cc670b2c6d78861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20Deli=C5=9B?= <137992536+froster82@users.noreply.github.com> Date: Mon, 15 Apr 2024 17:55:52 +0200 Subject: [PATCH 30/37] Update strings.xml PL Polish strings done. --- common/src/main/res/values-pl/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index 76f5dc94..41e84095 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -325,6 +325,6 @@ Wyłączono wyśietlanie powiadomień!!!\nGlucoDataHandler może nie działać poprawnie i nie może pokazywać alarmów!!!\nNaciśnij tutaj, aby przejść bezpośrednio do ustawień uprawnień. Brak danych! Najpierw zaakceptuj zaproszenie w aplikacji LibreLinkUp! - Colored status bar icon - If your device supports colored status bar icons, you can also deactivate it to use a white item. + Kolorowa ikona w pasku stanu + Jeżeli telefon obsługuje kolorowe ikony w pasku stanu, możesz wyłączyć tę opcję, by ikona była biała. From 29dc67dd8260f8c81e3ddd7a6029688677def91b Mon Sep 17 00:00:00 2001 From: pachi81 Date: Mon, 15 Apr 2024 17:59:25 +0200 Subject: [PATCH 31/37] v29 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b587f24b..ee0d0bd0 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'org.jetbrains.kotlin.android' version '1.9.23' apply false } -project.ext.set("versionCode", 28) +project.ext.set("versionCode", 29) project.ext.set("versionName", "0.9.12") project.ext.set("compileSdk", 34) project.ext.set("targetSdk", 33) From 40833975dbbceafcd2719da95fce38fb9ff352e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20Deli=C5=9B?= <137992536+froster82@users.noreply.github.com> Date: Mon, 15 Apr 2024 22:09:22 +0200 Subject: [PATCH 32/37] Update strings.xml small correction to one old string --- common/src/main/res/values-pl/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index 41e84095..e4bcf288 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -5,7 +5,7 @@ Wartość Surowe Delta - Tempo + Trend Znacznik czasu Czas IOB/COB Różnica czasu From 1812003eb867c32b8da4987ec73a0b7c6a9fa218 Mon Sep 17 00:00:00 2001 From: pachi81 Date: Tue, 16 Apr 2024 09:32:55 +0200 Subject: [PATCH 33/37] Improve wear phone check --- .../common/GlucoDataService.kt | 10 ++++-- .../common/WearPhoneConnection.kt | 31 ++++++++++++++----- .../GlucoDataServiceMobile.kt | 2 +- .../glucodatahandler/MainActivity.kt | 1 + mobile/src/main/res/layout/notification.xml | 1 + .../glucodatahandler/WaerActivity.kt | 1 + 6 files changed, 35 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt index d10eed4f..bb96f061 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt @@ -97,10 +97,14 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService() { } } - fun checkForConnectedNodes() { + fun checkForConnectedNodes(refreshDataOnly: Boolean = false) { try { - if (connection!=null) - connection!!.checkForConnectedNodes() + Log.d(LOG_ID, "checkForConnectedNodes called for dataOnly=$refreshDataOnly - connection: ${connection!=null}") + if (connection!=null) { + if(!refreshDataOnly) + connection!!.checkForConnectedNodes() + connection!!.checkForNodesWithoutData() + } } catch (exc: Exception) { Log.e( LOG_ID, diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/WearPhoneConnection.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/WearPhoneConnection.kt index bb3e42bc..fcc66ce5 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/WearPhoneConnection.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/WearPhoneConnection.kt @@ -123,6 +123,19 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC } } + fun checkForNodesWithoutData() { + try { + Log.d(LOG_ID, "check for connected nodes without data") + connectedNodes.forEach { node -> + if (!nodeBatteryLevel.containsKey(node.key)) { + sendDataRequest(node.value.id) + } + } + } catch (exc: Exception) { + Log.e(LOG_ID, "checkForNodesWithoutData exception: " + exc.toString()) + } + } + private fun setConnectedNodes(nodes: MutableSet) { val curNodes = connectedNodes.keys.toSortedSet() val newNodes = nodes.map { it.id }.toSortedSet() @@ -142,13 +155,17 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC } } } - val extras = ReceiveData.createExtras() - InternalNotifier.notify(context, NotifySource.CAPILITY_INFO, ReceiveData.createExtras()) - sendMessage(NotifySource.CAPILITY_INFO, extras) // send data request for new node + sendDataRequest() } } - fun checkConnectedNode(nodeId: String) { + private fun sendDataRequest(filterReceiverId: String? = null) { + val extras = ReceiveData.createExtras() + InternalNotifier.notify(context, NotifySource.CAPILITY_INFO, ReceiveData.createExtras()) + sendMessage(NotifySource.CAPILITY_INFO, extras, filterReceiverId = filterReceiverId) // send data request for new node + } + + private fun checkConnectedNode(nodeId: String) { if (!connectedNodes.containsKey(nodeId)) { Log.i(LOG_ID, "Node with id " + nodeId + " not yet connected, check connection!") checkForConnectedNodes() @@ -181,10 +198,10 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC else -> Constants.GLUCODATA_INTENT_MESSAGE_PATH } - fun sendMessage(dataSource: NotifySource, extras: Bundle?, ignoreReceiverId: String? = null, filterReiverId: String? = null) + fun sendMessage(dataSource: NotifySource, extras: Bundle?, ignoreReceiverId: String? = null, filterReceiverId: String? = null) { try { - Log.v(LOG_ID, "sendMessage called for $dataSource filter receiver $filterReiverId ignoring receiver $ignoreReceiverId with extras $extras") + Log.v(LOG_ID, "sendMessage called for $dataSource filter receiver $filterReceiverId ignoring receiver $ignoreReceiverId with extras $extras") if( nodesConnected && dataSource != NotifySource.NODE_BATTERY_LEVEL ) { Log.d(LOG_ID, connectedNodes.size.toString() + " nodes found for sending message for " + dataSource.toString()) if (extras != null && dataSource != NotifySource.BATTERY_LEVEL && BatteryReceiver.batteryPercentage > 0) { @@ -199,7 +216,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC connectedNodes.forEach { node -> Thread { try { - if ((ignoreReceiverId == null && filterReiverId == null) || ignoreReceiverId != node.value.id || filterReiverId == node.value.id) { + if ((ignoreReceiverId == null && filterReceiverId == null) || ignoreReceiverId != node.value.id || filterReceiverId == node.value.id) { if (dataSource == NotifySource.CAPILITY_INFO) Thread.sleep(1000) // wait a bit after the connection has changed sendMessage(node.value, getPath(dataSource), Utils.bundleToBytes(extras), dataSource) diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt index 359eae6e..ab1f32c5 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt @@ -36,7 +36,7 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt fun sendLogcatRequest() { if(connection != null) { Log.d(LOG_ID, "sendLogcatRequest called") - connection!!.sendMessage(NotifySource.LOGCAT_REQUEST, null, filterReiverId = connection!!.pickBestNodeId()) + connection!!.sendMessage(NotifySource.LOGCAT_REQUEST, null, filterReceiverId = connection!!.pickBestNodeId()) } } } diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt index fa2aae41..fc513339 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt @@ -164,6 +164,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface { txtNotificationPermission.visibility = View.GONE GlucoDataServiceMobile.start(this, true) } + GlucoDataService.checkForConnectedNodes(true) } catch (exc: Exception) { Log.e(LOG_ID, "onResume exception: " + exc.message.toString() ) } diff --git a/mobile/src/main/res/layout/notification.xml b/mobile/src/main/res/layout/notification.xml index 93bbd204..64b92990 100644 --- a/mobile/src/main/res/layout/notification.xml +++ b/mobile/src/main/res/layout/notification.xml @@ -31,6 +31,7 @@ android:layout_marginStart="10dp" android:gravity="center_vertical" android:textSize="18dp" + android:text="Please restart phone" android:textColor="@color/text_color" tools:ignore="HardcodedText,SpUsage" /> Date: Tue, 16 Apr 2024 10:23:16 +0200 Subject: [PATCH 34/37] Fix obsolete time handling --- build.gradle | 2 +- .../glucodatahandler/common/notifier/InternalNotifier.kt | 5 +++-- .../glucodatahandler/common/tasks/BackgroundTaskService.kt | 5 ++++- .../glucodatahandler/common/tasks/ElapsedTimeTask.kt | 5 +++-- .../glucodatahandler/common/tasks/ObsoleteTask.kt | 1 + 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index ee0d0bd0..785748c8 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'org.jetbrains.kotlin.android' version '1.9.23' apply false } -project.ext.set("versionCode", 29) +project.ext.set("versionCode", 30) project.ext.set("versionName", "0.9.12") project.ext.set("compileSdk", 34) project.ext.set("targetSdk", 33) diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt index 9e0e0ef6..616ae87e 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/InternalNotifier.kt @@ -16,7 +16,7 @@ object InternalNotifier { { val timeChanged = hasSource(notifier, NotifySource.TIME_VALUE) != sourceFilter.contains(NotifySource.TIME_VALUE) || hasSource(notifier, NotifySource.OBSOLETE_VALUE) != sourceFilter.contains(NotifySource.OBSOLETE_VALUE) - Log.i(LOG_ID, "add notifier " + notifier.toString() + " - filter: " + sourceFilter.toString() ) + Log.i(LOG_ID, "add notifier $notifier - filter: $sourceFilter - timechanged: $timeChanged") notifiers[notifier] = sourceFilter Log.d(LOG_ID, "notifier size: " + notifiers.size.toString() ) if(timeChanged) @@ -64,6 +64,7 @@ object InternalNotifier { } private fun checkTimeNotifierChanged(context: Context) { + Log.v(LOG_ID, "checkTimeNotifierChanged called cur-time-count=$timeNotifierCount - cur-obsolete-count=$obsoleteNotifierCount") var trigger = false val newTimeCount = getNotifierCount(NotifySource.TIME_VALUE) if(timeNotifierCount != newTimeCount) { @@ -73,7 +74,7 @@ object InternalNotifier { if(curCount == 0 || newTimeCount == 0) trigger = true } - val newObsoleteCount = getNotifierCount(NotifySource.TIME_VALUE) + val newObsoleteCount = getNotifierCount(NotifySource.OBSOLETE_VALUE) if(obsoleteNotifierCount != newObsoleteCount) { Log.d(LOG_ID, "Obsolete notifier have changed from $obsoleteNotifierCount to $newObsoleteCount") val curCount = obsoleteNotifierCount diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/BackgroundTaskService.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/BackgroundTaskService.kt index 4769fb83..668dde2f 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/BackgroundTaskService.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/BackgroundTaskService.kt @@ -148,9 +148,12 @@ abstract class BackgroundTaskService(val alarmReqId: Int, protected val LOG_ID: private fun getInterval(): Long { var newInterval = -1L backgroundTaskList.forEach { - if (it.active(elapsedTimeMinute) && newInterval <= 0L || it.getIntervalMinute() < newInterval) + if (it.active(elapsedTimeMinute) && newInterval <= 0L || it.getIntervalMinute() < newInterval) { newInterval = it.getIntervalMinute() + Log.v(LOG_ID, "New interval $newInterval set from task ${it.javaClass.simpleName}") + } } + Log.v(LOG_ID, "using interval $newInterval") return newInterval } diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt index 6eba2fbb..7f1157e8 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt @@ -36,14 +36,15 @@ class ElapsedTimeTask : BackgroundTask() { } override fun active(elapsetTimeMinute: Long): Boolean { - return (relativeTimeValue || interval > 0 || InternalNotifier.hasTimeNotifier) && elapsetTimeMinute <= 60 + Log.v(LOG_ID, "Check active for elapsed time $elapsetTimeMinute min - has notifier: ${InternalNotifier.hasTimeNotifier} - relativeTime: $relativeTime - interval: $interval") + return (relativeTime || interval > 0 || InternalNotifier.hasTimeNotifier) && elapsetTimeMinute <= 60 } override fun checkPreferenceChanged(sharedPreferences: SharedPreferences, key: String?, context: Context): Boolean { if ((key == null || key == Constants.SHARED_PREF_RELATIVE_TIME)) { if( relativeTimeValue != sharedPreferences.getBoolean(Constants.SHARED_PREF_RELATIVE_TIME, true) ) { relativeTimeValue = sharedPreferences.getBoolean(Constants.SHARED_PREF_RELATIVE_TIME, true) - Log.d(LOG_ID, "relative time setting changed to " + relativeTimeValue) + Log.d(LOG_ID, "relative time setting changed to " + relativeTime) InternalNotifier.notify(context, NotifySource.TIME_VALUE, null) return true } diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ObsoleteTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ObsoleteTask.kt index 941e9f7f..107ab8eb 100644 --- a/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ObsoleteTask.kt +++ b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ObsoleteTask.kt @@ -25,6 +25,7 @@ class ObsoleteTask : BackgroundTask() { } override fun active(elapsetTimeMinute: Long): Boolean { + Log.v(LOG_ID, "Check active for elapsed time $elapsetTimeMinute min - has notifier: ${InternalNotifier.hasObsoleteNotifier} - time active: ${(!ElapsedTimeTask.relativeTime && InternalNotifier.hasTimeNotifier)}") return elapsetTimeMinute <= 10 && (InternalNotifier.hasObsoleteNotifier || (!ElapsedTimeTask.relativeTime && InternalNotifier.hasTimeNotifier) ) } } \ No newline at end of file From 263024648225b5d370403338e5694f418d0180fc Mon Sep 17 00:00:00 2001 From: pachi81 Date: Tue, 16 Apr 2024 12:51:14 +0200 Subject: [PATCH 35/37] Try fix play crashes --- .../glucodatahandler/widget/LockScreenWallpaper.kt | 2 +- mobile/src/main/res/layout/notification.xml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt index 13831d24..1031494d 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt @@ -87,7 +87,7 @@ object LockScreenWallpaper : NotifierInterface, SharedPreferences.OnSharedPrefer val wallpaperManager = WallpaperManager.getInstance(context) val wallpaper = if(bitmap != null) createWallpaper(bitmap, context) else null wallpaperManager.setBitmap(wallpaper, null, false, WallpaperManager.FLAG_LOCK) - wallpaper?.recycle() + //wallpaper?.recycle() } catch (exc: Exception) { Log.e(LOG_ID, "updateLockScreen exception: " + exc.message.toString() ) } diff --git a/mobile/src/main/res/layout/notification.xml b/mobile/src/main/res/layout/notification.xml index 64b92990..123b3c71 100644 --- a/mobile/src/main/res/layout/notification.xml +++ b/mobile/src/main/res/layout/notification.xml @@ -22,7 +22,8 @@ android:layout_height="match_parent" android:layout_marginStart="6dp" app:srcCompat="@drawable/icon_question" - android:contentDescription="@string/info_label_rate" /> + android:contentDescription="@string/info_label_rate" + android:translationZ="2dp" /> Date: Tue, 16 Apr 2024 20:11:31 +0200 Subject: [PATCH 36/37] Update strings.xml PL I fine tuned some strings in context. --- common/src/main/res/values-pl/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index e4bcf288..aba474df 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -29,10 +29,10 @@ Anuluj - - bardzo niski - niski - wysoki - bardzo wysoki + bardzo nisko + nisko + wysoko + bardzo wysoko @@ -212,7 +212,7 @@ - Otrzymywanie danych do komplikacji + Odbiór danych do komplikacji Telefon: połączony (%1$s) Telefon: rozłączony From ba10c7caee9480effc4559904c3ee7f9418cfb8e Mon Sep 17 00:00:00 2001 From: pachi81 Date: Thu, 18 Apr 2024 16:36:36 +0200 Subject: [PATCH 37/37] change apk name --- mobile/build.gradle | 2 +- wear/build.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mobile/build.gradle b/mobile/build.gradle index dd21c207..abbd6677 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -36,7 +36,7 @@ android { } second { applicationIdSuffix '.second' - versionNameSuffix '-SECOND' + versionNameSuffix '_SECOND' minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' resValue "string", "app_name", "GDH Second" diff --git a/wear/build.gradle b/wear/build.gradle index 481b3330..8d5f7d50 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -33,7 +33,7 @@ android { } second { applicationIdSuffix '.second' - versionNameSuffix '-SECOND' + versionNameSuffix '_SECOND' minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' resValue "string", "app_name", "GDH Second" @@ -46,7 +46,7 @@ android { // on below line we are setting a // name to our apk as GlucoDataHandler.apk output -> - def name = "GlucoDataHandler-Wear_" + versionName + ".apk" + def name = "GlucoDataHandler_Wear_" + versionName + ".apk" // on below line we are setting the // outputFile Name to our apk file. output.outputFileName = name