diff --git a/.idea/misc.xml b/.idea/misc.xml
index 48c257b4..ea848077 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,14 +1,12 @@
-
-
-
-
-
-
-
-
+
+
\ No newline at end of file
diff --git a/auto/build.gradle b/auto/build.gradle
index 884fcb75..490c2650 100644
--- a/auto/build.gradle
+++ b/auto/build.gradle
@@ -3,6 +3,8 @@ plugins {
id 'org.jetbrains.kotlin.android'
}
+project.ext.set("releasePath", "D:/Daten/Michel/OneDrive/Projekte/Release")
+
android {
namespace 'de.michelinside.glucodataauto'
compileSdk rootProject.compileSdk
@@ -12,7 +14,7 @@ android {
minSdk rootProject.minSdk
targetSdk rootProject.targetSdk
versionCode 1025
- versionName "0.9.10"
+ versionName "1.0-beta1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -29,6 +31,7 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-dev-rules.pro'
versionNameSuffix '-dev'
resValue "string", "app_name", "GlucoDataAuto"
+ signingConfig signingConfigs.debug
}
debug {
minifyEnabled false
@@ -69,9 +72,9 @@ android {
}
dependencies {
- implementation 'androidx.core:core-ktx:1.12.0'
+ implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
- implementation 'com.google.android.material:material:1.11.0'
+ implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.joaomgcd:taskerpluginlibrary:0.4.4'
implementation project(path: ':common')
diff --git a/auto/proguard-dev-rules.pro b/auto/proguard-dev-rules.pro
new file mode 100644
index 00000000..9f50960e
--- /dev/null
+++ b/auto/proguard-dev-rules.pro
@@ -0,0 +1,32 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+-dontwarn **
+-keep class **
+-keepclassmembers class *{*;}
+-keepattributes *
+
+# --------------------------------------------------------------------
+# REMOVE all debug log messages
+# --------------------------------------------------------------------
+-assumenosideeffects class android.util.Log {
+ public static *** v(...);
+}
\ No newline at end of file
diff --git a/auto/src/main/AndroidManifest.xml b/auto/src/main/AndroidManifest.xml
index 46b4c8b6..1b7a4cbd 100644
--- a/auto/src/main/AndroidManifest.xml
+++ b/auto/src/main/AndroidManifest.xml
@@ -48,7 +48,7 @@
android:value="" />
@@ -104,6 +104,8 @@
+
+
+ Log.d(LOG_ID, "Notification permission allowed: $isGranted")
+ }
+
fun requestPermission() : Boolean {
requestNotificationPermission = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@@ -146,9 +207,52 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
startActivity(Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM))
}
}
+ 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
+ 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(Settings.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(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 checkBatteryOptimization() {
try {
val pm = getSystemService(POWER_SERVICE) as PowerManager
@@ -175,6 +279,8 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
val inflater = menuInflater
inflater.inflate(R.menu.menu_items, menu)
MenuCompat.setGroupDividerEnabled(menu!!, true)
+ notificationIcon = menu.findItem(R.id.action_notification_toggle)
+ updateNotificationIcon()
return true
} catch (exc: Exception) {
Log.e(LOG_ID, "onCreateOptionsMenu exception: " + exc.message.toString() )
@@ -182,15 +288,42 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
return true
}
+
+ private fun updateNotificationIcon() {
+ try {
+ if(notificationIcon != null) {
+ val enabled = sharedPref.getBoolean(Constants.SHARED_PREF_CAR_NOTIFICATION, false)
+ notificationIcon!!.icon = ContextCompat.getDrawable(this, if(enabled) R.drawable.icon_popup_white else R.drawable.icon_popup_off_white)
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "updateAlarmIcon exception: " + exc.message.toString() )
+ }
+ }
+
override fun onOptionsItemSelected(item: MenuItem): Boolean {
try {
Log.v(LOG_ID, "onOptionsItemSelected for " + item.itemId.toString())
when(item.itemId) {
R.id.action_settings -> {
+ menuOpen = true
val intent = Intent(this, SettingsActivity::class.java)
startActivity(intent)
return true
}
+ R.id.action_sources -> {
+ menuOpen = true
+ val intent = Intent(this, SettingsActivity::class.java)
+ intent.putExtra(SettingsActivity.FRAGMENT_EXTRA, SettingsFragmentClass.SORUCE_FRAGMENT.value)
+ startActivity(intent)
+ return true
+ }
+ R.id.action_alarms -> {
+ menuOpen = true
+ val intent = Intent(this, SettingsActivity::class.java)
+ intent.putExtra(SettingsActivity.FRAGMENT_EXTRA, SettingsFragmentClass.ALARM_FRAGMENT.value)
+ startActivity(intent)
+ return true
+ }
R.id.action_help -> {
val browserIntent = Intent(
Intent.ACTION_VIEW,
@@ -218,6 +351,14 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
SaveMobileLogs()
return true
}
+ R.id.action_notification_toggle -> {
+ Log.v(LOG_ID, "notification toggle")
+ with(sharedPref.edit()) {
+ putBoolean(Constants.SHARED_PREF_CAR_NOTIFICATION, !sharedPref.getBoolean(Constants.SHARED_PREF_CAR_NOTIFICATION, false))
+ apply()
+ }
+ updateNotificationIcon()
+ }
else -> return super.onOptionsItemSelected(item)
}
} catch (exc: Exception) {
@@ -229,21 +370,120 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
private fun update() {
try {
Log.v(LOG_ID, "update values")
- txtBgValue.text = ReceiveData.getClucoseAsString()
- txtBgValue.setTextColor(ReceiveData.getClucoseColor())
+ txtBgValue.text = ReceiveData.getGlucoseAsString()
+ txtBgValue.setTextColor(ReceiveData.getGlucoseColor())
if (ReceiveData.isObsolete(Constants.VALUE_OBSOLETE_SHORT_SEC) && !ReceiveData.isObsolete()) {
txtBgValue.paintFlags = Paint.STRIKE_THRU_TEXT_FLAG
} else {
txtBgValue.paintFlags = 0
}
- viewIcon.setImageIcon(BitmapUtils.getRateAsIcon())
- txtLastValue.text = ReceiveData.getAsString(this, CR.string.gda_no_data)
- txtCarInfo.text = if (GlucoDataServiceAuto.connected) resources.getText(CR.string.activity_main_car_connected_label) else resources.getText(CR.string.activity_main_car_disconnected_label)
+ viewIcon.setImageIcon(BitmapUtils.getRateAsIcon(withShadow = true))
+ timeText.text = "🕒 ${ReceiveData.getElapsedRelativeTimeAsString(this)}"
+ deltaText.text = "Δ ${ReceiveData.getDeltaAsString()}"
+ iobText.text = "💉 " + ReceiveData.getIobAsString()
+ cobText.text = "🍔 " + ReceiveData.getCobAsString()
+ iobText.visibility = if (ReceiveData.isIobCobObsolete(Constants.VALUE_OBSOLETE_LONG_SEC)) View.GONE else View.VISIBLE
+ cobText.visibility = iobText.visibility
+
+ if(ReceiveData.time == 0L) {
+ txtLastValue.visibility = View.VISIBLE
+ txtNoData.visibility = View.VISIBLE
+ btnSources.visibility = View.VISIBLE
+ } else {
+ txtLastValue.visibility = View.GONE
+ txtNoData.visibility = View.GONE
+ btnSources.visibility = View.GONE
+ }
+ updateAlarmsTable()
+ updateConnectionsTable()
+ updateDetailsTable()
+
+ updateNotificationIcon()
} catch (exc: Exception) {
Log.e(LOG_ID, "update exception: " + exc.message.toString() )
}
}
+ private fun updateConnectionsTable() {
+ tableConnections.removeViews(1, maxOf(0, tableConnections.childCount - 1))
+ if (SourceStateData.lastState != SourceState.NONE)
+ tableConnections.addView(createRow(
+ SourceStateData.lastSource.resId,
+ SourceStateData.getStateMessage(this)))
+
+ if (WearPhoneConnection.nodesConnected) {
+ val onClickListener = View.OnClickListener {
+ GlucoDataService.checkForConnectedNodes(true)
+ }
+ WearPhoneConnection.getNodeBatterLevels().forEach { name, level ->
+ tableConnections.addView(createRow(name, if (level > 0) "$level%" else "?%", onClickListener))
+ }
+ }
+ tableConnections.addView(createRow(CR.string.pref_cat_android_auto, if (GlucoDataServiceAuto.connected) resources.getString(CR.string.connected_label) else resources.getString(CR.string.disconnected_label)))
+ checkTableVisibility(tableConnections)
+ }
+
+ private fun updateAlarmsTable() {
+ tableAlarms.removeViews(1, maxOf(0, tableAlarms.childCount - 1))
+ if(ReceiveData.time > 0 && ReceiveData.getAlarmType() != AlarmType.OK) {
+ tableAlarms.addView(createRow(CR.string.info_label_alarm, resources.getString(ReceiveData.getAlarmType().resId) + (if (ReceiveData.forceAlarm) " ⚠" else "" )))
+ }
+ if (AlarmHandler.isSnoozeActive)
+ tableAlarms.addView(createRow(CR.string.snooze, AlarmHandler.snoozeTimestamp))
+ checkTableVisibility(tableAlarms)
+ }
+
+ private fun updateDetailsTable() {
+ tableDetails.removeViews(1, maxOf(0, tableDetails.childCount - 1))
+ if(ReceiveData.time > 0) {
+ if (ReceiveData.isMmol)
+ tableDetails.addView(createRow(CR.string.info_label_raw, "${ReceiveData.rawValue} mg/dl"))
+ tableDetails.addView(createRow(CR.string.info_label_timestamp, DateFormat.getTimeInstance(
+ DateFormat.DEFAULT).format(Date(ReceiveData.time))))
+ if (!ReceiveData.isIobCobObsolete(1.days.inWholeSeconds.toInt()))
+ tableDetails.addView(createRow(CR.string.info_label_iob_cob_timestamp, DateFormat.getTimeInstance(
+ DateFormat.DEFAULT).format(Date(ReceiveData.iobCobTime))))
+ if (ReceiveData.sensorID?.isNotEmpty() == true) {
+ tableDetails.addView(createRow(CR.string.info_label_sensor_id, if(BuildConfig.DEBUG) "ABCDE12345" else ReceiveData.sensorID!!))
+ }
+ if(ReceiveData.source != DataSource.NONE)
+ tableDetails.addView(createRow(CR.string.info_label_source, resources.getString(ReceiveData.source.resId)))
+ }
+ checkTableVisibility(tableDetails)
+ }
+
+ private fun checkTableVisibility(table: TableLayout) {
+ table.visibility = if(table.childCount <= 1) View.GONE else View.VISIBLE
+ }
+
+ private fun createColumn(text: String, end: Boolean, onClickListener: View.OnClickListener? = null) : TextView {
+ val textView = TextView(this)
+ textView.layoutParams = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 1F)
+ textView.text = text
+ textView.textSize = 18F
+ if (end)
+ textView.gravity = Gravity.CENTER_VERTICAL or Gravity.END
+ else
+ textView.gravity = Gravity.CENTER_VERTICAL
+ if(onClickListener != null)
+ textView.setOnClickListener(onClickListener)
+ return textView
+ }
+
+ private fun createRow(keyResId: Int, value: String, onClickListener: View.OnClickListener? = null) : TableRow {
+ return createRow(resources.getString(keyResId), value, onClickListener)
+ }
+
+ private fun createRow(key: String, value: String, onClickListener: View.OnClickListener? = null) : TableRow {
+ val row = TableRow(this)
+ row.weightSum = 2f
+ //row.setBackgroundColor(resources.getColor(R.color.table_row))
+ row.setPadding(Utils.dpToPx(5F, this))
+ row.addView(createColumn(key, false, onClickListener))
+ row.addView(createColumn(value, true, onClickListener))
+ return row
+ }
+
override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) {
Log.v(LOG_ID, "new intent received")
update()
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 f49cba84..e65b2439 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
@@ -1,9 +1,12 @@
package de.michelinside.glucodataauto.android_auto
+import de.michelinside.glucodataauto.R
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Bitmap
+import android.media.MediaPlayer
import android.media.session.PlaybackState
+import android.net.Uri
import android.os.Bundle
import android.os.SystemClock
import android.support.v4.media.MediaBrowserCompat
@@ -12,28 +15,35 @@ import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
+import androidx.core.content.ContextCompat
+import androidx.core.graphics.drawable.toBitmap
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.utils.BitmapUtils
+import de.michelinside.glucodatahandler.common.R as CR
+
class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, SharedPreferences.OnSharedPreferenceChangeListener {
private val LOG_ID = "GDH.AA.CarMediaBrowserService"
private val MEDIA_ROOT_ID = "root"
private val MEDIA_GLUCOSE_ID = "glucose_value"
+ private val MEDIA_NOTIFICATION_TOGGLE_ID = "toggle_notification"
private lateinit var sharedPref: SharedPreferences
private lateinit var session: MediaSessionCompat
+ private val player = MediaPlayer()
+ private var curMediaItem = MEDIA_GLUCOSE_ID
companion object {
var active = false
}
override fun onCreate() {
- Log.v(LOG_ID, "onCreate")
+ Log.d(LOG_ID, "onCreate")
try {
super.onCreate()
active = true
@@ -47,12 +57,47 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh
session.setCallback(object : MediaSessionCompat.Callback() {
override fun onPlayFromMediaId(mediaId: String, extras: Bundle?) {
Log.i(LOG_ID, "onPlayFromMediaId: " + mediaId)
- if (!sharedPref.getBoolean(Constants.SHARED_PREF_CAR_MEDIA,true)) {
- with(sharedPref.edit()) {
- putBoolean(Constants.SHARED_PREF_CAR_MEDIA,true)
- apply()
+ curMediaItem = mediaId
+ setItem()
+ }
+
+ override fun onPlay() {
+ Log.i(LOG_ID, "onPlay called for $curMediaItem")
+ try {
+ if(curMediaItem == MEDIA_GLUCOSE_ID) {
+ // Current song is ready, but paused, so start playing the music.
+ player.reset()
+ val uri =
+ "android.resource://" + applicationContext.packageName + "/" + CR.raw.silence
+ player.setDataSource(applicationContext, Uri.parse(uri))
+ player.setOnCompletionListener {
+ Log.d(LOG_ID, "setOnCompletionListener called")
+ onStop()
+ }
+ player.start()
+ // Update the UI to show we are playing.
+ session.setPlaybackState(buildState(PlaybackState.STATE_PLAYING))
+ } else if(curMediaItem == MEDIA_NOTIFICATION_TOGGLE_ID) {
+ Log.d(LOG_ID, "Toggle notification")
+ with(sharedPref.edit()) {
+ putBoolean(Constants.SHARED_PREF_CAR_NOTIFICATION, !CarNotification.enable_notification)
+ apply()
+ }
}
- createMediaItem()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onPlay exception: " + exc.message.toString() )
+ }
+ }
+
+ override fun onStop() {
+ Log.i(LOG_ID, "onStop called playing: ${player.isPlaying}")
+ try {
+ if(player.isPlaying) {
+ player.stop()
+ }
+ session.setPlaybackState(buildState(PlaybackState.STATE_STOPPED))
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onStop exception: " + exc.message.toString() )
}
}
})
@@ -70,7 +115,7 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh
}
override fun onDestroy() {
- Log.v(LOG_ID, "onDestroy")
+ Log.d(LOG_ID, "onDestroy")
try {
active = false
InternalNotifier.remNotifier(this, this)
@@ -89,7 +134,7 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh
rootHints: Bundle?
): BrowserRoot? {
try {
- Log.v(LOG_ID, "onGetRoot - package: " + clientPackageName + " - UID: " + clientUid.toString())
+ Log.d(LOG_ID, "onGetRoot - package: " + clientPackageName + " - UID: " + clientUid.toString())
return BrowserRoot(MEDIA_ROOT_ID, null)
} catch (exc: Exception) {
Log.e(LOG_ID, "onGetRoot exception: " + exc.message.toString() )
@@ -102,12 +147,13 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh
result: Result>
) {
try {
- Log.v(LOG_ID, "onLoadChildren for parent: " + parentId)
+ Log.d(LOG_ID, "onLoadChildren for parent: " + parentId)
if (MEDIA_ROOT_ID == parentId) {
- result.sendResult(mutableListOf(createMediaItem()))
+ result.sendResult(mutableListOf(createMediaItem(), createToggleItem()))
} else {
result.sendResult(null)
}
+ setItem()
} catch (exc: Exception) {
Log.e(LOG_ID, "onLoadChildren exception: " + exc.message.toString() )
}
@@ -126,6 +172,7 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh
Log.v(LOG_ID, "onSharedPreferenceChanged called for key " + key)
try {
when(key) {
+ Constants.SHARED_PREF_CAR_NOTIFICATION,
Constants.SHARED_PREF_CAR_MEDIA,
Constants.SHARED_PREF_CAR_MEDIA_ICON_STYLE -> {
notifyChildrenChanged(MEDIA_ROOT_ID)
@@ -147,15 +194,32 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh
}
}
- private fun createMediaItem(): MediaBrowserCompat.MediaItem {
- Log.v(LOG_ID, "createMediaItem called")
+ fun setItem() {
+ Log.d(LOG_ID, "set current media: $curMediaItem")
+ when(curMediaItem) {
+ MEDIA_GLUCOSE_ID -> {
+ setGlucose()
+ }
+ MEDIA_NOTIFICATION_TOGGLE_ID -> {
+ curMediaItem = MEDIA_GLUCOSE_ID
+ Log.d(LOG_ID, "Toggle notification")
+ with(sharedPref.edit()) {
+ putBoolean(Constants.SHARED_PREF_CAR_NOTIFICATION, !CarNotification.enable_notification)
+ apply()
+ }
+ }
+ }
+ }
+
+ private fun setGlucose() {
if (sharedPref.getBoolean(Constants.SHARED_PREF_CAR_MEDIA,true)) {
+ Log.i(LOG_ID, "setGlucose called")
session.setPlaybackState(buildState(PlaybackState.STATE_PAUSED))
session.setMetadata(
MediaMetadataCompat.Builder()
.putString(
MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE,
- ReceiveData.getClucoseAsString() + " (Δ " + ReceiveData.getDeltaAsString() + ")"
+ ReceiveData.getGlucoseAsString() + " (Δ " + ReceiveData.getDeltaAsString() + ")"
)
.putString(
MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE,
@@ -167,21 +231,63 @@ class CarMediaBrowserService: MediaBrowserServiceCompat(), NotifierInterface, Sh
} else {
session.setPlaybackState(buildState(PlaybackState.STATE_NONE))
}
+ }
+
+ private fun createMediaItem(): MediaBrowserCompat.MediaItem {
val mediaDescriptionBuilder = MediaDescriptionCompat.Builder()
.setMediaId(MEDIA_GLUCOSE_ID)
- .setTitle(ReceiveData.getClucoseAsString() + " (Δ " + ReceiveData.getDeltaAsString() + ")\n" + ReceiveData.getElapsedTimeMinuteAsString(this))
+ .setTitle(ReceiveData.getGlucoseAsString() + " (Δ " + ReceiveData.getDeltaAsString() + ")\n" + ReceiveData.getElapsedTimeMinuteAsString(this))
//.setSubtitle(ReceiveData.timeformat.format(Date(ReceiveData.time)))
.setIconBitmap(getIcon()!!)
return MediaBrowserCompat.MediaItem(
mediaDescriptionBuilder.build(), MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
}
+ private fun getToggleIcon(): Bitmap? {
+ if(CarNotification.enable_notification) {
+ return ContextCompat.getDrawable(applicationContext, R.drawable.icon_popup_white)?.toBitmap()
+ }
+ return ContextCompat.getDrawable(applicationContext, R.drawable.icon_popup_off_white)?.toBitmap()
+ }
+
+ private fun setToggle() {
+ if (sharedPref.getBoolean(Constants.SHARED_PREF_CAR_MEDIA,true)) {
+ Log.i(LOG_ID, "setToggle called")
+ session.setPlaybackState(buildState(PlaybackState.STATE_PAUSED))
+ session.setMetadata(
+ MediaMetadataCompat.Builder()
+ .putString(
+ MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, resources.getString(if(CarNotification.enable_notification) CR.string.gda_notifications_on else CR.string.gda_notifications_off)
+ )
+ .putString(
+ MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE,
+ resources.getString(CR.string.gda_media_notification_toggle_action)
+ )
+ //.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, getToggleIcon())
+ .build()
+ )
+ } else {
+ session.setPlaybackState(buildState(PlaybackState.STATE_NONE))
+ }
+ }
+
+ private fun createToggleItem(): MediaBrowserCompat.MediaItem {
+ val mediaDescriptionBuilder = MediaDescriptionCompat.Builder()
+ .setMediaId(MEDIA_NOTIFICATION_TOGGLE_ID)
+ .setTitle(resources.getString(CR.string.gda_media_notification_toggle_title))
+ .setSubtitle(resources.getString(if(CarNotification.enable_notification) CR.string.gda_notifications_on else CR.string.gda_notifications_off))
+ .setIconBitmap(getToggleIcon())
+ return MediaBrowserCompat.MediaItem(
+ mediaDescriptionBuilder.build(), MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
+ }
+
private fun buildState(state: Int): PlaybackStateCompat? {
- Log.v(LOG_ID, "buildState called for state " + state)
- return PlaybackStateCompat.Builder()
+ Log.d(LOG_ID, "buildState called for state $state - pos: ${player.currentPosition}")
+ return PlaybackStateCompat.Builder().setActions(
+ PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_STOP)
.setState(
state,
- 0,
+ player.currentPosition.toLong(),
1f,
SystemClock.elapsedRealtime()
)
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 eca2a026..25683b11 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
@@ -18,6 +18,7 @@ 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.notification.AlarmType
import de.michelinside.glucodatahandler.common.notifier.*
import de.michelinside.glucodatahandler.common.utils.BitmapUtils
import de.michelinside.glucodatahandler.common.notification.ChannelType
@@ -211,7 +212,7 @@ object CarNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceC
Log.v(LOG_ID, "Notification has forced by interval or alarm")
return true
}
- if (ReceiveData.getAlarmType() == ReceiveData.AlarmType.VERY_LOW) {
+ if (ReceiveData.getAlarmType() == 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
@@ -259,14 +260,14 @@ object CarNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceC
private fun createMessageStyle(context: Context, isObsolete: Boolean): NotificationCompat.MessagingStyle {
val person = Person.Builder()
.setIcon(IconCompat.createWithBitmap(BitmapUtils.getRateAsBitmap(resizeFactor = 0.75F)!!))
- .setName(ReceiveData.getClucoseAsString())
+ .setName(ReceiveData.getGlucoseAsString())
.setImportant(true)
.build()
val messagingStyle = NotificationCompat.MessagingStyle(person)
if (isObsolete)
messagingStyle.conversationTitle = context.getString(CR.string.no_new_value, ReceiveData.getElapsedTimeMinute())
else
- messagingStyle.conversationTitle = ReceiveData.getClucoseAsString() + " (Δ " + ReceiveData.getDeltaAsString() + ")"
+ messagingStyle.conversationTitle = ReceiveData.getGlucoseAsString() + " (Δ " + ReceiveData.getDeltaAsString() + ")"
messagingStyle.isGroupConversation = false
messagingStyle.addMessage(DateFormat.getTimeInstance(DateFormat.SHORT).format(Date(ReceiveData.time)), System.currentTimeMillis(), person)
return messagingStyle
@@ -281,7 +282,7 @@ object CarNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceC
context,
1,
intent,
- PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
return NotificationCompat.Action.Builder(R.mipmap.ic_launcher, "Reply", pendingIntent)
//.setAllowGeneratedReplies(true)
@@ -297,7 +298,7 @@ object CarNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceC
context,
2,
intent,
- PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
return NotificationCompat.Action.Builder(
R.mipmap.ic_launcher,
diff --git a/auto/src/main/java/de/michelinside/glucodataauto/preferences/AlarmFragment.kt b/auto/src/main/java/de/michelinside/glucodataauto/preferences/AlarmFragment.kt
new file mode 100644
index 00000000..bf460376
--- /dev/null
+++ b/auto/src/main/java/de/michelinside/glucodataauto/preferences/AlarmFragment.kt
@@ -0,0 +1,123 @@
+package de.michelinside.glucodataauto.preferences
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.util.Log
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import de.michelinside.glucodataauto.R
+import de.michelinside.glucodatahandler.common.R as CR
+import de.michelinside.glucodatahandler.common.Constants
+import de.michelinside.glucodatahandler.common.ReceiveData
+import de.michelinside.glucodatahandler.common.notification.AlarmHandler
+import de.michelinside.glucodatahandler.common.notification.AlarmType
+import de.michelinside.glucodatahandler.common.notifier.InternalNotifier
+import de.michelinside.glucodatahandler.common.notifier.NotifySource
+import de.michelinside.glucodatahandler.common.utils.Utils
+
+class AlarmFragment : PreferenceFragmentCompat() {
+ private val LOG_ID = "GDH.AA.AlarmFragment"
+ companion object {
+ var settingsChanged = false
+ }
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ Log.d(LOG_ID, "onCreatePreferences called")
+ try {
+ settingsChanged = false
+ preferenceManager.sharedPreferencesName = Constants.SHARED_PREF_TAG
+ setPreferencesFromResource(R.xml.alarms, rootKey)
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString())
+ }
+ }
+
+ override fun onDestroyView() {
+ Log.d(LOG_ID, "onDestroyView called")
+ try {
+ if (settingsChanged) {
+ Log.v(LOG_ID, "Notify alarm_settings change")
+ InternalNotifier.notify(requireContext(), NotifySource.ALARM_SETTINGS, AlarmHandler.getSettings())
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onDestroyView exception: " + exc.toString())
+ }
+ super.onDestroyView()
+ }
+
+
+ override fun onResume() {
+ Log.d(LOG_ID, "onResume called")
+ try {
+ update()
+ super.onResume()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onResume exception: " + exc.toString())
+ }
+ }
+
+ override fun onPause() {
+ Log.d(LOG_ID, "onPause called")
+ try {
+ super.onPause()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onPause exception: " + exc.toString())
+ }
+ }
+
+ @SuppressLint("InlinedApi")
+ private fun update() {
+ Log.d(LOG_ID, "update called")
+ try {
+ updateAlarmCat(Constants.SHARED_PREF_ALARM_LOW)
+ updateAlarmCat(Constants.SHARED_PREF_ALARM_HIGH)
+ updateAlarmCat(Constants.SHARED_PREF_ALARM_VERY_HIGH)
+ updateAlarmCat(Constants.SHARED_PREF_ALARM_OBSOLETE)
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onPause exception: " + exc.toString())
+ }
+ }
+
+ private fun updateAlarmCat(key: String) {
+ val pref = findPreference(key) ?: return
+ val alarmType = AlarmType.fromIndex(pref.extras.getInt("type"))
+ pref.summary = getAlarmCatSummary(alarmType)
+ }
+
+ private fun getAlarmCatSummary(alarmType: AlarmType): String {
+ return when(alarmType) {
+ AlarmType.VERY_LOW,
+ AlarmType.LOW,
+ AlarmType.HIGH,
+ AlarmType.VERY_HIGH -> resources.getString(CR.string.alarm_type_summary, getBorderText(alarmType))
+ AlarmType.OBSOLETE -> resources.getString(CR.string.alarm_obsolete_summary)
+ else -> ""
+ }
+ }
+
+ private fun getBorderText(alarmType: AlarmType): String {
+ var value = when(alarmType) {
+ AlarmType.VERY_LOW -> ReceiveData.low
+ AlarmType.LOW -> ReceiveData.targetMin
+ AlarmType.HIGH -> ReceiveData.targetMax
+ AlarmType.VERY_HIGH -> ReceiveData.high
+ else -> 0F
+ }
+ if (alarmType == AlarmType.LOW) {
+ if(ReceiveData.isMmol)
+ value = Utils.round(value-0.1F, 1)
+ else
+ value -= 1F
+ }
+
+ if (alarmType == AlarmType.HIGH) {
+ if(ReceiveData.isMmol)
+ value = Utils.round(value+0.1F, 1)
+ else
+ value += 1F
+ }
+ return "$value ${ReceiveData.getUnit()}"
+ }
+
+}
+
diff --git a/auto/src/main/java/de/michelinside/glucodataauto/preferences/AlarmTypeFragment.kt b/auto/src/main/java/de/michelinside/glucodataauto/preferences/AlarmTypeFragment.kt
new file mode 100644
index 00000000..d951e5f9
--- /dev/null
+++ b/auto/src/main/java/de/michelinside/glucodataauto/preferences/AlarmTypeFragment.kt
@@ -0,0 +1,107 @@
+package de.michelinside.glucodataauto.preferences
+
+import android.os.Bundle
+import android.util.Log
+import androidx.preference.Preference
+import androidx.preference.PreferenceCategory
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.SeekBarPreference
+import androidx.preference.SwitchPreferenceCompat
+import de.michelinside.glucodataauto.R
+import de.michelinside.glucodatahandler.common.Constants
+import de.michelinside.glucodatahandler.common.notification.AlarmHandler
+import de.michelinside.glucodatahandler.common.notification.AlarmType
+import de.michelinside.glucodatahandler.common.utils.Utils
+
+class AlarmTypeFragment : PreferenceFragmentCompat() {
+ private val LOG_ID = "GDH.AA.AlarmTypeFragment"
+ private var alarmType = AlarmType.NONE
+ private var alarmPrefix = ""
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ try {
+ Log.v(LOG_ID, "onCreatePreferences called for key: ${Utils.dumpBundle(this.arguments)}" )
+ preferenceManager.sharedPreferencesName = Constants.SHARED_PREF_TAG
+ setPreferencesFromResource(R.xml.alarm_type, rootKey)
+ if (requireArguments().containsKey("prefix") && requireArguments().containsKey("type")) {
+ alarmType = AlarmType.fromIndex(requireArguments().getInt("type"))
+ alarmPrefix = requireArguments().getString("prefix")!!
+ createAlarmPrefSettings()
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString())
+ }
+ }
+
+ override fun onResume() {
+ Log.d(LOG_ID, "onResume called")
+ try {
+ super.onResume()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onResume exception: " + exc.toString())
+ }
+ }
+
+ override fun onPause() {
+ Log.d(LOG_ID, "onPause called")
+ try {
+ super.onPause()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onPause exception: " + exc.toString())
+ }
+ }
+
+ private fun createAlarmPrefSettings() {
+ Log.v(LOG_ID, "createAlarmPrefSettings for alarm $alarmType with prefix $alarmPrefix")
+ updatePreferenceKeys()
+ updateData()
+ }
+
+ private fun updatePreferenceKeys() {
+ for (i in 0 until preferenceScreen.preferenceCount) {
+ val pref: Preference = preferenceScreen.getPreference(i)
+ if(!pref.key.isNullOrEmpty()) {
+ val newKey = alarmPrefix + pref.key
+ Log.v(LOG_ID, "Replace key ${pref.key} with $newKey")
+ pref.key = newKey
+ } else {
+ val cat = pref as PreferenceCategory
+ updatePreferenceKeys(cat)
+ }
+ }
+ }
+
+
+ private fun updatePreferenceKeys(preferenceCategory: PreferenceCategory) {
+ for (i in 0 until preferenceCategory.preferenceCount) {
+ val pref: Preference = preferenceCategory.getPreference(i)
+ if(!pref.key.isNullOrEmpty()) {
+ val newKey = alarmPrefix + pref.key
+ Log.v(LOG_ID, "Replace key ${pref.key} with $newKey")
+ pref.key = newKey
+ } else {
+ val cat = pref as PreferenceCategory
+ updatePreferenceKeys(cat)
+ }
+ }
+ }
+ private fun updateData() {
+ val enablePref = findPreference(alarmPrefix+"enabled")
+ enablePref!!.isChecked = preferenceManager.sharedPreferences!!.getBoolean(enablePref.key, true)
+
+ val intervalPref = findPreference(alarmPrefix+"interval")
+ intervalPref!!.value = preferenceManager.sharedPreferences!!.getInt(intervalPref.key, AlarmHandler.getDefaultIntervalMin(alarmType))
+ intervalPref.summary = getIntervalSummary(alarmType)
+ }
+
+ private fun getIntervalSummary(alarmType: AlarmType): String {
+ return when(alarmType) {
+ AlarmType.VERY_LOW,
+ AlarmType.LOW -> resources.getString(de.michelinside.glucodatahandler.common.R.string.alarm_interval_summary_low)
+ AlarmType.HIGH,
+ AlarmType.VERY_HIGH -> resources.getString(de.michelinside.glucodatahandler.common.R.string.alarm_interval_summary_high)
+ AlarmType.OBSOLETE -> resources.getString(de.michelinside.glucodatahandler.common.R.string.alarm_interval_summary_obsolete)
+ else -> ""
+ }
+ }
+}
diff --git a/auto/src/main/java/de/michelinside/glucodataauto/preferences/AppSettingsActivity.kt b/auto/src/main/java/de/michelinside/glucodataauto/preferences/AppSettingsActivity.kt
index 3825b41c..a4e20657 100644
--- a/auto/src/main/java/de/michelinside/glucodataauto/preferences/AppSettingsActivity.kt
+++ b/auto/src/main/java/de/michelinside/glucodataauto/preferences/AppSettingsActivity.kt
@@ -1,26 +1,104 @@
-package de.michelinside.glucodataauto
+package de.michelinside.glucodataauto.preferences
//noinspection SuspiciousImport
import android.R
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
-import de.michelinside.glucodataauto.preferences.SettingsFragment
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
import de.michelinside.glucodatahandler.common.R as RC
-class SettingsActivity : AppCompatActivity() {
+enum class SettingsFragmentClass(val value: Int, val titleRes: Int) {
+ SETTINGS_FRAGMENT(0, RC.string.menu_settings),
+ SORUCE_FRAGMENT(1, RC.string.menu_sources),
+ ALARM_FRAGMENT(2, RC.string.menu_alarms)
+}
+class SettingsActivity : AppCompatActivity(),
+ PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
private val LOG_ID = "GDH.AA.SettingsActivity"
+ private var titleMap = mutableMapOf()
+ companion object {
+ const val FRAGMENT_EXTRA = "fragment"
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
try {
+ Log.v(LOG_ID, "onCreate called for fragment " + intent.getIntExtra(FRAGMENT_EXTRA, 0) + " with instance: " + (savedInstanceState!=null) )
super.onCreate(savedInstanceState)
- if (savedInstanceState==null) {
- this.supportActionBar!!.title = this.applicationContext.resources.getText(RC.string.menu_settings)
- supportFragmentManager.beginTransaction()
- .replace(R.id.content, SettingsFragment())
- .commit()
+ if(savedInstanceState==null) {
+ when (intent.getIntExtra(FRAGMENT_EXTRA, 0)) {
+ SettingsFragmentClass.SETTINGS_FRAGMENT.value -> {
+ this.supportActionBar!!.title =
+ this.applicationContext.resources.getText(SettingsFragmentClass.SETTINGS_FRAGMENT.titleRes)
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.content, SettingsFragment())
+ .commit()
+ }
+
+ SettingsFragmentClass.SORUCE_FRAGMENT.value -> {
+ this.supportActionBar!!.title =
+ this.applicationContext.resources.getText(SettingsFragmentClass.SORUCE_FRAGMENT.titleRes)
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.content, SourceFragment())
+ .commit()
+ }
+
+ SettingsFragmentClass.ALARM_FRAGMENT.value -> {
+ this.supportActionBar!!.title =
+ this.applicationContext.resources.getText(SettingsFragmentClass.ALARM_FRAGMENT.titleRes)
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.content, AlarmFragment())
+ .commit()
+ }
+ }
}
+
+ supportFragmentManager.addOnBackStackChangedListener {
+ Log.v(LOG_ID, "addOnBackStackChangedListener called count=${supportFragmentManager.backStackEntryCount}")
+ if (titleMap.containsKey(supportFragmentManager.backStackEntryCount)) {
+ this.supportActionBar!!.title = titleMap[supportFragmentManager.backStackEntryCount]
+ }
+ }
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ } catch (ex: Exception) {
+ Log.e(LOG_ID, "onCreate exception: " + ex)
+ }
+ }
+
+ override fun onSupportNavigateUp(): Boolean {
+ if (supportFragmentManager.popBackStackImmediate()) {
+ return true
+ }
+ return super.onSupportNavigateUp()
+ }
+
+ override fun onPreferenceStartFragment(
+ caller: PreferenceFragmentCompat,
+ pref: Preference
+ ): Boolean {
+ try {
+ // Instantiate the new Fragment
+ Log.d(LOG_ID, "onPreferenceStartFragment called at ${supportFragmentManager.backStackEntryCount} for preference ${pref.title}")
+ val args = pref.extras
+ val fragment = supportFragmentManager.fragmentFactory.instantiate(
+ classLoader,
+ pref.fragment ?: return false
+ ).apply {
+ arguments = args
+ setTargetFragment(caller, 0)
+ }
+ // Replace the existing Fragment with the new Fragment
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.content, fragment)
+ .addToBackStack(null)
+ .commit()
+
+ titleMap.put(supportFragmentManager.backStackEntryCount, this.supportActionBar!!.title!!)
+ this.supportActionBar!!.title = pref.title
} catch (ex: Exception) {
Log.e(LOG_ID, "onCreate exception: " + ex)
}
+ return true
}
}
\ No newline at end of file
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 596d72dc..7caab0e9 100644
--- a/auto/src/main/java/de/michelinside/glucodataauto/preferences/SettingsFragment.kt
+++ b/auto/src/main/java/de/michelinside/glucodataauto/preferences/SettingsFragment.kt
@@ -1,6 +1,5 @@
package de.michelinside.glucodataauto.preferences
-import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import androidx.preference.*
@@ -9,7 +8,7 @@ import de.michelinside.glucodataauto.R
import de.michelinside.glucodatahandler.common.Constants
-class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
+class SettingsFragment : PreferenceFragmentCompat() {
private val LOG_ID = "GDH.AA.SettingsFragment"
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@@ -28,54 +27,4 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
}
}
- override fun onResume() {
- Log.d(LOG_ID, "onResume called")
- try {
- preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
- updateEnableStates(preferenceManager.sharedPreferences!!)
- super.onResume()
- } catch (exc: Exception) {
- Log.e(LOG_ID, "onResume exception: " + exc.toString())
- }
- }
-
- override fun onPause() {
- Log.d(LOG_ID, "onPause called")
- try {
- preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
- super.onPause()
- } catch (exc: Exception) {
- Log.e(LOG_ID, "onPause exception: " + exc.toString())
- }
- }
-
- override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
- Log.d(LOG_ID, "onSharedPreferenceChanged called for " + key)
- try {
- when(key) {
- Constants.SHARED_PREF_CAR_NOTIFICATION,
- Constants.SHARED_PREF_CAR_NOTIFICATION_ALARM_ONLY -> {
- updateEnableStates(sharedPreferences!!)
- }
- }
- } catch (exc: Exception) {
- Log.e(LOG_ID, "onSharedPreferenceChanged exception: " + exc.toString())
- }
- }
-
- fun setEnableState(sharedPreferences: SharedPreferences, key: String, enableKey: String, secondEnableKey: String? = null, defValue: Boolean = false) {
- val pref = findPreference(key)
- if (pref != null)
- pref.isEnabled = sharedPreferences.getBoolean(enableKey, defValue) && (if (secondEnableKey != null) !sharedPreferences.getBoolean(secondEnableKey, defValue) else true)
- }
-
- fun updateEnableStates(sharedPreferences: SharedPreferences) {
- 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/preferences/SettingsFragmentBase.kt b/auto/src/main/java/de/michelinside/glucodataauto/preferences/SettingsFragmentBase.kt
new file mode 100644
index 00000000..b4dfe19b
--- /dev/null
+++ b/auto/src/main/java/de/michelinside/glucodataauto/preferences/SettingsFragmentBase.kt
@@ -0,0 +1,119 @@
+package de.michelinside.glucodataauto.preferences
+
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.util.Log
+import androidx.core.content.ContextCompat
+import androidx.preference.*
+import de.michelinside.glucodatahandler.common.Constants
+import de.michelinside.glucodataauto.R
+
+
+abstract class SettingsFragmentBase(private val prefResId: Int) : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
+ protected val LOG_ID = "GDH.SettingsFragmentBase"
+ private val updateEnablePrefs = mutableSetOf()
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ Log.d(LOG_ID, "onCreatePreferences called")
+ try {
+ preferenceManager.sharedPreferencesName = Constants.SHARED_PREF_TAG
+ setPreferencesFromResource(prefResId, rootKey)
+
+ initPreferences()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString())
+ }
+ }
+
+ open fun initPreferences() {
+ }
+
+
+ open fun updatePreferences() {
+
+ }
+
+ override fun onResume() {
+ Log.d(LOG_ID, "onResume called")
+ try {
+ super.onResume()
+ preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
+ updateEnablePrefs.clear()
+ update()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onResume exception: " + exc.toString())
+ }
+ }
+
+ override fun onPause() {
+ Log.d(LOG_ID, "onPause called")
+ try {
+ super.onPause()
+ preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onPause exception: " + exc.toString())
+ }
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
+ Log.d(LOG_ID, "onSharedPreferenceChanged called for " + key)
+ try {
+ if(updateEnablePrefs.isEmpty() || updateEnablePrefs.contains(key!!)) {
+ updateEnableStates(sharedPreferences!!)
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onSharedPreferenceChanged exception: " + exc.toString())
+ }
+ }
+
+
+ fun setEnableState(sharedPreferences: SharedPreferences, key: String, enableKey: String, secondEnableKey: String? = null, defValue: Boolean = false) {
+ val pref = findPreference(key)
+ if (pref != null) {
+ pref.isEnabled = sharedPreferences.getBoolean(enableKey, defValue) && (if (secondEnableKey != null) !sharedPreferences.getBoolean(secondEnableKey, defValue) else true)
+ if(!updateEnablePrefs.contains(enableKey)) {
+ Log.v(LOG_ID, "Add update enable pref $enableKey")
+ updateEnablePrefs.add(enableKey)
+ }
+ if (secondEnableKey != null && !updateEnablePrefs.contains(secondEnableKey)) {
+ Log.v(LOG_ID, "Add update second enable pref $secondEnableKey")
+ updateEnablePrefs.add(secondEnableKey)
+ }
+ }
+ }
+
+ private fun update() {
+ val sharedPref = preferenceManager.sharedPreferences!!
+ updateEnableStates(sharedPref)
+ updatePreferences()
+ }
+
+
+ fun updateEnableStates(sharedPreferences: SharedPreferences) {
+ try {
+ Log.v(LOG_ID, "updateEnableStates called")
+ 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())
+ }
+ }
+}
+
+class GeneralSettingsFragment: SettingsFragmentBase(R.xml.pref_general) {}
+class RangeSettingsFragment: SettingsFragmentBase(R.xml.pref_target_range) {}
+class GDASettingsFragment: SettingsFragmentBase(R.xml.pref_gda) {
+ override fun updatePreferences() {
+ super.updatePreferences()
+ val pref = findPreference(Constants.SHARED_PREF_CAR_NOTIFICATION) ?: return
+ pref.icon = ContextCompat.getDrawable(requireContext(), if(pref.isChecked) R.drawable.icon_popup else R.drawable.icon_popup_off)
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
+ super.onSharedPreferenceChanged(sharedPreferences, key)
+ if(key == Constants.SHARED_PREF_CAR_NOTIFICATION)
+ updatePreferences()
+ }
+
+}
\ No newline at end of file
diff --git a/auto/src/main/java/de/michelinside/glucodataauto/preferences/SourceFragment.kt b/auto/src/main/java/de/michelinside/glucodataauto/preferences/SourceFragment.kt
new file mode 100644
index 00000000..07a116f3
--- /dev/null
+++ b/auto/src/main/java/de/michelinside/glucodataauto/preferences/SourceFragment.kt
@@ -0,0 +1,144 @@
+package de.michelinside.glucodataauto.preferences
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.text.InputType
+import android.util.Log
+import androidx.preference.*
+import de.michelinside.glucodataauto.R
+import de.michelinside.glucodatahandler.common.Constants
+import de.michelinside.glucodatahandler.common.notifier.InternalNotifier
+import de.michelinside.glucodatahandler.common.notifier.NotifierInterface
+import de.michelinside.glucodatahandler.common.notifier.NotifySource
+import de.michelinside.glucodatahandler.common.tasks.DataSourceTask
+import de.michelinside.glucodatahandler.common.tasks.LibreLinkSourceTask
+
+
+class SourceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener, NotifierInterface {
+ private val LOG_ID = "GDH.AA.SourceFragment"
+ private var settingsChanged = false
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ Log.d(LOG_ID, "onCreatePreferences called")
+ try {
+ settingsChanged = false
+ preferenceManager.sharedPreferencesName = Constants.SHARED_PREF_TAG
+ setPreferencesFromResource(R.xml.sources, rootKey)
+
+ val librePassword = findPreference(Constants.SHARED_PREF_LIBRE_PASSWORD)
+ librePassword?.setOnBindEditTextListener {editText ->
+ editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
+ }
+
+ val nightscoutSecret = findPreference(Constants.SHARED_PREF_NIGHTSCOUT_SECRET)
+ nightscoutSecret?.setOnBindEditTextListener {editText ->
+ editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
+ }
+
+ setupLibrePatientData()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString())
+ }
+ }
+
+ override fun onDestroyView() {
+ Log.d(LOG_ID, "onDestroyView called")
+ try {
+ if (settingsChanged) {
+ InternalNotifier.notify(requireContext(), NotifySource.SOURCE_SETTINGS, DataSourceTask.getSettingsBundle(preferenceManager.sharedPreferences!!))
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onDestroyView exception: " + exc.toString())
+ }
+ super.onDestroyView()
+ }
+
+
+ override fun onResume() {
+ Log.d(LOG_ID, "onResume called")
+ try {
+ preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
+ updateEnableStates(preferenceManager.sharedPreferences!!)
+ InternalNotifier.addNotifier(requireContext(), this, mutableSetOf(NotifySource.PATIENT_DATA_CHANGED))
+ super.onResume()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onResume exception: " + exc.toString())
+ }
+ }
+
+ override fun onPause() {
+ Log.d(LOG_ID, "onPause called")
+ try {
+ preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
+ InternalNotifier.remNotifier(requireContext(), this)
+ super.onPause()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onPause exception: " + exc.toString())
+ }
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
+ Log.d(LOG_ID, "onSharedPreferenceChanged called for " + key)
+ try {
+ if(DataSourceTask.preferencesToSend.contains(key))
+ settingsChanged = true
+
+ when(key) {
+ Constants.SHARED_PREF_LIBRE_PASSWORD,
+ Constants.SHARED_PREF_LIBRE_USER,
+ Constants.SHARED_PREF_NIGHTSCOUT_URL -> {
+ updateEnableStates(sharedPreferences!!)
+ }
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onSharedPreferenceChanged exception: " + exc.toString())
+ }
+ }
+
+ fun updateEnableStates(sharedPreferences: SharedPreferences) {
+ try {
+ val switchLibreSource = findPreference(Constants.SHARED_PREF_LIBRE_ENABLED)
+ if (switchLibreSource != null) {
+ val libreUser = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_USER, "")!!.trim()
+ val librePassword = sharedPreferences.getString(Constants.SHARED_PREF_LIBRE_PASSWORD, "")!!.trim()
+ switchLibreSource.isEnabled = libreUser.isNotEmpty() && librePassword.isNotEmpty()
+ if(!switchLibreSource.isEnabled)
+ switchLibreSource.isChecked = false
+ }
+
+ val switchNightscoutSource = findPreference(Constants.SHARED_PREF_NIGHTSCOUT_ENABLED)
+ if (switchNightscoutSource != null) {
+ val url = sharedPreferences.getString(Constants.SHARED_PREF_NIGHTSCOUT_URL, "")!!.trim()
+ switchNightscoutSource.isEnabled = url.isNotEmpty() && url.isNotEmpty()
+ if(!switchNightscoutSource.isEnabled)
+ switchNightscoutSource.isChecked = false
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "updateEnableStates exception: " + exc.toString())
+ }
+ }
+
+ private fun setupLibrePatientData() {
+ try {
+ val listPreference = findPreference(Constants.SHARED_PREF_LIBRE_PATIENT_ID)
+ // force "global broadcast" to be the first entry
+ listPreference!!.entries = LibreLinkSourceTask.patientData.values.toTypedArray()
+ listPreference.entryValues = LibreLinkSourceTask.patientData.keys.toTypedArray()
+ listPreference.isVisible = LibreLinkSourceTask.patientData.size > 1
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "setupLibrePatientData exception: $exc")
+ }
+ }
+
+ override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) {
+ try {
+ Log.v(LOG_ID, "OnNotifyData called for source $dataSource")
+ if (dataSource == NotifySource.PATIENT_DATA_CHANGED)
+ setupLibrePatientData()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "OnNotifyData exception for source $dataSource: $exc")
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/auto/src/main/java/de/michelinside/glucodataauto/receiver/GlucoDataActionReceiver.kt b/auto/src/main/java/de/michelinside/glucodataauto/receiver/GlucoDataActionReceiver.kt
index 1998bc5d..8333fafc 100644
--- a/auto/src/main/java/de/michelinside/glucodataauto/receiver/GlucoDataActionReceiver.kt
+++ b/auto/src/main/java/de/michelinside/glucodataauto/receiver/GlucoDataActionReceiver.kt
@@ -7,6 +7,7 @@ import android.util.Log
import de.michelinside.glucodataauto.GlucoDataServiceAuto
import de.michelinside.glucodatahandler.common.Constants
import de.michelinside.glucodatahandler.common.ReceiveData
+import de.michelinside.glucodatahandler.common.notification.AlarmHandler
import de.michelinside.glucodatahandler.common.notifier.DataSource
open class GlucoDataActionReceiver: BroadcastReceiver() {
@@ -24,10 +25,16 @@ open class GlucoDataActionReceiver: BroadcastReceiver() {
if (extras != null) {
if (extras.containsKey(Constants.SETTINGS_BUNDLE)) {
val bundle = extras.getBundle(Constants.SETTINGS_BUNDLE)
- Log.d(LOG_ID, "Glucose settings receceived: " + bundle.toString())
+ Log.d(LOG_ID, "Glucose settings receceived")
ReceiveData.setSettings(context, bundle!!)
extras.remove(Constants.SETTINGS_BUNDLE)
}
+ if (extras.containsKey(Constants.ALARM_SETTINGS_BUNDLE)) {
+ val bundle = extras.getBundle(Constants.ALARM_SETTINGS_BUNDLE)
+ Log.d(LOG_ID, "Alarm settings receceived")
+ AlarmHandler.setSettings(context, bundle!!)
+ extras.remove(Constants.ALARM_SETTINGS_BUNDLE)
+ }
ReceiveData.handleIntent(context, DataSource.GDH, extras, true)
}
} catch (exc: Exception) {
diff --git a/auto/src/main/res/drawable-night/icon_off.xml b/auto/src/main/res/drawable-night/icon_off.xml
new file mode 100644
index 00000000..12657be1
--- /dev/null
+++ b/auto/src/main/res/drawable-night/icon_off.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/auto/src/main/res/drawable-night/icon_popup.xml b/auto/src/main/res/drawable-night/icon_popup.xml
new file mode 100644
index 00000000..c1b5ad07
--- /dev/null
+++ b/auto/src/main/res/drawable-night/icon_popup.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/auto/src/main/res/drawable-night/icon_popup_off.xml b/auto/src/main/res/drawable-night/icon_popup_off.xml
new file mode 100644
index 00000000..7b12cdd1
--- /dev/null
+++ b/auto/src/main/res/drawable-night/icon_popup_off.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/auto/src/main/res/drawable/icon_off.xml b/auto/src/main/res/drawable/icon_off.xml
new file mode 100644
index 00000000..2b5ead57
--- /dev/null
+++ b/auto/src/main/res/drawable/icon_off.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/auto/src/main/res/drawable/icon_off_white.xml b/auto/src/main/res/drawable/icon_off_white.xml
new file mode 100644
index 00000000..12657be1
--- /dev/null
+++ b/auto/src/main/res/drawable/icon_off_white.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/auto/src/main/res/drawable/icon_popup.xml b/auto/src/main/res/drawable/icon_popup.xml
new file mode 100644
index 00000000..68dbb958
--- /dev/null
+++ b/auto/src/main/res/drawable/icon_popup.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/auto/src/main/res/drawable/icon_popup_off.xml b/auto/src/main/res/drawable/icon_popup_off.xml
new file mode 100644
index 00000000..9dd3cdd9
--- /dev/null
+++ b/auto/src/main/res/drawable/icon_popup_off.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/auto/src/main/res/drawable/icon_popup_off_white.xml b/auto/src/main/res/drawable/icon_popup_off_white.xml
new file mode 100644
index 00000000..7b12cdd1
--- /dev/null
+++ b/auto/src/main/res/drawable/icon_popup_off_white.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/auto/src/main/res/drawable/icon_popup_white.xml b/auto/src/main/res/drawable/icon_popup_white.xml
new file mode 100644
index 00000000..c1b5ad07
--- /dev/null
+++ b/auto/src/main/res/drawable/icon_popup_white.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/auto/src/main/res/layout-land/activity_main.xml b/auto/src/main/res/layout-land/activity_main.xml
new file mode 100644
index 00000000..e01487e5
--- /dev/null
+++ b/auto/src/main/res/layout-land/activity_main.xml
@@ -0,0 +1,310 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/auto/src/main/res/layout/activity_main.xml b/auto/src/main/res/layout/activity_main.xml
index c889991c..c6b0f994 100644
--- a/auto/src/main/res/layout/activity_main.xml
+++ b/auto/src/main/res/layout/activity_main.xml
@@ -1,109 +1,250 @@
-
+
-
+
-
+
-
+
-
+
+
-
+
+
+
+
+
-
+
+
+ android:textSize="20sp" />
+ android:textSize="20sp" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/auto/src/main/res/layout/activity_main_copy.xml b/auto/src/main/res/layout/activity_main_copy.xml
new file mode 100644
index 00000000..d9283a3b
--- /dev/null
+++ b/auto/src/main/res/layout/activity_main_copy.xml
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/auto/src/main/res/menu/menu_items.xml b/auto/src/main/res/menu/menu_items.xml
index 7bc8cd06..7e66a17b 100644
--- a/auto/src/main/res/menu/menu_items.xml
+++ b/auto/src/main/res/menu/menu_items.xml
@@ -1,13 +1,33 @@
+
+
+
+
+
+
+
+
+
= Build.VERSION_CODES.S) {
+ // If you don't want to adapt the device's theme settings, uncomment the snippet below
+ val uiModeManager = context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
+ uiModeManager.setApplicationNightMode(
+ when (colorScheme) {
+ "light" -> UiModeManager.MODE_NIGHT_NO // User set this explicitly
+ "dark" -> UiModeManager.MODE_NIGHT_YES // User set this explicitly
+ else -> UiModeManager.MODE_NIGHT_AUTO // Follow the device Dark Theme settings when not define yet by user
+ }
+ )
+ } else {
+ AppCompatDelegate.setDefaultNightMode(
+ when (colorScheme) {
+ "light" -> AppCompatDelegate.MODE_NIGHT_NO // User set this explicitly
+ "dark" -> AppCompatDelegate.MODE_NIGHT_YES // User set this explicitly
+ else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM // For Android 10 and 11, follow the device Dark Theme settings when not define yet by user
+ }
+ )
+
+ }
+ }
+}
\ 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 ab1f32c5..ca103f69 100644
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/GlucoDataServiceMobile.kt
@@ -8,9 +8,11 @@ import android.os.Bundle
import android.util.Log
import de.michelinside.glucodatahandler.android_auto.CarModeReceiver
import de.michelinside.glucodatahandler.common.*
+import de.michelinside.glucodatahandler.notification.AlarmNotification
import de.michelinside.glucodatahandler.common.notifier.*
import de.michelinside.glucodatahandler.common.receiver.XDripBroadcastReceiver
import de.michelinside.glucodatahandler.common.utils.GlucoDataUtils
+import de.michelinside.glucodatahandler.common.utils.PackageUtils
import de.michelinside.glucodatahandler.tasker.setWearConnectionState
import de.michelinside.glucodatahandler.watch.WatchDrip
import de.michelinside.glucodatahandler.widget.FloatingWidget
@@ -23,7 +25,17 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt
init {
Log.d(LOG_ID, "init called")
- InternalNotifier.addNotifier(this, TaskerDataReceiver, mutableSetOf(NotifySource.BROADCAST,NotifySource.IOB_COB_CHANGE,NotifySource.MESSAGECLIENT,NotifySource.OBSOLETE_VALUE))
+ InternalNotifier.addNotifier(
+ this,
+ TaskerDataReceiver,
+ mutableSetOf(
+ NotifySource.BROADCAST,
+ NotifySource.IOB_COB_CHANGE,
+ NotifySource.MESSAGECLIENT,
+ NotifySource.OBSOLETE_VALUE,
+ NotifySource.ALARM_TRIGGER
+ )
+ )
}
companion object {
@@ -45,6 +57,7 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt
try {
Log.d(LOG_ID, "onCreate called")
super.onCreate()
+ migrateSettings()
val filter = mutableSetOf(
NotifySource.BROADCAST,
NotifySource.MESSAGECLIENT,
@@ -59,6 +72,84 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt
WatchDrip.init(applicationContext)
floatingWidget.create()
LockScreenWallpaper.create(this)
+ AlarmNotification.initNotifications(this)
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onCreate exception: " + exc.message.toString() )
+ }
+ }
+
+ private fun migrateSettings() {
+ try {
+ Log.v(LOG_ID, "migrateSettings called")
+
+ if(!sharedPref!!.contains(Constants.SHARED_PREF_GLUCODATA_RECEIVERS)) {
+ val receivers = HashSet()
+ val sendToAod = sharedPref!!.getBoolean(Constants.SHARED_PREF_SEND_TO_GLUCODATA_AOD, false)
+ if (sendToAod)
+ receivers.add("de.metalgearsonic.glucodata.aod")
+ Log.i(LOG_ID, "Upgrade receivers to " + receivers.toString())
+ with(sharedPref!!.edit()) {
+ putStringSet(Constants.SHARED_PREF_GLUCODATA_RECEIVERS, receivers)
+ apply()
+ }
+ }
+
+ if(!sharedPref!!.contains(Constants.SHARED_PREF_XDRIP_RECEIVERS)) {
+ val receivers = HashSet()
+ receivers.add("com.eveningoutpost.dexdrip")
+ Log.i(LOG_ID, "Upgrade receivers to " + receivers.toString())
+ with(sharedPref!!.edit()) {
+ putStringSet(Constants.SHARED_PREF_XDRIP_RECEIVERS, receivers)
+ apply()
+ }
+ }
+
+ // create default tap actions as it was before
+ // notifications
+ if(!sharedPref!!.contains(Constants.SHARED_PREF_PERMANENT_NOTIFICATION_TAP_ACTION)) {
+ val curApp = this.packageName
+ Log.i(LOG_ID, "Setting default tap action for notification to $curApp")
+ with(sharedPref!!.edit()) {
+ putString(Constants.SHARED_PREF_PERMANENT_NOTIFICATION_TAP_ACTION, curApp)
+ apply()
+ }
+ }
+ if(!sharedPref!!.contains(Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION_TAP_ACTION)) {
+ val curApp = this.packageName
+ Log.i(LOG_ID, "Setting default tap action for second notification to $curApp")
+ with(sharedPref!!.edit()) {
+ putString(Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION_TAP_ACTION, curApp)
+ apply()
+ }
+ }
+ // widgets
+ if(!sharedPref!!.contains(Constants.SHARED_PREF_FLOATING_WIDGET_TAP_ACTION)) {
+ val curApp = if(PackageUtils.isPackageAvailable(this, Constants.PACKAGE_JUGGLUCO)) Constants.PACKAGE_JUGGLUCO else this.packageName
+ Log.i(LOG_ID, "Setting default tap action for floating widget to $curApp")
+ with(sharedPref!!.edit()) {
+ putString(Constants.SHARED_PREF_FLOATING_WIDGET_TAP_ACTION, curApp)
+ apply()
+ }
+ }
+ if(!sharedPref!!.contains(Constants.SHARED_PREF_WIDGET_TAP_ACTION)) {
+ val curApp = if(PackageUtils.isPackageAvailable(this, Constants.PACKAGE_JUGGLUCO)) Constants.PACKAGE_JUGGLUCO else this.packageName
+ Log.i(LOG_ID, "Setting default tap action for widget to $curApp")
+ with(sharedPref!!.edit()) {
+ putString(Constants.SHARED_PREF_WIDGET_TAP_ACTION, curApp)
+ apply()
+ }
+ }
+ // full screen alarm notification
+ if(!sharedPref!!.contains(Constants.SHARED_PREF_ALARM_FULLSCREEN_NOTIFICATION_ENABLED)) {
+ if (AlarmNotification.hasFullscreenPermission()) {
+ Log.i(LOG_ID, "Enabling fullscreen notification as default")
+ with(sharedPref!!.edit()) {
+ putBoolean(Constants.SHARED_PREF_ALARM_FULLSCREEN_NOTIFICATION_ENABLED, true)
+ apply()
+ }
+ }
+ }
+
} catch (exc: Exception) {
Log.e(LOG_ID, "onCreate exception: " + exc.message.toString() )
}
@@ -76,6 +167,7 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt
try {
Log.d(LOG_ID, "onDestroy called")
PermanentNotification.destroy()
+ AlarmNotification.destroy(this)
CarModeReceiver.cleanup(applicationContext)
WatchDrip.close(applicationContext)
floatingWidget.destroy()
@@ -173,7 +265,7 @@ class GlucoDataServiceMobile: GlucoDataService(AppSource.PHONE_APP), NotifierInt
if (dataSource == NotifySource.CAR_CONNECTION && CarModeReceiver.connected) {
val autoExtras = ReceiveData.createExtras()
if (autoExtras != null)
- CarModeReceiver.sendToGlucoDataAuto(context, autoExtras)
+ CarModeReceiver.sendToGlucoDataAuto(context, autoExtras, true)
}
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 fc513339..ed099c2e 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.annotation.SuppressLint
import android.app.Activity
import android.app.AlarmManager
import android.content.Context
@@ -13,43 +14,64 @@ import android.os.PowerManager
import android.provider.Settings
import android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM
import android.util.Log
+import android.view.Gravity
import android.view.Menu
import android.view.MenuItem
import android.view.View
+import android.view.View.OnClickListener
import android.widget.Button
import android.widget.ImageView
+import android.widget.TableLayout
+import android.widget.TableRow
import android.widget.TextView
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
import androidx.core.view.MenuCompat
+import androidx.core.view.setPadding
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.SourceState
import de.michelinside.glucodatahandler.common.SourceStateData
import de.michelinside.glucodatahandler.common.WearPhoneConnection
+import de.michelinside.glucodatahandler.common.notification.AlarmHandler
+import de.michelinside.glucodatahandler.common.notification.AlarmState
+import de.michelinside.glucodatahandler.common.notification.AlarmType
+import de.michelinside.glucodatahandler.common.notifier.DataSource
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.PackageUtils
import de.michelinside.glucodatahandler.common.utils.Utils
+import de.michelinside.glucodatahandler.notification.AlarmNotification
import de.michelinside.glucodatahandler.watch.LogcatReceiver
+import de.michelinside.glucodatahandler.watch.WatchDrip
+import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
+import kotlin.time.Duration.Companion.days
import de.michelinside.glucodatahandler.common.R as CR
class MainActivity : AppCompatActivity(), NotifierInterface {
private lateinit var txtBgValue: TextView
private lateinit var viewIcon: ImageView
+ private lateinit var timeText: TextView
+ private lateinit var deltaText: TextView
+ private lateinit var iobText: TextView
+ private lateinit var cobText: TextView
private lateinit var txtLastValue: TextView
private lateinit var txtVersion: TextView
- private lateinit var txtWearInfo: TextView
- private lateinit var txtCarInfo: TextView
- private lateinit var txtSourceInfo: TextView
+ private lateinit var tableDetails: TableLayout
+ private lateinit var tableConnections: TableLayout
+ private lateinit var tableAlarms: TableLayout
private lateinit var txtBatteryOptimization: TextView
private lateinit var txtHighContrastEnabled: TextView
private lateinit var txtScheduleExactAlarm: TextView
@@ -57,6 +79,8 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
private lateinit var btnSources: Button
private lateinit var sharedPref: SharedPreferences
private lateinit var optionsMenu: Menu
+ private var alarmIcon: MenuItem? = null
+ private var snoozeMenu: MenuItem? = null
private var floatingWidgetItem: MenuItem? = null
private val LOG_ID = "GDH.Main"
private var requestNotificationPermission = false
@@ -71,15 +95,21 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
txtBgValue = findViewById(R.id.txtBgValue)
viewIcon = findViewById(R.id.viewIcon)
+ timeText = findViewById(R.id.timeText)
+ deltaText = findViewById(R.id.deltaText)
+ iobText = findViewById(R.id.iobText)
+ cobText = findViewById(R.id.cobText)
txtLastValue = findViewById(R.id.txtLastValue)
- txtWearInfo = findViewById(R.id.txtWearInfo)
- txtCarInfo = findViewById(R.id.txtCarInfo)
- txtSourceInfo = findViewById(R.id.txtSourceInfo)
txtBatteryOptimization = findViewById(R.id.txtBatteryOptimization)
txtHighContrastEnabled = findViewById(R.id.txtHighContrastEnabled)
txtScheduleExactAlarm = findViewById(R.id.txtScheduleExactAlarm)
txtNotificationPermission = findViewById(R.id.txtNotificationPermission)
btnSources = findViewById(R.id.btnSources)
+ tableConnections = findViewById(R.id.tableConnections)
+ tableAlarms = findViewById(R.id.tableAlarms)
+ tableDetails = findViewById(R.id.tableDetails)
+
+ PackageUtils.updatePackages(this)
PreferenceManager.setDefaultValues(this, R.xml.preferences, false)
sharedPref = this.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
@@ -89,41 +119,16 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
txtVersion = findViewById(R.id.txtVersion)
txtVersion.text = BuildConfig.VERSION_NAME
- txtWearInfo.setOnClickListener{
- GlucoDataService.checkForConnectedNodes()
- }
-
btnSources.setOnClickListener{
val intent = Intent(this, SettingsActivity::class.java)
intent.putExtra(SettingsActivity.FRAGMENT_EXTRA, SettingsFragmentClass.SORUCE_FRAGMENT.value)
startActivity(intent)
}
- val sendToAod = sharedPref.getBoolean(Constants.SHARED_PREF_SEND_TO_GLUCODATA_AOD, false)
-
- if(!sharedPref.contains(Constants.SHARED_PREF_GLUCODATA_RECEIVERS)) {
- val receivers = HashSet()
- if (sendToAod)
- receivers.add("de.metalgearsonic.glucodata.aod")
- Log.i(LOG_ID, "Upgrade receivers to " + receivers.toString())
- with(sharedPref.edit()) {
- putStringSet(Constants.SHARED_PREF_GLUCODATA_RECEIVERS, receivers)
- apply()
- }
- }
-
- if(!sharedPref.contains(Constants.SHARED_PREF_XDRIP_RECEIVERS)) {
- val receivers = HashSet()
- receivers.add("com.eveningoutpost.dexdrip")
- Log.i(LOG_ID, "Upgrade receivers to " + receivers.toString())
- with(sharedPref.edit()) {
- putStringSet(Constants.SHARED_PREF_XDRIP_RECEIVERS, receivers)
- apply()
- }
- }
-
if (requestPermission())
GlucoDataServiceMobile.start(this, true)
+
+ Dialogs.updateColorScheme(this)
} catch (exc: Exception) {
Log.e(LOG_ID, "onCreate exception: " + exc.message.toString() )
}
@@ -147,12 +152,14 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
InternalNotifier.addNotifier(this, this, mutableSetOf(
NotifySource.BROADCAST,
NotifySource.IOB_COB_CHANGE,
+ NotifySource.IOB_COB_TIME,
NotifySource.MESSAGECLIENT,
NotifySource.CAPILITY_INFO,
NotifySource.NODE_BATTERY_LEVEL,
NotifySource.SETTINGS,
NotifySource.CAR_CONNECTION,
- NotifySource.OBSOLETE_VALUE,
+ NotifySource.TIME_VALUE,
+ NotifySource.ALARM_STATE_CHANGED,
NotifySource.SOURCE_STATE_CHANGE))
checkExactAlarmPermission()
checkBatteryOptimization()
@@ -162,13 +169,20 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
Log.i(LOG_ID, "Notification permission granted")
requestNotificationPermission = false
txtNotificationPermission.visibility = View.GONE
- GlucoDataServiceMobile.start(this, true)
+ PermanentNotification.showNotifications()
}
GlucoDataService.checkForConnectedNodes(true)
} catch (exc: Exception) {
Log.e(LOG_ID, "onResume exception: " + exc.message.toString() )
}
}
+ private val requestPermissionLauncher =
+ registerForActivityResult(
+ ActivityResultContracts.RequestPermission()
+ ) { isGranted: Boolean ->
+ Log.d(LOG_ID, "Notification permission allowed: $isGranted")
+ }
+
fun requestPermission() : Boolean {
requestNotificationPermission = false
@@ -182,7 +196,12 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
.putExtra(Settings.EXTRA_APP_PACKAGE, this.packageName)
startActivity(intent)
}*/
- this.requestPermissions(arrayOf(android.Manifest.permission.FOREGROUND_SERVICE, android.Manifest.permission.POST_NOTIFICATIONS), 3)
+ if (this.shouldShowRequestPermissionRationale(
+ android.Manifest.permission.POST_NOTIFICATIONS)) {
+ Dialogs.showOkDialog(this, resources.getString(CR.string.permission_notification_title), resources.getString(CR.string.permission_notification_message)) { _, _ -> requestPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS) }
+ } else {
+ this.requestPermissions(arrayOf(android.Manifest.permission.POST_NOTIFICATIONS), 3)
+ }
return false
} else {
txtNotificationPermission.visibility = View.GONE
@@ -277,7 +296,10 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
inflater.inflate(R.menu.menu_items, menu)
MenuCompat.setGroupDividerEnabled(menu!!, true)
optionsMenu = menu
+ alarmIcon = optionsMenu.findItem(R.id.action_alarm_toggle)
+ snoozeMenu = optionsMenu.findItem(R.id.group_snooze_title)
floatingWidgetItem = optionsMenu.findItem(R.id.action_floating_widget_toggle)
+ updateAlarmIcon()
updateMenuItems()
return true
} catch (exc: Exception) {
@@ -307,6 +329,12 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
startActivity(intent)
return true
}
+ R.id.action_alarms -> {
+ val intent = Intent(this, SettingsActivity::class.java)
+ intent.putExtra(SettingsActivity.FRAGMENT_EXTRA, SettingsFragmentClass.ALARM_FRAGMENT.value)
+ startActivity(intent)
+ return true
+ }
R.id.action_help -> {
val browserIntent = Intent(
Intent.ACTION_VIEW,
@@ -346,6 +374,35 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
val menuIt: MenuItem = optionsMenu.findItem(R.id.action_save_wear_logs)
menuIt.isEnabled = WearPhoneConnection.nodesConnected && !LogcatReceiver.isActive
}
+ R.id.group_snooze_title -> {
+ Log.v(LOG_ID, "snooze group selected - snoozeActive=${AlarmHandler.isSnoozeActive}")
+ val snoozeStop: MenuItem = optionsMenu.findItem(R.id.action_stop_snooze)
+ snoozeStop.isVisible = AlarmHandler.isSnoozeActive
+ }
+ R.id.action_stop_snooze -> {
+ AlarmHandler.setSnooze(0L)
+ return true
+ }
+ R.id.action_snooze_30 -> {
+ AlarmHandler.setSnooze(30L)
+ return true
+ }
+ R.id.action_snooze_60 -> {
+ AlarmHandler.setSnooze(60L)
+ return true
+ }
+ R.id.action_snooze_90 -> {
+ AlarmHandler.setSnooze(90L)
+ return true
+ }
+ R.id.action_snooze_120 -> {
+ AlarmHandler.setSnooze(120L)
+ return true
+ }
+ R.id.action_alarm_toggle -> {
+ toggleAlarm()
+ return true
+ }
R.id.action_floating_widget_toggle -> {
Log.v(LOG_ID, "Floating widget toggle")
with(sharedPref.edit()) {
@@ -361,29 +418,86 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
return super.onOptionsItemSelected(item)
}
+ private fun toggleAlarm() {
+ try {
+ val state = AlarmNotification.getAlarmState(this)
+ if(AlarmNotification.channelActive(this)) {
+ Log.v(LOG_ID, "toggleAlarm called for state $state")
+ when (state) {
+ AlarmState.SNOOZE -> AlarmHandler.setSnooze(0) // disable snooze
+ AlarmState.DISABLED -> {
+ with(sharedPref.edit()) {
+ putBoolean(Constants.SHARED_PREF_ALARM_NOTIFICATION_ENABLED, true)
+ apply()
+ }
+ }
+ AlarmState.INACTIVE,
+ AlarmState.ACTIVE -> {
+ with(sharedPref.edit()) {
+ putBoolean(Constants.SHARED_PREF_ALARM_NOTIFICATION_ENABLED, false)
+ apply()
+ }
+ }
+ }
+ } else {
+ with(sharedPref.edit()) {
+ putBoolean(Constants.SHARED_PREF_ALARM_NOTIFICATION_ENABLED, false)
+ apply()
+ }
+ Dialogs.showOkDialog(this, resources.getString(CR.string.permission_alarm_notification_title), resources.getString(CR.string.permission_alarm_notification_message)) { _, _ ->
+ val intent: Intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
+ .putExtra(Settings.EXTRA_APP_PACKAGE, this.packageName)
+ startActivity(intent)
+ }
+ }
+ //updateAlarmIcon()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "updateAlarmIcon exception: " + exc.message.toString() )
+ }
+ }
+
+ private fun updateAlarmIcon() {
+ try {
+ if(!AlarmNotification.channelActive(this)) {
+ with(sharedPref.edit()) {
+ putBoolean(Constants.SHARED_PREF_ALARM_NOTIFICATION_ENABLED, false)
+ apply()
+ }
+ }
+ val state = AlarmNotification.getAlarmState(this)
+ Log.v(LOG_ID, "updateAlarmIcon called for state $state")
+ if(alarmIcon != null) {
+ alarmIcon!!.icon = ContextCompat.getDrawable(this, state.icon)
+ }
+ if(snoozeMenu != null) {
+ snoozeMenu!!.isVisible = (state != AlarmState.DISABLED)
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "updateAlarmIcon exception: " + exc.message.toString() )
+ }
+ }
+
+ @SuppressLint("SetTextI18n")
private fun update() {
try {
Log.v(LOG_ID, "update values")
- txtBgValue.text = ReceiveData.getClucoseAsString()
- txtBgValue.setTextColor(ReceiveData.getClucoseColor())
+ txtBgValue.text = ReceiveData.getGlucoseAsString()
+ txtBgValue.setTextColor(ReceiveData.getGlucoseColor())
if (ReceiveData.isObsolete(Constants.VALUE_OBSOLETE_SHORT_SEC) && !ReceiveData.isObsolete()) {
txtBgValue.paintFlags = Paint.STRIKE_THRU_TEXT_FLAG
} else {
txtBgValue.paintFlags = 0
}
- viewIcon.setImageIcon(BitmapUtils.getRateAsIcon())
- txtLastValue.text = ReceiveData.getAsString(this)
- if (WearPhoneConnection.nodesConnected) {
- txtWearInfo.text = resources.getString(CR.string.activity_main_connected_label, WearPhoneConnection.getBatterLevelsAsString())
- }
- else
- txtWearInfo.text = resources.getText(CR.string.activity_main_disconnected_label)
- if (Utils.isPackageAvailable(this, Constants.PACKAGE_GLUCODATAAUTO)) {
- 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
- }
+ viewIcon.setImageIcon(BitmapUtils.getRateAsIcon(withShadow = true))
+
+ timeText.text = "🕒 ${ReceiveData.getElapsedRelativeTimeAsString(this)}"
+ deltaText.text = "Δ ${ReceiveData.getDeltaAsString()}"
+ iobText.text = "💉 " + ReceiveData.getIobAsString()
+ cobText.text = "🍔 " + ReceiveData.getCobAsString()
+ iobText.visibility = if (ReceiveData.isIobCobObsolete(Constants.VALUE_OBSOLETE_LONG_SEC)) View.GONE else View.VISIBLE
+ cobText.visibility = iobText.visibility
+
+ txtLastValue.visibility = if(ReceiveData.time>0) View.GONE else View.VISIBLE
if (ReceiveData.time == 0L) {
btnSources.visibility = View.VISIBLE
@@ -391,8 +505,11 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
btnSources.visibility = View.GONE
}
- txtSourceInfo.text = SourceStateData.getState(this)
+ updateAlarmsTable()
+ updateConnectionsTable()
+ updateDetailsTable()
+ updateAlarmIcon()
updateMenuItems()
} catch (exc: Exception) {
Log.e(LOG_ID, "update exception: " + exc.message.toString() )
@@ -400,7 +517,7 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
}
override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) {
- Log.v(LOG_ID, "new intent received")
+ Log.v(LOG_ID, "OnNotifyData called for $dataSource")
update()
}
@@ -440,6 +557,91 @@ class MainActivity : AppCompatActivity(), NotifierInterface {
}
}
+ private fun updateConnectionsTable() {
+ tableConnections.removeViews(1, maxOf(0, tableConnections.childCount - 1))
+ if (SourceStateData.lastState != SourceState.NONE)
+ tableConnections.addView(createRow(SourceStateData.lastSource.resId,
+ SourceStateData.getStateMessage(this)))
+
+ if (WearPhoneConnection.nodesConnected) {
+ val onClickListener = OnClickListener {
+ GlucoDataService.checkForConnectedNodes(true)
+ }
+ WearPhoneConnection.getNodeBatterLevels().forEach { name, level ->
+ tableConnections.addView(createRow(name, if (level > 0) "$level%" else "?%", onClickListener))
+ }
+ }
+ if (WatchDrip.connected) {
+ tableConnections.addView(createRow(CR.string.pref_switch_watchdrip_enabled, resources.getString(CR.string.connected_label)))
+ }
+
+ if (CarModeReceiver.AA_connected) {
+ tableConnections.addView(createRow(CR.string.pref_cat_android_auto, resources.getString(CR.string.connected_label)))
+ }
+ checkTableVisibility(tableConnections)
+ }
+
+ private fun updateAlarmsTable() {
+ tableAlarms.removeViews(1, maxOf(0, tableAlarms.childCount - 1))
+ if(ReceiveData.time > 0 && ReceiveData.getAlarmType() != AlarmType.OK) {
+ tableAlarms.addView(createRow(CR.string.info_label_alarm, resources.getString(ReceiveData.getAlarmType().resId) + (if (ReceiveData.forceAlarm) " ⚠" else "" )))
+ }
+ if (AlarmHandler.isSnoozeActive)
+ tableAlarms.addView(createRow(CR.string.snooze, AlarmHandler.snoozeTimestamp))
+ checkTableVisibility(tableAlarms)
+ }
+
+ private fun updateDetailsTable() {
+ tableDetails.removeViews(1, maxOf(0, tableDetails.childCount - 1))
+ if(ReceiveData.time > 0) {
+ if (ReceiveData.isMmol)
+ tableDetails.addView(createRow(CR.string.info_label_raw, "${ReceiveData.rawValue} mg/dl"))
+ tableDetails.addView(createRow(CR.string.info_label_timestamp, DateFormat.getTimeInstance(
+ DateFormat.DEFAULT).format(Date(ReceiveData.time))))
+ if (!ReceiveData.isIobCobObsolete(1.days.inWholeSeconds.toInt()))
+ tableDetails.addView(createRow(CR.string.info_label_iob_cob_timestamp, DateFormat.getTimeInstance(
+ DateFormat.DEFAULT).format(Date(ReceiveData.iobCobTime))))
+ if (ReceiveData.sensorID?.isNotEmpty() == true) {
+ tableDetails.addView(createRow(CR.string.info_label_sensor_id, if(BuildConfig.DEBUG) "ABCDE12345" else ReceiveData.sensorID!!))
+ }
+ if(ReceiveData.source != DataSource.NONE)
+ tableDetails.addView(createRow(CR.string.info_label_source, resources.getString(ReceiveData.source.resId)))
+ }
+ checkTableVisibility(tableDetails)
+ }
+
+ private fun checkTableVisibility(table: TableLayout) {
+ table.visibility = if(table.childCount <= 1) View.GONE else View.VISIBLE
+ }
+
+ private fun createColumn(text: String, end: Boolean, onClickListener: OnClickListener? = null) : TextView {
+ val textView = TextView(this)
+ textView.layoutParams = TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 1F)
+ textView.text = text
+ textView.textSize = 18F
+ if (end)
+ textView.gravity = Gravity.CENTER_VERTICAL or Gravity.END
+ else
+ textView.gravity = Gravity.CENTER_VERTICAL
+ if(onClickListener != null)
+ textView.setOnClickListener(onClickListener)
+ return textView
+ }
+
+ private fun createRow(keyResId: Int, value: String, onClickListener: OnClickListener? = null) : TableRow {
+ return createRow(resources.getString(keyResId), value, onClickListener)
+ }
+
+ private fun createRow(key: String, value: String, onClickListener: OnClickListener? = null) : TableRow {
+ val row = TableRow(this)
+ row.weightSum = 2f
+ //row.setBackgroundColor(resources.getColor(R.color.table_row))
+ row.setPadding(Utils.dpToPx(5F, this))
+ row.addView(createColumn(key, false, onClickListener))
+ row.addView(createColumn(value, true, onClickListener))
+ return row
+ }
+
companion object {
const val CREATE_PHONE_FILE = 1
const val CREATE_WEAR_FILE = 2
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 6789c104..b39a0652 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
@@ -10,8 +10,10 @@ import android.os.Bundle
import android.util.Log
import androidx.car.app.connection.CarConnection
import de.michelinside.glucodatahandler.common.*
+import de.michelinside.glucodatahandler.common.notification.AlarmHandler
import de.michelinside.glucodatahandler.common.notifier.*
import de.michelinside.glucodatahandler.common.tasks.ElapsedTimeTask
+import de.michelinside.glucodatahandler.common.utils.PackageUtils
import de.michelinside.glucodatahandler.common.utils.Utils
import de.michelinside.glucodatahandler.tasker.setAndroidAutoConnectionState
@@ -84,7 +86,7 @@ object CarModeReceiver {
else -> "Unknown car connection type"
}
Log.d(LOG_ID, "onConnectionStateUpdated: " + message + " (" + connectionState.toString() + ")")
- val curState = connected
+ val curState = AA_connected
if (connectionState == CarConnection.CONNECTION_TYPE_NOT_CONNECTED) {
if (car_connected) {
Log.i(LOG_ID, "Exited Car Mode")
@@ -96,22 +98,25 @@ object CarModeReceiver {
car_connected = true
GlucoDataService.context?.setAndroidAutoConnectionState(true)
}
- if (curState != connected)
+ if (curState != AA_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) {
+ fun sendToGlucoDataAuto(context: Context, extras: Bundle, withSettings: Boolean = false) {
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)) {
+ if (connected && sharedPref.getBoolean(Constants.SHARED_PREF_SEND_TO_GLUCODATAAUTO, true) && PackageUtils.isGlucoDataAutoAvailable(context)) {
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)
+ if(withSettings && sharedPref.getBoolean(Constants.SHARED_PREF_SEND_PREF_TO_GLUCODATAAUTO, true)) {
+ val settings = ReceiveData.getSettingsBundle()
+ settings.putBoolean(Constants.SHARED_PREF_RELATIVE_TIME, ElapsedTimeTask.relativeTime)
+ extras.putBundle(Constants.SETTINGS_BUNDLE, settings)
+ extras.putBundle(Constants.ALARM_SETTINGS_BUNDLE, AlarmHandler.getSettings(false))
+ }
intent.putExtras(extras)
intent.setPackage(Constants.PACKAGE_GLUCODATAAUTO)
context.sendBroadcast(intent)
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/notification/AlarmNotification.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/notification/AlarmNotification.kt
new file mode 100644
index 00000000..0516fa26
--- /dev/null
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/notification/AlarmNotification.kt
@@ -0,0 +1,236 @@
+package de.michelinside.glucodatahandler.notification
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.graphics.Paint
+import android.media.AudioAttributes
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.util.Log
+import android.view.View
+import android.widget.RemoteViews
+import android.widget.Toast
+import de.michelinside.glucodatahandler.android_auto.CarModeReceiver
+import de.michelinside.glucodatahandler.common.Constants
+import de.michelinside.glucodatahandler.common.GlucoDataService
+import de.michelinside.glucodatahandler.R
+import de.michelinside.glucodatahandler.common.Command
+import de.michelinside.glucodatahandler.common.R as CR
+import de.michelinside.glucodatahandler.common.ReceiveData
+import de.michelinside.glucodatahandler.common.WearPhoneConnection
+import de.michelinside.glucodatahandler.common.notification.AlarmNotificationBase
+import de.michelinside.glucodatahandler.common.notification.AlarmType
+import de.michelinside.glucodatahandler.common.notifier.NotifySource
+import de.michelinside.glucodatahandler.common.utils.BitmapUtils
+import de.michelinside.glucodatahandler.watch.WatchDrip
+import java.io.FileOutputStream
+
+object AlarmNotification : AlarmNotificationBase() {
+ private var noAlarmOnWearConnected = false
+ private var noAlarmOnAAConnected = false
+ private var fullscreenEnabled = true
+
+ override val active: Boolean get() {
+ if(getEnabled()) {
+ if(noAlarmOnWearConnected && WearPhoneConnection.nodesConnected) {
+ Log.d(LOG_ID, "No alarm as wear is connected")
+ return false
+ }
+ if(noAlarmOnAAConnected && CarModeReceiver.AA_connected) {
+ Log.d(LOG_ID, "No alarm as Android Auto is connected")
+ return false
+ }
+ return true
+ }
+ Log.d(LOG_ID, "No alarm as enabled is ${getEnabled()}")
+ return false
+ }
+
+ override fun adjustNoticiationChannel(context: Context, channel: NotificationChannel) {
+ val audioAttributes = AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_ALARM)
+ .build()
+ channel.setSound(getUri(CR.raw.silence, context), audioAttributes)
+ channel.enableVibration(false)
+ channel.enableLights(true)
+ }
+
+ override fun onNotificationStopped(noticationId: Int, context: Context?) {
+ LockscreenActivity.close()
+ }
+
+ override fun canReshowNotification(): Boolean {
+ return !LockscreenActivity.isActive()
+ }
+
+ override fun buildNotification(
+ notificationBuilder: Notification.Builder,
+ context: Context,
+ alarmType: AlarmType
+ ) {
+ @Suppress("DEPRECATION")
+ notificationBuilder.setLights(ReceiveData.getAlarmTypeColor(alarmType), 300, 100)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ @Suppress("DEPRECATION")
+ notificationBuilder.setFlag(Notification.FLAG_SHOW_LIGHTS, true)
+ }
+
+ val resId = getAlarmTextRes(alarmType)
+ val contentView = RemoteViews(GlucoDataService.context!!.packageName, R.layout.alarm_notification)
+ contentView.setTextViewText(R.id.alarm, context.getString(resId!!))
+ contentView.setTextViewText(R.id.snooze, context.getString(CR.string.snooze))
+ if(alarmType == AlarmType.OBSOLETE) {
+ contentView.setTextViewText(R.id.deltaText, "🕒 ${ReceiveData.getElapsedTimeMinuteAsString(context)}")
+ contentView.setViewVisibility(R.id.glucose, View.GONE)
+ contentView.setViewVisibility(R.id.trendImage, View.GONE)
+ } else {
+ contentView.setViewVisibility(R.id.glucose, View.VISIBLE)
+ contentView.setViewVisibility(R.id.trendImage, View.VISIBLE)
+ contentView.setTextViewText(R.id.glucose, ReceiveData.getGlucoseAsString())
+ contentView.setTextColor(R.id.glucose, ReceiveData.getGlucoseColor())
+ contentView.setImageViewBitmap(R.id.trendImage, BitmapUtils.getRateAsBitmap(withShadow = true))
+ contentView.setTextViewText(R.id.deltaText, "Δ " + ReceiveData.getDeltaAsString())
+ }
+ contentView.setOnClickPendingIntent(R.id.snooze_60, createSnoozeIntent(context, 60L, getNotificationId(alarmType)))
+ contentView.setOnClickPendingIntent(R.id.snooze_90, createSnoozeIntent(context, 90L, getNotificationId(alarmType)))
+ contentView.setOnClickPendingIntent(R.id.snooze_120, createSnoozeIntent(context, 120L, getNotificationId(alarmType)))
+ if (ReceiveData.isObsolete(Constants.VALUE_OBSOLETE_SHORT_SEC)) {
+ if (!ReceiveData.isObsolete())
+ contentView.setInt(R.id.glucose, "setPaintFlags", Paint.STRIKE_THRU_TEXT_FLAG or Paint.ANTI_ALIAS_FLAG)
+ contentView.setTextColor(R.id.deltaText, ReceiveData.getAlarmTypeColor(AlarmType.OBSOLETE) )
+ }
+
+ if (getAddSnooze()) {
+ val bigContentView = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ RemoteViews(contentView)
+ } else {
+ @Suppress("DEPRECATION")
+ contentView.clone()
+ }
+ notificationBuilder.setCustomBigContentView(bigContentView)
+ } else {
+ notificationBuilder.setCustomBigContentView(null)
+ }
+ contentView.setViewVisibility(R.id.snoozeLayout, View.GONE)
+ notificationBuilder.setCustomContentView(contentView)
+
+ if (fullscreenEnabled && hasFullscreenPermission()) {
+ val fullScreenIntent = Intent(context, LockscreenActivity::class.java)
+ fullScreenIntent.flags =
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or
+ Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or Intent.FLAG_ACTIVITY_NO_USER_ACTION
+ fullScreenIntent.putExtra(Constants.ALARM_SNOOZE_EXTRA_NOTIFY_ID, getNotificationId(alarmType))
+ fullScreenIntent.putExtra(Constants.ALARM_NOTIFICATION_EXTRA_ALARM_TYPE, alarmType.ordinal)
+ val fullScreenPendingIntent = PendingIntent.getActivity(context, 800, fullScreenIntent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
+ notificationBuilder.setFullScreenIntent(fullScreenPendingIntent, true)
+ }
+ }
+
+ override fun stopNotificationForRetrigger(): Boolean {
+ return LockscreenActivity.isActive()
+ }
+
+ fun setFullscreenEnabled(fsEnable: Boolean) {
+ try {
+ Log.v(LOG_ID, "setFullscreenEnabled called: current=$fullscreenEnabled - new=$fsEnable")
+ fullscreenEnabled = fsEnable
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "setFullscreenEnabled exception: " + exc.toString() )
+ }
+ }
+
+ override fun getStartDelayMs(context: Context): Int {
+ val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
+ return sharedPref.getInt(Constants.SHARED_PREF_ALARM_START_DELAY, 1000)
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
+ try {
+ if (key == null) {
+ onSharedPreferenceChanged(sharedPreferences, Constants.SHARED_PREF_ALARM_FULLSCREEN_NOTIFICATION_ENABLED)
+ onSharedPreferenceChanged(sharedPreferences, Constants.SHARED_PREF_NO_ALARM_NOTIFICATION_WEAR_CONNECTED)
+ onSharedPreferenceChanged(sharedPreferences, Constants.SHARED_PREF_NO_ALARM_NOTIFICATION_AUTO_CONNECTED)
+ } else {
+ when(key) {
+ Constants.SHARED_PREF_ALARM_FULLSCREEN_NOTIFICATION_ENABLED -> setFullscreenEnabled(sharedPreferences.getBoolean(Constants.SHARED_PREF_ALARM_FULLSCREEN_NOTIFICATION_ENABLED, fullscreenEnabled))
+ Constants.SHARED_PREF_NO_ALARM_NOTIFICATION_WEAR_CONNECTED -> noAlarmOnWearConnected = sharedPreferences.getBoolean(Constants.SHARED_PREF_NO_ALARM_NOTIFICATION_WEAR_CONNECTED, noAlarmOnWearConnected)
+ Constants.SHARED_PREF_NO_ALARM_NOTIFICATION_AUTO_CONNECTED -> noAlarmOnAAConnected = sharedPreferences.getBoolean(Constants.SHARED_PREF_NO_ALARM_NOTIFICATION_AUTO_CONNECTED, noAlarmOnAAConnected)
+ }
+ }
+ super.onSharedPreferenceChanged(sharedPreferences, key)
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onSharedPreferenceChanged exception: " + exc.toString())
+ }
+ }
+
+ fun saveAlarm(context: Context, alarmType: AlarmType, uri: Uri) {
+ try {
+ Log.v(LOG_ID, "saveAlarm called for $alarmType to $uri")
+ val resId = getAlarmSoundRes(alarmType)
+ if (resId != null) {
+ Thread {
+ context.contentResolver.openFileDescriptor(uri, "w")?.use {
+ FileOutputStream(it.fileDescriptor).use { outputStream ->
+ val inputStream = context.resources.openRawResource(resId)
+ val buffer = ByteArray(4 * 1024) // or other buffer size
+ var read: Int
+ while (inputStream.read(buffer).also { rb -> read = rb } != -1) {
+ outputStream.write(buffer, 0, read)
+ }
+ Log.v(LOG_ID, "flush")
+ outputStream.flush()
+ outputStream.close()
+ }
+ }
+ val text = context.resources.getText(CR.string.alarm_saved)
+ Handler(GlucoDataService.context!!.mainLooper).post {
+ Toast.makeText(GlucoDataService.context!!, text, Toast.LENGTH_SHORT).show()
+ }
+ }.start()
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "Saving alarm to file exception: " + exc.message.toString() )
+ }
+ }
+
+ override fun getNotifierFilter(): MutableSet {
+ return mutableSetOf(NotifySource.CAR_CONNECTION, NotifySource.CAPILITY_INFO)
+ }
+
+ override fun executeTest(alarmType: AlarmType, context: Context, fromIntern: Boolean) {
+ super.executeTest(alarmType, context, fromIntern)
+ WatchDrip.sendTestAlert(context, alarmType)
+ }
+
+ override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) {
+ try {
+ when(dataSource) {
+ NotifySource.CAR_CONNECTION,
+ NotifySource.CAPILITY_INFO -> {
+ if(WearPhoneConnection.nodesConnected) {
+ // update AA state on wear to prevent alarms, if they should not been executed while AA connected
+ Log.i(LOG_ID, "Car connection has changed: ${CarModeReceiver.AA_connected}")
+ val bundle = Bundle()
+ bundle.putBoolean(
+ Constants.EXTRA_AA_CONNECTED,
+ CarModeReceiver.AA_connected
+ )
+ GlucoDataService.sendCommand(Command.AA_CONNECTION_STATE, bundle)
+ }
+ }
+ else -> super.OnNotifyData(context, dataSource, extras)
+
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "OnNotifyData exception: " + exc.toString())
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/notification/LockscreenActivity.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/notification/LockscreenActivity.kt
new file mode 100644
index 00000000..0f0e4206
--- /dev/null
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/notification/LockscreenActivity.kt
@@ -0,0 +1,273 @@
+package de.michelinside.glucodatahandler.notification
+
+import android.app.KeyguardManager
+import android.content.Context
+import android.graphics.Paint
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import android.view.WindowInsets
+import android.view.WindowInsetsController
+import android.view.WindowManager
+import android.widget.Button
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.app.AppCompatDelegate
+import com.ncorti.slidetoact.SlideToActView
+import de.michelinside.glucodatahandler.R
+import de.michelinside.glucodatahandler.common.Constants
+import de.michelinside.glucodatahandler.common.ReceiveData
+import de.michelinside.glucodatahandler.common.notification.AlarmHandler
+import de.michelinside.glucodatahandler.common.notification.AlarmType
+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.common.R as CR
+
+
+class LockscreenActivity : AppCompatActivity(), NotifierInterface {
+
+ private lateinit var txtBgValue: TextView
+ private lateinit var viewIcon: ImageView
+ private lateinit var txtDelta: TextView
+ private lateinit var txtTime: TextView
+ private lateinit var txtAlarm: TextView
+ private lateinit var txtSnooze: TextView
+ private lateinit var btnDismiss: SlideToActView
+ private lateinit var btnSnooze: SlideToActView
+ private lateinit var btnSnooze60: Button
+ private lateinit var btnSnooze90: Button
+ private lateinit var btnSnooze120: Button
+ private lateinit var layoutSnooze: LinearLayout
+ private lateinit var layoutSnoozeButtons: LinearLayout
+ private var alarmType: AlarmType? = null
+ private var notificationId: Int = -1
+ private var createTime = 0L
+
+ companion object {
+ private val LOG_ID = "GDH.AlarmLockscreenActivity"
+ private var activity: AppCompatActivity? = null
+ fun close() {
+ try {
+ Log.d(LOG_ID, "close called for activity ${activity}")
+ if(isActive()) {
+ activity?.finish()
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "close exception: " + exc.message.toString() )
+ }
+ }
+
+ fun isActive(): Boolean {
+ return activity != null
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ try {
+ Log.d(LOG_ID, "onCreate called with params ${Utils.dumpBundle(this.intent.extras)}")
+ activity = this
+ showWhenLockedAndTurnScreenOn()
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_lockscreen)
+ hideSystemUI()
+
+ if(this.intent.extras?.containsKey(Constants.ALARM_NOTIFICATION_EXTRA_ALARM_TYPE) == true) {
+ alarmType = AlarmType.fromIndex(this.intent.extras!!.getInt(Constants.ALARM_NOTIFICATION_EXTRA_ALARM_TYPE, ReceiveData.getAlarmType().ordinal))
+ }
+
+ if(this.intent.extras?.containsKey(Constants.ALARM_SNOOZE_EXTRA_NOTIFY_ID) == true) {
+ notificationId = this.intent.extras!!.getInt(Constants.ALARM_SNOOZE_EXTRA_NOTIFY_ID, -1)
+ }
+ createTime = System.currentTimeMillis()
+ txtBgValue = findViewById(R.id.txtBgValue)
+ viewIcon = findViewById(R.id.viewIcon)
+ txtDelta = findViewById(R.id.txtDelta)
+ txtTime = findViewById(R.id.txtTime)
+ txtAlarm = findViewById(R.id.txtAlarm)
+ btnDismiss = findViewById(R.id.btnDismiss)
+ btnSnooze = findViewById(R.id.btnSnooze)
+ txtSnooze = findViewById(R.id.txtSnooze)
+ btnSnooze60 = findViewById(R.id.btnSnooze60)
+ btnSnooze90 = findViewById(R.id.btnSnooze90)
+ btnSnooze120 = findViewById(R.id.btnSnooze120)
+ layoutSnooze = findViewById(R.id.layoutSnooze)
+ layoutSnoozeButtons = findViewById(R.id.layoutSnoozeButtons)
+
+ val sharedPref = this.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
+ if (sharedPref.getBoolean(Constants.SHARED_PREF_ALARM_SNOOZE_ON_NOTIFICATION, false))
+ layoutSnooze.visibility = View.VISIBLE
+ else
+ layoutSnooze.visibility = View.GONE
+
+ btnDismiss.onSlideCompleteListener =
+ object : SlideToActView.OnSlideCompleteListener {
+ override fun onSlideComplete(view: SlideToActView) {
+ Log.d(LOG_ID, "Slide to stop completed!")
+ stop()
+ }
+ }
+ btnSnooze.onSlideCompleteListener =
+ object : SlideToActView.OnSlideCompleteListener {
+ override fun onSlideComplete(view: SlideToActView) {
+ Log.d(LOG_ID, "Slide to snooze completed!")
+ AlarmNotification.stopVibrationAndSound()
+ btnSnooze.visibility = View.GONE
+ txtSnooze.visibility = View.VISIBLE
+ layoutSnoozeButtons.visibility = View.VISIBLE
+ }
+ }
+ btnSnooze60.setOnClickListener{
+ AlarmHandler.setSnooze(60)
+ stop()
+ }
+ btnSnooze90.setOnClickListener{
+ AlarmHandler.setSnooze(90)
+ stop()
+ }
+ btnSnooze120.setOnClickListener{
+ AlarmHandler.setSnooze(120)
+ stop()
+ }
+ delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES
+
+ if (sharedPref.getBoolean(Constants.SHARED_PREF_ALARM_FULLSCREEN_DISMISS_KEYGUARD, false)) {
+ val keyguardManager = getSystemService(KEYGUARD_SERVICE) as KeyguardManager
+ keyguardManager.requestDismissKeyguard(this, null)
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onCreate exception: " + exc.message.toString() )
+ }
+ }
+
+ override fun onDestroy() {
+ try {
+ Log.v(LOG_ID, "onDestroy called")
+ super.onDestroy()
+ activity = null
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onDestroy exception: " + exc.message.toString() )
+ }
+ }
+
+ override fun onResume() {
+ try {
+ Log.v(LOG_ID, "onResume called")
+ super.onResume()
+ update()
+ InternalNotifier.addNotifier(this,this, mutableSetOf(
+ NotifySource.BROADCAST,
+ NotifySource.MESSAGECLIENT,
+ NotifySource.TIME_VALUE))
+ if(!AlarmNotification.notificationActive)
+ stop()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onCreate exception: " + exc.message.toString() )
+ }
+ }
+
+ override fun onPause() {
+ try {
+ Log.v(LOG_ID, "onPause called")
+ super.onPause()
+ InternalNotifier.remNotifier(this, this)
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onCreate exception: " + exc.message.toString() )
+ }
+ }
+
+ private fun stop() {
+ try {
+ Log.d(LOG_ID, "stop called for id $notificationId")
+ activity = null
+ if (notificationId > 0)
+ AlarmNotification.stopNotification(notificationId, this)
+ else
+ AlarmNotification.stopCurrentNotification(this)
+ finish()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onCreate exception: " + exc.message.toString() )
+ }
+ }
+
+ private fun showWhenLockedAndTurnScreenOn() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
+ setShowWhenLocked(true)
+ setTurnScreenOn(true)
+ }
+
+ @Suppress("DEPRECATION")
+ window.addFlags(
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
+ WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON or
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
+ WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+ )
+ }
+
+ private fun hideSystemUI() {
+ // Enables sticky immersive mode.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ window.setDecorFitsSystemWindows(false)
+ window.insetsController?.let {
+ it.hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
+ it.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ }
+ } else {
+ @Suppress("DEPRECATION")
+ window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_FULLSCREEN
+ or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
+ }
+ }
+
+ private fun update() {
+ try {
+ Log.v(LOG_ID, "update values")
+ txtBgValue.text = ReceiveData.getGlucoseAsString()
+ txtBgValue.setTextColor(ReceiveData.getGlucoseColor())
+ if (ReceiveData.isObsolete(Constants.VALUE_OBSOLETE_SHORT_SEC) && !ReceiveData.isObsolete()) {
+ txtBgValue.paintFlags = Paint.STRIKE_THRU_TEXT_FLAG
+ } else {
+ txtBgValue.paintFlags = 0
+ }
+ viewIcon.setImageIcon(BitmapUtils.getRateAsIcon(withShadow = true))
+ txtDelta.text = "Δ ${ReceiveData.getDeltaAsString()}"
+ txtTime.text = "🕒 ${ReceiveData.getElapsedTimeMinuteAsString(this)}"
+ val resId = (if(alarmType != null) alarmType else ReceiveData.getAlarmType())?.let {
+ AlarmNotification.getAlarmTextRes(
+ it
+ )
+ }
+ if (resId != null) {
+ txtAlarm.text = resources.getString(resId)
+ } else {
+ txtAlarm.text = resources.getString(CR.string.test_alarm)
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onCreate exception: " + exc.message.toString())
+ }
+ }
+
+ override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) {
+ try {
+ Log.v(LOG_ID, "OnNotifyData called for $dataSource")
+ if((System.currentTimeMillis()-createTime) >= (5*60*1000)) {
+ finish()
+ } else {
+ update()
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onCreate exception: " + exc.message.toString())
+ }
+ }
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/notification/PermanentNotification.kt
similarity index 70%
rename from mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt
rename to mobile/src/main/java/de/michelinside/glucodatahandler/notification/PermanentNotification.kt
index 4d717ef5..dba4068b 100644
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/PermanentNotification.kt
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/notification/PermanentNotification.kt
@@ -1,25 +1,31 @@
package de.michelinside.glucodatahandler
import android.app.Notification
+import android.app.PendingIntent
import android.content.Context
import android.content.SharedPreferences
+import android.graphics.Bitmap
+import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.drawable.Icon
import android.os.Bundle
import android.util.Log
import android.view.View
+import android.widget.ImageView
import android.widget.RemoteViews
+import android.widget.TextView
import de.michelinside.glucodatahandler.common.Constants
import de.michelinside.glucodatahandler.common.GlucoDataService
import de.michelinside.glucodatahandler.common.ReceiveData
-import de.michelinside.glucodatahandler.common.utils.Utils
+import de.michelinside.glucodatahandler.common.notification.AlarmType
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.notification.ChannelType
import de.michelinside.glucodatahandler.common.notification.Channels
+import de.michelinside.glucodatahandler.common.utils.PackageUtils
object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPreferenceChangeListener {
@@ -81,7 +87,6 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe
notificationCompat = Notification.Builder(context, ChannelType.MOBILE_SECOND.channelId)
.setSmallIcon(R.mipmap.ic_launcher)
- .setContentIntent(Utils.getAppIntent(context, MainActivity::class.java, 5, false))
.setOngoing(true)
.setOnlyAlertOnce(true)
.setAutoCancel(false)
@@ -93,7 +98,6 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe
foregroundNotificationCompat = Notification.Builder(context, ChannelType.MOBILE_FOREGROUND.channelId)
.setSmallIcon(R.mipmap.ic_launcher)
- .setContentIntent(Utils.getAppIntent(context, MainActivity::class.java, 4, false))
.setOngoing(true)
.setOnlyAlertOnce(true)
.setAutoCancel(false)
@@ -115,59 +119,95 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe
val bigIcon = sharedPref.getBoolean(Constants.SHARED_PREF_PERMANENT_NOTIFICATION_USE_BIG_ICON, false)
val coloredIcon = sharedPref.getBoolean(Constants.SHARED_PREF_PERMANENT_NOTIFICATION_COLORED_ICON, true)
return when(sharedPref.getString(iconKey, StatusBarIcon.APP.pref)) {
- StatusBarIcon.GLUCOSE.pref -> BitmapUtils.getGlucoseAsIcon(roundTarget=!bigIcon, color = if(coloredIcon) ReceiveData.getClucoseColor() else Color.WHITE)
- StatusBarIcon.TREND.pref -> BitmapUtils.getRateAsIcon(roundTarget=true, color = if(coloredIcon) ReceiveData.getClucoseColor() else Color.WHITE, resizeFactor = if (bigIcon) 1.5F else 1F)
- StatusBarIcon.DELTA.pref -> BitmapUtils.getDeltaAsIcon(roundTarget=!bigIcon, color = if(coloredIcon) ReceiveData.getClucoseColor(true) else Color.WHITE)
+ StatusBarIcon.GLUCOSE.pref -> BitmapUtils.getGlucoseAsIcon(roundTarget=!bigIcon, color = if(coloredIcon) ReceiveData.getGlucoseColor() else Color.WHITE, withShadow = if(coloredIcon) true else false)
+ StatusBarIcon.TREND.pref -> BitmapUtils.getRateAsIcon(
+ color = if(coloredIcon) ReceiveData.getGlucoseColor() else Color.WHITE,
+ resizeFactor = if (bigIcon) 1.5F else 1F,
+ withShadow = if(coloredIcon) true else false
+ )
+ StatusBarIcon.DELTA.pref -> BitmapUtils.getDeltaAsIcon(roundTarget=!bigIcon, color = if(coloredIcon) ReceiveData.getGlucoseColor(true) else Color.WHITE)
else -> Icon.createWithResource(GlucoDataService.context, R.mipmap.ic_launcher)
}
}
+ private fun getTapActionIntent(foreground: Boolean): PendingIntent? {
+ val tapAction = if(foreground)
+ sharedPref.getString(Constants.SHARED_PREF_PERMANENT_NOTIFICATION_TAP_ACTION, "")
+ else
+ sharedPref.getString(Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION_TAP_ACTION, "")
+ if(tapAction.isNullOrEmpty())
+ return null
+ val requestCode = if(foreground) 4 else 5
+ return PackageUtils.getTapActionIntent(GlucoDataService.context!!, tapAction, requestCode)
+ }
+
+ fun createNotificationView(context: Context): Bitmap? {
+ try {
+ val notificationView = View.inflate(context, R.layout.notification_layout, null)
+ val textGlucose: TextView = notificationView.findViewById(R.id.glucose)
+ val trendIcon: ImageView = notificationView.findViewById(R.id.trendImage)
+ val textDelta: TextView = notificationView.findViewById(R.id.deltaText)
+ val textIob: TextView = notificationView.findViewById(R.id.iobText)
+ val textCob: TextView = notificationView.findViewById(R.id.cobText)
+
+
+ textGlucose.text = ReceiveData.getGlucoseAsString()
+ textGlucose.setTextColor(ReceiveData.getGlucoseColor())
+
+ if (ReceiveData.isObsolete(Constants.VALUE_OBSOLETE_SHORT_SEC) && !ReceiveData.isObsolete()) {
+ textGlucose.paintFlags = Paint.STRIKE_THRU_TEXT_FLAG
+ } else {
+ textGlucose.paintFlags = 0
+ }
+
+ textDelta.text = "Δ ${ReceiveData.getDeltaAsString()}"
+ if (ReceiveData.isObsolete(Constants.VALUE_OBSOLETE_SHORT_SEC)) {
+ textDelta.setTextColor(ReceiveData.getAlarmTypeColor(AlarmType.OBSOLETE))
+ }
+
+ trendIcon.setImageIcon(BitmapUtils.getRateAsIcon(withShadow = true))
+
+ textIob.text = "💉 " + ReceiveData.getIobAsString()
+ textCob.text = "🍔 " + ReceiveData.getCobAsString()
+ textIob.visibility = if (ReceiveData.isIobCobObsolete(Constants.VALUE_OBSOLETE_LONG_SEC)) View.GONE else View.VISIBLE
+ textCob.visibility = textIob.visibility
+
+ notificationView.setDrawingCacheEnabled(true);
+ notificationView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
+ notificationView.layout(0, 0, notificationView.measuredWidth, notificationView.measuredHeight)
+
+ val bitmap = Bitmap.createBitmap(notificationView.width, notificationView.height, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bitmap)
+ notificationView.draw(canvas)
+ return bitmap
+
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "createImage exception: " + exc.message.toString() )
+ }
+ return null
+ }
+
fun getNotification(withContent: Boolean, iconKey: String, foreground: Boolean) : Notification {
var remoteViews: RemoteViews? = null
if (withContent) {
- remoteViews = RemoteViews(GlucoDataService.context!!.packageName, R.layout.notification)
- remoteViews.setTextViewText(R.id.glucose, ReceiveData.getClucoseAsString())
- remoteViews.setTextColor(R.id.glucose, ReceiveData.getClucoseColor())
- remoteViews.setImageViewBitmap(R.id.trendImage, BitmapUtils.getRateAsBitmap())
- remoteViews.setTextViewText(R.id.deltaText, "Δ " + ReceiveData.getDeltaAsString())
- if (ReceiveData.isObsolete(Constants.VALUE_OBSOLETE_SHORT_SEC)) {
- if (!ReceiveData.isObsolete())
- remoteViews.setInt(R.id.glucose, "setPaintFlags", Paint.STRIKE_THRU_TEXT_FLAG or Paint.ANTI_ALIAS_FLAG)
- remoteViews.setTextColor(R.id.deltaText, Color.GRAY )
- }
- if(!ReceiveData.isIobCob()) {
- remoteViews.setViewVisibility(R.id.iobText, View.GONE)
- remoteViews.setViewVisibility(R.id.cobText, View.GONE)
- } else {
- remoteViews.setTextViewText(R.id.iobText, GlucoDataService.context!!.getString(R.string.iob_label) + ": " + ReceiveData.getIobAsString() )
- remoteViews.setTextViewText(R.id.cobText, GlucoDataService.context!!.getString(R.string.cob_label) + ": " + ReceiveData.getCobAsString())
- remoteViews.setViewVisibility(R.id.iobText, View.VISIBLE)
- if (ReceiveData.cob.isNaN())
- remoteViews.setViewVisibility(R.id.cobText, View.GONE)
- else
- remoteViews.setViewVisibility(R.id.cobText, View.VISIBLE)
+ val bitmap = createNotificationView(GlucoDataService.context!!)
+ if(bitmap != null) {
+ remoteViews = RemoteViews(GlucoDataService.context!!.packageName, R.layout.image_view)
+ remoteViews.setImageViewBitmap(R.id.imageLayout, bitmap)
}
}
val notificationBuilder = if(foreground) foregroundNotificationCompat else notificationCompat
- notificationBuilder
+ val notification = notificationBuilder
.setSmallIcon(getStatusBarIcon(iconKey))
+ .setContentIntent(getTapActionIntent(foreground))
.setWhen(ReceiveData.time)
.setCustomContentView(remoteViews)
- .setCustomBigContentView(null)
+ .setCustomBigContentView(remoteViews)
.setColorized(false)
.setStyle(Notification.DecoratedCustomViewStyle())
- .setContentTitle(if (withContent) ReceiveData.getClucoseAsString() else "")
- .setContentText(if (withContent) "Delta: " + ReceiveData.getDeltaAsString() else "")
-
- when(sharedPref.getString(iconKey, StatusBarIcon.APP.pref)) {
- StatusBarIcon.GLUCOSE.pref,
- StatusBarIcon.TREND.pref -> {
- notificationBuilder.setColor(Color.TRANSPARENT)
- }
- }
-
- val notification = notificationBuilder.build()
+ .build()
notification.visibility = Notification.VISIBILITY_PUBLIC
notification.flags = notification.flags or Notification.FLAG_NO_CLEAR
@@ -186,7 +226,7 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe
}
}
- private fun showNotifications() {
+ fun showNotifications() {
showPrimaryNotification(true)
if (sharedPref.getBoolean(Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION, false)) {
Log.d(LOG_ID, "show second notification")
@@ -198,22 +238,7 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe
private fun showPrimaryNotification(show: Boolean) {
Log.d(LOG_ID, "showPrimaryNotification " + show)
- //if (show)
- showNotification(GlucoDataService.NOTIFICATION_ID, !sharedPref.getBoolean(Constants.SHARED_PREF_PERMANENT_NOTIFICATION_EMPTY, false), Constants.SHARED_PREF_PERMANENT_NOTIFICATION_ICON, true)
- /*if (show != GlucoDataService.foreground) {
- Log.d(LOG_ID, "change foreground notification mode")
- with(sharedPref.edit()) {
- putBoolean(Constants.SHARED_PREF_FOREGROUND_SERVICE, show)
- apply()
- }
- val serviceIntent =
- Intent(GlucoDataService.context!!, GlucoDataServiceMobile::class.java)
- if (show)
- serviceIntent.putExtra(Constants.SHARED_PREF_FOREGROUND_SERVICE, true)
- else
- serviceIntent.putExtra(Constants.ACTION_STOP_FOREGROUND, true)
- GlucoDataService.context!!.startService(serviceIntent)
- }*/
+ showNotification(GlucoDataService.NOTIFICATION_ID, !sharedPref.getBoolean(Constants.SHARED_PREF_PERMANENT_NOTIFICATION_EMPTY, false), Constants.SHARED_PREF_PERMANENT_NOTIFICATION_ICON, true)
}
private fun hasContent(): Boolean {
@@ -270,8 +295,10 @@ object PermanentNotification: NotifierInterface, SharedPreferences.OnSharedPrefe
Constants.SHARED_PREF_PERMANENT_NOTIFICATION_ICON,
Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION,
Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION_ICON,
+ Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION_TAP_ACTION,
Constants.SHARED_PREF_PERMANENT_NOTIFICATION_USE_BIG_ICON,
Constants.SHARED_PREF_PERMANENT_NOTIFICATION_COLORED_ICON,
+ Constants.SHARED_PREF_PERMANENT_NOTIFICATION_TAP_ACTION,
Constants.SHARED_PREF_PERMANENT_NOTIFICATION_EMPTY -> {
updatePreferences()
}
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/notification/SnoozeAlarmReceiver.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/notification/SnoozeAlarmReceiver.kt
new file mode 100644
index 00000000..fa2c42b1
--- /dev/null
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/notification/SnoozeAlarmReceiver.kt
@@ -0,0 +1,31 @@
+package de.michelinside.glucodatahandler.notification
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import de.michelinside.glucodatahandler.MainActivity
+import de.michelinside.glucodatahandler.common.Constants
+import de.michelinside.glucodatahandler.common.notification.AlarmHandler
+import de.michelinside.glucodatahandler.common.utils.Utils
+
+class SnoozeAlarmReceiver: BroadcastReceiver() {
+ private val LOG_ID = "GDH.AlarmSnoozeReceiver"
+ override fun onReceive(context: Context, intent: Intent) {
+ try {
+ Log.i(LOG_ID, "Intent ${intent.action} received: ${Utils.dumpBundle(intent.extras)}" )
+ if (intent.hasExtra(Constants.ALARM_SNOOZE_EXTRA_NOTIFY_ID))
+ AlarmNotification.stopNotification(intent.getIntExtra(Constants.ALARM_SNOOZE_EXTRA_NOTIFY_ID, 0), context)
+ if (intent.hasExtra(Constants.ALARM_SNOOZE_EXTRA_TIME))
+ AlarmHandler.setSnooze(intent.getLongExtra(Constants.ALARM_SNOOZE_EXTRA_TIME, 0L))
+ if(intent.hasExtra(Constants.ALARM_SNOOZE_EXTRA_START_APP) && intent.getBooleanExtra(Constants.ALARM_SNOOZE_EXTRA_START_APP, true)) {
+ val startIntent = Intent(context, MainActivity::class.java)
+ startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(startIntent)
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onReceive exception: " + exc.toString() )
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmAdvancedFragment.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmAdvancedFragment.kt
new file mode 100644
index 00000000..ef778574
--- /dev/null
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmAdvancedFragment.kt
@@ -0,0 +1,157 @@
+package de.michelinside.glucodatahandler.preferences
+
+import android.content.Intent
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.provider.Settings
+import android.util.Log
+import androidx.preference.ListPreference
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.SwitchPreferenceCompat
+import de.michelinside.glucodatahandler.R
+import de.michelinside.glucodatahandler.common.Constants
+import de.michelinside.glucodatahandler.common.notification.AlarmHandler
+import de.michelinside.glucodatahandler.common.notification.Channels
+import de.michelinside.glucodatahandler.common.utils.PackageUtils
+import de.michelinside.glucodatahandler.common.utils.Utils
+
+class AlarmAdvancedFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
+ private val LOG_ID = "GDH.AlarmAdvancedFragment"
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ try {
+ Log.v(LOG_ID, "onCreatePreferences called for key: ${Utils.dumpBundle(this.arguments)}" )
+ preferenceManager.sharedPreferencesName = Constants.SHARED_PREF_TAG
+ setPreferencesFromResource(R.xml.alarm_advanced, rootKey)
+ initPreferences()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString())
+ }
+ }
+
+ private fun initPreferences() {
+ val listPreference = findPreference(Constants.SHARED_PREF_ALARM_START_DELAY_STRING)
+ val delayMap = mutableMapOf()
+ for (i in 0 until 3500 step 500) {
+ delayMap.put(i.toString(), "${(i.toFloat()/1000)} s")
+ }
+ listPreference!!.entries = delayMap.values.toTypedArray()
+ listPreference.entryValues = delayMap.keys.toTypedArray()
+ }
+
+ override fun onResume() {
+ Log.d(LOG_ID, "onResume called")
+ try {
+ preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
+ updateEnableStates(preferenceManager.sharedPreferences!!)
+ update()
+ super.onResume()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onResume exception: " + exc.toString())
+ }
+ }
+
+ override fun onPause() {
+ Log.d(LOG_ID, "onPause called")
+ try {
+ preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
+ super.onPause()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onPause exception: " + exc.toString())
+ }
+ }
+
+
+ fun setEnableState(sharedPreferences: SharedPreferences, key: String, enableKey: String, invert: Boolean = false) {
+ val pref = findPreference(key)
+ if (pref != null)
+ pref.isEnabled = invert != sharedPreferences.getBoolean(enableKey, false)
+ }
+
+ fun updateEnableStates(sharedPreferences: SharedPreferences) {
+ try {
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_ALARM_FORCE_SOUND, Constants.SHARED_PREF_NOTIFICATION_VIBRATE, invert = true)
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_NOTIFICATION_USE_ALARM_SOUND, Constants.SHARED_PREF_NOTIFICATION_VIBRATE, invert = true)
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_ALARM_FULLSCREEN_DISMISS_KEYGUARD, Constants.SHARED_PREF_ALARM_FULLSCREEN_NOTIFICATION_ENABLED)
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "updateEnableStates exception: " + exc.toString())
+ }
+ }
+
+ private fun update() {
+ if (!Channels.getNotificationManager(requireContext()).isNotificationPolicyAccessGranted) {
+ disableSwitch(Constants.SHARED_PREF_ALARM_FORCE_SOUND)
+ disableSwitch(Constants.SHARED_PREF_ALARM_FORCE_VIBRATION)
+ }
+ val aaPref = findPreference(Constants.SHARED_PREF_NO_ALARM_NOTIFICATION_AUTO_CONNECTED)
+ if(aaPref != null) {
+ aaPref.isEnabled = PackageUtils.isGlucoDataAutoAvailable(requireContext())
+ if(!aaPref.isEnabled && aaPref.isChecked) {
+ disableSwitch(Constants.SHARED_PREF_NO_ALARM_NOTIFICATION_AUTO_CONNECTED)
+ }
+ }
+ }
+
+ private fun disableSwitch(prefname: String) {
+ val pref =
+ findPreference(prefname)
+ if (pref != null && pref.isChecked) {
+ Log.i(LOG_ID, "Disable preference $prefname as there is no permission!")
+ pref.isChecked = false
+ with(preferenceManager.sharedPreferences!!.edit()) {
+ putBoolean(prefname, false)
+ apply()
+ }
+ }
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
+ Log.d(LOG_ID, "onSharedPreferenceChanged called for " + key)
+ try {
+ updateEnableStates(sharedPreferences)
+ if(AlarmHandler.alarmPreferencesToSend.contains(key))
+ AlarmFragment.settingsChanged = true
+ when(key) {
+ Constants.SHARED_PREF_ALARM_FORCE_SOUND -> checkDisablePref(Constants.SHARED_PREF_ALARM_FORCE_SOUND, Constants.SHARED_PREF_ALARM_FORCE_VIBRATION)
+ Constants.SHARED_PREF_ALARM_FORCE_VIBRATION -> checkDisablePref(Constants.SHARED_PREF_ALARM_FORCE_VIBRATION, Constants.SHARED_PREF_ALARM_FORCE_SOUND)
+ Constants.SHARED_PREF_ALARM_START_DELAY_STRING -> {
+ val delayString = sharedPreferences.getString(Constants.SHARED_PREF_ALARM_START_DELAY_STRING, "")
+ if (!delayString.isNullOrEmpty()) {
+ val delay = delayString.toInt()
+ with(sharedPreferences.edit()) {
+ putInt(Constants.SHARED_PREF_ALARM_START_DELAY, delay)
+ apply()
+ }
+ }
+ }
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onSharedPreferenceChanged exception: " + exc.toString())
+ }
+ }
+
+ private fun checkDisablePref(changedPref: String, targetPref: String) {
+ val changed = findPreference(changedPref)
+ if (changed!!.isChecked) {
+ if (!Channels.getNotificationManager(requireContext()).isNotificationPolicyAccessGranted) {
+ requestNotificationPolicyPermission()
+ } else {
+ val target = findPreference(targetPref)
+ if (target!!.isChecked) {
+ target.isChecked = false
+ with(preferenceManager.sharedPreferences!!.edit()) {
+ putBoolean(targetPref, false)
+ apply()
+ }
+ }
+ }
+ }
+ }
+
+ private fun requestNotificationPolicyPermission() {
+ if (!Channels.getNotificationManager(requireContext()).isNotificationPolicyAccessGranted) {
+ val intent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)
+ startActivity(intent)
+ }
+ }
+}
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmFragment.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmFragment.kt
new file mode 100644
index 00000000..e7b4d828
--- /dev/null
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmFragment.kt
@@ -0,0 +1,215 @@
+package de.michelinside.glucodatahandler.preferences
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.net.Uri
+import android.os.Build
+import android.provider.Settings
+import android.os.Bundle
+import android.util.Log
+import androidx.core.content.ContextCompat
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.SwitchPreferenceCompat
+import de.michelinside.glucodatahandler.Dialogs
+import de.michelinside.glucodatahandler.R
+import de.michelinside.glucodatahandler.common.R as CR
+import de.michelinside.glucodatahandler.common.Constants
+import de.michelinside.glucodatahandler.common.ReceiveData
+import de.michelinside.glucodatahandler.common.notification.AlarmHandler
+import de.michelinside.glucodatahandler.common.notification.AlarmType
+import de.michelinside.glucodatahandler.common.notification.SoundMode
+import de.michelinside.glucodatahandler.common.notifier.InternalNotifier
+import de.michelinside.glucodatahandler.common.notifier.NotifySource
+import de.michelinside.glucodatahandler.common.utils.Utils
+import de.michelinside.glucodatahandler.notification.AlarmNotification
+
+class AlarmFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
+ private val LOG_ID = "GDH.AlarmFragment"
+ companion object {
+ var settingsChanged = false
+ }
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ Log.d(LOG_ID, "onCreatePreferences called")
+ try {
+ settingsChanged = false
+ preferenceManager.sharedPreferencesName = Constants.SHARED_PREF_TAG
+ setPreferencesFromResource(R.xml.alarms, rootKey)
+ if(!AlarmNotification.channelActive(requireContext())) {
+ Log.e(LOG_ID, "Notification disabled!!!")
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString())
+ }
+ }
+
+ override fun onDestroyView() {
+ Log.d(LOG_ID, "onDestroyView called")
+ try {
+ if (settingsChanged) {
+ Log.v(LOG_ID, "Notify alarm_settings change")
+ InternalNotifier.notify(requireContext(), NotifySource.ALARM_SETTINGS, AlarmHandler.getSettings())
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onDestroyView exception: " + exc.toString())
+ }
+ super.onDestroyView()
+ }
+
+
+ override fun onResume() {
+ Log.d(LOG_ID, "onResume called")
+ try {
+ preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
+ update()
+ super.onResume()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onResume exception: " + exc.toString())
+ }
+ }
+
+ override fun onPause() {
+ Log.d(LOG_ID, "onPause called")
+ try {
+ preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
+ super.onPause()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onPause exception: " + exc.toString())
+ }
+ }
+
+ @SuppressLint("InlinedApi")
+ private fun update() {
+ Log.d(LOG_ID, "update called")
+ try {
+ if(!AlarmNotification.channelActive(requireContext())) {
+ disableSwitch(Constants.SHARED_PREF_ALARM_NOTIFICATION_ENABLED)
+ }
+
+ if (!AlarmNotification.hasFullscreenPermission()) {
+ disableSwitch(Constants.SHARED_PREF_ALARM_FULLSCREEN_NOTIFICATION_ENABLED)
+ }
+
+ updateAlarmCat(Constants.SHARED_PREF_ALARM_VERY_LOW)
+ updateAlarmCat(Constants.SHARED_PREF_ALARM_LOW)
+ updateAlarmCat(Constants.SHARED_PREF_ALARM_HIGH)
+ updateAlarmCat(Constants.SHARED_PREF_ALARM_VERY_HIGH)
+ updateAlarmCat(Constants.SHARED_PREF_ALARM_OBSOLETE)
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onPause exception: " + exc.toString())
+ }
+ }
+
+
+ private fun disableSwitch(prefname: String) {
+ val pref =
+ findPreference(prefname)
+ if (pref != null && pref.isChecked) {
+ Log.i(LOG_ID, "Disable preference $prefname as there is no permission!")
+ pref.isChecked = false
+ with(preferenceManager.sharedPreferences!!.edit()) {
+ putBoolean(prefname, false)
+ apply()
+ }
+ }
+ }
+
+ private fun updateAlarmCat(key: String) {
+ val pref = findPreference(key) ?: return
+ val alarmType = AlarmType.fromIndex(pref.extras.getInt("type"))
+ pref.icon = ContextCompat.getDrawable(requireContext(), getAlarmCatIcon(alarmType, key + "_enabled", requireContext()))
+ pref.summary = getAlarmCatSummary(alarmType)
+ }
+
+ private fun getAlarmCatIcon(alarmType: AlarmType, enableKey: String, context: Context): Int {
+ if(!preferenceManager.sharedPreferences!!.getBoolean(enableKey, true)) {
+ return SoundMode.OFF.icon
+ }
+ return AlarmNotification.getSoundMode(alarmType, context).icon
+ }
+
+ private fun getAlarmCatSummary(alarmType: AlarmType): String {
+ return when(alarmType) {
+ AlarmType.VERY_LOW,
+ AlarmType.LOW,
+ AlarmType.HIGH,
+ AlarmType.VERY_HIGH -> resources.getString(CR.string.alarm_type_summary, getBorderText(alarmType))
+ AlarmType.OBSOLETE -> resources.getString(CR.string.alarm_obsolete_summary)
+ else -> ""
+ }
+ }
+
+ private fun getBorderText(alarmType: AlarmType): String {
+ var value = when(alarmType) {
+ AlarmType.VERY_LOW -> ReceiveData.low
+ AlarmType.LOW -> ReceiveData.targetMin
+ AlarmType.HIGH -> ReceiveData.targetMax
+ AlarmType.VERY_HIGH -> ReceiveData.high
+ else -> 0F
+ }
+ if (alarmType == AlarmType.LOW) {
+ if(ReceiveData.isMmol)
+ value = Utils.round(value-0.1F, 1)
+ else
+ value -= 1F
+ }
+
+ if (alarmType == AlarmType.HIGH) {
+ if(ReceiveData.isMmol)
+ value = Utils.round(value+0.1F, 1)
+ else
+ value += 1F
+ }
+ return "$value ${ReceiveData.getUnit()}"
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
+ Log.d(LOG_ID, "onSharedPreferenceChanged called for " + key)
+ try {
+ when(key) {
+ Constants.SHARED_PREF_ALARM_NOTIFICATION_ENABLED -> {
+ if (sharedPreferences.getBoolean(Constants.SHARED_PREF_ALARM_NOTIFICATION_ENABLED, false) && !AlarmNotification.channelActive(requireContext())) {
+ requestChannelActivation()
+ }
+ }
+ Constants.SHARED_PREF_ALARM_FULLSCREEN_NOTIFICATION_ENABLED -> {
+ if (sharedPreferences.getBoolean(Constants.SHARED_PREF_ALARM_FULLSCREEN_NOTIFICATION_ENABLED, false) && !AlarmNotification.hasFullscreenPermission()) {
+ requestFullScreenPermission()
+ }
+ }
+ }
+ if(AlarmHandler.alarmPreferencesToSend.contains(key))
+ settingsChanged = true
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onSharedPreferenceChanged exception: " + exc.toString())
+ }
+ }
+ private fun requestChannelActivation() {
+ Dialogs.showOkDialog(requireContext(), resources.getString(CR.string.permission_alarm_notification_title), resources.getString(CR.string.permission_alarm_notification_message)) { _, _ ->
+ val intent: Intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
+ .putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
+ startActivity(intent)
+ }
+ }
+
+ private fun requestFullScreenPermission() {
+ try {
+ Log.v(LOG_ID, "requestFullScreenPermission called")
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ val intent = Intent(
+ Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT,
+ Uri.parse("package:" + requireContext().packageName)
+ )
+ intent.putExtra(Settings.EXTRA_APP_PACKAGE,requireContext().packageName)
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ startActivity(intent)
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "requestOverlayPermission exception: " + exc.toString())
+ }
+ }
+}
+
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmTypeFragment.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmTypeFragment.kt
new file mode 100644
index 00000000..c535fea1
--- /dev/null
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AlarmTypeFragment.kt
@@ -0,0 +1,389 @@
+package de.michelinside.glucodatahandler.preferences
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.media.MediaScannerConnection
+import android.media.RingtoneManager
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.Environment
+import android.provider.DocumentsContract
+import android.util.Log
+import androidx.activity.result.ActivityResult
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.preference.Preference
+import androidx.preference.PreferenceCategory
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.SeekBarPreference
+import androidx.preference.SwitchPreferenceCompat
+import de.michelinside.glucodatahandler.R
+import de.michelinside.glucodatahandler.common.R as CR
+import de.michelinside.glucodatahandler.common.Constants
+import de.michelinside.glucodatahandler.common.notification.AlarmHandler
+import de.michelinside.glucodatahandler.common.notification.AlarmType
+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.Utils
+import de.michelinside.glucodatahandler.notification.AlarmNotification
+
+class AlarmTypeFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener, NotifierInterface {
+ private val LOG_ID = "GDH.AlarmTypeFragment"
+ private lateinit var soundSaver: ActivityResultLauncher
+ private var ringtoneSelecter: ActivityResultLauncher? = null
+ private var alarmType = AlarmType.NONE
+ private var alarmPrefix = ""
+ private var curAlarmLevel = -1
+ private val useCustomSoundPref: String get() {
+ return alarmPrefix + "use_custom_sound"
+ }
+ private val customSoundPref: String get() {
+ return alarmPrefix + "custom_sound"
+ }
+ private val testAlarmPref: String get() {
+ return alarmPrefix + "test"
+ }
+ private val saveSoundPref: String get() {
+ return alarmPrefix + "save_sound"
+ }
+ private val soundLevelPref: String get() {
+ return alarmPrefix + "sound_level"
+ }
+ /*
+ private val settingsPref: String get() {
+ return alarmPrefix + "settings"
+ }*/
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ try {
+ Log.v(LOG_ID, "onCreatePreferences called for key: ${Utils.dumpBundle(this.arguments)}" )
+ preferenceManager.sharedPreferencesName = Constants.SHARED_PREF_TAG
+ setPreferencesFromResource(R.xml.alarm_type, rootKey)
+ if (requireArguments().containsKey("prefix") && requireArguments().containsKey("type")) {
+ alarmType = AlarmType.fromIndex(requireArguments().getInt("type"))
+ alarmPrefix = requireArguments().getString("prefix")!!
+ createAlarmPrefSettings()
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString())
+ }
+ }
+
+ override fun onResume() {
+ Log.d(LOG_ID, "onResume called")
+ try {
+ preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
+ InternalNotifier.addNotifier(requireContext(), this, mutableSetOf(NotifySource.NOTIFICATION_STOPPED))
+ update()
+ super.onResume()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onResume exception: " + exc.toString())
+ }
+ }
+
+ override fun onPause() {
+ Log.d(LOG_ID, "onPause called")
+ try {
+ stopTestSound()
+ preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
+ InternalNotifier.remNotifier(requireContext(), this)
+ super.onPause()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onPause exception: " + exc.toString())
+ }
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
+ Log.d(LOG_ID, "onSharedPreferenceChanged called for " + key)
+ try {
+ if(AlarmHandler.alarmPreferencesToSend.contains(key))
+ AlarmFragment.settingsChanged = true
+ update()
+ if(key == soundLevelPref) {
+ startTestSound()
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onSharedPreferenceChanged exception: " + exc.toString())
+ }
+ }
+
+ private fun update() {
+ Log.d(LOG_ID, "update called")
+ try {
+ /*
+ val pref = findPreference(settingsPref)
+ if (pref != null) {
+ pref.icon = ContextCompat.getDrawable(
+ requireContext(),
+ AlarmNotification.getSoundMode(alarmType, requireContext()).icon
+ )
+ }*/
+ val prefTest = findPreference(testAlarmPref)
+ if (prefTest != null) {
+ prefTest.isEnabled = true
+ }
+ val prefSelectRingtone = findPreference(customSoundPref)
+ val prefUseCustomRingtone = findPreference(useCustomSoundPref)
+ prefSelectRingtone!!.isEnabled = prefUseCustomRingtone!!.isChecked
+ updateRingtoneSelectSummary()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onPause exception: " + exc.toString())
+ }
+ }
+
+ private fun createAlarmPrefSettings() {
+ Log.v(LOG_ID, "createAlarmPrefSettings for alarm $alarmType with prefix $alarmPrefix")
+ updatePreferenceKeys()
+ updateData()
+
+ soundSaver = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
+ Log.v(LOG_ID, "$alarmType result ${result.resultCode}: ${result.data}")
+ if (result.resultCode == Activity.RESULT_OK) {
+ val intent = result.data
+ intent?.data?.also { uri ->
+ Log.v(LOG_ID, "Save media to " + uri)
+ AlarmNotification.saveAlarm(requireContext(), alarmType, uri)
+ }
+ }
+ }
+ setAlarmTest(testAlarmPref, alarmType)
+ //setAlarmSettings(settingsPref, alarmType)
+ setAlarmSave(saveSoundPref, alarmType, soundSaver)
+ setRingtoneSelect(customSoundPref, Uri.parse(preferenceManager.sharedPreferences!!.getString(customSoundPref, "")))
+ }
+
+ private fun updatePreferenceKeys() {
+ for (i in 0 until preferenceScreen.preferenceCount) {
+ val pref: Preference = preferenceScreen.getPreference(i)
+ if(!pref.key.isNullOrEmpty()) {
+ val newKey = alarmPrefix + pref.key
+ Log.v(LOG_ID, "Replace key ${pref.key} with $newKey")
+ pref.key = newKey
+ } else {
+ val cat = pref as PreferenceCategory
+ updatePreferenceKeys(cat)
+ }
+ }
+ }
+
+
+ private fun updatePreferenceKeys(preferenceCategory: PreferenceCategory) {
+ for (i in 0 until preferenceCategory.preferenceCount) {
+ val pref: Preference = preferenceCategory.getPreference(i)
+ if(!pref.key.isNullOrEmpty()) {
+ val newKey = alarmPrefix + pref.key
+ Log.v(LOG_ID, "Replace key ${pref.key} with $newKey")
+ pref.key = newKey
+ } else {
+ val cat = pref as PreferenceCategory
+ updatePreferenceKeys(cat)
+ }
+ }
+ }
+ private fun updateData() {
+ val enablePref = findPreference(alarmPrefix+"enabled")
+ enablePref!!.isChecked = preferenceManager.sharedPreferences!!.getBoolean(enablePref.key, true)
+
+ val intervalPref = findPreference(alarmPrefix+"interval")
+ intervalPref!!.value = preferenceManager.sharedPreferences!!.getInt(intervalPref.key, AlarmHandler.getDefaultIntervalMin(alarmType))
+ intervalPref.summary = getIntervalSummary(alarmType)
+
+ val retriggerPref = findPreference(alarmPrefix+"retrigger")
+ retriggerPref!!.value = preferenceManager.sharedPreferences!!.getInt(retriggerPref.key, 0)
+
+ val prefSoundDelay = findPreference(alarmPrefix+"sound_delay")
+ prefSoundDelay!!.value = preferenceManager.sharedPreferences!!.getInt(prefSoundDelay.key, 0)
+
+ val prefUseCustomRingtone = findPreference(useCustomSoundPref)
+ prefUseCustomRingtone!!.isChecked = preferenceManager.sharedPreferences!!.getBoolean(prefUseCustomRingtone.key, false)
+
+ val levelPref = findPreference(soundLevelPref)
+ if (levelPref != null) {
+ levelPref.max = AlarmNotification.getMaxSoundLevel()
+ levelPref.value = preferenceManager.sharedPreferences!!.getInt(levelPref.key, -1)
+ }
+ }
+
+ private fun getIntervalSummary(alarmType: AlarmType): String {
+ return when(alarmType) {
+ AlarmType.VERY_LOW,
+ AlarmType.LOW -> resources.getString(de.michelinside.glucodatahandler.common.R.string.alarm_interval_summary_low)
+ AlarmType.HIGH,
+ AlarmType.VERY_HIGH -> resources.getString(de.michelinside.glucodatahandler.common.R.string.alarm_interval_summary_high)
+ AlarmType.OBSOLETE -> resources.getString(de.michelinside.glucodatahandler.common.R.string.alarm_interval_summary_obsolete)
+ else -> ""
+ }
+ }
+
+ private fun setAlarmTest(preference: String, alarmType: AlarmType) {
+ val pref = findPreference(preference)
+ pref?.setOnPreferenceClickListener {
+ Log.d(LOG_ID, "Trigger test for $alarmType")
+ pref.isEnabled = false
+ AlarmNotification.triggerTest(alarmType, requireContext())
+ true
+ }
+ }
+
+ /*
+ private fun setAlarmSettings(preference: String, alarmType: AlarmType) {
+ val pref = findPreference(preference)
+ if (pref != null) {
+ pref.icon = ContextCompat.getDrawable(requireContext(), AlarmNotification.getSoundMode(alarmType, requireContext()).icon)
+ pref.setOnPreferenceClickListener {
+ Log.d(LOG_ID, "Open settings for $alarmType")
+ val intent: Intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
+ .putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
+ .putExtra(Settings.EXTRA_CHANNEL_ID, AlarmNotification.getChannelId(alarmType))
+ startActivity(intent)
+ true
+ }
+ }
+ }
+
+ */
+ private fun getAlarmFileName(resId: Int): String {
+ val name = requireContext().resources.getResourceEntryName(resId).replace("gdh", "GDH").replace("_", " ")
+ return "$name.mp3"
+ }
+ private fun setAlarmSave(
+ preference: String,
+ alarmType: AlarmType,
+ soundSaver: ActivityResultLauncher
+ ) {
+ val pref = findPreference(preference)
+ val resId = AlarmNotification.getAlarmSoundRes(alarmType)
+ if (pref != null && resId != null) {
+ pref.setOnPreferenceClickListener {
+ var alarmUri: Uri? = null
+ MediaScannerConnection.scanFile(requireContext(), arrayOf(
+ Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_NOTIFICATIONS
+ ).absolutePath), null
+ ) { s: String, uri: Uri ->
+ Log.v(LOG_ID, "Set URI $uri for path $s")
+ alarmUri = uri
+ }
+ Log.d(LOG_ID, "Save sound for $alarmType to ${alarmUri}")
+ val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ type = "audio/mpeg"
+ val fileName = getAlarmFileName(resId)
+ putExtra(Intent.EXTRA_TITLE, fileName)
+ putExtra(DocumentsContract.EXTRA_INITIAL_URI, alarmUri)
+ }
+ soundSaver.launch(intent)
+ true
+ }
+ }
+ }
+
+ private fun updateRingtoneSelectSummary() {
+ val pref = findPreference(customSoundPref)
+ val soundLevelPref = findPreference(soundLevelPref)
+ if(pref != null) {
+ if(pref.isEnabled) {
+ val uri = Uri.parse(preferenceManager.sharedPreferences!!.getString(customSoundPref, ""))
+ if (uri != null && uri.toString().isNotEmpty()) {
+ val ringtone = RingtoneManager.getRingtone(requireContext(), uri)
+ val title = ringtone.getTitle(requireContext())
+ if (title.isNullOrEmpty()) {
+ pref.summary = resources.getString(de.michelinside.glucodatahandler.common.R.string.alarm_sound_summary)
+ soundLevelPref!!.isEnabled = false
+ } else {
+ Log.d(LOG_ID, "Ringtone '$title' for uri $uri")
+ pref.summary = title
+ soundLevelPref!!.isEnabled = true
+ }
+ } else {
+ pref.summary = resources.getString(de.michelinside.glucodatahandler.common.R.string.alarm_sound_summary)
+ soundLevelPref!!.isEnabled = false
+ }
+ } else {
+ pref.summary = resources.getString(CR.string.alarm_app_sound)
+ soundLevelPref!!.isEnabled = true
+ }
+ }
+ }
+
+ private fun setRingtoneSelect(preference: String, curUri: Uri?) {
+ Log.v(LOG_ID, "setRingtoneSelect called for preference $preference with uri $curUri" )
+ val pref = findPreference(preference)
+ if (pref != null) {
+ if(ringtoneSelecter == null) {
+ ringtoneSelecter = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
+ Log.v(LOG_ID, "$alarmType result ${result.resultCode}: ${result.data}")
+ if (result.resultCode == Activity.RESULT_OK) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ setRingtoneResult(preference, result.data!!.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, Uri::class.java))
+ } else {
+ @Suppress("DEPRECATION")
+ setRingtoneResult(preference, result.data!!.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI))
+ }
+ }
+ }
+ }
+ pref.setOnPreferenceClickListener {
+ val ringtoneIntent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
+ ringtoneIntent.putExtra(
+ RingtoneManager.EXTRA_RINGTONE_TYPE,
+ RingtoneManager.TYPE_ALL
+ )
+ ringtoneIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true)
+ ringtoneIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true)
+ ringtoneIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, curUri)
+ ringtoneSelecter!!.launch(ringtoneIntent)
+ true
+ }
+ }
+ }
+
+ private fun setRingtoneResult(preference: String, newUri: Uri?) {
+ Log.i(LOG_ID, "Set custom ringtone for $preference: $newUri")
+ with (preferenceManager.sharedPreferences!!.edit()) {
+ putString(preference, newUri?.toString())
+ apply()
+ }
+ setRingtoneSelect(preference, newUri)
+ updateRingtoneSelectSummary()
+ }
+
+ override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) {
+ try {
+ Log.v(LOG_ID, "OnNotifyData called for $dataSource")
+ update()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "OnNotifyData exception: " + exc.toString())
+ }
+ }
+
+ private fun startTestSound() {
+ if(curAlarmLevel < 0) {
+ curAlarmLevel = AlarmNotification.getCurrentSoundLevel()
+ }
+ var soundLevel = preferenceManager!!.sharedPreferences!!.getInt(soundLevelPref, -1)
+ if(soundLevel<0)
+ soundLevel = curAlarmLevel
+ Log.d(LOG_ID, "Start test sound with level $soundLevel")
+ AlarmNotification.stopVibrationAndSound()
+ AlarmNotification.setSoundLevel(soundLevel)
+ if(!AlarmNotification.isRingtonePlaying()) {
+ AlarmNotification.startSound(alarmType, requireContext(), false, forTest = true)
+ }
+ }
+
+ private fun stopTestSound() {
+ Log.d(LOG_ID, "Stop test sound")
+ if(curAlarmLevel >= 0) {
+ AlarmNotification.stopVibrationAndSound()
+ AlarmNotification.setSoundLevel(curAlarmLevel)
+ curAlarmLevel = -1
+ }
+ }
+
+
+}
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AppSettingsActivity.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AppSettingsActivity.kt
index 761381a6..c1b6963c 100644
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AppSettingsActivity.kt
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/AppSettingsActivity.kt
@@ -5,16 +5,22 @@ import android.R
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
import de.michelinside.glucodatahandler.preferences.SettingsFragment
import de.michelinside.glucodatahandler.preferences.SourceFragment
+import de.michelinside.glucodatahandler.preferences.AlarmFragment
import de.michelinside.glucodatahandler.common.R as RC
enum class SettingsFragmentClass(val value: Int, val titleRes: Int) {
SETTINGS_FRAGMENT(0, RC.string.menu_settings),
- SORUCE_FRAGMENT(1, RC.string.menu_sources)
+ SORUCE_FRAGMENT(1, RC.string.menu_sources),
+ ALARM_FRAGMENT(2, RC.string.menu_alarms)
}
-class SettingsActivity : AppCompatActivity() {
+class SettingsActivity : AppCompatActivity(),
+ PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
private val LOG_ID = "GDH.SettingsActivity"
+ private var titleMap = mutableMapOf()
companion object {
const val FRAGMENT_EXTRA = "fragment"
}
@@ -39,10 +45,62 @@ class SettingsActivity : AppCompatActivity() {
.replace(R.id.content, SourceFragment())
.commit()
}
+
+ SettingsFragmentClass.ALARM_FRAGMENT.value -> {
+ this.supportActionBar!!.title =
+ this.applicationContext.resources.getText(SettingsFragmentClass.ALARM_FRAGMENT.titleRes)
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.content, AlarmFragment())
+ .commit()
+ }
}
}
+
+ supportFragmentManager.addOnBackStackChangedListener {
+ Log.v(LOG_ID, "addOnBackStackChangedListener called count=${supportFragmentManager.backStackEntryCount}")
+ if (titleMap.containsKey(supportFragmentManager.backStackEntryCount)) {
+ this.supportActionBar!!.title = titleMap[supportFragmentManager.backStackEntryCount]
+ }
+ }
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ } catch (ex: Exception) {
+ Log.e(LOG_ID, "onCreate exception: " + ex)
+ }
+ }
+
+
+ override fun onSupportNavigateUp(): Boolean {
+ if (supportFragmentManager.popBackStackImmediate()) {
+ return true
+ }
+ return super.onSupportNavigateUp()
+ }
+ override fun onPreferenceStartFragment(
+ caller: PreferenceFragmentCompat,
+ pref: Preference
+ ): Boolean {
+ try {
+ // Instantiate the new Fragment
+ Log.v(LOG_ID, "onPreferenceStartFragment called at ${supportFragmentManager.backStackEntryCount} for preference ${pref.title}")
+ val args = pref.extras
+ val fragment = supportFragmentManager.fragmentFactory.instantiate(
+ classLoader,
+ pref.fragment ?: return false
+ ).apply {
+ arguments = args
+ setTargetFragment(caller, 0)
+ }
+ // Replace the existing Fragment with the new Fragment
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.content, fragment)
+ .addToBackStack(null)
+ .commit()
+
+ titleMap.put(supportFragmentManager.backStackEntryCount, this.supportActionBar!!.title!!)
+ this.supportActionBar!!.title = pref.title
} catch (ex: Exception) {
Log.e(LOG_ID, "onCreate exception: " + ex)
}
+ return true
}
}
\ No newline at end of file
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SelectReceiverPreferenceDialogFragmentCompat.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SelectReceiverPreferenceDialogFragmentCompat.kt
deleted file mode 100644
index 30a3954b..00000000
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SelectReceiverPreferenceDialogFragmentCompat.kt
+++ /dev/null
@@ -1,154 +0,0 @@
-package de.michelinside.glucodatahandler.preferences
-
-import android.content.Context
-import android.content.Intent
-import android.content.SharedPreferences
-import android.content.pm.PackageManager
-import android.content.pm.ResolveInfo
-import android.os.Bundle
-import android.util.Log
-import android.view.View
-import android.view.ViewGroup
-import android.widget.CheckBox
-import android.widget.LinearLayout
-import android.widget.ScrollView
-import android.widget.TextView
-import androidx.appcompat.widget.SwitchCompat
-import androidx.preference.PreferenceDialogFragmentCompat
-import de.michelinside.glucodatahandler.BuildConfig
-import de.michelinside.glucodatahandler.R
-import de.michelinside.glucodatahandler.common.Constants
-import de.michelinside.glucodatahandler.common.R as CR
-
-
-class SelectReceiverPreferenceDialogFragmentCompat : PreferenceDialogFragmentCompat() {
- companion object {
- private val LOG_ID = "GDH.SelectReceiverPreferenceDialog"
- fun initial(key: String) : SelectReceiverPreferenceDialogFragmentCompat {
- Log.d(LOG_ID, "initial called for key: " + key )
- val dialog = SelectReceiverPreferenceDialogFragmentCompat()
- val bundle = Bundle(1)
- bundle.putString(ARG_KEY, key)
- dialog.arguments = bundle
- return dialog
- }
- }
- private var receiverSet = HashSet()
- private lateinit var showAllSwitch: SwitchCompat
- private lateinit var sharedPref: SharedPreferences
- private var selectReceiverPreference: SelectReceiverPreference? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- Log.d(LOG_ID, "onCreate called with bundle: " + savedInstanceState?.toString() )
- try {
- selectReceiverPreference = preference as SelectReceiverPreference
- } catch (exc: Exception) {
- Log.e(LOG_ID, "Setting preference exception: " + exc.toString())
- }
- }
-
- override fun onBindDialogView(view: View) {
- super.onBindDialogView(view)
- Log.d(LOG_ID, "onBindDialogView called for view: " + view.transitionName.toString() + " preference " + preference.javaClass )
- try {
- val savedReceivers = selectReceiverPreference!!.getReceivers()
- Log.d(LOG_ID, savedReceivers.size.toString() + " receivers loaded: " + savedReceivers.toString())
- receiverSet.addAll(savedReceivers)
-
- sharedPref = requireContext().getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
- showAllSwitch = view.findViewById(R.id.showAllSwitch)
- showAllSwitch.isChecked = sharedPref.getBoolean(Constants.SHARED_PREF_GLUCODATA_RECEIVER_SHOW_ALL, false)
- showAllSwitch.setOnCheckedChangeListener { _, isChecked ->
- updateReceivers(view, isChecked)
- }
-
- updateReceivers(view, showAllSwitch.isChecked)
- } catch (exc: Exception) {
- Log.e(LOG_ID, "onBindDialogView exception: " + exc.toString())
- }
- }
-
- override fun onDialogClosed(positiveResult: Boolean) {
- Log.d(LOG_ID, "onDialogClosed called with positiveResult: " + positiveResult.toString() )
- try {
- if(positiveResult) {
- with(sharedPref.edit()) {
- putBoolean(Constants.SHARED_PREF_GLUCODATA_RECEIVER_SHOW_ALL, showAllSwitch.isChecked)
- apply()
- }
- selectReceiverPreference!!.setReceivers(receiverSet)
- }
- } catch (exc: Exception) {
- Log.e(LOG_ID, "onDialogClosed exception: " + exc.toString())
- }
- }
-
-
- private fun updateReceivers(view: View, all: Boolean) {
- try {
- val receiverLayout = view.findViewById(R.id.receiverLayout)
- val receivers = getReceivers(all)
- Log.d(LOG_ID, receivers.size.toString() + " receivers found!" )
- val receiverScrollView = view.findViewById(R.id.receiverScrollView)
- if (receivers.size > 5) {
- receiverScrollView.layoutParams.height = resources.displayMetrics.heightPixels/2
- } else
- receiverScrollView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
- receiverLayout.removeAllViews()
- val currentReceivers = receiverSet.toHashSet()
- receiverSet.clear()
- if (receivers.size == 0) {
- val txt = TextView(requireContext())
- txt.setText(CR.string.select_receiver_no_glucodata_receiver)
- receiverLayout.addView(txt)
- }
- else {
- for (receiver in receivers.toSortedMap(String.CASE_INSENSITIVE_ORDER)) {
- val ch = CheckBox(requireContext())
- ch.text = receiver.key
- ch.hint = receiver.value
- ch.setOnCheckedChangeListener { buttonView, isChecked ->
- if (isChecked) {
- receiverSet.add(buttonView.hint.toString())
- } else {
- receiverSet.remove(buttonView.hint.toString())
- }
- }
- if (currentReceivers.contains(receiver.value) ) {
- ch.isChecked = true
- receiverSet.add(receiver.value)
- }
- receiverLayout.addView(ch)
- }
- }
- } catch (exc: Exception) {
- Log.e(LOG_ID, "updateReceivers exception: " + exc.message.toString() )
- }
- }
-
- private fun getReceivers(all: Boolean): HashMap {
- val names = HashMap()
- if (BuildConfig.DEBUG) {
- names["Wusel Dusel"] = "wusel.dusel"
- names["dummy"] = "dummy"
- }
- val receivers: List
- if (all) {
- val intent = Intent(Intent.ACTION_MAIN)
- intent.addCategory(Intent.CATEGORY_LAUNCHER)
- receivers = requireContext().packageManager.queryIntentActivities(intent, PackageManager.GET_META_DATA)
- } else {
- val intent = Intent(Constants.GLUCODATA_BROADCAST_ACTION)
- receivers = requireContext().packageManager.queryBroadcastReceivers(intent, PackageManager.GET_META_DATA)
- }
- for (resolveInfo in receivers) {
- val pkgName = resolveInfo.activityInfo.packageName
- val name = resolveInfo.activityInfo.loadLabel(requireContext().packageManager).toString()
- if (pkgName != null && pkgName != requireContext().packageName ) {
- names[name] = pkgName
- }
- }
- return names
- }
-}
\ No newline at end of file
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SettingsFragment.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SettingsFragment.kt
index 0f5170c6..dd213506 100644
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SettingsFragment.kt
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SettingsFragment.kt
@@ -1,26 +1,15 @@
package de.michelinside.glucodatahandler.preferences
-import android.content.Intent
-import android.content.SharedPreferences
-import android.content.pm.PackageManager
-import android.content.pm.ResolveInfo
-import android.net.Uri
import android.os.Bundle
-import android.provider.Settings
import android.util.Log
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.contract.ActivityResultContracts
import androidx.preference.*
import de.michelinside.glucodatahandler.BuildConfig
import de.michelinside.glucodatahandler.R
import de.michelinside.glucodatahandler.common.Constants
-import de.michelinside.glucodatahandler.common.utils.Utils
-import de.michelinside.glucodatahandler.common.R as CR
-class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
+class SettingsFragment : PreferenceFragmentCompat() {
private val LOG_ID = "GDH.SettingsFragment"
- private lateinit var activityResultOverlayLauncher: ActivityResultLauncher
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
Log.d(LOG_ID, "onCreatePreferences called")
@@ -33,190 +22,9 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
findPreference(Constants.SHARED_PREF_DUMMY_VALUES)
notifySwitch!!.isVisible = true
}
-
- setupReceivers(Constants.GLUCODATA_BROADCAST_ACTION, Constants.SHARED_PREF_GLUCODATA_RECEIVERS)
- setupReceivers(Constants.XDRIP_ACTION_GLUCOSE_READING, Constants.SHARED_PREF_XDRIP_RECEIVERS)
- setupReceivers(Constants.XDRIP_BROADCAST_ACTION, Constants.SHARED_PREF_XDRIP_BROADCAST_RECEIVERS)
-
- if (Utils.isPackageAvailable(requireContext(), Constants.PACKAGE_GLUCODATAAUTO)) {
- val sendToGDA = findPreference(Constants.SHARED_PREF_SEND_TO_GLUCODATAAUTO)
- sendToGDA!!.isVisible = true
- } else {
- val no_gda_info = findPreference(Constants.SHARED_PREF_NO_GLUCODATAAUTO)
- if (no_gda_info != null) {
- no_gda_info.isVisible = true
- no_gda_info.setOnPreferenceClickListener {
- val browserIntent = Intent(
- Intent.ACTION_VIEW,
- Uri.parse(resources.getText(CR.string.glucodataauto_link).toString())
- )
- startActivity(browserIntent)
- true
- }
- }
- }
-
- activityResultOverlayLauncher = registerForActivityResult(
- ActivityResultContracts.StartActivityForResult()
- ) {
- if (!Settings.canDrawOverlays(requireContext())) {
- Log.w(LOG_ID, "Overlay permission denied!")
- } else {
- // setting to true
- Log.i(LOG_ID, "Overlay permission granted!")
- val pref = findPreference(Constants.SHARED_PREF_FLOATING_WIDGET)
- if (pref != null) {
- pref.isChecked = true
- }
- with(preferenceManager.sharedPreferences!!.edit()) {
- putBoolean(Constants.SHARED_PREF_FLOATING_WIDGET, true)
- apply()
- }
- }
- }
} catch (exc: Exception) {
Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString())
}
}
- override fun onResume() {
- Log.d(LOG_ID, "onResume called")
- try {
- preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
- updateEnableStates(preferenceManager.sharedPreferences!!)
- super.onResume()
- } catch (exc: Exception) {
- Log.e(LOG_ID, "onResume exception: " + exc.toString())
- }
- }
-
- override fun onPause() {
- Log.d(LOG_ID, "onPause called")
- try {
- preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
- super.onPause()
- } catch (exc: Exception) {
- Log.e(LOG_ID, "onPause exception: " + exc.toString())
- }
- }
-
- override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
- Log.d(LOG_ID, "onSharedPreferenceChanged called for " + key)
- try {
- when(key) {
- //Constants.SHARED_PREF_PERMANENT_NOTIFICATION,
- Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION,
- Constants.SHARED_PREF_SEND_TO_GLUCODATA_AOD,
- Constants.SHARED_PREF_SEND_TO_XDRIP,
- Constants.SHARED_PREF_SEND_XDRIP_BROADCAST -> {
- updateEnableStates(sharedPreferences!!)
- }
- Constants.SHARED_PREF_FLOATING_WIDGET -> {
- updateEnableStates(sharedPreferences!!)
- if (sharedPreferences.getBoolean(Constants.SHARED_PREF_FLOATING_WIDGET, false) && !Settings.canDrawOverlays(requireContext())) {
- // as long as permission is not granted, disable immediately
- val pref = findPreference(Constants.SHARED_PREF_FLOATING_WIDGET)
- if (pref != null) {
- pref.isChecked = false
- }
- with(preferenceManager.sharedPreferences!!.edit()) {
- putBoolean(Constants.SHARED_PREF_FLOATING_WIDGET, false)
- apply()
- }
- requestOverlayPermission()
- } else if (!sharedPreferences.getBoolean(Constants.SHARED_PREF_FLOATING_WIDGET, false)) {
- val floatingWidget = findPreference(Constants.SHARED_PREF_FLOATING_WIDGET)
- if (floatingWidget != null)
- floatingWidget.isChecked = false
- }
- }
- }
- } catch (exc: Exception) {
- Log.e(LOG_ID, "onSharedPreferenceChanged exception: " + exc.toString())
- }
- }
-
- private fun requestOverlayPermission() {
- try {
- val intent = Intent(
- Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
- Uri.parse("package:" + requireContext().packageName)
- )
- activityResultOverlayLauncher.launch(intent)
- } catch (exc: Exception) {
- Log.e(LOG_ID, "requestOverlayPermission exception: " + exc.toString())
- }
- }
-
- override fun onDisplayPreferenceDialog(preference: Preference) {
- Log.d(LOG_ID, "onDisplayPreferenceDialog called for " + preference.javaClass)
- try {
- if (preference is SelectReceiverPreference) {
- Log.d(LOG_ID, "Show SelectReceiver Dialog")
- val dialogFragment = SelectReceiverPreferenceDialogFragmentCompat.initial(preference.key)
- dialogFragment.setTargetFragment(this, 0)
- dialogFragment.show(parentFragmentManager, "androidx.preference.PreferenceFragment.DIALOG")
- } else {
- super.onDisplayPreferenceDialog(preference)
- }
- } catch (exc: Exception) {
- Log.e(LOG_ID, "onDisplayPreferenceDialog exception: " + exc.toString())
- }
- }
-
- fun setEnableState(sharedPreferences: SharedPreferences, key: String, enableKey: String, secondEnableKey: String? = null, defValue: Boolean = false) {
- val pref = findPreference(key)
- if (pref != null)
- pref.isEnabled = sharedPreferences.getBoolean(enableKey, defValue) && (if (secondEnableKey != null) sharedPreferences.getBoolean(secondEnableKey, defValue) else true)
- }
-
- fun updateEnableStates(sharedPreferences: SharedPreferences) {
- try {
- setEnableState(sharedPreferences, Constants.SHARED_PREF_GLUCODATA_RECEIVERS, Constants.SHARED_PREF_SEND_TO_GLUCODATA_AOD)
- setEnableState(sharedPreferences, Constants.SHARED_PREF_XDRIP_RECEIVERS, Constants.SHARED_PREF_SEND_TO_XDRIP)
- setEnableState(sharedPreferences, Constants.SHARED_PREF_XDRIP_BROADCAST_RECEIVERS, Constants.SHARED_PREF_SEND_XDRIP_BROADCAST)
- /*setEnableState(sharedPreferences, Constants.SHARED_PREF_PERMANENT_NOTIFICATION_ICON, Constants.SHARED_PREF_PERMANENT_NOTIFICATION, defValue = true)
- setEnableState(sharedPreferences, Constants.SHARED_PREF_PERMANENT_NOTIFICATION_EMPTY, Constants.SHARED_PREF_PERMANENT_NOTIFICATION, defValue = true)
- setEnableState(sharedPreferences, Constants.SHARED_PREF_PERMANENT_NOTIFICATION_USE_BIG_ICON, Constants.SHARED_PREF_PERMANENT_NOTIFICATION, defValue = true)
- setEnableState(sharedPreferences, Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION, Constants.SHARED_PREF_PERMANENT_NOTIFICATION, defValue = true)*/
- setEnableState(sharedPreferences, Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION_ICON, /*Constants.SHARED_PREF_PERMANENT_NOTIFICATION,*/Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION, defValue = false)
- setEnableState(sharedPreferences, Constants.SHARED_PREF_FLOATING_WIDGET_SIZE, Constants.SHARED_PREF_FLOATING_WIDGET)
- setEnableState(sharedPreferences, Constants.SHARED_PREF_FLOATING_WIDGET_STYLE, Constants.SHARED_PREF_FLOATING_WIDGET)
- setEnableState(sharedPreferences, Constants.SHARED_PREF_FLOATING_WIDGET_TRANSPARENCY, Constants.SHARED_PREF_FLOATING_WIDGET)
- } catch (exc: Exception) {
- Log.e(LOG_ID, "updateEnableStates exception: " + exc.toString())
- }
- }
-
- private fun getReceivers(broadcastAction: String): HashMap {
- val names = HashMap()
- try {
- val receivers: List
- val intent = Intent(broadcastAction)
- receivers = requireContext().packageManager.queryBroadcastReceivers(
- intent,
- PackageManager.GET_META_DATA
- )
- for (resolveInfo in receivers) {
- val pkgName = resolveInfo.activityInfo.packageName
- val name =
- resolveInfo.activityInfo.loadLabel(requireContext().packageManager).toString()
- if (pkgName != null && pkgName != requireContext().packageName) {
- names[name] = pkgName
- }
- }
- } catch (exc: Exception) {
- Log.e(LOG_ID, "getReceivers exception: " + exc.toString())
- }
- return names
- }
-
-
- private fun setupReceivers(broadcastAction: String, multiSelectPrefKey: String) {
- val selectTargets = findPreference(multiSelectPrefKey)
- val receivers = getReceivers(broadcastAction)
- // force "global broadcast" to be the first entry
- selectTargets!!.entries = arrayOf(resources.getString(CR.string.pref_global_broadcast)) + receivers.keys.toTypedArray()
- selectTargets.entryValues = arrayOf("") + receivers.values.toTypedArray()
- }
}
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SettingsFragmentBase.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SettingsFragmentBase.kt
new file mode 100644
index 00000000..bb5dd184
--- /dev/null
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SettingsFragmentBase.kt
@@ -0,0 +1,314 @@
+package de.michelinside.glucodatahandler.preferences
+
+import android.content.Intent
+import android.content.SharedPreferences
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.net.Uri
+import android.os.Bundle
+import android.provider.Settings
+import android.util.Log
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.preference.*
+import de.michelinside.glucodatahandler.Dialogs
+import de.michelinside.glucodatahandler.R
+import de.michelinside.glucodatahandler.android_auto.CarModeReceiver
+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.NotifySource
+import de.michelinside.glucodatahandler.common.utils.PackageUtils
+import de.michelinside.glucodatahandler.common.R as CR
+
+
+abstract class SettingsFragmentBase(private val prefResId: Int) : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
+ protected val LOG_ID = "GDH.SettingsFragmentBase"
+ private val updateEnablePrefs = mutableSetOf()
+ private lateinit var activityResultOverlayLauncher: ActivityResultLauncher
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ Log.d(LOG_ID, "onCreatePreferences called")
+ try {
+ preferenceManager.sharedPreferencesName = Constants.SHARED_PREF_TAG
+ setPreferencesFromResource(prefResId, rootKey)
+
+ initPreferences()
+
+ activityResultOverlayLauncher = registerForActivityResult(
+ ActivityResultContracts.StartActivityForResult()
+ ) {
+ if (!Settings.canDrawOverlays(requireContext())) {
+ Log.w(LOG_ID, "Overlay permission denied!")
+ } else {
+ // setting to true
+ Log.i(LOG_ID, "Overlay permission granted!")
+ val pref = findPreference(Constants.SHARED_PREF_FLOATING_WIDGET)
+ if (pref != null) {
+ pref.isChecked = true
+ }
+ with(preferenceManager.sharedPreferences!!.edit()) {
+ putBoolean(Constants.SHARED_PREF_FLOATING_WIDGET, true)
+ apply()
+ }
+ }
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString())
+ }
+ }
+
+ open fun initPreferences() {
+ }
+
+ override fun onResume() {
+ Log.d(LOG_ID, "onResume called")
+ try {
+ preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
+ updateEnablePrefs.clear()
+ update()
+ super.onResume()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onResume exception: " + exc.toString())
+ }
+ }
+
+ override fun onPause() {
+ Log.d(LOG_ID, "onPause called")
+ try {
+ preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)
+ super.onPause()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onPause exception: " + exc.toString())
+ }
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
+ Log.d(LOG_ID, "onSharedPreferenceChanged called for " + key)
+ try {
+ if(updateEnablePrefs.isEmpty() || updateEnablePrefs.contains(key!!)) {
+ updateEnableStates(sharedPreferences!!)
+ }
+ when(key) {
+ Constants.SHARED_PREF_FLOATING_WIDGET -> {
+ if (sharedPreferences!!.getBoolean(Constants.SHARED_PREF_FLOATING_WIDGET, false) && !Settings.canDrawOverlays(requireContext())) {
+ // as long as permission is not granted, disable immediately
+ val pref = findPreference(Constants.SHARED_PREF_FLOATING_WIDGET)
+ if (pref != null) {
+ pref.isChecked = false
+ }
+ with(preferenceManager.sharedPreferences!!.edit()) {
+ putBoolean(Constants.SHARED_PREF_FLOATING_WIDGET, false)
+ apply()
+ }
+ requestOverlayPermission()
+ } else if (!sharedPreferences.getBoolean(Constants.SHARED_PREF_FLOATING_WIDGET, false)) {
+ val floatingWidget = findPreference(Constants.SHARED_PREF_FLOATING_WIDGET)
+ if (floatingWidget != null)
+ floatingWidget.isChecked = false
+ }
+ }
+ Constants.SHARED_PREF_APP_COLOR_SCHEME -> {
+ update()
+ Dialogs.updateColorScheme(requireContext())
+ }
+ Constants.SHARED_PREF_LARGE_ARROW_ICON -> {
+ InternalNotifier.notify(GlucoDataService.context!!, NotifySource.SETTINGS, null)
+ }
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onSharedPreferenceChanged exception: " + exc.toString())
+ }
+ }
+
+ private fun requestOverlayPermission() {
+ try {
+ val intent = Intent(
+ Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
+ Uri.parse("package:" + requireContext().packageName)
+ )
+ intent.putExtra(Settings.EXTRA_APP_PACKAGE,requireContext().packageName)
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ activityResultOverlayLauncher.launch(intent)
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "requestOverlayPermission exception: " + exc.toString())
+ }
+ }
+
+ override fun onDisplayPreferenceDialog(preference: Preference) {
+ Log.d(LOG_ID, "onDisplayPreferenceDialog called for " + preference.javaClass)
+ try {
+ if (preference is TapActionPreference) {
+ Log.d(LOG_ID, "Show SelectReceiver Dialog")
+ val dialogFragment = TapActionPreferenceDialogFragmentCompat.initial(preference.key)
+ dialogFragment.setTargetFragment(this, 0)
+ dialogFragment.show(parentFragmentManager, "androidx.preference.PreferenceFragment.DIALOG")
+ } else {
+ super.onDisplayPreferenceDialog(preference)
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onDisplayPreferenceDialog exception: " + exc.toString())
+ }
+ }
+
+ fun setEnableState(sharedPreferences: SharedPreferences, key: String, enableKey: String, secondEnableKey: String? = null, defValue: Boolean = false) {
+ val pref = findPreference(key)
+ if (pref != null) {
+ pref.isEnabled = sharedPreferences.getBoolean(enableKey, defValue) && (if (secondEnableKey != null) sharedPreferences.getBoolean(secondEnableKey, defValue) else true)
+
+ if(!updateEnablePrefs.contains(enableKey)) {
+ Log.v(LOG_ID, "Add update enable pref $enableKey")
+ updateEnablePrefs.add(enableKey)
+ }
+ if (secondEnableKey != null && !updateEnablePrefs.contains(secondEnableKey)) {
+ Log.v(LOG_ID, "Add update second enable pref $secondEnableKey")
+ updateEnablePrefs.add(secondEnableKey)
+ }
+ }
+ }
+
+ private fun update() {
+ val sharedPref = preferenceManager.sharedPreferences!!
+ updateEnableStates(sharedPref)
+ val colorSchemePref = findPreference(Constants.SHARED_PREF_APP_COLOR_SCHEME)
+ if(colorSchemePref != null) {
+ colorSchemePref.summary = resources.getString(when (sharedPref.getString(Constants.SHARED_PREF_APP_COLOR_SCHEME, "")) {
+ "dark" -> CR.string.application_color_scheme_dark
+ "light" -> CR.string.application_color_scheme_light
+ else -> CR.string.application_color_scheme_system
+ })
+ }
+ }
+
+ fun updateEnableStates(sharedPreferences: SharedPreferences) {
+ try {
+ Log.d(LOG_ID, "updateEnableStates called")
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_GLUCODATA_RECEIVERS, Constants.SHARED_PREF_SEND_TO_GLUCODATA_AOD)
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_XDRIP_RECEIVERS, Constants.SHARED_PREF_SEND_TO_XDRIP)
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_XDRIP_BROADCAST_RECEIVERS, Constants.SHARED_PREF_SEND_XDRIP_BROADCAST)
+ /*setEnableState(sharedPreferences, Constants.SHARED_PREF_PERMANENT_NOTIFICATION_ICON, Constants.SHARED_PREF_PERMANENT_NOTIFICATION, defValue = true)
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_PERMANENT_NOTIFICATION_EMPTY, Constants.SHARED_PREF_PERMANENT_NOTIFICATION, defValue = true)
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_PERMANENT_NOTIFICATION_USE_BIG_ICON, Constants.SHARED_PREF_PERMANENT_NOTIFICATION, defValue = true)
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION, Constants.SHARED_PREF_PERMANENT_NOTIFICATION, defValue = true)*/
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION_ICON, /*Constants.SHARED_PREF_PERMANENT_NOTIFICATION,*/Constants.SHARED_PREF_SECOND_PERMANENT_NOTIFICATION, defValue = false)
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_FLOATING_WIDGET_SIZE, Constants.SHARED_PREF_FLOATING_WIDGET)
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_FLOATING_WIDGET_STYLE, Constants.SHARED_PREF_FLOATING_WIDGET)
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_FLOATING_WIDGET_TRANSPARENCY, Constants.SHARED_PREF_FLOATING_WIDGET)
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_SEND_PREF_TO_GLUCODATAAUTO, Constants.SHARED_PREF_SEND_TO_GLUCODATAAUTO, defValue = true)
+ setEnableState(sharedPreferences, Constants.SHARED_PREF_LOCKSCREEN_WP_Y_POS, Constants.SHARED_PREF_LOCKSCREEN_WP_ENABLED)
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "updateEnableStates exception: " + exc.toString())
+ }
+ }
+
+ private fun getReceivers(broadcastAction: String): HashMap {
+ val names = HashMap()
+ try {
+ val receivers: List
+ val intent = Intent(broadcastAction)
+ receivers = requireContext().packageManager.queryBroadcastReceivers(
+ intent,
+ PackageManager.GET_META_DATA
+ )
+ for (resolveInfo in receivers) {
+ val pkgName = resolveInfo.activityInfo.packageName
+ val name =
+ resolveInfo.activityInfo.loadLabel(requireContext().packageManager).toString()
+ if (pkgName != null && pkgName != requireContext().packageName) {
+ names[name] = pkgName
+ }
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "getReceivers exception: " + exc.toString())
+ }
+ return names
+ }
+
+
+ protected fun setupReceivers(broadcastAction: String, multiSelectPrefKey: String) {
+ val selectTargets = findPreference(multiSelectPrefKey)
+ if(selectTargets != null) {
+ val receivers = getReceivers(broadcastAction)
+ // force "global broadcast" to be the first entry
+ selectTargets.entries =
+ arrayOf(resources.getString(CR.string.pref_global_broadcast)) + receivers.keys.toTypedArray()
+ selectTargets.entryValues = arrayOf("") + receivers.values.toTypedArray()
+ }
+ }
+}
+
+class GeneralSettingsFragment: SettingsFragmentBase(R.xml.pref_general) {}
+class RangeSettingsFragment: SettingsFragmentBase(R.xml.pref_target_range) {}
+class UiSettingsFragment: SettingsFragmentBase(R.xml.pref_ui) {}
+class WidgetSettingsFragment: SettingsFragmentBase(R.xml.pref_widgets) {}
+class NotificaitonSettingsFragment: SettingsFragmentBase(R.xml.pref_notification) {}
+class LockscreenSettingsFragment: SettingsFragmentBase(R.xml.pref_lockscreen) {}
+class WatchSettingsFragment: SettingsFragmentBase(R.xml.pref_watch) {
+ override fun initPreferences() {
+ Log.v(LOG_ID, "initPreferences called")
+ val prefCheckWearOS = findPreference(Constants.SHARED_PREF_CHECK_WEAR_OS_CONNECTION)
+ prefCheckWearOS!!.setOnPreferenceClickListener {
+ GlucoDataService.checkForConnectedNodes()
+ true
+ }
+
+ val prefWatchDripLink = findPreference(Constants.SHARED_PREF_OPEN_WATCH_DRIP_LINK)
+ prefWatchDripLink!!.setOnPreferenceClickListener {
+ val browserIntent = Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse(resources.getText(CR.string.watchdrip_link).toString())
+ )
+ startActivity(browserIntent)
+ true
+ }
+ }
+}
+class TransferSettingsFragment: SettingsFragmentBase(R.xml.pref_transfer) {
+ override fun initPreferences() {
+ Log.v(LOG_ID, "initPreferences called")
+ setupReceivers(Constants.GLUCODATA_BROADCAST_ACTION, Constants.SHARED_PREF_GLUCODATA_RECEIVERS)
+ setupReceivers(Constants.XDRIP_ACTION_GLUCOSE_READING, Constants.SHARED_PREF_XDRIP_RECEIVERS)
+ setupReceivers(Constants.XDRIP_BROADCAST_ACTION, Constants.SHARED_PREF_XDRIP_BROADCAST_RECEIVERS)
+ }
+
+}
+class GDASettingsFragment: SettingsFragmentBase(R.xml.pref_gda) {
+ override fun initPreferences() {
+ Log.v(LOG_ID, "initPreferences called")
+ if (PackageUtils.isGlucoDataAutoAvailable(requireContext())) {
+ val sendToGDA = findPreference(Constants.SHARED_PREF_SEND_TO_GLUCODATAAUTO)
+ sendToGDA!!.isVisible = true
+ val sendPrefToGDA = findPreference(Constants.SHARED_PREF_SEND_PREF_TO_GLUCODATAAUTO)
+ sendPrefToGDA!!.isVisible = true
+ } else {
+ val no_gda_info = findPreference(Constants.SHARED_PREF_NO_GLUCODATAAUTO)
+ if (no_gda_info != null) {
+ no_gda_info.isVisible = true
+ no_gda_info.setOnPreferenceClickListener {
+ val browserIntent = Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse(resources.getText(CR.string.glucodataauto_link).toString())
+ )
+ startActivity(browserIntent)
+ true
+ }
+ }
+ }
+ }
+
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
+ super.onSharedPreferenceChanged(sharedPreferences, key)
+ when(key) {
+ Constants.SHARED_PREF_SEND_TO_GLUCODATAAUTO,
+ Constants.SHARED_PREF_SEND_PREF_TO_GLUCODATAAUTO -> {
+ if(CarModeReceiver.AA_connected) {
+ val extras = ReceiveData.createExtras()
+ if (extras != null)
+ CarModeReceiver.sendToGlucoDataAuto(requireContext(), extras, true)
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SourceFragment.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SourceFragment.kt
index 0f1acd7d..493d8414 100644
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SourceFragment.kt
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SourceFragment.kt
@@ -6,13 +6,15 @@ import android.os.Bundle
import android.text.InputType
import android.util.Log
import androidx.preference.*
+import de.michelinside.glucodatahandler.Dialogs
import de.michelinside.glucodatahandler.R
+import de.michelinside.glucodatahandler.common.R as CR
import de.michelinside.glucodatahandler.common.Constants
import de.michelinside.glucodatahandler.common.notifier.InternalNotifier
import de.michelinside.glucodatahandler.common.notifier.NotifierInterface
import de.michelinside.glucodatahandler.common.notifier.NotifySource
import de.michelinside.glucodatahandler.common.tasks.DataSourceTask
-import de.michelinside.glucodatahandler.common.tasks.LibreViewSourceTask
+import de.michelinside.glucodatahandler.common.tasks.LibreLinkSourceTask
class SourceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener, NotifierInterface {
@@ -36,6 +38,10 @@ class SourceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPre
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
}
+ setupLocalIobAction(findPreference(Constants.SHARED_PREF_SOURCE_JUGGLUCO_SET_NS_IOB_ACTION))
+ setupLocalIobAction(findPreference(Constants.SHARED_PREF_SOURCE_XDRIP_SET_NS_IOB_ACTION))
+
+
setupLibrePatientData()
} catch (exc: Exception) {
Log.e(LOG_ID, "onCreatePreferences exception: " + exc.toString())
@@ -61,6 +67,7 @@ class SourceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPre
preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
updateEnableStates(preferenceManager.sharedPreferences!!)
InternalNotifier.addNotifier(requireContext(), this, mutableSetOf(NotifySource.PATIENT_DATA_CHANGED))
+ update()
super.onResume()
} catch (exc: Exception) {
Log.e(LOG_ID, "onResume exception: " + exc.toString())
@@ -78,22 +85,6 @@ class SourceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPre
}
}
- override fun onDisplayPreferenceDialog(preference: Preference) {
- Log.d(LOG_ID, "onDisplayPreferenceDialog called for " + preference.javaClass)
- try {
- if (preference is SelectReceiverPreference) {
- Log.d(LOG_ID, "Show SelectReceiver Dialog")
- val dialogFragment = SelectReceiverPreferenceDialogFragmentCompat.initial(preference.key)
- dialogFragment.setTargetFragment(this, 0)
- dialogFragment.show(parentFragmentManager, "androidx.preference.PreferenceFragment.DIALOG")
- } else {
- super.onDisplayPreferenceDialog(preference)
- }
- } catch (exc: Exception) {
- Log.e(LOG_ID, "onDisplayPreferenceDialog exception: " + exc.toString())
- }
- }
-
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
Log.d(LOG_ID, "onSharedPreferenceChanged called for " + key)
try {
@@ -105,6 +96,10 @@ class SourceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPre
Constants.SHARED_PREF_LIBRE_USER,
Constants.SHARED_PREF_NIGHTSCOUT_URL -> {
updateEnableStates(sharedPreferences!!)
+ update()
+ }
+ Constants.SHARED_PREF_LIBRE_PATIENT_ID -> {
+ update()
}
}
} catch (exc: Exception) {
@@ -135,18 +130,79 @@ class SourceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPre
}
}
+ private fun setSummary(key: String, defaultResId: Int) {
+ val pref = findPreference(key)
+ if(pref != null) {
+ val value = preferenceManager.sharedPreferences!!.getString(key, "")!!.trim()
+ pref.summary = if(value.isNullOrEmpty())
+ resources.getString(defaultResId)
+ else
+ value
+ }
+ }
+ private fun update() {
+ setSummary(Constants.SHARED_PREF_LIBRE_USER, CR.string.src_libre_user_summary)
+ setSummary(Constants.SHARED_PREF_NIGHTSCOUT_URL, CR.string.src_ns_url_summary)
+ setPatientSummary()
+ }
+
+
+ private fun setPatientSummary() {
+ val listPreference = findPreference(Constants.SHARED_PREF_LIBRE_PATIENT_ID)
+ if(listPreference != null && listPreference.isVisible) {
+ val pref = findPreference(Constants.SHARED_PREF_LIBRE_PATIENT_ID)
+ if (pref != null) {
+ val value = preferenceManager.sharedPreferences!!.getString(
+ Constants.SHARED_PREF_LIBRE_PATIENT_ID,
+ ""
+ )!!.trim()
+ if (value.isNullOrEmpty() || !LibreLinkSourceTask.patientData.containsKey(value))
+ pref.summary = resources.getString(CR.string.src_libre_patient_summary)
+ else {
+ pref.summary = LibreLinkSourceTask.patientData[value]
+ }
+ }
+ }
+ }
private fun setupLibrePatientData() {
try {
val listPreference = findPreference(Constants.SHARED_PREF_LIBRE_PATIENT_ID)
// force "global broadcast" to be the first entry
- listPreference!!.entries = LibreViewSourceTask.patientData.values.toTypedArray()
- listPreference.entryValues = LibreViewSourceTask.patientData.keys.toTypedArray()
- listPreference.isVisible = LibreViewSourceTask.patientData.size > 1
+ listPreference!!.entries = LibreLinkSourceTask.patientData.values.toTypedArray()
+ listPreference.entryValues = LibreLinkSourceTask.patientData.keys.toTypedArray()
+ listPreference.isVisible = LibreLinkSourceTask.patientData.size > 1
+ if(listPreference.isVisible)
+ setPatientSummary()
} catch (exc: Exception) {
Log.e(LOG_ID, "setupLibrePatientData exception: $exc")
}
}
+ private fun setupLocalIobAction(preference: Preference?) {
+ if(preference != null) {
+ preference.setOnPreferenceClickListener {
+ Dialogs.showOkCancelDialog(requireContext(), resources.getString(de.michelinside.glucodatahandler.common.R.string.activate_local_nightscout_iob_title), resources.getString(
+ de.michelinside.glucodatahandler.common.R.string.activate_local_nightscout_iob_message)) { _, _ ->
+ with(preferenceManager!!.sharedPreferences!!.edit()) {
+ putBoolean(Constants.SHARED_PREF_NIGHTSCOUT_IOB_COB, true)
+ putString(Constants.SHARED_PREF_NIGHTSCOUT_URL, "http://127.0.0.1:17580")
+ putString(Constants.SHARED_PREF_NIGHTSCOUT_SECRET, "")
+ putString(Constants.SHARED_PREF_NIGHTSCOUT_TOKEN, "")
+ putBoolean(Constants.SHARED_PREF_NIGHTSCOUT_ENABLED, true)
+ apply()
+ }
+ findPreference(Constants.SHARED_PREF_NIGHTSCOUT_IOB_COB)!!.isChecked = true
+ findPreference(Constants.SHARED_PREF_NIGHTSCOUT_URL)!!.text = "http://127.0.0.1:17580"
+ findPreference(Constants.SHARED_PREF_NIGHTSCOUT_SECRET)!!.text = ""
+ findPreference(Constants.SHARED_PREF_NIGHTSCOUT_TOKEN)!!.text = ""
+ findPreference(Constants.SHARED_PREF_NIGHTSCOUT_ENABLED)!!.isChecked = true
+
+ }
+ true
+ }
+ }
+ }
+
override fun OnNotifyData(context: Context, dataSource: NotifySource, extras: Bundle?) {
try {
Log.v(LOG_ID, "OnNotifyData called for source $dataSource")
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SelectReceiverPreference.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/TapActionPreference.kt
similarity index 66%
rename from mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SelectReceiverPreference.kt
rename to mobile/src/main/java/de/michelinside/glucodatahandler/preferences/TapActionPreference.kt
index 39c4c80c..3868262a 100644
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/SelectReceiverPreference.kt
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/TapActionPreference.kt
@@ -8,9 +8,9 @@ import androidx.preference.DialogPreference
import de.michelinside.glucodatahandler.R
-class SelectReceiverPreference : DialogPreference {
- private val LOG_ID = "GDH.SelectReceiverPreference"
- private var receiverSet = HashSet()
+class TapActionPreference : DialogPreference {
+ private val LOG_ID = "GDH.TapActionPreference"
+ private var receiver = ""
constructor(context: Context?) : super(context!!) {
initPreference()
}
@@ -25,6 +25,7 @@ class SelectReceiverPreference : DialogPreference {
fun initPreference() {
Log.d(LOG_ID, "initPreference called")
+ setReceiver("", false)
}
override fun getDialogLayoutResource(): Int {
@@ -32,24 +33,26 @@ class SelectReceiverPreference : DialogPreference {
return R.layout.fragment_select_receiver
}
- fun getReceivers(): HashSet {
+ fun getReceiver(): String {
Log.d(LOG_ID, "getReceivers called")
- return receiverSet
+ return receiver
}
- fun setReceivers(receivers: HashSet) {
- Log.d(LOG_ID, "setReceivers called for " + receivers.toString())
+ fun setReceiver(newReceiver: String, save: Boolean = true) {
+ Log.d(LOG_ID, "setReceiver called for $newReceiver - save: $save")
try {
- receiverSet = receivers // Save to Shared Preferences
- persistStringSet(receiverSet)
+ receiver = newReceiver // Save to Shared Preferences
+ if(save)
+ persistString(receiver)
+ this.summary = TapActionPreferenceDialogFragmentCompat.getSummary(context, receiver)
} catch (exc: Exception) {
- Log.e(LOG_ID, "setReceivers exception: " + exc.toString())
+ Log.e(LOG_ID, "setReceiver exception: " + exc.toString())
}
}
override fun onGetDefaultValue(a: TypedArray, index: Int): Any? {
Log.d(LOG_ID, "onGetDefaultValue called for " + a.toString() + " with index " + index.toString())
- return HashSet()
+ return context.packageName
}
override fun onSetInitialValue(
@@ -59,7 +62,7 @@ class SelectReceiverPreference : DialogPreference {
try {
Log.d(LOG_ID, "onSetInitialValue called with restorePersistedValue " + restorePersistedValue.toString() + " - defaultValue " + defaultValue.toString())
// Read the value. Use the default value if it is not possible.
- setReceivers(if (restorePersistedValue || defaultValue == null) getPersistedStringSet(receiverSet) as HashSet else defaultValue as HashSet)
+ setReceiver(if (restorePersistedValue || defaultValue == null) getPersistedString(receiver) as String else defaultValue as String)
} catch (exc: Exception) {
Log.e(LOG_ID, "onSetInitialValue exception: " + exc.toString())
}
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/TapActionPreferenceDialogFragmentCompat.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/TapActionPreferenceDialogFragmentCompat.kt
new file mode 100644
index 00000000..a8917a29
--- /dev/null
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/preferences/TapActionPreferenceDialogFragmentCompat.kt
@@ -0,0 +1,195 @@
+package de.michelinside.glucodatahandler.preferences
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.provider.Settings
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import android.widget.RadioButton
+import android.widget.RadioGroup
+import android.widget.ScrollView
+import androidx.appcompat.widget.SwitchCompat
+import androidx.preference.PreferenceDialogFragmentCompat
+import de.michelinside.glucodatahandler.BuildConfig
+import de.michelinside.glucodatahandler.R
+import de.michelinside.glucodatahandler.common.Constants
+import de.michelinside.glucodatahandler.common.utils.PackageUtils
+import de.michelinside.glucodatahandler.common.R as CR
+
+
+class TapActionPreferenceDialogFragmentCompat : PreferenceDialogFragmentCompat() {
+ companion object {
+ private val LOG_ID = "GDH.TapActionPreferenceDialog"
+ private val filter = mutableSetOf()
+ fun initial(key: String) : TapActionPreferenceDialogFragmentCompat {
+ Log.d(LOG_ID, "initial called for key: " + key )
+ val dialog = TapActionPreferenceDialogFragmentCompat()
+ val bundle = Bundle(1)
+ bundle.putString(ARG_KEY, key)
+ dialog.arguments = bundle
+ return dialog
+ }
+
+ private fun getReceivers(context: Context): HashMap {
+ return PackageUtils.getPackages(context)
+ }
+ private fun getActions(context: Context): HashMap {
+ val actions = HashMap()
+ actions[""] = context.resources.getString(CR.string.no_action)
+ if(Settings.canDrawOverlays(context)) {
+ actions[Constants.ACTION_FLOATING_WIDGET_TOGGLE] =
+ context.resources.getString(CR.string.action_floating_widget_toggle)
+ }
+ if(BuildConfig.DEBUG /*|| BuildConfig.BUILD_TYPE == "second"*/) {
+ actions[Constants.ACTION_DUMMY_VALUE] = "Dummy value"
+ }
+ return actions
+ }
+
+ fun getSummary(context: Context, value: String): String {
+ val actions = getActions(context)
+ if(actions.containsKey(value))
+ return actions[value].toString()
+ val receivers = getReceivers(context)
+ if(receivers.containsKey(value))
+ return receivers[value].toString()
+ return context.resources.getString(CR.string.no_action)
+ }
+
+ }
+ private var receiver = ""
+ private lateinit var showAllSwitch: SwitchCompat
+ private lateinit var sharedPref: SharedPreferences
+ private var tapActionPreference: TapActionPreference? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ Log.d(LOG_ID, "onCreate called with bundle: " + savedInstanceState?.toString() )
+ try {
+ initFilter()
+ tapActionPreference = preference as TapActionPreference
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "Setting preference exception: " + exc.toString())
+ }
+ }
+
+ private fun initFilter() {
+ filter.add(requireContext().packageName)
+ filter.add(Constants.PACKAGE_JUGGLUCO)
+ filter.add(Constants.PACKAGE_GLUCODATAAUTO)
+ filter.add(Constants.PACKAGE_AAPS)
+ filter.add(Constants.PACKAGE_XDRIP)
+ filter.add(Constants.PACKAGE_XDRIP_PLUS)
+ filter.add(Constants.PACKAGE_LIBRELINK)
+ filter.add(Constants.PACKAGE_LIBRELINKUP)
+ }
+
+ private fun filterContains(value: String): Boolean {
+ filter.forEach {
+ if(value.lowercase().startsWith(it.lowercase()))
+ return true
+ }
+ return false
+ }
+
+ override fun onBindDialogView(view: View) {
+ super.onBindDialogView(view)
+ Log.d(LOG_ID, "onBindDialogView called for view: " + view.transitionName.toString() + " preference " + preference.javaClass )
+ try {
+ receiver = tapActionPreference!!.getReceiver()
+ Log.d(LOG_ID, "Receiver loaded: " + receiver)
+
+ sharedPref = requireContext().getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
+ showAllSwitch = view.findViewById(R.id.showAllSwitch)
+ showAllSwitch.isChecked = sharedPref.getBoolean(Constants.SHARED_PREF_GLUCODATA_RECEIVER_SHOW_ALL, false)
+ showAllSwitch.setOnCheckedChangeListener { _, isChecked ->
+ updateReceiver(view, isChecked)
+ }
+
+ updateReceiver(view, showAllSwitch.isChecked)
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "onBindDialogView exception: " + exc.toString())
+ }
+ }
+
+ override fun onDialogClosed(positiveResult: Boolean) {
+ Log.d(LOG_ID, "onDialogClosed called with positiveResult: " + positiveResult.toString() )
+ try {
+ if(positiveResult) {
+ with(sharedPref.edit()) {
+ putBoolean(Constants.SHARED_PREF_GLUCODATA_RECEIVER_SHOW_ALL, showAllSwitch.isChecked)
+ apply()
+ }
+ tapActionPreference!!.setReceiver(receiver)
+ }
+ } catch (exc: Exception) {
+
+ Log.e(LOG_ID, "onDialogClosed exception: " + exc.toString())
+ }
+ }
+
+ private fun createRadioButtons(group: RadioGroup, list: HashMap, all: Boolean, sort: Boolean): RadioButton? {
+ var current: RadioButton? = null
+ val map = if(sort) {
+ list.toList()
+ .sortedBy { (_, value) -> value.lowercase() }
+ .toMap()
+ } else {
+ list.toMap()
+ }
+
+ for (item in map) {
+ if (all || filterContains(item.key)) {
+ val ch = RadioButton(requireContext())
+ ch.text = item.value
+ ch.hint = item.key
+ if (receiver == ch.hint) {
+ current = ch
+ }
+ ch.setOnCheckedChangeListener { buttonView, isChecked ->
+ if (isChecked) {
+ receiver = buttonView.hint.toString()
+ Log.v(LOG_ID, "Set receiver: $receiver")
+ }
+ }
+ group.addView(ch)
+ }
+ }
+ return current
+ }
+
+ private fun updateReceiver(view: View, all: Boolean) {
+ try {
+ val receiverLayout = view.findViewById(R.id.receiverLayout)
+ val receivers = getReceivers(requireContext())
+ Log.d(LOG_ID, receivers.size.toString() + " receivers found!")
+ val receiverScrollView = view.findViewById(R.id.receiverScrollView)
+ receiverLayout.removeAllViews()
+
+ var current: RadioButton?
+ val group = RadioGroup(requireContext())
+ current = createRadioButtons(group, getActions(requireContext()), true, false)
+ val curApp = createRadioButtons(group, receivers, all, true)
+ if(current == null && curApp != null)
+ current = curApp
+ receiverLayout.addView(group)
+ if (current != null) {
+ current.isChecked = true
+ Log.v(LOG_ID, "Current receiver set for ${current.text}")
+ } else {
+ receiver = ""
+ }
+ if (group.childCount > 10) {
+ receiverScrollView.layoutParams.height = resources.displayMetrics.heightPixels / 2
+ } else {
+ receiverScrollView.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
+ }
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "updateReceiver exception: " + exc.message.toString() )
+ }
+ }
+
+}
\ 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
index 0264869c..ed54dbbb 100644
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/watch/WatchDrip.kt
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/watch/WatchDrip.kt
@@ -9,6 +9,7 @@ import android.content.SharedPreferences
import android.os.Build
import android.os.Bundle
import android.util.Log
+import de.michelinside.glucodatahandler.common.notification.AlarmType
import de.michelinside.glucodatahandler.common.BuildConfig
import de.michelinside.glucodatahandler.common.Constants
import de.michelinside.glucodatahandler.common.GlucoDataService
@@ -18,6 +19,7 @@ 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
+import de.michelinside.glucodatahandler.notification.AlarmNotification
object WatchDrip: SharedPreferences.OnSharedPreferenceChangeListener, NotifierInterface {
private val LOG_ID = "GDH.WatchDrip"
@@ -32,6 +34,8 @@ object WatchDrip: SharedPreferences.OnSharedPreferenceChangeListener, NotifierIn
const val CMD_UPDATE_BG_FORCE = "update_bg_force"
const val CMD_UPDATE_BG = "update_bg"
const val CMD_ALARM = "alarm"
+ const val CMD_CANCEL_ALARM= "cancel_alarm"
+ const val CMD_SNOOZE_ALARM = "snooze_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"
@@ -46,6 +50,8 @@ object WatchDrip: SharedPreferences.OnSharedPreferenceChangeListener, NotifierIn
private val watchDripReceiver = WatchDripReceiver()
+ val connected: Boolean get() = (active && receivers.size > 0)
+
fun init(context: Context) {
try {
if (!init) {
@@ -73,12 +79,14 @@ object WatchDrip: SharedPreferences.OnSharedPreferenceChangeListener, NotifierIn
}
}
- private fun handleNewReceiver(pkg: String) {
- if(!receivers.contains(pkg)) {
+ private fun handleNewReceiver(pkg: String): Boolean {
+ if(pkg != "" && !receivers.contains(pkg)) {
Log.i(LOG_ID, "Adding new receiver " + pkg)
receivers.add(pkg)
saveReceivers()
+ return true
}
+ return false
}
private fun handleIntent(context: Context, intent: Intent) {
@@ -95,35 +103,53 @@ object WatchDrip: SharedPreferences.OnSharedPreferenceChangeListener, NotifierIn
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)
+ val newReceiver = handleNewReceiver(pkg)
+ if(newReceiver) {
sendBroadcast(context, CMD_UPDATE_BG_FORCE, pkg)
- } else {
- Log.d(LOG_ID, "Unknown command received: " + cmd + " received from " + pkg)
+ }
+ when(cmd) {
+ CMD_UPDATE_BG_FORCE -> {
+ if(!newReceiver)
+ sendBroadcast(context, CMD_UPDATE_BG_FORCE, pkg)
+ }
+ CMD_CANCEL_ALARM,
+ CMD_SNOOZE_ALARM -> {
+ AlarmNotification.stopCurrentNotification(context)
+ }
+ 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 {
+ private fun createBundle(context: Context, cmd: String, alarmType: AlarmType): Bundle {
return when(cmd) {
- CMD_ALARM -> createAlarmBundle()
- else -> createBgBundle(cmd)
+ CMD_ALARM -> createAlarmBundle(context, alarmType)
+ CMD_UPDATE_BG,
+ CMD_UPDATE_BG_FORCE -> createBgBundle(cmd)
+ else -> createCmdBundle(cmd)
}
}
- private fun createBgBundle(cmd: String): Bundle {
+ private fun createCmdBundle(cmd: String): Bundle {
val bundle = Bundle()
bundle.putString(EXTRA_FUNCTION, cmd)
+ return bundle
+ }
+
+ private fun createBgBundle(cmd: String): Bundle {
+ val bundle = createCmdBundle(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.putBoolean("bg.isHigh", ReceiveData.getAlarmType() == AlarmType.VERY_HIGH)
+ bundle.putBoolean("bg.isLow", ReceiveData.getAlarmType() == AlarmType.VERY_LOW)
bundle.putString("pumpJSON", "{}")
if (!ReceiveData.isIobCobObsolete(Constants.VALUE_OBSOLETE_SHORT_SEC) && !ReceiveData.iob.isNaN()) {
bundle.putString("predict.IOB", ReceiveData.iobString)
@@ -132,30 +158,36 @@ object WatchDrip: SharedPreferences.OnSharedPreferenceChangeListener, NotifierIn
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())
+ private fun createAlarmBundle(context: Context, alarmType: AlarmType): Bundle {
+ val bundle = createCmdBundle(CMD_ALARM)
+ bundle.putString(EXTRA_TYPE, getAlertType(alarmType))
+ bundle.putString(EXTRA_MESSAGE, getAlarmMessage(context, alarmType))
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
+ private fun getAlertType(alarmType: AlarmType): String {
+ return when(alarmType) {
+ AlarmType.VERY_LOW,
+ AlarmType.VERY_HIGH -> TYPE_ALERT
+ AlarmType.LOW,
+ AlarmType.HIGH,
+ AlarmType.OBSOLETE -> 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()
+ private fun getAlarmMessage(context: Context, alarmType: AlarmType): String {
+ val resId = AlarmNotification.getAlarmTextRes(alarmType)
+ if(resId == null) {
+ return "No alarm!"
+ }
+ val msg = context.resources.getString(resId)
+ return when(alarmType) {
+ AlarmType.VERY_LOW,
+ AlarmType.LOW,
+ AlarmType.HIGH,
+ AlarmType.VERY_HIGH -> msg + " " + ReceiveData.getGlucoseAsString()
+ AlarmType.OBSOLETE -> msg + " " + ReceiveData.getElapsedRelativeTimeAsString(context)
else -> "No alarm!"
}
}
@@ -168,10 +200,10 @@ object WatchDrip: SharedPreferences.OnSharedPreferenceChangeListener, NotifierIn
context.sendBroadcast(intent)
}
- private fun sendBroadcast(context: Context, cmd: String, receiver: String? = null) {
+ private fun sendBroadcast(context: Context, cmd: String, receiver: String? = null, alarmType: AlarmType = AlarmType.NONE) {
try {
if (receiver != null || receivers.size > 0) {
- val bundle = createBundle(cmd)
+ val bundle = createBundle(context, cmd, alarmType)
if (receiver != null) {
sendBroadcastToReceiver(context, receiver, bundle)
} else {
@@ -203,7 +235,10 @@ object WatchDrip: SharedPreferences.OnSharedPreferenceChangeListener, NotifierIn
NotifySource.BROADCAST,
NotifySource.MESSAGECLIENT,
NotifySource.IOB_COB_CHANGE,
- NotifySource.OBSOLETE_VALUE))
+ NotifySource.OBSOLETE_VALUE,
+ NotifySource.ALARM_TRIGGER,
+ NotifySource.OBSOLETE_ALARM_TRIGGER,
+ NotifySource.NOTIFICATION_STOPPED))
active = true
if (receivers.size > 0) {
sendBroadcast(GlucoDataService.context!!, CMD_UPDATE_BG)
@@ -288,11 +323,24 @@ object WatchDrip: SharedPreferences.OnSharedPreferenceChangeListener, NotifierIn
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)
+ when(dataSource) {
+ NotifySource.NOTIFICATION_STOPPED -> {
+ sendBroadcast(context, CMD_CANCEL_ALARM)
+ }
+ NotifySource.ALARM_TRIGGER,
+ NotifySource.OBSOLETE_ALARM_TRIGGER -> {
+ sendBroadcast(context, CMD_ALARM, alarmType = ReceiveData.getAlarmType())
+ }
+ else -> {
+ sendBroadcast(context, CMD_UPDATE_BG)
+ }
+ }
} catch (exc: Exception) {
Log.e(LOG_ID, "OnNotifyData exception: " + exc.toString() + "\n" + exc.stackTraceToString() )
}
}
+
+ fun sendTestAlert(context: Context, alarmType: AlarmType) {
+ sendBroadcast(context, CMD_ALARM, alarmType = alarmType)
+ }
}
\ No newline at end of file
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/ActiveWidgetHandler.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/ActiveWidgetHandler.kt
index 93e43a01..7611cfea 100644
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/ActiveWidgetHandler.kt
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/ActiveWidgetHandler.kt
@@ -81,7 +81,8 @@ object ActiveWidgetHandler: NotifierInterface, SharedPreferences.OnSharedPrefere
Log.d(LOG_ID, "onSharedPreferenceChanged called for key " + key)
if (GlucoDataService.context != null) {
when (key) {
- Constants.SHARED_PREF_WIDGET_TRANSPARENCY -> {
+ Constants.SHARED_PREF_WIDGET_TRANSPARENCY,
+ Constants.SHARED_PREF_WIDGET_TAP_ACTION -> {
OnNotifyData(GlucoDataService.context!!, NotifySource.SETTINGS, null)
}
}
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 25dad0a6..d17e03e2 100644
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/FloatingWidget.kt
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/FloatingWidget.kt
@@ -15,7 +15,6 @@ import android.view.*
import android.view.View.*
import android.widget.ImageView
import android.widget.TextView
-import de.michelinside.glucodatahandler.MainActivity
import de.michelinside.glucodatahandler.R
import de.michelinside.glucodatahandler.common.Constants
import de.michelinside.glucodatahandler.common.ReceiveData
@@ -24,6 +23,7 @@ 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.PackageUtils
import java.util.*
@@ -144,8 +144,8 @@ class FloatingWidget(val context: Context) : NotifierInterface, SharedPreference
@SuppressLint("SetTextI18n")
private fun setContent() {
val textSize = applyStyle()
- txtBgValue.text = ReceiveData.getClucoseAsString()
- txtBgValue.setTextColor(ReceiveData.getClucoseColor())
+ txtBgValue.text = ReceiveData.getGlucoseAsString()
+ txtBgValue.setTextColor(ReceiveData.getGlucoseColor())
if (ReceiveData.isObsolete(Constants.VALUE_OBSOLETE_SHORT_SEC) && !ReceiveData.isObsolete()) {
txtBgValue.paintFlags = Paint.STRIKE_THRU_TEXT_FLAG
} else {
@@ -225,13 +225,15 @@ class FloatingWidget(val context: Context) : NotifierInterface, SharedPreference
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 action = PackageUtils.getTapAction(context, sharedPref.getString(Constants.SHARED_PREF_FLOATING_WIDGET_TAP_ACTION, ""))
+ if(action.first != null) {
+ if (action.second) {
+ context.sendBroadcast(action.first!!)
+ } else {
+ action.first!!.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(action.first)
+ }
}
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- context.startActivity(launchIntent)
}
widget.setOnLongClickListener {
Log.d(LOG_ID, "onLongClick called")
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/GlucoseBaseWidget.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/GlucoseBaseWidget.kt
index b2e635c4..cc3ff27f 100644
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/GlucoseBaseWidget.kt
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/GlucoseBaseWidget.kt
@@ -11,7 +11,6 @@ import android.util.Log
import android.view.View
import android.widget.RemoteViews
import de.michelinside.glucodatahandler.GlucoDataServiceMobile
-import de.michelinside.glucodatahandler.MainActivity
import de.michelinside.glucodatahandler.R
import de.michelinside.glucodatahandler.common.Constants
import de.michelinside.glucodatahandler.common.ReceiveData
@@ -19,6 +18,7 @@ import de.michelinside.glucodatahandler.common.utils.Utils
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.PackageUtils
enum class WidgetType(val cls: Class<*>) {
@@ -185,8 +185,8 @@ abstract class GlucoseBaseWidget(private val type: WidgetType,
if (!hasTrend || !shortWidget) {
// short widget with trend, using the glucose+trend image
- remoteViews.setTextViewText(R.id.glucose, ReceiveData.getClucoseAsString())
- remoteViews.setTextColor(R.id.glucose, ReceiveData.getClucoseColor())
+ remoteViews.setTextViewText(R.id.glucose, ReceiveData.getGlucoseAsString())
+ remoteViews.setTextColor(R.id.glucose, ReceiveData.getGlucoseColor())
if (ReceiveData.isObsolete(Constants.VALUE_OBSOLETE_SHORT_SEC) && !ReceiveData.isObsolete()) {
remoteViews.setInt(R.id.glucose, "setPaintFlags", Paint.STRIKE_THRU_TEXT_FLAG)
} else {
@@ -198,7 +198,10 @@ abstract class GlucoseBaseWidget(private val type: WidgetType,
if (shortWidget)
remoteViews.setImageViewBitmap(R.id.glucose_trend, BitmapUtils.getGlucoseTrendBitmap(width = width, height = width))
else
- remoteViews.setImageViewBitmap(R.id.trendImage, BitmapUtils.getRateAsBitmap(roundTarget = false, width = size, height = size))
+ remoteViews.setImageViewBitmap(R.id.trendImage, BitmapUtils.getRateAsBitmap(
+ width = size,
+ height = size
+ ))
}
if (hasTime) {
@@ -242,12 +245,8 @@ abstract class GlucoseBaseWidget(private val type: WidgetType,
val height = if (isPortrait) maxHeight else minHeight
val remoteViews = getRemoteViews(context, width, height)
- /*if (BuildConfig.DEBUG) {
- // for debug create dummy broadcast (to check in emulator)
- val pendingIntent = PendingIntent.getBroadcast(context, 5, Utils.getDummyGlucodataIntent(false), PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
- remoteViews.setOnClickPendingIntent(R.id.widget, pendingIntent)
- } else*/
- remoteViews.setOnClickPendingIntent(R.id.widget, Utils.getAppIntent(context, MainActivity::class.java, 5, true))
+ val sharedPref = context.getSharedPreferences(Constants.SHARED_PREF_TAG, Context.MODE_PRIVATE)
+ remoteViews.setOnClickPendingIntent(R.id.widget, PackageUtils.getTapActionIntent(context, sharedPref.getString(Constants.SHARED_PREF_WIDGET_TAP_ACTION, ""), appWidgetId))
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
diff --git a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt
index 1031494d..a6a3e84b 100644
--- a/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt
+++ b/mobile/src/main/java/de/michelinside/glucodatahandler/widget/LockScreenWallpaper.kt
@@ -15,6 +15,8 @@ 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 kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
import kotlin.math.max
@@ -82,16 +84,17 @@ object LockScreenWallpaper : NotifierInterface, SharedPreferences.OnSharedPrefer
}
private fun setWallpaper(bitmap: Bitmap?, context: Context) {
- try {
- Log.v(LOG_ID, "updateLockScreen called for bitmap $bitmap")
- val wallpaperManager = WallpaperManager.getInstance(context)
- val wallpaper = if(bitmap != null) createWallpaper(bitmap, context) else null
- wallpaperManager.setBitmap(wallpaper, null, false, WallpaperManager.FLAG_LOCK)
- //wallpaper?.recycle()
- } catch (exc: Exception) {
- Log.e(LOG_ID, "updateLockScreen exception: " + exc.message.toString() )
+ GlobalScope.launch {
+ try {
+ Log.v(LOG_ID, "updateLockScreen called for bitmap $bitmap")
+ val wallpaperManager = WallpaperManager.getInstance(context)
+ val wallpaper = if (bitmap != null) createWallpaper(bitmap, context) else null
+ wallpaperManager.setBitmap(wallpaper, null, false, WallpaperManager.FLAG_LOCK)
+ //wallpaper?.recycle()
+ } catch (exc: Exception) {
+ Log.e(LOG_ID, "updateLockScreen exception: " + exc.message.toString())
+ }
}
-
}
private fun createWallpaper(bitmap: Bitmap, context: Context): Bitmap? {
diff --git a/mobile/src/main/res/layout-land/activity_lockscreen.xml b/mobile/src/main/res/layout-land/activity_lockscreen.xml
new file mode 100644
index 00000000..d578a071
--- /dev/null
+++ b/mobile/src/main/res/layout-land/activity_lockscreen.xml
@@ -0,0 +1,241 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/src/main/res/layout-land/activity_main.xml b/mobile/src/main/res/layout-land/activity_main.xml
new file mode 100644
index 00000000..02c71a52
--- /dev/null
+++ b/mobile/src/main/res/layout-land/activity_main.xml
@@ -0,0 +1,306 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/src/main/res/layout/activity_lockscreen.xml b/mobile/src/main/res/layout/activity_lockscreen.xml
new file mode 100644
index 00000000..de773e82
--- /dev/null
+++ b/mobile/src/main/res/layout/activity_lockscreen.xml
@@ -0,0 +1,254 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/src/main/res/layout/activity_main.xml b/mobile/src/main/res/layout/activity_main.xml
index e64a4490..f5b0140b 100644
--- a/mobile/src/main/res/layout/activity_main.xml
+++ b/mobile/src/main/res/layout/activity_main.xml
@@ -1,7 +1,6 @@
+
+
+
+
+
+
+
+
+
+ android:textSize="20sp" />
-
+
-
+
+
+
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile/src/main/res/layout/alarm_notification.xml b/mobile/src/main/res/layout/alarm_notification.xml
new file mode 100644
index 00000000..264ecc3a
--- /dev/null
+++ b/mobile/src/main/res/layout/alarm_notification.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mobile/src/main/res/layout/fragment_select_receiver.xml b/mobile/src/main/res/layout/fragment_select_receiver.xml
index 652cfe32..11a67ba8 100644
--- a/mobile/src/main/res/layout/fragment_select_receiver.xml
+++ b/mobile/src/main/res/layout/fragment_select_receiver.xml
@@ -5,15 +5,25 @@
android:layout_height="wrap_content"
android:paddingHorizontal="30dp">
+
+ app:layout_constraintTop_toBottomOf="@+id/txtTapActionSummary" />
+
+
+
\ No newline at end of file
diff --git a/mobile/src/main/res/layout/notification.xml b/mobile/src/main/res/layout/notification_layout.xml
similarity index 97%
rename from mobile/src/main/res/layout/notification.xml
rename to mobile/src/main/res/layout/notification_layout.xml
index 123b3c71..84225714 100644
--- a/mobile/src/main/res/layout/notification.xml
+++ b/mobile/src/main/res/layout/notification_layout.xml
@@ -14,6 +14,8 @@
android:text="No data!"
android:textSize="30dp"
android:textColor="@color/text_color"
+ android:shadowColor="@color/black"
+ android:shadowRadius="3"
tools:ignore="HardcodedText,SpUsage" />
-