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/.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/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/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/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 237f9244..785748c8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,12 @@ // 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 'org.jetbrains.kotlin.android' version '1.8.20' 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.9.23' apply false } -project.ext.set("versionCode", 26) -project.ext.set("versionName", "0.9.11") +project.ext.set("versionCode", 30) +project.ext.set("versionName", "0.9.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/Constants.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/Constants.kt index 2fb9a266..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" @@ -105,6 +106,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" @@ -114,6 +116,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/GlucoDataService.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt index 523b1faf..bb96f061 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,8 +62,10 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService() { } fun start(source: AppSource, context: Context, cls: Class<*>, force: Boolean = false) { - if (!running || force) { + if (!running || !foreground) { + Log.v(LOG_ID, "start called (running: $running - foreground: $foreground)") try { + isRunning = true appSource = source val serviceIntent = Intent( context, @@ -80,20 +81,30 @@ 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 { + Log.v(LOG_ID, "start foreground service") + context.startForegroundService(serviceIntent) + } } catch (exc: Exception) { Log.e( LOG_ID, "start exception: " + exc.message.toString() ) + isRunning = false } } } - 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, @@ -112,10 +123,10 @@ 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)) { + 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) @@ -199,6 +210,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()) } 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/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/WearPhoneConnection.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/WearPhoneConnection.kt index 45b4a7ce..fcc66ce5 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! @@ -122,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() @@ -141,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() @@ -180,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) { @@ -198,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) @@ -391,7 +409,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..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 @@ -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) { - Log.i(LOG_ID, "add notifier " + notifier.toString() + " - filter: " + sourceFilter.toString() ) + 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 - filter: $sourceFilter - timechanged: $timeChanged") 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 { @@ -28,11 +40,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) { @@ -50,4 +62,34 @@ object InternalNotifier { } return count } + + 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) { + 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.OBSOLETE_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 79f4e731..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,9 @@ enum class NotifySource { TIME_VALUE, SOURCE_SETTINGS, SOURCE_STATE_CHANGE, - NOTIFIER_CHANGE, + TIME_NOTIFIER_CHANGE, IOB_COB_CHANGE, - LOGCAT_REQUEST; + 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/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/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/ElapsedTimeTask.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/tasks/ElapsedTimeTask.kt index 11fbd9a2..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 @@ -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) { @@ -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,14 +36,15 @@ class ElapsedTimeTask : BackgroundTask() { } override fun active(elapsetTimeMinute: Long): Boolean { - return (relativeTimeValue || interval > 0) && elapsetTimeMinute <= 60 && InternalNotifier.getNotifierCount(NotifySource.TIME_VALUE) > 0 + 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, false) ) { - relativeTimeValue = sharedPreferences.getBoolean(Constants.SHARED_PREF_RELATIVE_TIME, false) - Log.d(LOG_ID, "relative time setting changed to " + relativeTimeValue) + 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 " + relativeTime) InternalNotifier.notify(context, NotifySource.TIME_VALUE, null) return true } 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..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 @@ -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,9 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, private var token = "" 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" const val LOGIN_ENDPOINT = "/llu/auth/login" @@ -67,6 +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? { @@ -76,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")) { @@ -95,6 +117,7 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, return jsonObj } setLastError("Missing data in response!", 500) + reset() return null } @@ -130,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) @@ -160,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) @@ -240,10 +276,20 @@ class LibreViewSourceTask : DataSourceTask(Constants.SHARED_PREF_LIBRE_ENABLED, val array = jsonObject.optJSONArray("data") if (array != null) { if (array.length() == 0) { - setLastError(GlucoDataService.context!!.getString(R.string.src_libre_setup_librelink)) + 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) + if (data == null) { + setState(SourceState.NO_NEW_VALUE) return } - val data = array.optJSONObject(0) if(data.has("glucoseMeasurement")) { val glucoseData = data.optJSONObject("glucoseMeasurement") if (glucoseData != null) { @@ -271,13 +317,57 @@ 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: ${replaceSensitiveData(body!!)}") + setLastError("Invalid response! Please send logs to developer.") + reset() + return } } } + 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) + if(data.has("patientId") && data.has("firstName") && data.has("lastName")) { + val id = data.getString("patientId") + val name = data.getString("firstName") + " " + data.getString("lastName") + Log.v(LOG_ID, "New patient found: $name") + 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(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) + } + private fun getConnection(firstCall: Boolean = true) { if (login()) { handleGlucoseResponse(httpGet(getUrl(CONNECTION_ENDPOINT), getHeader())) @@ -287,13 +377,17 @@ 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() 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, "")!! InternalNotifier.notify(GlucoDataService.context!!, NotifySource.SOURCE_STATE_CHANGE, null) trigger = true } else { @@ -317,6 +411,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/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()))) { 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..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 { - return elapsetTimeMinute <= 10 && (InternalNotifier.getNotifierCount(NotifySource.OBSOLETE_VALUE) > 0 || (!ElapsedTimeTask.relativeTime && InternalNotifier.getNotifierCount(NotifySource.TIME_VALUE) > 0) ) + 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 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()) 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/java/de/michelinside/glucodatahandler/common/utils/Utils.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/utils/Utils.kt index 4a1eafb0..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 @@ -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 @@ -121,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 { @@ -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/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/common/src/main/res/values-de/strings.xml b/common/src/main/res/values-de/strings.xml index 4a04ffc4..78e7e99b 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 @@ -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 @@ -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 @@ -310,5 +311,14 @@ 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. + 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 + !!!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 a497d0cd..aba474df 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 @@ -29,10 +29,10 @@ Anuluj - - bardzo niski - niski - wysoki - bardzo wysoki + bardzo nisko + nisko + wysoko + bardzo wysoko @@ -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 @@ -129,13 +128,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 stężenia glukozy. Powiadomienia w Android Auto Powiadomienie dla Android Auto. Odstęp między powiadomieniami @@ -213,7 +212,7 @@ - Otrzymywanie danych do komplikacji + Odbiór danych do komplikacji Telefon: połączony (%1$s) Telefon: rozłączony @@ -286,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 @@ -313,4 +313,18 @@ 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ń. + + + 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ń. + + Brak danych! Najpierw zaakceptuj zaproszenie w aplikacji LibreLinkUp! + 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. 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..6917d421 --- /dev/null +++ b/common/src/main/res/values-pt/strings.xml @@ -0,0 +1,335 @@ + + + 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: desconectado + Android Auto: conectado + Android Auto: desconectado + 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. + + 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. + 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. + 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 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) + 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 + 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 + 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 + 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 + 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: desconectado + + 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á 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\" 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 + IOB/COB support + 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 + 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. + + + + 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! + Í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. + diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 8d1b13de..e3270c74 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 @@ -140,13 +139,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 @@ -299,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 @@ -325,4 +325,19 @@ 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 + 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! + 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/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 diff --git a/images/LockscreenWallpaper.png b/images/LockscreenWallpaper.png new file mode 100644 index 00000000..d7bba8b7 Binary files /dev/null and b/images/LockscreenWallpaper.png differ diff --git a/mobile/build.gradle b/mobile/build.gradle index 1c480f8a..abbd6677 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -25,17 +25,18 @@ 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" } debug { - applicationIdSuffix '.debug' minifyEnabled false resValue "string", "app_name", "GlucoDataHandler" } 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/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/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..ab1f32c5 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 { @@ -35,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()) } } } @@ -57,12 +58,14 @@ 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() ) } } 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 @@ -76,6 +79,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/MainActivity.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt index c73b31a0..fc513339 100644 --- a/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt @@ -53,9 +53,11 @@ 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 + private var floatingWidgetItem: MenuItem? = null private val LOG_ID = "GDH.Main" private var requestNotificationPermission = false @@ -76,6 +78,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,8 +161,10 @@ 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) } + GlucoDataService.checkForConnectedNodes(true) } catch (exc: Exception) { Log.e(LOG_ID, "onResume exception: " + exc.message.toString() ) } @@ -167,13 +172,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 - this.requestPermissions(arrayOf(android.Manifest.permission.POST_NOTIFICATIONS), 3) - return false - } + 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 @@ -225,7 +237,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 +246,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() ) @@ -265,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() ) @@ -272,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()) @@ -308,7 +327,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 -> { @@ -324,6 +346,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) { @@ -363,6 +392,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/java/de/michelinside/glucodatahandler/PermanentNotification.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt index 2c0f8266..4d717ef5 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) } @@ -108,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) } } @@ -144,7 +150,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) @@ -153,9 +159,17 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe .setStyle(Notification.DecoratedCustomViewStyle()) .setContentTitle(if (withContent) ReceiveData.getClucoseAsString() else "") .setContentText(if (withContent) "Delta: " + ReceiveData.getDeltaAsString() else "") - .build() - notification.visibility + 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 return notification } @@ -257,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/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/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt new file mode 100644 index 00000000..1031494d --- /dev/null +++ b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt @@ -0,0 +1,153 @@ +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) + 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() ) + } + + } + + 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/activity_main.xml b/mobile/src/main/res/layout/activity_main.xml index 3dd63c46..e64a4490 100644 --- a/mobile/src/main/res/layout/activity_main.xml +++ b/mobile/src/main/res/layout/activity_main.xml @@ -86,13 +86,26 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:ignore="UselessParent"> + @@ -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" /> + + + + + + + + + + + + + + + + + + + \ No newline at end of file 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 @@ + + + + + + + + - 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 @@ -86,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 { 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..9de8dcd5 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 { @@ -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, @@ -221,6 +222,7 @@ class WaerActivity : AppCompatActivity(), NotifierInterface { requestNotificationPermission = false GlucoDataServiceWear.start(this, true) } + GlucoDataService.checkForConnectedNodes(true) } catch( exc: Exception ) { Log.e(LOG_ID, exc.message + "\n" + exc.stackTraceToString()) }