diff --git a/auto/build.gradle b/auto/build.gradle
index ca1c44c2..5b510e64 100644
--- a/auto/build.gradle
+++ b/auto/build.gradle
@@ -11,8 +11,8 @@ android {
applicationId "de.michelinside.glucodataauto"
minSdk rootProject.minSdk
targetSdk rootProject.targetSdk
- versionCode 1000 + rootProject.versionCode
- versionName rootProject.versionName
+ versionCode 1022
+ versionName "0.9.9.7"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -29,7 +29,6 @@ android {
resValue "string", "app_name", "GlucoDataAuto"
}
debug {
- applicationIdSuffix '.debug'
minifyEnabled false
resValue "string", "app_name", "GlucoDataAuto Debug"
}
@@ -59,6 +58,12 @@ android {
buildFeatures {
viewBinding true
}
+ dependenciesInfo {
+ // Disables dependency metadata when building APKs.
+ includeInApk = false
+ // Disables dependency metadata when building Android App Bundles.
+ includeInBundle = false
+ }
}
dependencies {
@@ -72,6 +77,7 @@ dependencies {
implementation "androidx.preference:preference:1.2.1"
implementation "com.jaredrummler:colorpicker:1.1.0"
implementation "androidx.media:media:1.7.0"
+ implementation 'androidx.work:work-runtime:2.9.0'
}
afterEvaluate {
diff --git a/auto/src/main/AndroidManifest.xml b/auto/src/main/AndroidManifest.xml
index e74cd133..46b4c8b6 100644
--- a/auto/src/main/AndroidManifest.xml
+++ b/auto/src/main/AndroidManifest.xml
@@ -6,6 +6,8 @@
+
+
@@ -54,6 +56,17 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
+
+
+
+
+
+
+
+
+
+
"Not connected to a head unit"
+ CarConnection.CONNECTION_TYPE_NATIVE -> "Connected to Android Automotive OS"
+ CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto"
+ else -> "Unknown car connection type"
+ }
+ Log.v(LOG_ID, "onConnectionStateUpdated: " + message + " (" + connectionState.toString() + ")")
+ if (init) {
+ if (connectionState == CarConnection.CONNECTION_TYPE_NOT_CONNECTED) {
+ if(car_connected) {
+ Log.i(LOG_ID, "Exited Car Mode")
+ car_connected = false
+ stop(GlucoDataService.context!!)
+ }
+ } else {
+ if(!car_connected) {
+ Log.i(LOG_ID, "Entered Car Mode")
+ car_connected = true
+ start(GlucoDataService.context!!)
+ }
+ }
+ InternalNotifier.notify(GlucoDataService.context!!, NotifySource.CAR_CONNECTION, null)
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onConnectionStateUpdated exception: " + exc.message.toString() + "\n" + exc.stackTraceToString() )
+ }
+ }
+
+ private fun sendStateBroadcast(context: Context, enabled: Boolean) {
+ try {
+ Log.d(LOG_ID, "Sending state broadcast for state: " + enabled)
+ val intent = Intent(Constants.GLUCODATAAUTO_STATE_ACTION)
+ intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
+ intent.putExtra(Constants.GLUCODATAAUTO_STATE_EXTRA, enabled)
+ context.sendBroadcast(intent)
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "sendStateBroadcast exception: " + exc.toString())
+ }
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.Q)
+ override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+ try {
+ 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)) {
+ 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)
+ else
+ startForeground(NOTIFICATION_ID, getNotification())
+ isForegroundService = true
+ } else if ( isForegroundService && !isForeground ) {
+ isForegroundService = false
+ Log.i(LOG_ID, "Stopping service in foreground!")
+ stopForeground(STOP_FOREGROUND_REMOVE)
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onStartCommand exception: " + exc.toString())
+ }
+ if (isForegroundService)
+ return START_STICKY // keep alive
+ return START_NOT_STICKY
+ }
+
+ override fun onDestroy() {
+ Log.v(LOG_ID, "onDestroy called")
+ super.onDestroy()
+ }
+
+ override fun onBind(intent: Intent?): IBinder? {
+ Log.v(LOG_ID, "onBind called with intent " + intent)
+ return null
+ }
+
+ private fun getNotification(): Notification {
+ Channels.createNotificationChannel(this, ChannelType.ANDROID_AUTO_FOREGROUND)
+
+ val pendingIntent = Utils.getAppIntent(this, MainActivity::class.java, 11, false)
+
+ return Notification.Builder(this, ChannelType.ANDROID_AUTO_FOREGROUND.channelId)
+ .setContentTitle(getString(de.michelinside.glucodatahandler.common.R.string.activity_main_car_connected_label))
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentIntent(pendingIntent)
+ .setOngoing(true)
+ .setOnlyAlertOnce(true)
+ .setAutoCancel(false)
+ .setCategory(Notification.CATEGORY_STATUS)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .build()
+ }
+
+}
\ No newline at end of file
diff --git a/auto/src/main/java/de/michelinside/glucodataauto/MainActivity.kt b/auto/src/main/java/de/michelinside/glucodataauto/MainActivity.kt
index 937ac13b..f7cfb66f 100644
--- a/auto/src/main/java/de/michelinside/glucodataauto/MainActivity.kt
+++ b/auto/src/main/java/de/michelinside/glucodataauto/MainActivity.kt
@@ -1,5 +1,6 @@
package de.michelinside.glucodataauto
+import android.app.Activity
import android.app.AlarmManager
import android.content.Context
import android.content.Intent
@@ -19,7 +20,6 @@ import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.MenuCompat
import androidx.preference.PreferenceManager
-import de.michelinside.glucodataauto.android_auto.CarNotification
import de.michelinside.glucodatahandler.common.Constants
import de.michelinside.glucodatahandler.common.ReceiveData
import de.michelinside.glucodatahandler.common.notifier.InternalNotifier
@@ -27,6 +27,9 @@ import de.michelinside.glucodatahandler.common.notifier.NotifierInterface
import de.michelinside.glucodatahandler.common.notifier.NotifySource
import de.michelinside.glucodatahandler.common.utils.BitmapUtils
import de.michelinside.glucodatahandler.common.utils.Utils
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
import de.michelinside.glucodatahandler.common.R as CR
class MainActivity : AppCompatActivity(), NotifierInterface {
@@ -81,23 +84,18 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
apply()
}
}
- CarNotification.initNotification(this)
+ GlucoDataServiceAuto.init(this)
requestPermission()
} catch (exc: Exception) {
Log.e(LOG_ID, "onCreate exception: " + exc.message.toString() )
}
}
- override fun onDestroy() {
- super.onDestroy()
- if (!CarNotification.connected)
- CarNotification.cleanupNotification(this)
- }
-
override fun onPause() {
try {
super.onPause()
InternalNotifier.remNotifier(this, this)
+ GlucoDataServiceAuto.stopDataSync(this)
Log.v(LOG_ID, "onPause called")
} catch (exc: Exception) {
Log.e(LOG_ID, "onPause exception: " + exc.message.toString() )
@@ -125,6 +123,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
Log.i(LOG_ID, "Notification permission granted")
requestNotificationPermission = false
}
+ GlucoDataServiceAuto.startDataSync(this)
} catch (exc: Exception) {
Log.e(LOG_ID, "onResume exception: " + exc.message.toString() )
}
@@ -214,6 +213,10 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
startActivity(mailIntent)
return true
}
+ R.id.action_save_mobile_logs -> {
+ SaveMobileLogs()
+ return true
+ }
else -> return super.onOptionsItemSelected(item)
}
} catch (exc: Exception) {
@@ -234,7 +237,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
}
viewIcon.setImageIcon(BitmapUtils.getRateAsIcon())
txtLastValue.text = ReceiveData.getAsString(this, CR.string.gda_no_data)
- txtCarInfo.text = if (CarNotification.connected) resources.getText(CR.string.activity_main_car_connected_label) else resources.getText(CR.string.activity_main_car_disconnected_label)
+ txtCarInfo.text = if (GlucoDataServiceAuto.connected) resources.getText(CR.string.activity_main_car_connected_label) else resources.getText(CR.string.activity_main_car_disconnected_label)
} catch (exc: Exception) {
Log.e(LOG_ID, "update exception: " + exc.message.toString() )
}
@@ -244,4 +247,43 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
Log.v(LOG_ID, "new intent received")
update()
}
+
+ private fun SaveMobileLogs() {
+ try {
+ Log.v(LOG_ID, "Save mobile logs called")
+ val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "text/plain"
+ val currentDateandTime = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(
+ Date()
+ )
+ val fileName = "GDA_" + currentDateandTime + ".txt"
+ putExtra(Intent.EXTRA_TITLE, fileName)
+ }
+ startActivityForResult(intent, CREATE_FILE)
+
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "Saving mobile logs exception: " + exc.message.toString() )
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ try {
+ Log.v(LOG_ID, "onActivityResult called for requestCode: " + requestCode + " - resultCode: " + resultCode + " - data: " + Utils.dumpBundle(data?.extras))
+ super.onActivityResult(requestCode, resultCode, data)
+ if (resultCode == Activity.RESULT_OK) {
+ if (requestCode == CREATE_FILE) {
+ data?.data?.also { uri ->
+ Utils.saveLogs(this, uri)
+ }
+ }
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "Saving logs exception: " + exc.message.toString() )
+ }
+ }
+
+ companion object {
+ const val CREATE_FILE = 1
+ }
}
\ No newline at end of file
diff --git a/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarMediaBrowserService.kt b/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarMediaBrowserService.kt
index 14207f7b..f49cba84 100644
--- a/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarMediaBrowserService.kt
+++ b/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarMediaBrowserService.kt
@@ -13,14 +13,13 @@ import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import androidx.media.MediaBrowserServiceCompat
+import de.michelinside.glucodataauto.GlucoDataServiceAuto
import de.michelinside.glucodatahandler.common.Constants
import de.michelinside.glucodatahandler.common.ReceiveData
import de.michelinside.glucodatahandler.common.utils.BitmapUtils
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.BackgroundWorker
-import de.michelinside.glucodatahandler.common.tasks.TimeTaskService
class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, SharedPreferences.OnSharedPreferenceChangeListener {
private val LOG_ID = "GDH.AA.CarMediaBrowserService"
@@ -29,13 +28,17 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh
private lateinit var sharedPref: SharedPreferences
private lateinit var session: MediaSessionCompat
+ companion object {
+ var active = false
+ }
+
override fun onCreate() {
Log.v(LOG_ID, "onCreate")
try {
super.onCreate()
- TimeTaskService.useWorker = true
- CarNotification.initNotification(this)
- ReceiveData.initData(this)
+ active = true
+ GlucoDataServiceAuto.init(this)
+ GlucoDataServiceAuto.start(this)
sharedPref = this.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
sharedPref.registerOnSharedPreferenceChangeListener(this)
@@ -69,11 +72,11 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh
override fun onDestroy() {
Log.v(LOG_ID, "onDestroy")
try {
+ active = false
InternalNotifier.remNotifier(this, this)
sharedPref.unregisterOnSharedPreferenceChangeListener(this)
session.release()
- CarNotification.cleanupNotification(this)
- BackgroundWorker.stopAllWork(this)
+ GlucoDataServiceAuto.stop(this)
super.onDestroy()
} catch (exc: Exception) {
Log.e(LOG_ID, "onDestroy exception: " + exc.message.toString() )
@@ -123,7 +126,8 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh
Log.v(LOG_ID, "onSharedPreferenceChanged called for key " + key)
try {
when(key) {
- Constants.SHARED_PREF_CAR_MEDIA -> {
+ Constants.SHARED_PREF_CAR_MEDIA,
+ Constants.SHARED_PREF_CAR_MEDIA_ICON_STYLE -> {
notifyChildrenChanged(MEDIA_ROOT_ID)
}
}
@@ -133,12 +137,18 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh
}
private fun getIcon(size: Int = 100): Bitmap? {
- return BitmapUtils.textRateToBitmap(ReceiveData.getClucoseAsString(), ReceiveData.rate, ReceiveData.getClucoseColor(), ReceiveData.isObsolete(
- Constants.VALUE_OBSOLETE_SHORT_SEC), ReceiveData.isObsolete(Constants.VALUE_OBSOLETE_SHORT_SEC) && !ReceiveData.isObsolete(),
- size, size)
+ return when(sharedPref.getString(Constants.SHARED_PREF_CAR_MEDIA_ICON_STYLE, Constants.AA_MEDIA_ICON_STYLE_GLUCOSE_TREND)) {
+ Constants.AA_MEDIA_ICON_STYLE_TREND -> {
+ BitmapUtils.getRateAsBitmap(width = size, height = size)
+ }
+ else -> {
+ BitmapUtils.getGlucoseTrendBitmap(width = size, height = size)
+ }
+ }
}
private fun createMediaItem(): MediaBrowserCompat.MediaItem {
+ Log.v(LOG_ID, "createMediaItem called")
if (sharedPref.getBoolean(Constants.SHARED_PREF_CAR_MEDIA,true)) {
session.setPlaybackState(buildState(PlaybackState.STATE_PAUSED))
session.setMetadata(
@@ -159,7 +169,7 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh
}
val mediaDescriptionBuilder = MediaDescriptionCompat.Builder()
.setMediaId(MEDIA_GLUCOSE_ID)
- .setTitle("Delta: " + ReceiveData.getDeltaAsString() + "\n" + ReceiveData.getElapsedTimeMinuteAsString(this))
+ .setTitle(ReceiveData.getClucoseAsString() + " (Δ " + ReceiveData.getDeltaAsString() + ")\n" + ReceiveData.getElapsedTimeMinuteAsString(this))
//.setSubtitle(ReceiveData.timeformat.format(Date(ReceiveData.time)))
.setIconBitmap(getIcon()!!)
return MediaBrowserCompat.MediaItem(
@@ -167,6 +177,7 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh
}
private fun buildState(state: Int): PlaybackStateCompat? {
+ Log.v(LOG_ID, "buildState called for state " + state)
return PlaybackStateCompat.Builder()
.setState(
state,
diff --git a/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarNotification.kt b/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarNotification.kt
index 5f354a24..eca2a026 100644
--- a/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarNotification.kt
+++ b/auto/src/main/java/de/michelinside/glucodataauto/android_auto/CarNotification.kt
@@ -7,7 +7,6 @@ import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
-import androidx.car.app.connection.CarConnection
import androidx.car.app.notification.CarAppExtender
import androidx.car.app.notification.CarNotificationManager
import androidx.core.app.NotificationChannelCompat
@@ -15,12 +14,14 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.Person
import androidx.core.app.RemoteInput
import androidx.core.graphics.drawable.IconCompat
+import de.michelinside.glucodataauto.GlucoDataServiceAuto
import de.michelinside.glucodataauto.R
import de.michelinside.glucodatahandler.common.R as CR
import de.michelinside.glucodatahandler.common.*
import de.michelinside.glucodatahandler.common.notifier.*
import de.michelinside.glucodatahandler.common.utils.BitmapUtils
import de.michelinside.glucodatahandler.common.notification.ChannelType
+import de.michelinside.glucodatahandler.common.tasks.ElapsedTimeTask
import de.michelinside.glucodatahandler.common.utils.Utils
import java.text.DateFormat
import java.util.*
@@ -35,17 +36,14 @@ object CarNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceC
@SuppressLint("StaticFieldLeak")
private lateinit var notificationMgr: CarNotificationManager
private var show_notification = false // default: no notification
- private var car_connected = false
private var notification_interval = 1L // every minute -> always, -1L: only for alarms
+ private var notification_reappear_interval = 5L
const val LAST_NOTIFCATION_TIME = "last_notification_time"
private var last_notification_time = 0L
const val FORCE_NEXT_NOTIFY = "force_next_notify"
private var forceNextNotify = false
- val connected: Boolean get() = car_connected
@SuppressLint("StaticFieldLeak")
private lateinit var notificationCompat: NotificationCompat.Builder
- @SuppressLint("StaticFieldLeak")
- private var context: Context? = null
var enable_notification : Boolean get() {
return show_notification
@@ -89,27 +87,34 @@ object CarNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceC
enable_notification = sharedPref.getBoolean(Constants.SHARED_PREF_CAR_NOTIFICATION, enable_notification)
val alarmOnly = sharedPref.getBoolean(Constants.SHARED_PREF_CAR_NOTIFICATION_ALARM_ONLY, true)
notification_interval = if (alarmOnly) -1 else sharedPref.getInt(Constants.SHARED_PREF_CAR_NOTIFICATION_INTERVAL_NUM, 1).toLong()
- Log.i(LOG_ID, "notification settings changed: active: " + enable_notification + " - interval: " + notification_interval)
- if(init && car_connected && cur_enabled != enable_notification) {
- if(enable_notification)
- showNotification(context!!, false)
- else
- removeNotification()
+ val reappear_active = notification_reappear_interval > 0
+ notification_reappear_interval = if (alarmOnly) 0L else sharedPref.getInt(Constants.SHARED_PREF_CAR_NOTIFICATION_REAPPEAR_INTERVAL, 5).toLong()
+ Log.i(LOG_ID, "notification settings changed: active: " + enable_notification + " - interval: " + notification_interval + " - reappear:" + notification_reappear_interval)
+ if(init && GlucoDataServiceAuto.connected) {
+ if (enable_notification)
+ ElapsedTimeTask.setInterval(notification_reappear_interval)
+ if (cur_enabled != enable_notification) {
+ if (enable_notification) {
+ showNotification(GlucoDataService.context!!, NotifySource.BROADCAST)
+ } else
+ removeNotification()
+ } else if (reappear_active != (notification_reappear_interval > 0) && InternalNotifier.hasNotifier(this) ) {
+ Log.d(LOG_ID, "Update internal notification filter for reappear interval: " + notification_reappear_interval)
+ InternalNotifier.addNotifier(GlucoDataService.context!!, this, getFilter())
+ }
}
}
- fun initNotification(context: Context) {
+ private fun initNotification(context: Context) {
try {
if(!init) {
Log.v(LOG_ID, "initNotification called")
- CarNotification.context = context
val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
migrateSettings(sharedPref)
sharedPref.registerOnSharedPreferenceChangeListener(this)
updateSettings(sharedPref)
loadExtras(context)
createNofitication(context)
- CarConnection(context.applicationContext).type.observeForever(CarNotification::onConnectionStateUpdated)
init = true
}
} catch (exc: Exception) {
@@ -132,57 +137,49 @@ object CarNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceC
}
}
- fun cleanupNotification(context: Context) {
+ private fun cleanupNotification(context: Context) {
try {
if (init) {
- Log.v(LOG_ID, "remNotification called")
- CarConnection(context.applicationContext).type.removeObserver(CarNotification::onConnectionStateUpdated)
+ Log.v(LOG_ID, "cleanupNotification called")
val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
sharedPref.unregisterOnSharedPreferenceChangeListener(this)
init = false
- CarNotification.context = null
}
} catch (exc: Exception) {
- Log.e(LOG_ID, "init exception: " + exc.message.toString())
+ Log.e(LOG_ID, "cleanupNotification exception: " + exc.message.toString())
}
}
- fun onConnectionStateUpdated(connectionState: Int) {
- try {
- val message = when(connectionState) {
- CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit"
- CarConnection.CONNECTION_TYPE_NATIVE -> "Connected to Android Automotive OS"
- CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto"
- else -> "Unknown car connection type"
- }
- Log.v(LOG_ID, "onConnectionStateUpdated: " + message + " (" + connectionState.toString() + ")")
- if (init) {
- if (connectionState == CarConnection.CONNECTION_TYPE_NOT_CONNECTED) {
- Log.i(LOG_ID, "Exited Car Mode")
- removeNotification()
- car_connected = false
- InternalNotifier.remNotifier(context!!, this)
- } else {
- Log.i(LOG_ID, "Entered Car Mode")
- forceNextNotify = false
- car_connected = true
- InternalNotifier.addNotifier(context!!, this, mutableSetOf(
- NotifySource.BROADCAST,
- NotifySource.MESSAGECLIENT,
- NotifySource.OBSOLETE_VALUE))
- showNotification(context!!, false)
- }
- InternalNotifier.notify(context!!, NotifySource.CAR_CONNECTION, null)
- }
- } catch (exc: Exception) {
- Log.e(LOG_ID, "onConnectionStateUpdated exception: " + exc.message.toString() + "\n" + exc.stackTraceToString() )
- }
+ private fun getFilter(): MutableSet {
+ val filter = mutableSetOf(
+ NotifySource.BROADCAST,
+ NotifySource.MESSAGECLIENT)
+ if (notification_reappear_interval > 0)
+ filter.add(NotifySource.TIME_VALUE)
+ return filter
+ }
+
+ fun enable(context: Context) {
+ Log.d(LOG_ID, "enable called")
+ initNotification(context)
+ forceNextNotify = false
+ InternalNotifier.addNotifier(GlucoDataService.context!!, this, getFilter())
+ showNotification(context, NotifySource.BROADCAST)
+ if (enable_notification)
+ ElapsedTimeTask.setInterval(notification_reappear_interval)
+ }
+
+ fun disable(context: Context) {
+ Log.d(LOG_ID, "disable called")
+ removeNotification()
+ InternalNotifier.remNotifier(context, this)
+ cleanupNotification(context)
}
override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) {
Log.v(LOG_ID, "OnNotifyData called for source " + dataSource)
try {
- showNotification(context, dataSource == NotifySource.OBSOLETE_VALUE)
+ showNotification(context, dataSource)
} catch (exc: Exception) {
Log.e(LOG_ID, "OnNotifyData exception: " + exc.message.toString() + "\n" + exc.stackTraceToString() )
}
@@ -191,43 +188,68 @@ object CarNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceC
fun removeNotification() {
notificationMgr.cancel(NOTIFICATION_ID) // remove notification
forceNextNotify = false
+ ElapsedTimeTask.setInterval(0L)
}
private fun getTimeDiffMinute(): Long {
return Utils.round((ReceiveData.time-last_notification_time).toFloat()/60000, 0).toLong()
}
- private fun canShowNotification(isObsolete: Boolean): Boolean {
- if (init && enable_notification && car_connected) {
- if(notification_interval == 1L || ReceiveData.forceAlarm)
- return true
- if (ReceiveData.getAlarmType() == ReceiveData.AlarmType.VERY_LOW || isObsolete) {
- forceNextNotify = true // if obsolete or VERY_LOW, the next value is important!
- return true
- }
- if (forceNextNotify) {
- forceNextNotify = false
- return true
- }
- if (notification_interval > 1L) {
- return getTimeDiffMinute() >= notification_interval
+ private fun canShowNotification(dataSource: NotifySource): Boolean {
+ if (init && enable_notification && GlucoDataServiceAuto.connected) {
+ Log.d(LOG_ID, "Check showing notificiation:"
+ + "\ndataSource: " + dataSource
+ + "\nalarm-type: " + ReceiveData.getAlarmType()
+ + "\nforceNextNotify: " + forceNextNotify
+ + "\nnotify-elapsed: " + getTimeDiffMinute()
+ + "\ndata-elapsed: " + ReceiveData.getElapsedTimeMinute()
+ + "\nnotification_interval: " + notification_interval
+ + "\nnotification_reappear_interval: " + notification_reappear_interval
+ )
+ if (dataSource == NotifySource.BROADCAST || dataSource == NotifySource.MESSAGECLIENT) {
+ if(notification_interval == 1L || ReceiveData.forceAlarm) {
+ Log.v(LOG_ID, "Notification has forced by interval or alarm")
+ return true
+ }
+ if (ReceiveData.getAlarmType() == ReceiveData.AlarmType.VERY_LOW) {
+ Log.v(LOG_ID, "Notification for very low-alarm")
+ forceNextNotify = true // if obsolete or VERY_LOW, the next value is important!
+ return true
+ }
+ if (forceNextNotify) {
+ Log.v(LOG_ID, "Force notification")
+ forceNextNotify = false
+ return true
+ }
+ if (notification_interval > 1L && getTimeDiffMinute() >= notification_interval && ReceiveData.getElapsedTimeMinute() == 0L) {
+ Log.v(LOG_ID, "Interval for new value elapsed")
+ return true
+ }
+ } else if(dataSource == NotifySource.TIME_VALUE) {
+ if (notification_reappear_interval > 0 && ReceiveData.getElapsedTimeMinute().mod(notification_reappear_interval) == 0L) {
+ Log.v(LOG_ID, "reappear after: " + ReceiveData.getElapsedTimeMinute() + " - interval: " + notification_reappear_interval)
+ return true
+ }
}
+ Log.v(LOG_ID, "No notification to show")
return false
}
return false
}
- fun showNotification(context: Context, isObsolete: Boolean) {
+ fun showNotification(context: Context, dataSource: NotifySource) {
try {
- if (canShowNotification(isObsolete)) {
+ if (canShowNotification(dataSource)) {
Log.v(LOG_ID, "showNotification called")
notificationCompat
.setLargeIcon(BitmapUtils.getRateAsBitmap(resizeFactor = 0.75F))
.setWhen(ReceiveData.time)
- .setStyle(createMessageStyle(context, isObsolete))
+ .setStyle(createMessageStyle(context, ReceiveData.isObsolete(Constants.VALUE_OBSOLETE_SHORT_SEC)))
notificationMgr.notify(NOTIFICATION_ID, notificationCompat)
- last_notification_time = ReceiveData.time
- saveExtras(context)
+ if(dataSource != NotifySource.TIME_VALUE) {
+ last_notification_time = ReceiveData.time
+ saveExtras(context)
+ }
}
} catch (exc: Exception) {
Log.e(LOG_ID, "showNotification exception: " + exc.toString() )
@@ -293,7 +315,8 @@ object CarNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceC
when(key) {
Constants.SHARED_PREF_CAR_NOTIFICATION,
Constants.SHARED_PREF_CAR_NOTIFICATION_ALARM_ONLY,
- Constants.SHARED_PREF_CAR_NOTIFICATION_INTERVAL_NUM -> {
+ Constants.SHARED_PREF_CAR_NOTIFICATION_INTERVAL_NUM,
+ Constants.SHARED_PREF_CAR_NOTIFICATION_REAPPEAR_INTERVAL -> {
updateSettings(sharedPreferences!!)
}
}
@@ -327,7 +350,7 @@ object CarNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceC
forceNextNotify = sharedAutoPref.getBoolean(FORCE_NEXT_NOTIFY, forceNextNotify)
}
} catch (exc: Exception) {
- Log.e(LOG_ID, "Saving extras exception: " + exc.toString() + "\n" + exc.stackTraceToString() )
+ Log.e(LOG_ID, "Loading extras exception: " + exc.toString() + "\n" + exc.stackTraceToString() )
}
}
diff --git a/auto/src/main/java/de/michelinside/glucodataauto/preferences/SettingsFragment.kt b/auto/src/main/java/de/michelinside/glucodataauto/preferences/SettingsFragment.kt
index 3a0bdf41..596d72dc 100644
--- a/auto/src/main/java/de/michelinside/glucodataauto/preferences/SettingsFragment.kt
+++ b/auto/src/main/java/de/michelinside/glucodataauto/preferences/SettingsFragment.kt
@@ -73,6 +73,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
try {
setEnableState(sharedPreferences, Constants.SHARED_PREF_CAR_NOTIFICATION_ALARM_ONLY, Constants.SHARED_PREF_CAR_NOTIFICATION)
setEnableState(sharedPreferences, Constants.SHARED_PREF_CAR_NOTIFICATION_INTERVAL_NUM, Constants.SHARED_PREF_CAR_NOTIFICATION, Constants.SHARED_PREF_CAR_NOTIFICATION_ALARM_ONLY)
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_CAR_NOTIFICATION_REAPPEAR_INTERVAL, Constants.SHARED_PREF_CAR_NOTIFICATION, Constants.SHARED_PREF_CAR_NOTIFICATION_ALARM_ONLY)
} catch (exc: Exception) {
Log.e(LOG_ID, "updateEnableStates exception: " + exc.toString())
}
diff --git a/auto/src/main/java/de/michelinside/glucodataauto/receiver/AAPSReceiver.kt b/auto/src/main/java/de/michelinside/glucodataauto/receiver/AAPSReceiver.kt
new file mode 100644
index 00000000..a220fc51
--- /dev/null
+++ b/auto/src/main/java/de/michelinside/glucodataauto/receiver/AAPSReceiver.kt
@@ -0,0 +1,20 @@
+package de.michelinside.glucodataauto.receiver
+
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import de.michelinside.glucodataauto.GlucoDataServiceAuto
+import de.michelinside.glucodatahandler.common.receiver.AAPSReceiver as BaseAAPSReceiver
+
+class AAPSReceiver : BaseAAPSReceiver() {
+ private val LOG_ID = "GDH.AA.AAPSReceiver"
+ override fun onReceive(context: Context, intent: Intent) {
+ try {
+ Log.v(LOG_ID, intent.action + " receveived: " + intent.extras.toString())
+ GlucoDataServiceAuto.init(context)
+ super.onReceive(context, intent)
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "Receive exception: " + exc.message.toString())
+ }
+ }
+}
\ No newline at end of file
diff --git a/auto/src/main/java/de/michelinside/glucodataauto/receiver/GlucoDataActionReceiver.kt b/auto/src/main/java/de/michelinside/glucodataauto/receiver/GlucoDataActionReceiver.kt
index fb8999f4..1998bc5d 100644
--- a/auto/src/main/java/de/michelinside/glucodataauto/receiver/GlucoDataActionReceiver.kt
+++ b/auto/src/main/java/de/michelinside/glucodataauto/receiver/GlucoDataActionReceiver.kt
@@ -4,7 +4,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
-import de.michelinside.glucodataauto.android_auto.CarNotification
+import de.michelinside.glucodataauto.GlucoDataServiceAuto
import de.michelinside.glucodatahandler.common.Constants
import de.michelinside.glucodatahandler.common.ReceiveData
import de.michelinside.glucodatahandler.common.notifier.DataSource
@@ -13,7 +13,7 @@ open class GlucoDataActionReceiver: BroadcastReceiver() {
private val LOG_ID = "GDH.AA.GlucoDataActionReceiver"
override fun onReceive(context: Context, intent: Intent) {
try {
- CarNotification.initNotification(context)
+ GlucoDataServiceAuto.init(context)
val action = intent.action
Log.v(LOG_ID, intent.action + " receveived: " + intent.extras.toString())
if (action != Constants.GLUCODATA_ACTION) {
@@ -28,7 +28,7 @@ open class GlucoDataActionReceiver: BroadcastReceiver() {
ReceiveData.setSettings(context, bundle!!)
extras.remove(Constants.SETTINGS_BUNDLE)
}
- ReceiveData.handleIntent(context, DataSource.PHONE, extras, true)
+ ReceiveData.handleIntent(context, DataSource.GDH, extras, true)
}
} catch (exc: Exception) {
Log.e(LOG_ID, "Receive exception: " + exc.message.toString() )
diff --git a/auto/src/main/java/de/michelinside/glucodataauto/receiver/GlucoDataReceiver.kt b/auto/src/main/java/de/michelinside/glucodataauto/receiver/GlucoDataReceiver.kt
index 3222ead0..da02eb59 100644
--- a/auto/src/main/java/de/michelinside/glucodataauto/receiver/GlucoDataReceiver.kt
+++ b/auto/src/main/java/de/michelinside/glucodataauto/receiver/GlucoDataReceiver.kt
@@ -3,7 +3,7 @@ package de.michelinside.glucodataauto.receiver
import android.content.Context
import android.content.Intent
import android.util.Log
-import de.michelinside.glucodataauto.android_auto.CarNotification
+import de.michelinside.glucodataauto.GlucoDataServiceAuto
import de.michelinside.glucodatahandler.common.receiver.GlucoseDataReceiver
class GlucoDataReceiver : GlucoseDataReceiver() {
@@ -11,7 +11,7 @@ class GlucoDataReceiver : GlucoseDataReceiver() {
override fun onReceive(context: Context, intent: Intent) {
try {
Log.v(LOG_ID, intent.action + " receveived: " + intent.extras.toString())
- CarNotification.initNotification(context)
+ GlucoDataServiceAuto.init(context)
super.onReceive(context, intent)
} catch (exc: Exception) {
Log.e(LOG_ID, "Receive exception: " + exc.message.toString())
diff --git a/auto/src/main/java/de/michelinside/glucodataauto/receiver/XDripReceiver.kt b/auto/src/main/java/de/michelinside/glucodataauto/receiver/XDripReceiver.kt
index 0efc353a..5ac7add4 100644
--- a/auto/src/main/java/de/michelinside/glucodataauto/receiver/XDripReceiver.kt
+++ b/auto/src/main/java/de/michelinside/glucodataauto/receiver/XDripReceiver.kt
@@ -3,7 +3,7 @@ package de.michelinside.glucodataauto.receiver
import android.content.Context
import android.content.Intent
import android.util.Log
-import de.michelinside.glucodataauto.android_auto.CarNotification
+import de.michelinside.glucodataauto.GlucoDataServiceAuto
import de.michelinside.glucodatahandler.common.receiver.XDripBroadcastReceiver
class XDripReceiver : XDripBroadcastReceiver() {
@@ -11,7 +11,7 @@ class XDripReceiver : XDripBroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
try {
Log.v(LOG_ID, intent.action + " receveived: " + intent.extras.toString())
- CarNotification.initNotification(context)
+ GlucoDataServiceAuto.init(context)
super.onReceive(context, intent)
} catch (exc: Exception) {
Log.e(LOG_ID, "Receive exception: " + exc.message.toString())
diff --git a/auto/src/main/res/menu/menu_items.xml b/auto/src/main/res/menu/menu_items.xml
index 85f3d839..7bc8cd06 100644
--- a/auto/src/main/res/menu/menu_items.xml
+++ b/auto/src/main/res/menu/menu_items.xml
@@ -26,6 +26,13 @@
+
+
+
- "30"
- "-1"
+
+ - @string/short_value_arrow
+ - @string/pref_status_bar_icon_trend
+
+
+ - "glucose_trend"
+ - "trend"
+
diff --git a/auto/src/main/res/xml/preferences.xml b/auto/src/main/res/xml/preferences.xml
index 475bc8be..9af69c9b 100644
--- a/auto/src/main/res/xml/preferences.xml
+++ b/auto/src/main/res/xml/preferences.xml
@@ -2,7 +2,7 @@
+
+
+
+
use separate tag for not trigger onChanged events
const val SHARED_PREF_INTERNAL_TAG = "GlucoDataHandlerInternalAppPrefs"
@@ -104,4 +113,8 @@ object Constants {
const val SHARED_PREF_NIGHTSCOUT_IOB_COB="src_ns_iob_cob"
const val SHARED_PREF_DUMMY_VALUES = "dummy_values"
+
+ // 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 7de9120b..3d56ddda 100644
--- a/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt
+++ b/common/src/main/java/de/michelinside/glucodatahandler/common/GlucoDataService.kt
@@ -36,19 +36,25 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService() {
companion object {
private val LOG_ID = "GDH.GlucoDataService"
private var isForegroundService = false
+ @JvmStatic
@SuppressLint("StaticFieldLeak")
- private var connection: WearPhoneConnection? = null
+ protected var connection: WearPhoneConnection? = null
val foreground get() = isForegroundService
const val NOTIFICATION_ID = 123
var appSource = AppSource.NOT_SET
private var isRunning = false
val running get() = isRunning
+ @SuppressLint("StaticFieldLeak")
var service: GlucoDataService? = null
- val context: Context? get() {
+ var context: Context? get() {
if(service != null)
return service!!.applicationContext
- return null
+ return extContext
+ } set(value) {
+ extContext = value
}
+ @SuppressLint("StaticFieldLeak")
+ private var extContext: Context? = null
val sharedPref: SharedPreferences? get() {
if (context != null) {
return context!!.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
@@ -64,16 +70,17 @@ abstract class GlucoDataService(source: AppSource) : WearableListenerService() {
context,
cls
)
+ /*
val sharedPref = context.getSharedPreferences(
Constants.SHARED_PREF_TAG,
Context.MODE_PRIVATE
- )
+ )*/
serviceIntent.putExtra(
Constants.SHARED_PREF_FOREGROUND_SERVICE,
- // on wear foreground is true as default: on phone it is set by notification
- sharedPref.getBoolean(Constants.SHARED_PREF_FOREGROUND_SERVICE, true)
+ // default on wear and phone
+ true//sharedPref.getBoolean(Constants.SHARED_PREF_FOREGROUND_SERVICE, true)
)
- context.startService(serviceIntent)
+ context.startForegroundService(serviceIntent)
} catch (exc: Exception) {
Log.e(
LOG_ID,
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 7d93f4c7..322a0a95 100644
--- a/common/src/main/java/de/michelinside/glucodatahandler/common/ReceiveData.kt
+++ b/common/src/main/java/de/michelinside/glucodatahandler/common/ReceiveData.kt
@@ -16,6 +16,7 @@ import java.math.RoundingMode
import java.text.DateFormat
import java.util.*
import kotlin.math.abs
+import kotlin.time.Duration.Companion.days
object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener {
private const val LOG_ID = "GDH.ReceiveData"
@@ -110,6 +111,12 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener {
}
return Utils.round(deltaValue, 1)
}
+ val deltaValueMgDl: Float get() {
+ if( deltaValue.isNaN() )
+ return deltaValue
+ return Utils.round(deltaValue, 1)
+ }
+
private var isMmolValue = false
val isMmol get() = isMmolValue
private var use5minDelta = false
@@ -148,7 +155,7 @@ object ReceiveData: SharedPreferences.OnSharedPreferenceChangeListener {
context.getString(R.string.info_label_timestamp) + ": " + DateFormat.getTimeInstance(DateFormat.DEFAULT).format(Date(time)) + "\r\n" +
context.getString(R.string.info_label_alarm) + ": " + context.getString(getAlarmType().resId) + (if (forceAlarm) " ⚠" else "" ) + " (" + alarm + ")\r\n" +
(if (isMmol) context.getString(R.string.info_label_raw) + ": " + rawValue + " mg/dl\r\n" else "") +
- ( if (iobCobTime > 0) {
+ ( if (!isIobCobObsolete(1.days.inWholeSeconds.toInt())) {
context.getString(R.string.info_label_iob) + ": " + getIobAsString() + " / " + context.getString(R.string.info_label_cob) + ": " + getCobAsString() + "\r\n" +
context.getString(R.string.info_label_iob_cob_timestamp) + ": " + DateFormat.getTimeInstance(DateFormat.DEFAULT).format(Date(iobCobTime)) + "\r\n"
}
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 a5f27adb..45b4a7ce 100644
--- a/common/src/main/java/de/michelinside/glucodatahandler/common/WearPhoneConnection.kt
+++ b/common/src/main/java/de/michelinside/glucodatahandler/common/WearPhoneConnection.kt
@@ -154,6 +154,12 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC
}
}
+ fun pickBestNodeId(): String? {
+ // Find a nearby node or pick one arbitrarily.
+ return connectedNodes.values.firstOrNull { it.isNearby }?.id ?: connectedNodes.values.firstOrNull()?.id
+ }
+
+
private fun setNodeBatteryLevel(nodeId: String, level: Int) {
if (level >= 0 && (!nodeBatteryLevel.containsKey(nodeId) || nodeBatteryLevel.getValue(nodeId) != level )) {
Log.d(LOG_ID, "Setting new battery level for node " + nodeId + ": " + level + "%")
@@ -170,14 +176,16 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC
NotifySource.CAPILITY_INFO -> Constants.REQUEST_DATA_MESSAGE_PATH
NotifySource.SETTINGS -> Constants.SETTINGS_INTENT_MESSAGE_PATH
NotifySource.SOURCE_SETTINGS -> Constants.SOURCE_SETTINGS_INTENT_MESSAGE_PATH
+ NotifySource.LOGCAT_REQUEST -> Constants.REQUEST_LOGCAT_MESSAGE_PATH
else -> Constants.GLUCODATA_INTENT_MESSAGE_PATH
}
- fun sendMessage(dataSource: NotifySource, extras: Bundle?, receiverId: String? = null)
+ fun sendMessage(dataSource: NotifySource, extras: Bundle?, ignoreReceiverId: String? = null, filterReiverId: String? = null)
{
try {
+ Log.v(LOG_ID, "sendMessage called for $dataSource filter receiver $filterReiverId 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 to")
+ 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) {
extras.putInt(BatteryReceiver.LEVEL, BatteryReceiver.batteryPercentage)
}
@@ -190,7 +198,7 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC
connectedNodes.forEach { node ->
Thread {
try {
- if (receiverId == null || receiverId != node.key) {
+ if ((ignoreReceiverId == null && filterReiverId == null) || ignoreReceiverId != node.value.id || filterReiverId == 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)
@@ -341,11 +349,37 @@ class WearPhoneConnection : MessageClient.OnMessageReceivedListener, CapabilityC
}
}
}
+ if(p0.path == Constants.REQUEST_LOGCAT_MESSAGE_PATH) {
+ sendLogcat(p0.sourceNodeId)
+ }
} catch (exc: Exception) {
Log.e(LOG_ID, "onMessageReceived exception: " + exc.message.toString() )
}
}
+ private fun sendLogcat(phoneNodeId: String) {
+ try {
+ val channelClient = Wearable.getChannelClient(context)
+ val channelTask =
+ channelClient.openChannel(phoneNodeId, Constants.LOGCAT_CHANNEL_PATH)
+ channelTask.addOnSuccessListener { channel ->
+ Thread {
+ try {
+ val outputStream = Tasks.await(channelClient.getOutputStream(channel))
+ Log.d(LOG_ID, "sending Logcat")
+ Utils.saveLogs(outputStream)
+ channelClient.close(channel)
+ Log.d(LOG_ID, "Logcat sent")
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "sendLogcat exception: " + exc.toString())
+ }
+ }.start()
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "sendLogcat exception: " + exc.toString())
+ }
+ }
+
override fun onCapabilityChanged(capabilityInfo: CapabilityInfo) {
try {
Log.i(LOG_ID, "onCapabilityChanged called: " + capabilityInfo.toString())
diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notification/Channels.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notification/Channels.kt
index f8bca263..5d68a8f1 100644
--- a/common/src/main/java/de/michelinside/glucodatahandler/common/notification/Channels.kt
+++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notification/Channels.kt
@@ -10,7 +10,8 @@ enum class ChannelType(val channelId: String, val nameResId: Int, val descrResId
MOBILE_SECOND("GlucoDataNotify_permanent", R.string.mobile_second_notification_name, R.string.mobile_second_notification_descr ),
WORKER("worker_notification_01", R.string.worker_notification_name, R.string.worker_notification_descr, NotificationManager.IMPORTANCE_LOW ),
WEAR_FOREGROUND("glucodatahandler_service_01", R.string.wear_foreground_notification_name, R.string.wear_foreground_notification_descr, NotificationManager.IMPORTANCE_LOW),
- ANDROID_AUTO("GlucoDataNotify_Car", R.string.android_auto_notification_name, R.string.android_auto_notification_descr );
+ ANDROID_AUTO("GlucoDataNotify_Car", R.string.android_auto_notification_name, R.string.android_auto_notification_descr ),
+ ANDROID_AUTO_FOREGROUND("GlucoDataAuto_foreground", R.string.mobile_foreground_notification_name, R.string.mobile_foreground_notification_descr, NotificationManager.IMPORTANCE_LOW );
}
object Channels {
private var notificationMgr: NotificationManager? = null
diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/DataSource.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/DataSource.kt
index 8d46196e..eb8a38e5 100644
--- a/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/DataSource.kt
+++ b/common/src/main/java/de/michelinside/glucodatahandler/common/notifier/DataSource.kt
@@ -10,7 +10,8 @@ enum class DataSource(val resId: Int) {
WEAR(R.string.source_wear),
LIBREVIEW(R.string.source_libreview),
NIGHTSCOUT(R.string.source_nightscout),
- AAPS(R.string.source_aaps);
+ AAPS(R.string.source_aaps),
+ GDH(R.string.source_gdh);
companion object {
fun fromIndex(idx: Int): DataSource {
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 5032ee84..7fec0c65 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
@@ -22,6 +22,10 @@ object InternalNotifier {
notify(context, NotifySource.NOTIFIER_CHANGE, null)
}
+ fun hasNotifier(notifier: NotifierInterface): Boolean {
+ return notifiers.contains(notifier)
+ }
+
fun notify(context: Context, notifySource: NotifySource, extras: Bundle?)
{
Log.d(LOG_ID, "Sending new data to " + notifiers.size.toString() + " notifier(s).")
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 86ec20e0..79f4e731 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
@@ -13,5 +13,6 @@ enum class NotifySource {
SOURCE_SETTINGS,
SOURCE_STATE_CHANGE,
NOTIFIER_CHANGE,
- IOB_COB_CHANGE;
+ IOB_COB_CHANGE,
+ LOGCAT_REQUEST;
}
\ No newline at end of file
diff --git a/common/src/main/java/de/michelinside/glucodatahandler/common/receiver/AAPSReceiver.kt b/common/src/main/java/de/michelinside/glucodatahandler/common/receiver/AAPSReceiver.kt
index 43784c9f..397ab97e 100644
--- a/common/src/main/java/de/michelinside/glucodatahandler/common/receiver/AAPSReceiver.kt
+++ b/common/src/main/java/de/michelinside/glucodatahandler/common/receiver/AAPSReceiver.kt
@@ -21,7 +21,7 @@ open class AAPSReceiver: BroadcastReceiver() {
private const val BG_SLOPE = "slopeArrow" // string: direction arrow as string
private const val IOB_VALUE = "iob" // double
private const val COB_VALUE = "cob" // double: COB [g] or -1 if N/A
- private const val PUMP_STATUS = "pumpStatus" // string
+ //private const val PUMP_STATUS = "pumpStatus" // string
private const val PROFILE_NAME = "profile" // string
}
@@ -37,8 +37,14 @@ open class AAPSReceiver: BroadcastReceiver() {
val glucoExtras = Bundle()
glucoExtras.putLong(ReceiveData.TIME, extras.getLong(BG_TIMESTAMP))
glucoExtras.putInt(ReceiveData.MGDL,mgdl.toInt())
- if(extras.containsKey(BG_UNITS) && extras.getString(BG_UNITS) == "mmol") {
- glucoExtras.putFloat(ReceiveData.GLUCOSECUSTOM, GlucoDataUtils.mgToMmol(mgdl))
+ if(extras.containsKey(BG_UNITS)) {
+ val unit = extras.getString(BG_UNITS)
+ if(unit == "mmol")
+ glucoExtras.putFloat(ReceiveData.GLUCOSECUSTOM, GlucoDataUtils.mgToMmol(mgdl))
+ else if(unit == "mg/dl")
+ glucoExtras.putFloat(ReceiveData.GLUCOSECUSTOM, mgdl)
+ else
+ Log.w(LOG_ID, "No valid unit received: " + unit)
}
val slopeName = extras.getString(BG_SLOPE)
var slope = Float.NaN
@@ -61,9 +67,7 @@ open class AAPSReceiver: BroadcastReceiver() {
} else {
glucoExtras.putFloat(ReceiveData.COB, Float.NaN)
}
- if(extras.containsKey(PUMP_STATUS)) {
- glucoExtras.putString(ReceiveData.SERIAL, extras.getString(PUMP_STATUS))
- } else if(extras.containsKey(PROFILE_NAME)) {
+ if(extras.containsKey(PROFILE_NAME)) {
glucoExtras.putString(ReceiveData.SERIAL, extras.getString(PROFILE_NAME))
}
ReceiveData.handleIntent(context, DataSource.AAPS, glucoExtras)
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 f5c72ba1..4769fb83 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
@@ -2,9 +2,11 @@ package de.michelinside.glucodatahandler.common.tasks
import android.app.AlarmManager
import android.app.PendingIntent
+import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
+import android.os.Build
import android.os.Bundle
import android.util.Log
import de.michelinside.glucodatahandler.common.Constants
@@ -34,6 +36,7 @@ abstract class BackgroundTaskService(val alarmReqId: Int, protected val LOG_ID:
private var lastElapsedMinute = 0L
private var isRunning: Boolean = false
private var currentAlarmTime = 0L
+ protected var hasExactAlarmPermission = false
private val elapsedTimeMinute: Long
get() {
return ReceiveData.getElapsedTimeMinute()
@@ -170,8 +173,10 @@ abstract class BackgroundTaskService(val alarmReqId: Int, protected val LOG_ID:
return delayResult
}
- private fun checkTimer() {
+ fun checkTimer() {
try {
+ if (context == null)
+ return // not yet initialized
val newInterval = getInterval()
val newDelay = getDelay()
if (initialExecution || curInterval != newInterval || curDelay != newDelay) {
@@ -223,11 +228,12 @@ abstract class BackgroundTaskService(val alarmReqId: Int, protected val LOG_ID:
private fun init() {
if (pendingIntent == null) {
Log.v(LOG_ID, "init pendingIntent")
- val i = Intent(context, getAlarmReceiver())
+ val intent = Intent(context, getAlarmReceiver())
+ intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
pendingIntent = PendingIntent.getBroadcast(
context,
alarmReqId,
- i,
+ intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
)
}
@@ -238,20 +244,40 @@ abstract class BackgroundTaskService(val alarmReqId: Int, protected val LOG_ID:
}
}
- private fun startTimer() {
+ fun active(): Boolean {
+ return (alarmManager != null && pendingIntent != null)
+ }
+
+ fun startTimer() {
Log.v(LOG_ID, "startTimer called")
val nextAlarm = getNextAlarm()
if (nextAlarm != null) {
init()
if (alarmManager != null) {
+ hasExactAlarmPermission = true
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ if(!alarmManager!!.canScheduleExactAlarms()) {
+ Log.d(LOG_ID, "Need permission to set exact alarm!")
+ hasExactAlarmPermission = false
+ }
+ }
+
if (currentAlarmTime != nextAlarm.timeInMillis) {
+ if (hasExactAlarmPermission) {
+ alarmManager!!.setExactAndAllowWhileIdle(
+ AlarmManager.RTC_WAKEUP,
+ nextAlarm.timeInMillis,
+ pendingIntent!!
+ )
+ } else {
+ alarmManager!!.setAndAllowWhileIdle(
+ AlarmManager.RTC_WAKEUP,
+ nextAlarm.timeInMillis,
+ pendingIntent!!
+ )
+ }
currentAlarmTime = nextAlarm.timeInMillis
lastElapsedMinute = elapsedTimeMinute
- alarmManager!!.setExactAndAllowWhileIdle(
- AlarmManager.RTC_WAKEUP,
- nextAlarm.timeInMillis,
- pendingIntent!!
- )
} else {
Log.d(LOG_ID, "Ignore next alarm as it is already active")
}
@@ -264,7 +290,7 @@ abstract class BackgroundTaskService(val alarmReqId: Int, protected val LOG_ID:
}
}
- private fun stopTimer() {
+ fun stopTimer() {
if (alarmManager != null && pendingIntent != null) {
Log.v(LOG_ID, "stopTimer called")
alarmManager!!.cancel(pendingIntent!!)
@@ -349,3 +375,17 @@ abstract class BackgroundTaskService(val alarmReqId: Int, protected val LOG_ID:
}
}
+class AlarmPermissionReceiver: BroadcastReceiver() {
+ val LOG_ID = "GDH.Task.AlarmPermissionReceiver"
+ override fun onReceive(context: Context, intent: Intent) {
+ Log.i(LOG_ID, "Received broadcast " + intent.action + ": " + Utils.dumpBundle(intent.extras))
+ if (TimeTaskService.active()) {
+ TimeTaskService.stopTimer()
+ TimeTaskService.startTimer()
+ }
+ if (SourceTaskService.active()) {
+ SourceTaskService.stopTimer()
+ SourceTaskService.startTimer()
+ }
+ }
+}
\ No newline at end of file
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 d67fc083..11fbd9a2 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
@@ -9,15 +9,20 @@ import de.michelinside.glucodatahandler.common.notifier.InternalNotifier
import de.michelinside.glucodatahandler.common.notifier.NotifySource
class ElapsedTimeTask : BackgroundTask() {
- private val LOG_ID = "GDH.Task.Time.ElapsedTask"
-
companion object {
+ private val LOG_ID = "GDH.Task.Time.ElapsedTask"
private var relativeTimeValue = false
+ private var interval = 0L
val relativeTime: Boolean get() {return relativeTimeValue}
+ fun setInterval(new_interval: Long) {
+ Log.d(LOG_ID, "setInterval called for new interval: " + new_interval + " - current: " + interval)
+ interval = new_interval
+ TimeTaskService.checkTimer()
+ }
}
override fun getIntervalMinute(): Long {
- return 1
+ return if (relativeTimeValue) 1L else interval
}
override fun execute(context: Context) {
@@ -28,7 +33,7 @@ class ElapsedTimeTask : BackgroundTask() {
}
override fun active(elapsetTimeMinute: Long): Boolean {
- return relativeTimeValue && elapsetTimeMinute <= 60 && InternalNotifier.getNotifierCount(NotifySource.TIME_VALUE) > 0
+ return (relativeTimeValue || interval > 0) && elapsetTimeMinute <= 60 && InternalNotifier.getNotifierCount(NotifySource.TIME_VALUE) > 0
}
override fun checkPreferenceChanged(sharedPreferences: SharedPreferences, key: String?, context: Context): Boolean {
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 368c4e3f..4a1eafb0 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
@@ -6,15 +6,22 @@ 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
+import android.os.Handler
import android.os.Parcel
import android.provider.Settings
import android.util.Log
import android.util.TypedValue
+import android.widget.Toast
import de.michelinside.glucodatahandler.common.GlucoDataService
+import de.michelinside.glucodatahandler.common.R
+import java.io.FileOutputStream
+import java.io.OutputStream
import java.math.RoundingMode
import java.security.MessageDigest
+import java.util.concurrent.TimeUnit
object Utils {
@@ -69,15 +76,20 @@ object Utils {
}
fun dumpBundle(bundle: Bundle?): String {
- if (bundle == null) {
- return "NULL"
- }
- var string = "{"
- for (key in bundle.keySet()) {
- string += " " + key + " => " + (if (bundle[key] != null) bundle[key].toString() else "NULL") + "\r\n"
+ try {
+ if (bundle == null) {
+ return "NULL"
+ }
+ var string = "{"
+ for (key in bundle.keySet()) {
+ string += " " + key + " => " + (if (bundle[key] != null) bundle[key].toString() else "NULL") + "\r\n"
+ }
+ string += " }"
+ return string
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "dumpBundle exception: " + exc.toString() + "\n" + exc.stackTraceToString() )
}
- string += " }"
- return string
+ return bundle.toString()
}
@SuppressLint("ObsoleteSdkInt")
@@ -172,4 +184,64 @@ object Utils {
return ""
}
+ fun saveLogs(context: Context, uri: Uri) {
+ try {
+ Thread {
+ context.contentResolver.openFileDescriptor(uri, "w")?.use {
+ FileOutputStream(it.fileDescriptor).use { os ->
+ saveLogs(os)
+ }
+ }
+ }.start()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "Saving logs to file exception: " + exc.message.toString() )
+ }
+ }
+
+ fun saveLogs(outputStream: OutputStream) {
+ try {
+ val cmd = "logcat -t 3000"
+ Log.i(LOG_ID, "Getting logcat with command: $cmd")
+ val process = Runtime.getRuntime().exec(cmd)
+ val thread = Thread {
+ try {
+ Log.v(LOG_ID, "read")
+ val buffer = ByteArray(4 * 1024) // or other buffer size
+ var read: Int
+ while (process.inputStream.read(buffer).also { rb -> read = rb } != -1) {
+ Log.v(LOG_ID, "write")
+ outputStream.write(buffer, 0, read)
+ }
+ Log.v(LOG_ID, "flush")
+ outputStream.flush()
+ outputStream.close()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "Writing logs exception: " + exc.message.toString() )
+ }
+ }
+ thread.start()
+ Log.v(LOG_ID, "Waiting for saving logs")
+ process.waitFor(10, TimeUnit.SECONDS)
+ Log.v(LOG_ID, "Process alive: ${process.isAlive}")
+ var count = 0
+ while (process.isAlive && count < 10) {
+ Log.w(LOG_ID, "Killing process")
+ process.destroy()
+ Thread.sleep(1000)
+ count++
+ }
+ Log.v(LOG_ID, "Process exit: ${process.exitValue()}")
+ val text = if (process.exitValue() == 0) {
+ GlucoDataService.context!!.resources.getText(R.string.logcat_save_succeeded)
+ } else {
+ GlucoDataService.context!!.resources.getText(R.string.logcat_save_failed)
+ }
+ Handler(GlucoDataService.context!!.mainLooper).post {
+ Toast.makeText(GlucoDataService.context!!, text, Toast.LENGTH_SHORT).show()
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "Saving logs exception: " + exc.message.toString() )
+ }
+ }
+
}
\ No newline at end of file
diff --git a/common/src/main/res/values-de/strings.xml b/common/src/main/res/values-de/strings.xml
index 926496d2..4a04ffc4 100644
--- a/common/src/main/res/values-de/strings.xml
+++ b/common/src/main/res/values-de/strings.xml
@@ -294,5 +294,21 @@
AAPS
AndroidAPS
Konfiguration von AAPS:\n- AAPS App öffnen\n- \"Konfiguration\" öffnen\n- \"Samsung Tizen\" aktivieren
+ WatchDrip+
+ "Aktiviere WatchDrip+ Verbindung (ohne Graph).\nWICHTIG: Aktiviere \"Enable service\" in WatchDrip+ erst nachdem es hier aktiviert wurde!"
+ Wieder erscheinen
+ Intervall, wann die Benachrichtigung wieder erscheinen soll, wenn es keinen neuen Wert gibt (0 für nie).
+ Stil des Icons/Hintergrundbildes für den Dummy Media Player.
+ Icon/image style
+ Telefon
+ Uhr
+ Logs speichern
+ Logs erfolgreich gespeichert
+ Fehler beim Speichern der Logs!
+ Logs von der Uhr erfolgreich gespeichert
+ Fehler beim Speichern der Logs von der Uhr!
+ 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.
diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml
index ea5e8e2d..a497d0cd 100644
--- a/common/src/main/res/values-pl/strings.xml
+++ b/common/src/main/res/values-pl/strings.xml
@@ -281,12 +281,12 @@
Powiadomienie wyświetlane w Android Auto
IOB
COB
- Włączono blokowanie szumu w ustawieniach aplikacji xDrip+! \nZmień na "Ekstremalnie głośny"!
+ Włączono blokowanie szumu w ustawieniach aplikacji xDrip+! \nZmień na \"Ekstremalnie głośny\"!
Follower (Obserwator)
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 \"
+ 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\"\n- 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
@@ -296,5 +296,21 @@
AAPS
AndroidAPS
Aby odbierać wartości z AAPS:\n- otwórz AAPS\n- przejdź do \"Konfiguracja\"\n- włącz \"Samsung Tizen\"
+ WatchDrip+
+ "Włącz komunikację z WatchDrip+ (bez wykresu).\nWAŻNA UWAGA: opcję \"Enable service\" w WatchDrip+ należy włączyć dopiero po włączeniu tej opcji tutaj!"
+ Pokaż ponownie
+ Czas, po którym powiadomienie pojawi się ponownie, jeśli nie ma nowej wartości. (0 = nigdy).
+ Styl ikony/obrazu odtwarzacza fikcyjnych multimediów.
+ Styl ikony/obrazu
+ Telefon
+ Zegarek
+ Zapisywanie logów
+ Logi zapisane pomyślnie
+ Zapisanie logów nie powiodło się!
+ Logi z zegarka zapisane pomyślnie
+ Zapisanie logów z zegarka nie powiodło się!
+ W przypadku wszystkich zadań związanych z czasem i odstępami czasu aplikacja wymaga udzielenia uprawnień do ich obsługi.\nAplikacja nie dodaje ani nie zmienia żadnych alarmów i przypomnień, które ustawił użytkownik. Wymaga jedynie pozwolenia na uruchamianie wewnętrznych wyzwalaczy.\nJeśli naciśniesz OK, otwarte zostaną ustawienia uprawnień, gdzie można je nadać dla GlucoDataHandler.
+ Zezwalaj na ustawianie alarmów i przypomnień
+ Uprawnienie do ustawiania alarmów i przypomnień jest wyłączone!!!\nGlucoDataHandler może nie działać poprawnie!!!\nNaciśnij tutaj, aby przejść bezpośrednio do ustawień.
diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml
index bd04f5d1..8d1b13de 100644
--- a/common/src/main/res/values/strings.xml
+++ b/common/src/main/res/values/strings.xml
@@ -1,6 +1,7 @@
GlucoDataHandler
+ GlucoDataHandler
Sensor
Value
@@ -308,4 +309,20 @@
AAPS
AndroidAPS
To receive values from AAPS:\n- open AAPS app\n- go to \"Config Builder\"\n- enable \"Samsung Tizen\"
+ WatchDrip+
+ "Enable WatchDrip+ communication (without graph).\nIMPORTANT: enable \"Enable service\" in WatchDrip+ after enabling this setting here!"
+ Reappear
+ Interval when the notification shall reappear if there is no new value. (0 for never).
+ Style of the dummy media player icon/image.
+ Icon/image style
+ Mobile
+ Wear
+ Save logs
+ Logs saved successfully
+ Failed saving logs!
+ Logs from wear saved successfully
+ Failed saving logs from wear!
+ 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.
diff --git a/images/playstore/de/phone_settings_forward.png b/images/playstore/de/phone_settings_forward.png
index 51f1e640..30e7409d 100644
Binary files a/images/playstore/de/phone_settings_forward.png and b/images/playstore/de/phone_settings_forward.png differ
diff --git a/images/playstore/phone_settings_forward.png b/images/playstore/phone_settings_forward.png
index b877807d..6f39fbb0 100644
Binary files a/images/playstore/phone_settings_forward.png and b/images/playstore/phone_settings_forward.png differ
diff --git a/mobile/build.gradle b/mobile/build.gradle
index b5fbe85d..1c480f8a 100644
--- a/mobile/build.gradle
+++ b/mobile/build.gradle
@@ -66,6 +66,12 @@ android {
buildFeatures {
viewBinding true
}
+ dependenciesInfo {
+ // Disables dependency metadata when building APKs.
+ includeInApk = false
+ // Disables dependency metadata when building Android App Bundles.
+ includeInBundle = false
+ }
}
dependencies {
diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml
index 3e6b69fa..4b237cef 100644
--- a/mobile/src/main/AndroidManifest.xml
+++ b/mobile/src/main/AndroidManifest.xml
@@ -7,7 +7,6 @@
-
@@ -214,6 +213,14 @@
+
+
+
+
+
@@ -228,7 +235,6 @@
-
diff --git a/mobile/src/main/java/com/eveningoutpost/dexdrip/services/broadcastservice/models/Settings.java b/mobile/src/main/java/com/eveningoutpost/dexdrip/services/broadcastservice/models/Settings.java
new file mode 100644
index 00000000..a8eebdb7
--- /dev/null
+++ b/mobile/src/main/java/com/eveningoutpost/dexdrip/services/broadcastservice/models/Settings.java
@@ -0,0 +1,59 @@
+package com.eveningoutpost.dexdrip.services.broadcastservice.models;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import androidx.annotation.Keep;
+//import lombok.Setter;
+@Keep
+public class Settings implements Parcelable {
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public Settings createFromParcel(Parcel in) {
+ return new Settings(in);
+ }
+ @Override
+ public Settings[] newArray(int size) {
+ return new Settings[size];
+ }
+ };
+ // @Setter
+ private long graphStart;
+ private long graphEnd;
+ private String apkName;
+ private boolean displayGraph;
+
+ public Settings(Parcel in) {
+ apkName = in.readString();
+ graphStart = in.readLong();
+ graphEnd = in.readLong();
+ displayGraph = in.readInt() == 1;
+ }
+
+ public Settings() {
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ parcel.writeString(apkName);
+ parcel.writeLong(graphStart);
+ parcel.writeLong(graphEnd);
+ parcel.writeInt(displayGraph ? 1 : 0);
+ }
+
+ //
+ @SuppressWarnings("all")
+ public long getGraphStart() {
+ return this.graphStart;
+ }
+
+ @SuppressWarnings("all")
+ public boolean isDisplayGraph() {
+ return this.displayGraph;
+ }
+ //
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt
index 3ddec01e..46bd666b 100644
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt
@@ -10,26 +10,34 @@ import de.michelinside.glucodatahandler.android_auto.CarModeReceiver
import de.michelinside.glucodatahandler.common.*
import de.michelinside.glucodatahandler.common.notifier.*
import de.michelinside.glucodatahandler.common.receiver.XDripBroadcastReceiver
-import de.michelinside.glucodatahandler.common.tasks.ElapsedTimeTask
import de.michelinside.glucodatahandler.common.utils.GlucoDataUtils
-import de.michelinside.glucodatahandler.common.utils.Utils
import de.michelinside.glucodatahandler.tasker.setWearConnectionState
+import de.michelinside.glucodatahandler.watch.WatchDrip
import de.michelinside.glucodatahandler.widget.FloatingWidget
import de.michelinside.glucodatahandler.widget.GlucoseBaseWidget
class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInterface {
- private val LOG_ID = "GDH.GlucoDataServiceMobile"
private lateinit var floatingWidget: FloatingWidget
+
init {
Log.d(LOG_ID, "init called")
InternalNotifier.addNotifier(this, TaskerDataReceiver, mutableSetOf(NotifySource.BROADCAST,NotifySource.IOB_COB_CHANGE,NotifySource.MESSAGECLIENT,NotifySource.OBSOLETE_VALUE))
}
companion object {
+ private val LOG_ID = "GDH.GlucoDataServiceMobile"
fun start(context: Context, force: Boolean = false) {
+ Log.v(LOG_ID, "start called")
start(AppSource.PHONE_APP, context, GlucoDataServiceMobile::class.java, force)
}
+
+ fun sendLogcatRequest() {
+ if(connection != null) {
+ Log.d(LOG_ID, "sendLogcatRequest called")
+ connection!!.sendMessage(NotifySource.LOGCAT_REQUEST, null, filterReiverId = connection!!.pickBestNodeId())
+ }
+ }
}
override fun onCreate() {
@@ -47,6 +55,7 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt
PermanentNotification.create(applicationContext)
CarModeReceiver.init(applicationContext)
GlucoseBaseWidget.updateWidgets(applicationContext)
+ WatchDrip.init(applicationContext)
floatingWidget.create()
} catch (exc: Exception) {
Log.e(LOG_ID, "onCreate exception: " + exc.message.toString() )
@@ -65,6 +74,7 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt
Log.d(LOG_ID, "onDestroy called")
PermanentNotification.destroy()
CarModeReceiver.cleanup(applicationContext)
+ WatchDrip.close(applicationContext)
floatingWidget.destroy()
super.onDestroy()
} catch (exc: Exception) {
@@ -96,21 +106,6 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt
}
}
- private fun sendToGlucoDataAuto(context: Context, extras: Bundle) {
- val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
- if (CarModeReceiver.connected && sharedPref.getBoolean(Constants.SHARED_PREF_SEND_TO_GLUCODATAAUTO, true) && Utils.isPackageAvailable(context, Constants.PACKAGE_GLUCODATAAUTO)) {
- Log.d(LOG_ID, "sendToGlucoDataAuto")
- val intent = Intent(Constants.GLUCODATA_ACTION)
- intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
- val settings = ReceiveData.getSettingsBundle()
- settings.putBoolean(Constants.SHARED_PREF_RELATIVE_TIME, ElapsedTimeTask.relativeTime)
- extras.putBundle(Constants.SETTINGS_BUNDLE, settings)
- intent.putExtras(extras)
- intent.setPackage(Constants.PACKAGE_GLUCODATAAUTO)
- context.sendBroadcast(intent)
- }
- }
-
private fun sendToBangleJS(context: Context) {
val send2Bangle = "require(\"Storage\").writeJSON(\"widbgjs.json\", {" +
"'bg': " + ReceiveData.rawValue.toString() + "," +
@@ -127,7 +122,7 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt
private fun forwardBroadcast(context: Context, extras: Bundle) {
Log.v(LOG_ID, "forwardBroadcast called")
val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
- sendToGlucoDataAuto(context, extras.clone() as Bundle)
+ CarModeReceiver.sendToGlucoDataAuto(context, extras.clone() as Bundle)
if (sharedPref.getBoolean(Constants.SHARED_PREF_SEND_TO_XDRIP, false)) {
val intent = Intent(Constants.XDRIP_ACTION_GLUCOSE_READING)
// always sends time as start time, because it is only set, if the sensorId have changed!
@@ -174,7 +169,7 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt
if (dataSource == NotifySource.CAR_CONNECTION && CarModeReceiver.connected) {
val autoExtras = ReceiveData.createExtras()
if (autoExtras != null)
- sendToGlucoDataAuto(context, autoExtras)
+ CarModeReceiver.sendToGlucoDataAuto(context, autoExtras)
}
if (extras != null) {
if (dataSource == NotifySource.MESSAGECLIENT || dataSource == NotifySource.BROADCAST) {
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt
index f00c21d8..c73b31a0 100644
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/MainActivity.kt
@@ -1,5 +1,6 @@
package de.michelinside.glucodatahandler
+import android.app.Activity
import android.app.AlarmManager
import android.content.Context
import android.content.Intent
@@ -18,22 +19,29 @@ import android.view.View
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
+import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.MenuCompat
import androidx.preference.PreferenceManager
import de.michelinside.glucodatahandler.android_auto.CarModeReceiver
+import de.michelinside.glucodatahandler.common.AppSource
import de.michelinside.glucodatahandler.common.Constants
import de.michelinside.glucodatahandler.common.GlucoDataService
import de.michelinside.glucodatahandler.common.ReceiveData
import de.michelinside.glucodatahandler.common.SourceStateData
-import de.michelinside.glucodatahandler.common.utils.Utils
import de.michelinside.glucodatahandler.common.WearPhoneConnection
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 de.michelinside.glucodatahandler.common.utils.Utils
+import de.michelinside.glucodatahandler.watch.LogcatReceiver
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
import de.michelinside.glucodatahandler.common.R as CR
+
class MainActivity : AppCompatActivity(), NotifierInterface {
private lateinit var txtBgValue: TextView
private lateinit var viewIcon: ImageView
@@ -44,8 +52,10 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
private lateinit var txtSourceInfo: TextView
private lateinit var txtBatteryOptimization: TextView
private lateinit var txtHighContrastEnabled: TextView
+ private lateinit var txtScheduleExactAlarm: TextView
private lateinit var btnSources: Button
private lateinit var sharedPref: SharedPreferences
+ private lateinit var optionsMenu: Menu
private val LOG_ID = "GDH.Main"
private var requestNotificationPermission = false
@@ -65,6 +75,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
txtSourceInfo = findViewById(R.id.txtSourceInfo)
txtBatteryOptimization = findViewById(R.id.txtBatteryOptimization)
txtHighContrastEnabled = findViewById(R.id.txtHighContrastEnabled)
+ txtScheduleExactAlarm = findViewById(R.id.txtScheduleExactAlarm)
btnSources = findViewById(R.id.btnSources)
PreferenceManager.setDefaultValues(this, R.xml.preferences, false)
@@ -140,6 +151,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
NotifySource.CAR_CONNECTION,
NotifySource.OBSOLETE_VALUE,
NotifySource.SOURCE_STATE_CHANGE))
+ checkExactAlarmPermission()
checkBatteryOptimization()
checkHighContrast()
@@ -163,16 +175,52 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
return false
}
}
+ requestExactAlarmPermission()
+ return true
+ }
+
+ private fun canScheduleExactAlarms(): Boolean {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val alarmManager = this.getSystemService(Context.ALARM_SERVICE) as AlarmManager
- if (!alarmManager.canScheduleExactAlarms()) {
- Log.i(LOG_ID, "Request exact alarm permission...")
- startActivity(Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM))
- }
+ return alarmManager.canScheduleExactAlarms()
}
return true
}
+ private fun requestExactAlarmPermission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !canScheduleExactAlarms()) {
+ Log.i(LOG_ID, "Request exact alarm permission...")
+ val builder: AlertDialog.Builder = AlertDialog.Builder(this)
+ builder
+ .setTitle(CR.string.request_exact_alarm_title)
+ .setMessage(CR.string.request_exact_alarm_summary)
+ .setPositiveButton(CR.string.button_ok) { dialog, which ->
+ startActivity(Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM))
+ }
+ .setNegativeButton(CR.string.button_cancel) { dialog, which ->
+ // Do something else.
+ }
+ val dialog: AlertDialog = builder.create()
+ dialog.show()
+ }
+ }
+ private fun checkExactAlarmPermission() {
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !canScheduleExactAlarms()) {
+ Log.w(LOG_ID, "Schedule exact alarm is not active!!!")
+ txtScheduleExactAlarm.visibility = View.VISIBLE
+ txtScheduleExactAlarm.setOnClickListener {
+ startActivity(Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM))
+ }
+ } else {
+ txtScheduleExactAlarm.visibility = View.GONE
+ Log.i(LOG_ID, "Schedule exact alarm is active")
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "checkBatteryOptimization exception: " + exc.message.toString() )
+ }
+ }
+
private fun checkBatteryOptimization() {
try {
val pm = getSystemService(POWER_SERVICE) as PowerManager
@@ -216,6 +264,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
val inflater = menuInflater
inflater.inflate(R.menu.menu_items, menu)
MenuCompat.setGroupDividerEnabled(menu!!, true)
+ optionsMenu = menu
return true
} catch (exc: Exception) {
Log.e(LOG_ID, "onCreateOptionsMenu exception: " + exc.message.toString() )
@@ -262,6 +311,19 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
startActivity(mailIntent)
return true
}
+ R.id.action_save_mobile_logs -> {
+ SaveLogs(AppSource.PHONE_APP)
+ return true
+ }
+ R.id.action_save_wear_logs -> {
+ SaveLogs(AppSource.WEAR_APP)
+ return true
+ }
+ R.id.group_log_title -> {
+ Log.v(LOG_ID, "log group selected")
+ val menuIt: MenuItem = optionsMenu.findItem(R.id.action_save_wear_logs)
+ menuIt.isEnabled = WearPhoneConnection.nodesConnected && !LogcatReceiver.isActive
+ }
else -> return super.onOptionsItemSelected(item)
}
} catch (exc: Exception) {
@@ -288,7 +350,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
else
txtWearInfo.text = resources.getText(CR.string.activity_main_disconnected_label)
if (Utils.isPackageAvailable(this, Constants.PACKAGE_GLUCODATAAUTO)) {
- txtCarInfo.text = if (CarModeReceiver.connected) resources.getText(CR.string.activity_main_car_connected_label) else resources.getText(CR.string.activity_main_car_disconnected_label)
+ txtCarInfo.text = if (CarModeReceiver.AA_connected) resources.getText(CR.string.activity_main_car_connected_label) else resources.getText(CR.string.activity_main_car_disconnected_label)
txtCarInfo.visibility = View.VISIBLE
} else {
txtCarInfo.visibility = View.GONE
@@ -310,4 +372,45 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
Log.v(LOG_ID, "new intent received")
update()
}
+
+ private fun SaveLogs(source: AppSource) {
+ try {
+ Log.v(LOG_ID, "Save logs called for " + source)
+ val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "text/plain"
+ val currentDateandTime = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
+ val fileName = "GDH_" + source + "_" + currentDateandTime + ".txt"
+ putExtra(Intent.EXTRA_TITLE, fileName)
+ }
+ startActivityForResult(intent, if (source == AppSource.WEAR_APP) CREATE_WEAR_FILE else CREATE_PHONE_FILE)
+
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "Saving mobile logs exception: " + exc.message.toString() )
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ try {
+ Log.v(LOG_ID, "onActivityResult called for requestCode: " + requestCode + " - resultCode: " + resultCode + " - data: " + Utils.dumpBundle(data?.extras))
+ super.onActivityResult(requestCode, resultCode, data)
+ if (resultCode == Activity.RESULT_OK) {
+ data?.data?.also { uri ->
+ Log.v(LOG_ID, "Save logs to " + uri)
+ if (requestCode == CREATE_PHONE_FILE) {
+ Utils.saveLogs(this, uri)
+ } else if(requestCode == CREATE_WEAR_FILE) {
+ LogcatReceiver.requestLogs(this, uri)
+ }
+ }
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "Saving logs exception: " + exc.message.toString() )
+ }
+ }
+
+ companion object {
+ const val CREATE_PHONE_FILE = 1
+ const val CREATE_WEAR_FILE = 2
+ }
}
\ No newline at end of file
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/android_auto/CarModeReceiver.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/android_auto/CarModeReceiver.kt
index 3c5c2a52..6789c104 100644
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/android_auto/CarModeReceiver.kt
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/android_auto/CarModeReceiver.kt
@@ -1,23 +1,60 @@
package de.michelinside.glucodatahandler.android_auto
+import android.annotation.SuppressLint
+import android.content.BroadcastReceiver
import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Build
+import android.os.Bundle
import android.util.Log
import androidx.car.app.connection.CarConnection
import de.michelinside.glucodatahandler.common.*
import de.michelinside.glucodatahandler.common.notifier.*
+import de.michelinside.glucodatahandler.common.tasks.ElapsedTimeTask
+import de.michelinside.glucodatahandler.common.utils.Utils
import de.michelinside.glucodatahandler.tasker.setAndroidAutoConnectionState
object CarModeReceiver {
private const val LOG_ID = "GDH.CarModeReceiver"
private var init = false
private var car_connected = false
- val connected: Boolean get() = car_connected
+ private var gda_enabled = false
+ val connected: Boolean get() { // connected to GlucoDataAuto
+ if (!car_connected)
+ return gda_enabled
+ return car_connected
+ }
+
+ val AA_connected: Boolean get() { // connected to Android Auto
+ return car_connected
+ }
+
+
+ class GDAReceiver: BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ Log.d(LOG_ID, "onReceive called for intent " + intent + ": " + Utils.dumpBundle(intent.extras))
+ gda_enabled = intent.getBooleanExtra(Constants.GLUCODATAAUTO_STATE_EXTRA, false)
+ if(!car_connected && gda_enabled) {
+ InternalNotifier.notify(context, NotifySource.CAR_CONNECTION, null)
+ }
+ }
+
+ }
+ private val gdaReceiver = GDAReceiver()
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
fun init(context: Context) {
try {
if(!init) {
Log.v(LOG_ID, "init called")
CarConnection(context).type.observeForever(CarModeReceiver::onConnectionStateUpdated)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ GlucoDataService.context!!.registerReceiver(gdaReceiver, IntentFilter(Constants.GLUCODATAAUTO_STATE_ACTION),
+ Context.RECEIVER_EXPORTED or Context.RECEIVER_VISIBLE_TO_INSTANT_APPS)
+ } else {
+ GlucoDataService.context!!.registerReceiver(gdaReceiver, IntentFilter(Constants.GLUCODATAAUTO_STATE_ACTION))
+ }
init = true
}
} catch (exc: Exception) {
@@ -30,6 +67,7 @@ object CarModeReceiver {
if (init) {
Log.v(LOG_ID, "cleanup called")
CarConnection(context).type.removeObserver(CarModeReceiver::onConnectionStateUpdated)
+ GlucoDataService.context!!.unregisterReceiver(gdaReceiver)
init = false
}
} catch (exc: Exception) {
@@ -46,18 +84,37 @@ object CarModeReceiver {
else -> "Unknown car connection type"
}
Log.d(LOG_ID, "onConnectionStateUpdated: " + message + " (" + connectionState.toString() + ")")
+ val curState = connected
if (connectionState == CarConnection.CONNECTION_TYPE_NOT_CONNECTED) {
- Log.i(LOG_ID, "Exited Car Mode")
- car_connected = false
- GlucoDataService.context?.setAndroidAutoConnectionState(false)
- } else {
+ if (car_connected) {
+ Log.i(LOG_ID, "Exited Car Mode")
+ car_connected = false
+ GlucoDataService.context?.setAndroidAutoConnectionState(false)
+ }
+ } else if(!car_connected){
Log.i(LOG_ID, "Entered Car Mode")
car_connected = true
GlucoDataService.context?.setAndroidAutoConnectionState(true)
}
- InternalNotifier.notify(GlucoDataService.context!!, NotifySource.CAR_CONNECTION, null)
+ if (curState != connected)
+ InternalNotifier.notify(GlucoDataService.context!!, NotifySource.CAR_CONNECTION, null)
} catch (exc: Exception) {
Log.e(LOG_ID, "onConnectionStateUpdated exception: " + exc.message.toString() )
}
}
+
+ fun sendToGlucoDataAuto(context: Context, extras: Bundle) {
+ val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
+ if (connected && sharedPref.getBoolean(Constants.SHARED_PREF_SEND_TO_GLUCODATAAUTO, true) && Utils.isPackageAvailable(context, Constants.PACKAGE_GLUCODATAAUTO)) {
+ Log.d(LOG_ID, "sendToGlucoDataAuto")
+ val intent = Intent(Constants.GLUCODATA_ACTION)
+ intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
+ val settings = ReceiveData.getSettingsBundle()
+ settings.putBoolean(Constants.SHARED_PREF_RELATIVE_TIME, ElapsedTimeTask.relativeTime)
+ extras.putBundle(Constants.SETTINGS_BUNDLE, settings)
+ intent.putExtras(extras)
+ intent.setPackage(Constants.PACKAGE_GLUCODATAAUTO)
+ context.sendBroadcast(intent)
+ }
+ }
}
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/watch/LogcatReceiver.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/watch/LogcatReceiver.kt
new file mode 100644
index 00000000..de0c41f5
--- /dev/null
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/watch/LogcatReceiver.kt
@@ -0,0 +1,128 @@
+package de.michelinside.glucodatahandler.watch
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.net.Uri
+import android.os.Handler
+import android.util.Log
+import android.widget.Toast
+import com.google.android.gms.tasks.Tasks
+import com.google.android.gms.wearable.ChannelClient
+import com.google.android.gms.wearable.Wearable
+import de.michelinside.glucodatahandler.GlucoDataServiceMobile
+import de.michelinside.glucodatahandler.common.GlucoDataService
+import de.michelinside.glucodatahandler.common.R
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
+import java.io.FileOutputStream
+
+object LogcatReceiver : ChannelClient.ChannelCallback() {
+ private val LOG_ID = "GDH.wear.LogcatReceiver"
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
+ private var finished = true
+ private var fileUri: Uri? = null
+ private var channel: ChannelClient.Channel? = null
+
+ val isActive: Boolean get() = !finished
+ @SuppressLint("StaticFieldLeak")
+
+
+ fun registerChannel(context: Context, uri: Uri) {
+ Log.d(LOG_ID, "registerChannel called")
+ fileUri = uri
+ Wearable.getChannelClient(context).registerChannelCallback(this)
+ }
+
+ override fun onChannelOpened(p0: ChannelClient.Channel) {
+ try {
+ super.onChannelOpened(p0)
+ Log.d(LOG_ID, "onChannelOpened")
+ channel = p0
+ scope.launch {
+ try {
+ Log.d(LOG_ID, "receiving...")
+ val inputStream = Tasks.await(Wearable.getChannelClient(GlucoDataService.context!!).getInputStream(p0))
+ Log.d(LOG_ID, "received, save to file " + fileUri)
+ GlucoDataService.context!!.contentResolver.openFileDescriptor(fileUri!!, "w")?.use {
+ FileOutputStream(it.fileDescriptor).use { os ->
+ Log.v(LOG_ID, "read")
+ val buffer = ByteArray(4 * 1024) // or other buffer size
+ var read: Int
+ while (inputStream.read(buffer).also { rb -> read = rb } != -1) {
+ Log.v(LOG_ID, "write")
+ os.write(buffer, 0, read)
+ }
+ Log.v(LOG_ID, "flush")
+ os.flush()
+ }
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "reading input exception: " + exc.message.toString() )
+ }
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onChannelOpened exception: " + exc.message.toString() )
+ }
+ }
+
+ override fun onInputClosed(p0: ChannelClient.Channel, i: Int, i1: Int) {
+ try {
+ super.onInputClosed(p0, i, i1)
+ Log.d(LOG_ID, "onInputClosed")
+ Wearable.getChannelClient(GlucoDataService.context!!).close(p0)
+ channel = null
+ finished = true
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onInputClosed exception: " + exc.message.toString() )
+ }
+ }
+
+ fun waitFor(context: Context) {
+ Thread {
+ try {
+ Log.v(LOG_ID, "Waiting for receiving logs")
+ var count = 0
+ while (!finished && count < 10) {
+ Thread.sleep(1000)
+ count++
+ }
+ var success: Boolean
+ if (!finished) {
+ Log.w(LOG_ID, "Receiving still not finished!")
+ if(channel != null)
+ Wearable.getChannelClient(context).close(channel!!)
+ success = false
+ } else {
+ Log.d(LOG_ID, "Receiving finished!")
+ success = true
+ }
+ Wearable.getChannelClient(context).unregisterChannelCallback(this)
+ Log.d(LOG_ID, "unregisterChannel called")
+ finished = true
+ val text = if (success) {
+ GlucoDataService.context!!.resources.getText(R.string.logcat_wear_save_succeeded)
+ } else {
+ GlucoDataService.context!!.resources.getText(R.string.logcat_wear_save_failed)
+ }
+ Handler(GlucoDataService.context!!.mainLooper).post {
+ Toast.makeText(GlucoDataService.context!!, text, Toast.LENGTH_SHORT).show()
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "waitFor exception: " + exc.message.toString() )
+ }
+ }.start()
+ }
+
+ fun requestLogs(context: Context, uri: Uri) {
+ if (finished) {
+ Log.v(LOG_ID, "request logs to " + uri)
+ finished = false
+ channel = null
+ registerChannel(context, uri)
+ GlucoDataServiceMobile.sendLogcatRequest()
+ waitFor(context)
+ }
+ }
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/watch/WatchDrip.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/watch/WatchDrip.kt
new file mode 100644
index 00000000..0264869c
--- /dev/null
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/watch/WatchDrip.kt
@@ -0,0 +1,298 @@
+package de.michelinside.glucodatahandler.watch
+
+import android.annotation.SuppressLint
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.SharedPreferences
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import de.michelinside.glucodatahandler.common.BuildConfig
+import de.michelinside.glucodatahandler.common.Constants
+import de.michelinside.glucodatahandler.common.GlucoDataService
+import de.michelinside.glucodatahandler.common.ReceiveData
+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.GlucoDataUtils
+import de.michelinside.glucodatahandler.common.utils.Utils
+
+object WatchDrip: SharedPreferences.OnSharedPreferenceChangeListener, NotifierInterface {
+ private val LOG_ID = "GDH.WatchDrip"
+ private var init = false
+ private var active = false
+ const val BROADCAST_SENDER_ACTION = "com.eveningoutpost.dexdrip.watch.wearintegration.BROADCAST_SERVICE_SENDER"
+ const val BROADCAST_RECEIVE_ACTION = "com.eveningoutpost.dexdrip.watch.wearintegration.BROADCAST_SERVICE_RECEIVER"
+ const val EXTRA_FUNCTION = "FUNCTION"
+ const val EXTRA_PACKAGE = "PACKAGE"
+ const val EXTRA_TYPE = "type"
+ const val EXTRA_MESSAGE = "message"
+ const val CMD_UPDATE_BG_FORCE = "update_bg_force"
+ const val CMD_UPDATE_BG = "update_bg"
+ const val CMD_ALARM = "alarm"
+ const val TYPE_ALERT = "BG_ALERT_TYPE"
+ const val TYPE_OTHER_ALERT = "BG_OTHER_ALERT_TYPE"
+ const val TYPE_NO_ALERT = "BG_NO_ALERT_TYPE"
+ val receivers = mutableSetOf()
+ class WatchDripReceiver: BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ Log.v(LOG_ID, "onReceive called")
+ handleIntent(context, intent)
+ }
+
+ }
+
+ private val watchDripReceiver = WatchDripReceiver()
+
+ fun init(context: Context) {
+ try {
+ if (!init) {
+ Log.v(LOG_ID, "init called")
+ val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
+ sharedPref.registerOnSharedPreferenceChangeListener(this)
+ updateSettings(sharedPref)
+ init = true
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "init exception: " + exc.toString() + "\n" + exc.stackTraceToString() )
+ }
+ }
+
+ fun close(context: Context) {
+ try {
+ if (init) {
+ Log.v(LOG_ID, "close called")
+ val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
+ sharedPref.unregisterOnSharedPreferenceChangeListener(this)
+ init = false
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "close exception: " + exc.toString() + "\n" + exc.stackTraceToString() )
+ }
+ }
+
+ private fun handleNewReceiver(pkg: String) {
+ if(!receivers.contains(pkg)) {
+ Log.i(LOG_ID, "Adding new receiver " + pkg)
+ receivers.add(pkg)
+ saveReceivers()
+ }
+ }
+
+ private fun handleIntent(context: Context, intent: Intent) {
+ try {
+ Log.i(LOG_ID, "handleIntent called for " + intent.action + ":\n" + Utils.dumpBundle(intent.extras))
+ if (intent.extras == null) {
+ return
+ }
+ val extras = intent.extras!!
+ if (!extras.containsKey(EXTRA_FUNCTION) || !extras.containsKey(EXTRA_PACKAGE)) {
+ Log.w(LOG_ID, "Missing mandatory extras: " + Utils.dumpBundle(intent.extras))
+ return
+ }
+ val cmd = extras.getString(EXTRA_FUNCTION, "")
+ val pkg = extras.getString(EXTRA_PACKAGE, "")
+ Log.d(LOG_ID, "Command " + cmd + " received for package " + pkg)
+ if (CMD_UPDATE_BG_FORCE.equals(cmd) && pkg != "") {
+ handleNewReceiver(pkg)
+ sendBroadcast(context, CMD_UPDATE_BG_FORCE, pkg)
+ } else {
+ Log.d(LOG_ID, "Unknown command received: " + cmd + " received from " + pkg)
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "handleIntent exception: " + exc.toString() + "\n" + exc.stackTraceToString() )
+ }
+ }
+
+ private fun createBundle(cmd: String): Bundle {
+ return when(cmd) {
+ CMD_ALARM -> createAlarmBundle()
+ else -> createBgBundle(cmd)
+ }
+ }
+
+ private fun createBgBundle(cmd: String): Bundle {
+ val bundle = Bundle()
+ bundle.putString(EXTRA_FUNCTION, cmd)
+ bundle.putDouble("bg.valueMgdl", ReceiveData.rawValue.toDouble())
+ bundle.putDouble("bg.deltaValueMgdl", ReceiveData.deltaValueMgDl.toDouble())
+ bundle.putString("bg.deltaName", GlucoDataUtils.getDexcomLabel(ReceiveData.rate))
+ bundle.putLong("bg.timeStamp", ReceiveData.time)
+ bundle.putBoolean("bg.isStale", ReceiveData.isObsolete(Constants.VALUE_OBSOLETE_SHORT_SEC))
+ bundle.putBoolean("doMgdl", !ReceiveData.isMmol)
+ bundle.putBoolean("bg.isHigh", ReceiveData.getAlarmType() == ReceiveData.AlarmType.VERY_HIGH)
+ bundle.putBoolean("bg.isLow", ReceiveData.getAlarmType() == ReceiveData.AlarmType.VERY_LOW)
+ bundle.putString("pumpJSON", "{}")
+ if (!ReceiveData.isIobCobObsolete(Constants.VALUE_OBSOLETE_SHORT_SEC) && !ReceiveData.iob.isNaN()) {
+ bundle.putString("predict.IOB", ReceiveData.iobString)
+ bundle.putLong("predict.IOB.timeStamp", ReceiveData.iobCobTime)
+ }
+ return bundle
+ }
+
+ private fun createAlarmBundle(): Bundle {
+ val bundle = Bundle()
+ bundle.putString(EXTRA_FUNCTION, CMD_ALARM)
+ bundle.putString(EXTRA_TYPE, getAlertType())
+ bundle.putString(EXTRA_MESSAGE, getAlarmMessage())
+ return bundle
+ }
+
+ private fun getAlertType(): String {
+ return when(ReceiveData.getAlarmType()) {
+ ReceiveData.AlarmType.VERY_LOW,
+ ReceiveData.AlarmType.VERY_HIGH -> TYPE_ALERT
+ ReceiveData.AlarmType.LOW,
+ ReceiveData.AlarmType.HIGH -> TYPE_OTHER_ALERT
+ else -> TYPE_NO_ALERT
+ }
+ }
+
+ private fun getAlarmMessage(): String {
+ return when(ReceiveData.getAlarmType()) {
+ ReceiveData.AlarmType.VERY_LOW -> "VERY LOW " + ReceiveData.getClucoseAsString()
+ ReceiveData.AlarmType.LOW -> "LOW " + ReceiveData.getClucoseAsString()
+ ReceiveData.AlarmType.HIGH -> "HIGH " + ReceiveData.getClucoseAsString()
+ ReceiveData.AlarmType.VERY_HIGH -> "VERY HIGH " + ReceiveData.getClucoseAsString()
+ else -> "No alarm!"
+ }
+ }
+ private fun sendBroadcastToReceiver(context: Context, receiver: String, bundle: Bundle) {
+ Log.d(LOG_ID, "Sending broadcast to " + receiver + ":\n" + Utils.dumpBundle(bundle))
+ val intent = Intent(BROADCAST_SENDER_ACTION)
+ intent.putExtras(bundle)
+ intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
+ intent.setPackage(receiver)
+ context.sendBroadcast(intent)
+ }
+
+ private fun sendBroadcast(context: Context, cmd: String, receiver: String? = null) {
+ try {
+ if (receiver != null || receivers.size > 0) {
+ val bundle = createBundle(cmd)
+ if (receiver != null) {
+ sendBroadcastToReceiver(context, receiver, bundle)
+ } else {
+ receivers.forEach {
+ sendBroadcastToReceiver(context, it, bundle)
+ }
+ }
+ } else {
+ Log.i(LOG_ID, "No receiver found for sending broadcast")
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "sendBroadcast exception: " + exc.toString() + "\n" + exc.stackTraceToString() )
+ }
+ }
+
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
+ private fun activate() {
+ try {
+ if (GlucoDataService.context != null) {
+ Log.v(LOG_ID, "activate called")
+ loadReceivers()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ GlucoDataService.context!!.registerReceiver(watchDripReceiver, IntentFilter(BROADCAST_RECEIVE_ACTION),
+ Context.RECEIVER_EXPORTED or Context.RECEIVER_VISIBLE_TO_INSTANT_APPS)
+ } else {
+ GlucoDataService.context!!.registerReceiver(watchDripReceiver, IntentFilter(BROADCAST_RECEIVE_ACTION))
+ }
+ InternalNotifier.addNotifier(GlucoDataService.context!!, this, mutableSetOf(
+ NotifySource.BROADCAST,
+ NotifySource.MESSAGECLIENT,
+ NotifySource.IOB_COB_CHANGE,
+ NotifySource.OBSOLETE_VALUE))
+ active = true
+ if (receivers.size > 0) {
+ sendBroadcast(GlucoDataService.context!!, CMD_UPDATE_BG)
+ }
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "activate exception: " + exc.toString() + "\n" + exc.stackTraceToString() )
+ }
+ }
+
+ private fun deactivate() {
+ try {
+ if (GlucoDataService.context != null && active) {
+ Log.v(LOG_ID, "deactivate called")
+ InternalNotifier.remNotifier(GlucoDataService.context!!, this)
+ GlucoDataService.context!!.unregisterReceiver(watchDripReceiver)
+ active = false
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "deactivate exception: " + exc.toString() + "\n" + exc.stackTraceToString() )
+ }
+ }
+
+ private fun loadReceivers() {
+ try {
+ val sharedExtraPref = GlucoDataService.context!!.getSharedPreferences(Constants.SHARED_PREF_EXTRAS_TAG, Context.MODE_PRIVATE)
+ if (sharedExtraPref.contains(Constants.SHARED_PREF_WATCHDRIP_RECEIVERS)) {
+ Log.i(LOG_ID, "Reading saved values...")
+ val savedReceivers = sharedExtraPref.getStringSet(Constants.SHARED_PREF_WATCHDRIP_RECEIVERS, HashSet())
+ if (!savedReceivers.isNullOrEmpty()) {
+ Log.i(LOG_ID, "Loading receivers: " + savedReceivers)
+ receivers.addAll(savedReceivers)
+ }
+ }
+ if(BuildConfig.DEBUG && receivers.isEmpty()) {
+ receivers.add("dummy")
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "Loading receivers exception: " + exc.toString() + "\n" + exc.stackTraceToString() )
+ }
+ }
+
+ private fun saveReceivers() {
+ try {
+ Log.d(LOG_ID, "Saving receivers")
+ // use own tag to prevent trigger onChange event at every time!
+ val sharedExtraPref = GlucoDataService.context!!.getSharedPreferences(Constants.SHARED_PREF_EXTRAS_TAG, Context.MODE_PRIVATE)
+ with(sharedExtraPref.edit()) {
+ putStringSet(Constants.SHARED_PREF_WATCHDRIP_RECEIVERS, receivers)
+ apply()
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "Saving receivers exception: " + exc.toString() + "\n" + exc.stackTraceToString() )
+ }
+ }
+
+ private fun updateSettings(sharedPreferences: SharedPreferences) {
+ try {
+ Log.v(LOG_ID, "updateSettings called")
+ if (sharedPreferences.getBoolean(Constants.SHARED_PREF_WATCHDRIP, false))
+ activate()
+ else
+ deactivate()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "updateSettings exception: " + exc.toString() + "\n" + exc.stackTraceToString() )
+ }
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
+ try {
+ Log.v(LOG_ID, "onSharedPreferenceChanged called for key " + key)
+ when(key) {
+ Constants.SHARED_PREF_WATCHDRIP -> {
+ updateSettings(sharedPreferences!!)
+ }
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onSharedPreferenceChanged exception: " + exc.toString() + "\n" + exc.stackTraceToString() )
+ }
+ }
+
+ override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) {
+ try {
+ Log.v(LOG_ID, "OnNotifyData called for source " + dataSource)
+ sendBroadcast(context, CMD_UPDATE_BG)
+ if (ReceiveData.forceAlarm && dataSource != NotifySource.IOB_COB_CHANGE && ReceiveData.alarm > 0)
+ sendBroadcast(context, CMD_ALARM)
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "OnNotifyData exception: " + exc.toString() + "\n" + exc.stackTraceToString() )
+ }
+ }
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/FloatingWidget.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/FloatingWidget.kt
index 5ea149e9..25dad0a6 100644
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/FloatingWidget.kt
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/FloatingWidget.kt
@@ -43,34 +43,57 @@ class FloatingWidget(val context: Context) : NotifierInterface, SharedPreference
@SuppressLint("InflateParams")
fun create() {
- Log.d(LOG_ID, "create called")
- sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
- sharedPref.registerOnSharedPreferenceChangeListener(this)
+ try {
+ Log.d(LOG_ID, "create called")
+ sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
+ sharedPref.registerOnSharedPreferenceChangeListener(this)
- sharedInternalPref = context.getSharedPreferences(Constants.SHARED_PREF_INTERNAL_TAG, Context.MODE_PRIVATE)
+ sharedInternalPref = context.getSharedPreferences(Constants.SHARED_PREF_INTERNAL_TAG, Context.MODE_PRIVATE)
- //getting the widget layout from xml using layout inflater
- floatingView = LayoutInflater.from(context).inflate(R.layout.floating_widget, null)
- txtBgValue = floatingView.findViewById(R.id.glucose)
- viewIcon = floatingView.findViewById(R.id.trendImage)
- txtDelta = floatingView.findViewById(R.id.deltaText)
- txtTime = floatingView.findViewById(R.id.timeText)
- txtIob = floatingView.findViewById(R.id.iobText)
- txtCob = floatingView.findViewById(R.id.cobText)
- //setting the layout parameters
- update()
+ //getting the widget layout from xml using layout inflater
+ floatingView = LayoutInflater.from(context).inflate(R.layout.floating_widget, null)
+ txtBgValue = floatingView.findViewById(R.id.glucose)
+ viewIcon = floatingView.findViewById(R.id.trendImage)
+ txtDelta = floatingView.findViewById(R.id.deltaText)
+ txtTime = floatingView.findViewById(R.id.timeText)
+ txtIob = floatingView.findViewById(R.id.iobText)
+ txtCob = floatingView.findViewById(R.id.cobText)
+ //setting the layout parameters
+ update()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "create exception: " + exc.message.toString() )
+ }
}
fun destroy() {
- Log.d(LOG_ID, "destroy called")
- sharedPref.unregisterOnSharedPreferenceChangeListener(this)
- remove()
+ try {
+ Log.d(LOG_ID, "destroy called")
+ sharedPref.unregisterOnSharedPreferenceChangeListener(this)
+ remove()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "destroy exception: " + exc.message.toString() )
+ }
}
private fun remove() {
Log.d(LOG_ID, "remove called")
- InternalNotifier.remNotifier(context, this)
- if (windowManager != null) windowManager?.removeView(floatingView)
+ try {
+ InternalNotifier.remNotifier(context, this)
+ if (windowManager != null) {
+ try {
+ with(sharedInternalPref.edit()) {
+ putInt(Constants.SHARED_PREF_FLOATING_WIDGET_X,params.x)
+ putInt(Constants.SHARED_PREF_FLOATING_WIDGET_Y,params.y)
+ apply()
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "saving pos exception: " + exc.message.toString() )
+ }
+ windowManager?.removeView(floatingView)
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "remove exception: " + exc.message.toString() )
+ }
windowManager = null
}
@@ -182,89 +205,97 @@ class FloatingWidget(val context: Context) : NotifierInterface, SharedPreference
}
private fun createWindow() {
- Log.d(LOG_ID, "create window")
- params = WindowManager.LayoutParams(
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT
- )
- params.gravity = Gravity.TOP or Gravity.START
- params.x = maxOf(sharedInternalPref.getInt(Constants.SHARED_PREF_FLOATING_WIDGET_X, 100), 0)
- params.y = maxOf(sharedInternalPref.getInt(Constants.SHARED_PREF_FLOATING_WIDGET_Y, 100), 0)
+ try {
+ Log.d(LOG_ID, "create window")
+ params = WindowManager.LayoutParams(
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT
+ )
+ params.gravity = Gravity.TOP or Gravity.START
+ params.x = maxOf(sharedInternalPref.getInt(Constants.SHARED_PREF_FLOATING_WIDGET_X, 100), 0)
+ params.y = maxOf(sharedInternalPref.getInt(Constants.SHARED_PREF_FLOATING_WIDGET_Y, 100), 0)
- windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager?
- windowManager!!.addView(floatingView, params)
+ windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager?
+ windowManager!!.addView(floatingView, params)
- val widget = floatingView.findViewById(R.id.widget)
- widget.setBackgroundColor(Utils.getBackgroundColor(sharedPref.getInt(Constants.SHARED_PREF_FLOATING_WIDGET_TRANSPARENCY, 3)))
- widget.setOnClickListener {
- Log.d(LOG_ID, "onClick called")
- var launchIntent: Intent? =
- context.packageManager.getLaunchIntentForPackage("tk.glucodata")
- if (launchIntent == null) {
- launchIntent = Intent(context, MainActivity::class.java)
+ val widget = floatingView.findViewById(R.id.widget)
+ widget.setBackgroundColor(Utils.getBackgroundColor(sharedPref.getInt(Constants.SHARED_PREF_FLOATING_WIDGET_TRANSPARENCY, 3)))
+ widget.setOnClickListener {
+ Log.d(LOG_ID, "onClick called")
+ var launchIntent: Intent? =
+ context.packageManager.getLaunchIntentForPackage("tk.glucodata")
+ if (launchIntent == null) {
+ launchIntent = Intent(context, MainActivity::class.java)
+ }
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(launchIntent)
}
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- context.startActivity(launchIntent)
- }
- widget.setOnLongClickListener {
- Log.d(LOG_ID, "onLongClick called")
- with(sharedPref.edit()) {
- putBoolean(Constants.SHARED_PREF_FLOATING_WIDGET, false)
- apply()
+ widget.setOnLongClickListener {
+ Log.d(LOG_ID, "onLongClick called")
+ with(sharedPref.edit()) {
+ putBoolean(Constants.SHARED_PREF_FLOATING_WIDGET, false)
+ apply()
+ }
+ remove()
+ true
}
- remove()
- true
- }
- widget.setOnTouchListener(object : OnTouchListener {
- private var initialX = 0
- private var initialY = 0
- private var initialTouchX = 0f
- private var initialTouchY = 0f
- private var startClickTime : Long = 0
- override fun onTouch(v: View, event: MotionEvent): Boolean {
- when (event.action) {
- MotionEvent.ACTION_DOWN -> {
- initialX = params.x
- initialY = params.y
- initialTouchX = event.rawX
- initialTouchY = event.rawY
- startClickTime = Calendar.getInstance().timeInMillis
- return true
- }
- MotionEvent.ACTION_UP -> {
- // only check duration, if there was no movement...
- with(sharedInternalPref.edit()) {
- putInt(Constants.SHARED_PREF_FLOATING_WIDGET_X,params.x)
- putInt(Constants.SHARED_PREF_FLOATING_WIDGET_Y,params.y)
- apply()
- }
- if (Math.abs(params.x - initialX) < 50 && Math.abs(params.y - initialY) < 50 ) {
- val duration = Calendar.getInstance().timeInMillis - startClickTime
- Log.d(LOG_ID, "Duration: " + duration.toString() + " - x=" + Math.abs(params.x - initialX) + " y=" + Math.abs(params.y - initialY) )
- if (duration < 200) {
- Log.d(LOG_ID, "Call onClick after " + duration.toString() + "ms")
- widget.performClick()
- } else if (duration > 4000) {
- Log.d(LOG_ID, "Call onLongClick after " + duration.toString() + "ms")
- widget.performLongClick()
+ widget.setOnTouchListener(object : OnTouchListener {
+ private var initialX = 0
+ private var initialY = 0
+ private var initialTouchX = 0f
+ private var initialTouchY = 0f
+ private var startClickTime : Long = 0
+ override fun onTouch(v: View, event: MotionEvent): Boolean {
+ try {
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ initialX = params.x
+ initialY = params.y
+ initialTouchX = event.rawX
+ initialTouchY = event.rawY
+ startClickTime = Calendar.getInstance().timeInMillis
+ return true
+ }
+ MotionEvent.ACTION_UP -> {
+ // only check duration, if there was no movement...
+ with(sharedInternalPref.edit()) {
+ putInt(Constants.SHARED_PREF_FLOATING_WIDGET_X,params.x)
+ putInt(Constants.SHARED_PREF_FLOATING_WIDGET_Y,params.y)
+ apply()
+ }
+ if (Math.abs(params.x - initialX) < 50 && Math.abs(params.y - initialY) < 50 ) {
+ val duration = Calendar.getInstance().timeInMillis - startClickTime
+ Log.d(LOG_ID, "Duration: " + duration.toString() + " - x=" + Math.abs(params.x - initialX) + " y=" + Math.abs(params.y - initialY) )
+ if (duration < 200) {
+ Log.d(LOG_ID, "Call onClick after " + duration.toString() + "ms")
+ widget.performClick()
+ } else if (duration > 4000) {
+ Log.d(LOG_ID, "Call onLongClick after " + duration.toString() + "ms")
+ widget.performLongClick()
+ }
+ }
+ return true
+ }
+ MotionEvent.ACTION_MOVE -> {
+ //this code is helping the widget to move around the screen with fingers
+ params.x = initialX + (event.rawX - initialTouchX).toInt()
+ params.y = initialY + (event.rawY - initialTouchY).toInt()
+ windowManager!!.updateViewLayout(floatingView, params)
+ return true
}
}
- return true
- }
- MotionEvent.ACTION_MOVE -> {
- //this code is helping the widget to move around the screen with fingers
- params.x = initialX + (event.rawX - initialTouchX).toInt()
- params.y = initialY + (event.rawY - initialTouchY).toInt()
- windowManager!!.updateViewLayout(floatingView, params)
- return true
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onTouch exception: " + exc.toString() )
}
+ return false
}
- return false
- }
- })
+ })
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "createWindow exception: " + exc.message.toString() )
+ }
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
diff --git a/mobile/src/main/res/layout/activity_main.xml b/mobile/src/main/res/layout/activity_main.xml
index 81c7c663..3dd63c46 100644
--- a/mobile/src/main/res/layout/activity_main.xml
+++ b/mobile/src/main/res/layout/activity_main.xml
@@ -86,6 +86,18 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="UselessParent">
+
+
+ -
+
+
+
-
+ app:initialExpandedChildrenCount="1">
+
-
@@ -77,6 +76,14 @@
+
+
+
+
+
= Build.VERSION_CODES.S) {
val alarmManager = this.getSystemService(Context.ALARM_SERVICE) as AlarmManager
- if (!alarmManager.canScheduleExactAlarms()) {
- Log.i(LOG_ID, "Request exact alarm permission...")
- startActivity(Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM))
- }
+ return alarmManager.canScheduleExactAlarms()
}
return true
}
+ private fun requestExactAlarmPermission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !canScheduleExactAlarms()) {
+ Log.i(LOG_ID, "Request exact alarm permission...")
+ startActivity(Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM))
+ }
+ }
+ private fun checkExactAlarmPermission() {
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !canScheduleExactAlarms()) {
+ Log.w(LOG_ID, "Schedule exact alarm is not active!!!")
+ txtScheduleExactAlarm.visibility = View.VISIBLE
+ txtScheduleExactAlarm.setOnClickListener {
+ startActivity(Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM))
+ }
+ } else {
+ txtScheduleExactAlarm.visibility = View.GONE
+ Log.i(LOG_ID, "Schedule exact alarm is active")
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "checkBatteryOptimization exception: " + exc.message.toString() )
+ }
+ }
+
private fun checkHighContrast() {
try {
if (Utils.isHighContrastTextEnabled(this)) {
diff --git a/wear/src/main/res/layout/activity_waer.xml b/wear/src/main/res/layout/activity_waer.xml
index d17e5a6b..9a557c0c 100644
--- a/wear/src/main/res/layout/activity_waer.xml
+++ b/wear/src/main/res/layout/activity_waer.xml
@@ -73,6 +73,16 @@
android:textColor="#FFFFFF"
android:textSize="14sp" />
+
+