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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +