From 6e9ccc593f40b0908fa2293eee5caabfe89f2c06 Mon Sep 17 00:00:00 2001 From: Dominik Dzienia Date: Tue, 24 Mar 2020 22:22:23 +0100 Subject: [PATCH 01/39] Support for master password and storing password as hashes (HMAC) instead of plaintext, additional crypto utils with tests (partialy for in-progress pref export encryption), changed PasswordCheck to more common UI and to use lambdas (will need given password in lambda callback for prefs enc) --- .../activities/MyPreferenceFragment.kt | 45 +++++- .../nightscout/androidaps/utils/CryptoUtil.kt | 142 ++++++++++++++++++ .../utils/protection/PasswordCheck.kt | 60 +++++++- .../utils/protection/ProtectionCheck.kt | 12 +- app/src/main/res/drawable/ic_key.xml | 9 ++ app/src/main/res/drawable/ic_key_48dp.xml | 17 +++ app/src/main/res/layout/passwordprompt.xml | 10 +- app/src/main/res/values/protection.xml | 13 +- app/src/main/res/xml/pref_general.xml | 12 +- .../androidaps/utils/CryptoUtilTest.kt | 76 ++++++++++ 10 files changed, 369 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/info/nightscout/androidaps/utils/CryptoUtil.kt create mode 100644 app/src/main/res/drawable/ic_key.xml create mode 100644 app/src/main/res/drawable/ic_key_48dp.xml create mode 100644 app/src/test/java/info/nightscout/androidaps/utils/CryptoUtilTest.kt diff --git a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt index 86bee1bca2..122ec8d5f0 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt @@ -50,8 +50,10 @@ import info.nightscout.androidaps.plugins.source.EversensePlugin import info.nightscout.androidaps.plugins.source.GlimpPlugin import info.nightscout.androidaps.plugins.source.PoctechPlugin import info.nightscout.androidaps.plugins.source.TomatoPlugin +import info.nightscout.androidaps.utils.CryptoUtil import info.nightscout.androidaps.utils.OKDialog.show import info.nightscout.androidaps.utils.SafeParse +import info.nightscout.androidaps.utils.protection.PasswordCheck import info.nightscout.androidaps.utils.protection.ProtectionCheck import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.sharedPreferences.SP @@ -98,6 +100,8 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang @Inject lateinit var wearPlugin: WearPlugin @Inject lateinit var maintenancePlugin: MaintenancePlugin + @Inject lateinit var passwordCheck: PasswordCheck + @Inject lateinit var androidInjector: DispatchingAndroidInjector override fun androidInjector(): AndroidInjector = androidInjector @@ -254,19 +258,19 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang // Preferences if (pref.getKey() == resourceHelper.gs(R.string.key_settings_protection)) { val pass: Preference? = findPreference(resourceHelper.gs(R.string.key_settings_password)) - if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.PASSWORD.ordinal.toString() + if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString() } // Application // Application if (pref.getKey() == resourceHelper.gs(R.string.key_application_protection)) { val pass: Preference? = findPreference(resourceHelper.gs(R.string.key_application_password)) - if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.PASSWORD.ordinal.toString() + if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString() } // Bolus // Bolus if (pref.getKey() == resourceHelper.gs(R.string.key_bolus_protection)) { val pass: Preference? = findPreference(resourceHelper.gs(R.string.key_bolus_password)) - if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.PASSWORD.ordinal.toString() + if (pass != null) pass.isEnabled = pref.value == ProtectionCheck.ProtectionType.CUSTOM_PASSWORD.ordinal.toString() } } if (pref is EditTextPreference) { @@ -281,6 +285,16 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang } } } + + if (pref is Preference) { + if ((pref.getKey() != null) && (pref.getKey().contains("_password"))) { + if (sp.getString(pref.getKey(), "").startsWith("hmac:")) { + pref.setSummary("******") + } else { + pref.setSummary(resourceHelper.gs(R.string.password_not_set)) + } + } + } pref?.let { adjustUnitDependentPrefs(it) } } @@ -294,4 +308,29 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang updatePrefSummary(p) } } + + // We use Preference and custom editor instead of EditTextPreference + // to hash password while it is saved and never have to show it, even hashed + + override fun onPreferenceTreeClick(preference: Preference?): Boolean { + if (preference != null) { + if (preference.key == resourceHelper.gs(R.string.key_master_password)) { + passwordCheck.setPassword(this.context!!, R.string.master_password, R.string.key_master_password) + return true; + } + if (preference.key == resourceHelper.gs(R.string.key_settings_password)) { + passwordCheck.setPassword(this.context!!, R.string.settings_password, R.string.key_settings_password) + return true; + } + if (preference.key == resourceHelper.gs(R.string.key_bolus_password)) { + passwordCheck.setPassword(this.context!!, R.string.bolus_password, R.string.key_bolus_password) + return true; + } + if (preference.key == resourceHelper.gs(R.string.key_application_password)) { + passwordCheck.setPassword(this.context!!, R.string.application_password, R.string.key_application_password) + return true; + } + } + return super.onPreferenceTreeClick(preference) + } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/CryptoUtil.kt b/app/src/main/java/info/nightscout/androidaps/utils/CryptoUtil.kt new file mode 100644 index 0000000000..745f4449ee --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/CryptoUtil.kt @@ -0,0 +1,142 @@ +package info.nightscout.androidaps.utils + +import org.spongycastle.util.encoders.Base64 +import java.nio.ByteBuffer +import java.security.MessageDigest +import java.security.SecureRandom +import java.security.spec.KeySpec +import javax.crypto.Cipher +import javax.crypto.Mac +import javax.crypto.SecretKey +import javax.crypto.SecretKeyFactory +import javax.crypto.spec.GCMParameterSpec +import javax.crypto.spec.PBEKeySpec +import javax.crypto.spec.SecretKeySpec + +private val HEX_CHARS = "0123456789abcdef" +private val HEX_CHARS_ARRAY = "0123456789abcdef".toCharArray() + +fun String.hexStringToByteArray() : ByteArray { + + val upperCased = this.toLowerCase() + val result = ByteArray(length / 2) + for (i in 0 until length step 2) { + val firstIndex = HEX_CHARS.indexOf(upperCased[i]); + val secondIndex = HEX_CHARS.indexOf(upperCased[i + 1]); + + val octet = firstIndex.shl(4).or(secondIndex) + result.set(i.shr(1), octet.toByte()) + } + + return result +} + +fun ByteArray.toHex() : String{ + val result = StringBuffer() + + forEach { + val octet = it.toInt() + val firstIndex = (octet and 0xF0).ushr(4) + val secondIndex = octet and 0x0F + result.append(HEX_CHARS_ARRAY[firstIndex]) + result.append(HEX_CHARS_ARRAY[secondIndex]) + } + + return result.toString() +} + +object CryptoUtil { + + private const val IV_LENGTH_BYTE = 12 + private const val TAG_LENGTH_BIT = 128 + private const val AES_KEY_SIZE_BIT = 256 + private const val PBKDF2_ITERATIONS = 50000 // check delays it cause on real device + private const val SALT_SIZE_BYTE = 32 + + private val secureRandom: SecureRandom = SecureRandom() + + fun sha256(source: String): String { + val digest = MessageDigest.getInstance("SHA-256") + val hashRaw = digest.digest(source.toByteArray()) + return hashRaw.toHex() + } + + fun hmac256(str: String, secret: String): String? { + val sha256_HMAC = Mac.getInstance("HmacSHA256") + val secretKey = SecretKeySpec(secret.toByteArray(), "HmacSHA256") + sha256_HMAC.init(secretKey) + return sha256_HMAC.doFinal(str.toByteArray()).toHex() + } + + private fun prepCipherKey(passPhrase: String, salt:ByteArray, iterationCount:Int = PBKDF2_ITERATIONS, keyStrength:Int = AES_KEY_SIZE_BIT): SecretKeySpec { + val factory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1") + val spec: KeySpec = PBEKeySpec(passPhrase.toCharArray(), salt, iterationCount, keyStrength) + val tmp: SecretKey = factory.generateSecret(spec) + return SecretKeySpec(tmp.getEncoded(), "AES") + } + + fun mineSalt(len :Int = SALT_SIZE_BYTE): ByteArray { + val salt = ByteArray(len) + secureRandom.nextBytes(salt) + return salt + } + + fun encrypt(passPhrase: String, salt:ByteArray, rawData: String ): String? { + val iv: ByteArray? + val encrypted: ByteArray? + return try { + iv = ByteArray(IV_LENGTH_BYTE) + secureRandom.nextBytes(iv) + val cipherEnc: Cipher = Cipher.getInstance("AES/GCM/NoPadding") + cipherEnc.init(Cipher.ENCRYPT_MODE, prepCipherKey(passPhrase, salt), GCMParameterSpec(TAG_LENGTH_BIT, iv)) + encrypted = cipherEnc.doFinal(rawData.toByteArray()) + val byteBuffer: ByteBuffer = ByteBuffer.allocate(1 + iv.size + encrypted.size) + byteBuffer.put(iv.size.toByte()) + byteBuffer.put(iv) + byteBuffer.put(encrypted) + String(Base64.encode(byteBuffer.array())) + } catch (e: Exception) { + null + } + } + + fun decrypt(passPhrase: String, salt:ByteArray, encryptedData: String): String? { + val iv: ByteArray? + val encrypted: ByteArray? + return try { + val byteBuffer = ByteBuffer.wrap(Base64.decode(encryptedData)) + val ivLength = byteBuffer.get().toInt() + iv = ByteArray(ivLength) + byteBuffer[iv] + encrypted = ByteArray(byteBuffer.remaining()) + byteBuffer[encrypted] + val cipherDec: Cipher = Cipher.getInstance("AES/GCM/NoPadding") + cipherDec.init(Cipher.DECRYPT_MODE, prepCipherKey(passPhrase, salt), GCMParameterSpec(TAG_LENGTH_BIT, iv)) + val dec = cipherDec.doFinal(encrypted) + String(dec) + } catch (e: Exception) { + null + } + } + + fun checkPassword(password: String, referenceHash: String): Boolean { + return if (referenceHash.startsWith("hmac:")) { + val hashSegments = referenceHash.split(":") + if (hashSegments.size != 3) + return false + return hmac256(password, hashSegments[1]) == hashSegments[2] + } else { + password == referenceHash + } + } + + fun hashPassword(password: String): String { + return if (!password.startsWith("hmac:")) { + val salt = mineSalt().toHex() + return "hmac:${salt}:${hmac256(password, salt)}" + } else { + password + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt b/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt index 6a2e18c732..4d1adfb233 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt @@ -2,13 +2,17 @@ package info.nightscout.androidaps.utils.protection import android.annotation.SuppressLint import android.app.AlertDialog +import android.content.Context import android.view.LayoutInflater import android.view.View import android.widget.EditText +import android.widget.ImageView import android.widget.TextView import androidx.annotation.StringRes +import androidx.appcompat.view.ContextThemeWrapper import androidx.fragment.app.FragmentActivity import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.CryptoUtil import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.sharedPreferences.SP import javax.inject.Inject @@ -18,34 +22,74 @@ import javax.inject.Singleton class PasswordCheck @Inject constructor(val sp: SP) { @SuppressLint("InflateParams") - fun queryPassword(activity: FragmentActivity, @StringRes labelId: Int, @StringRes preference: Int, ok: Runnable?, cancel: Runnable? = null, fail: Runnable? = null) { + fun queryPassword(activity: FragmentActivity, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)?, cancel: (()->Unit)? = null, fail: (()->Unit)? = null) { val password = sp.getString(preference, "") if (password == "") { - ok?.run() + ok?.invoke("") return } + + val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null) + (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = activity.getString(labelId) + (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_key_48dp) + val promptsView = LayoutInflater.from(activity).inflate(R.layout.passwordprompt, null) - val alertDialogBuilder = AlertDialog.Builder(activity) + val alertDialogBuilder = AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme)) alertDialogBuilder.setView(promptsView) - val label = promptsView.findViewById(R.id.passwordprompt_text) as TextView - label.text = activity.getString(labelId) val userInput = promptsView.findViewById(R.id.passwordprompt_pass) as EditText alertDialogBuilder .setCancelable(false) + .setCustomTitle(titleLayout) .setPositiveButton(activity.getString(R.string.ok)) { _, _ -> val enteredPassword = userInput.text.toString() - if (password == enteredPassword) ok?.run() + if (CryptoUtil.checkPassword(enteredPassword, password)) ok?.invoke(enteredPassword) else { ToastUtils.showToastInUiThread(activity, activity.getString(R.string.wrongpassword)) - fail?.run() + fail?.invoke() } } .setNegativeButton(activity.getString(R.string.cancel) ) { dialog, _ -> - cancel?.run() + cancel?.invoke() + dialog.cancel() + } + + alertDialogBuilder.create().show() + } + + @SuppressLint("InflateParams") + fun setPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int) { + val promptsView = LayoutInflater.from(context).inflate(R.layout.passwordprompt, null) + + val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) + (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = context.getText(labelId) + (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_key_48dp) + + val alertDialogBuilder = AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme)) + alertDialogBuilder.setView(promptsView) + + val userInput = promptsView.findViewById(R.id.passwordprompt_pass) as EditText + + alertDialogBuilder + .setCancelable(false) + .setCustomTitle(titleLayout) + .setPositiveButton(context.getString(R.string.ok)) { _, _ -> + val enteredPassword = userInput.text.toString() + if (enteredPassword.isNotEmpty()) { + sp.putString(preference, CryptoUtil.hashPassword(enteredPassword)) + } else { + if (sp.contains(preference)) { + sp.remove(preference) + } + } + ToastUtils.showToastInUiThread(context, context.getString(R.string.password_set)) + } + .setNegativeButton(context.getString(R.string.cancel) + ) { dialog, _ -> + ToastUtils.showToastInUiThread(context, context.getString(R.string.password_not_changed)) dialog.cancel() } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/protection/ProtectionCheck.kt b/app/src/main/java/info/nightscout/androidaps/utils/protection/ProtectionCheck.kt index c4960f4999..a1cca9116e 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/protection/ProtectionCheck.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/protection/ProtectionCheck.kt @@ -21,7 +21,8 @@ class ProtectionCheck @Inject constructor( enum class ProtectionType { NONE, BIOMETRIC, - PASSWORD + MASTER_PASSWORD, + CUSTOM_PASSWORD } private val passwordsResourceIDs = listOf( @@ -43,7 +44,8 @@ class ProtectionCheck @Inject constructor( return when (ProtectionType.values()[sp.getInt(protectionTypeResourceIDs[protection.ordinal], ProtectionType.NONE.ordinal)]) { ProtectionType.NONE -> false ProtectionType.BIOMETRIC -> true - ProtectionType.PASSWORD -> sp.getString(passwordsResourceIDs[protection.ordinal], "") != "" + ProtectionType.MASTER_PASSWORD -> sp.getString(R.string.key_master_password, "") != "" + ProtectionType.CUSTOM_PASSWORD -> sp.getString(passwordsResourceIDs[protection.ordinal], "") != "" } } @@ -55,8 +57,10 @@ class ProtectionCheck @Inject constructor( ok?.run() ProtectionType.BIOMETRIC -> BiometricCheck.biometricPrompt(activity, titleResourceIDs[protection.ordinal], ok, cancel, fail) - ProtectionType.PASSWORD -> - passwordCheck.queryPassword(activity, titleResourceIDs[protection.ordinal], passwordsResourceIDs[protection.ordinal], ok, cancel, fail) + ProtectionType.MASTER_PASSWORD -> + passwordCheck.queryPassword(activity, R.string.master_password, R.string.key_master_password, { ok?.run() }, { cancel?.run() }, { fail?.run() }) + ProtectionType.CUSTOM_PASSWORD -> + passwordCheck.queryPassword(activity, titleResourceIDs[protection.ordinal], passwordsResourceIDs[protection.ordinal], { ok?.run() }, { cancel?.run() }, { fail?.run() }) } } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_key.xml b/app/src/main/res/drawable/ic_key.xml new file mode 100644 index 0000000000..4d4a6fa28d --- /dev/null +++ b/app/src/main/res/drawable/ic_key.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_key_48dp.xml b/app/src/main/res/drawable/ic_key_48dp.xml new file mode 100644 index 0000000000..813dc24d00 --- /dev/null +++ b/app/src/main/res/drawable/ic_key_48dp.xml @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/passwordprompt.xml b/app/src/main/res/layout/passwordprompt.xml index 63ff30e531..ed2298e4d8 100644 --- a/app/src/main/res/layout/passwordprompt.xml +++ b/app/src/main/res/layout/passwordprompt.xml @@ -5,17 +5,13 @@ android:orientation="vertical" android:padding="10dp" > - - + android:hint="@string/password_hint" + android:inputType="textPassword" + > diff --git a/app/src/main/res/values/protection.xml b/app/src/main/res/values/protection.xml index 6bf675ab44..8b025f68cb 100644 --- a/app/src/main/res/values/protection.xml +++ b/app/src/main/res/values/protection.xml @@ -5,15 +5,22 @@ Settings protection Application protection Bolus protection + Master password Settings password Application password Bolus password Unlock settings Biometric - Password + Custom password No protection Protection + Password set! + Password not set + Password not changed + Enter password here + + master_password settings_password application_password translatable="false"bolus_password @@ -24,13 +31,15 @@ @string/noprotection @string/biometric - @string/password + @string/master_password + @string/custom_password 0 1 2 + 3 diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml index 6daf95f3f8..b5f7074c7e 100644 --- a/app/src/main/res/xml/pref_general.xml +++ b/app/src/main/res/xml/pref_general.xml @@ -22,6 +22,12 @@ + + - @@ -41,7 +47,7 @@ android:key="@string/key_application_protection" android:title="@string/application_protection" /> - @@ -53,7 +59,7 @@ android:key="@string/key_bolus_protection" android:title="@string/bolus_protection" /> - diff --git a/app/src/test/java/info/nightscout/androidaps/utils/CryptoUtilTest.kt b/app/src/test/java/info/nightscout/androidaps/utils/CryptoUtilTest.kt new file mode 100644 index 0000000000..c147c0810c --- /dev/null +++ b/app/src/test/java/info/nightscout/androidaps/utils/CryptoUtilTest.kt @@ -0,0 +1,76 @@ +package info.nightscout.androidaps.utils + +import info.nightscout.androidaps.TestBase +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith +import org.powermock.core.classloader.annotations.PowerMockIgnore +import org.powermock.modules.junit4.PowerMockRunner + +@PowerMockIgnore("javax.crypto.*") +@RunWith(PowerMockRunner::class) +class CryptoUtilTest: TestBase() { + + @Test + fun testFixedSaltCrypto() { + val salt = byteArrayOf( + -33, -29, 16, -19, 99, -111, -3, 2, 116, 106, 47, 38, -54, 11, -77, 28, + 111, -15, -65, -110, 4, -32, -29, -70, -95, -88, -53, 19, 87, -103, 123, -15) + + val password = "thisIsFixedPassword" + val payload = "FIXED-PAYLOAD" + val encrypted = CryptoUtil.encrypt(password, salt, payload) + + Assert.assertNotNull(encrypted) + val decrypted = CryptoUtil.decrypt(password, salt, encrypted!!) + Assert.assertEquals(decrypted, payload) + } + + @Test + fun testStandardCrypto() { + val salt = CryptoUtil.mineSalt() + + val password = "topSikret" + val payload = "{what:payloadYouWantToProtect}" + val encrypted = CryptoUtil.encrypt(password, salt, payload) + + Assert.assertNotNull(encrypted) + val decrypted = CryptoUtil.decrypt(password, salt, encrypted!!) + Assert.assertEquals(decrypted, payload) + } + + @Test + fun testHashVector() { + val payload = "{what:payloadYouWantToProtect}" + val hash = CryptoUtil.sha256(payload) + Assert.assertEquals(hash, "a1aafe3ed6cc127e6d102ddbc40a205147230e9cfd178daf108c83543bbdcd13") + } + + @Test + fun testHmac() { + val payload = "{what:payloadYouWantToProtect}" + val password = "topSikret" + val expectedHmac = "ea2213953d0f2e55047cae2d23fb4f0de1b805d55e6271efa70d6b85fb692bea" // generated using other HMAC tool + val hash = CryptoUtil.hmac256(payload, password) + Assert.assertEquals(hash, expectedHmac) + } + + @Test + fun testPlainPasswordCheck() { + Assert.assertTrue(CryptoUtil.checkPassword("same", "same")) + Assert.assertFalse(CryptoUtil.checkPassword("same", "other")) + } + + @Test + fun testHashedPasswordCheck() { + Assert.assertTrue(CryptoUtil.checkPassword("givenSecret", CryptoUtil.hashPassword("givenSecret"))) + Assert.assertFalse(CryptoUtil.checkPassword("givenSecret", CryptoUtil.hashPassword("otherSecret"))) + + Assert.assertTrue(CryptoUtil.checkPassword("givenHashToCheck", "hmac:7fe5f9c7b4b97c5d32d5cfad9d07473543a9938dc07af48a46dbbb49f4f68c12:a0c7cee14312bbe31b51359a67f0d2dfdf46813f319180269796f1f617a64be1")) + Assert.assertFalse(CryptoUtil.checkPassword("givenMashToCheck", "hmac:7fe5f9c7b4b97c5d32d5cfad9d07473543a9938dc07af48a46dbbb49f4f68c12:a0c7cee14312bbe31b51359a67f0d2dfdf46813f319180269796f1f617a64be1")) + Assert.assertFalse(CryptoUtil.checkPassword("givenHashToCheck", "hmac:0fe5f9c7b4b97c5d32d5cfad9d07473543a9938dc07af48a46dbbb49f4f68c12:a0c7cee14312bbe31b51359a67f0d2dfdf46813f319180269796f1f617a64be1")) + Assert.assertFalse(CryptoUtil.checkPassword("givenHashToCheck", "hmac:7fe5f9c7b4b97c5d32d5cfad9d07473543a9938dc07af48a46dbbb49f4f68c12:b0c7cee14312bbe31b51359a67f0d2dfdf46813f319180269796f1f617a64be1")) + } + +} + From 39c08a5a2b68de4c184e537d467f0d1c18f21be9 Mon Sep 17 00:00:00 2001 From: Dominik Dzienia Date: Wed, 25 Mar 2020 14:37:23 +0100 Subject: [PATCH 02/39] Configured Autofill hint for password managers Added missing callback on password prompt dialog (will be usable in near future) --- .../utils/protection/PasswordCheck.kt | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt b/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt index 4d1adfb233..02afbea43e 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt @@ -3,6 +3,7 @@ package info.nightscout.androidaps.utils.protection import android.annotation.SuppressLint import android.app.AlertDialog import android.content.Context +import android.os.Build import android.view.LayoutInflater import android.view.View import android.widget.EditText @@ -18,6 +19,9 @@ import info.nightscout.androidaps.utils.sharedPreferences.SP import javax.inject.Inject import javax.inject.Singleton +// since androidx.autofill.HintConstants are not available +val AUTOFILL_HINT_NEW_PASSWORD = "newPassword" + @Singleton class PasswordCheck @Inject constructor(val sp: SP) { @@ -39,6 +43,11 @@ class PasswordCheck @Inject constructor(val sp: SP) { alertDialogBuilder.setView(promptsView) val userInput = promptsView.findViewById(R.id.passwordprompt_pass) as EditText + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val autoFillHintPasswordKind = activity.getString(preference) + userInput.setAutofillHints(View.AUTOFILL_HINT_PASSWORD, "aaps_${autoFillHintPasswordKind}") + userInput.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_YES + } alertDialogBuilder .setCancelable(false) @@ -61,7 +70,7 @@ class PasswordCheck @Inject constructor(val sp: SP) { } @SuppressLint("InflateParams") - fun setPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int) { + fun setPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)? = null, cancel: (()->Unit)? = null, clear: (()->Unit)? = null) { val promptsView = LayoutInflater.from(context).inflate(R.layout.passwordprompt, null) val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) @@ -73,6 +82,12 @@ class PasswordCheck @Inject constructor(val sp: SP) { val userInput = promptsView.findViewById(R.id.passwordprompt_pass) as EditText + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val autoFillHintPasswordKind = context.getString(preference) + userInput.setAutofillHints(AUTOFILL_HINT_NEW_PASSWORD, "aaps_${autoFillHintPasswordKind}") + userInput.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_YES + } + alertDialogBuilder .setCancelable(false) .setCustomTitle(titleLayout) @@ -80,9 +95,13 @@ class PasswordCheck @Inject constructor(val sp: SP) { val enteredPassword = userInput.text.toString() if (enteredPassword.isNotEmpty()) { sp.putString(preference, CryptoUtil.hashPassword(enteredPassword)) + ok?.invoke(enteredPassword) } else { if (sp.contains(preference)) { sp.remove(preference) + clear?.invoke() + } else { + cancel?.invoke() } } ToastUtils.showToastInUiThread(context, context.getString(R.string.password_set)) @@ -90,6 +109,7 @@ class PasswordCheck @Inject constructor(val sp: SP) { .setNegativeButton(context.getString(R.string.cancel) ) { dialog, _ -> ToastUtils.showToastInUiThread(context, context.getString(R.string.password_not_changed)) + cancel?.invoke() dialog.cancel() } From c58c62d4bcdc3ff87713b0c33c4f79e4a4a47317 Mon Sep 17 00:00:00 2001 From: Dominik Dzienia Date: Wed, 25 Mar 2020 14:47:28 +0100 Subject: [PATCH 03/39] Tost shown when password is set now show when password is not set at end (cancel) or unset --- .../nightscout/androidaps/utils/protection/PasswordCheck.kt | 5 ++++- app/src/main/res/values/protection.xml | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt b/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt index 02afbea43e..65f1e76e44 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt @@ -95,16 +95,19 @@ class PasswordCheck @Inject constructor(val sp: SP) { val enteredPassword = userInput.text.toString() if (enteredPassword.isNotEmpty()) { sp.putString(preference, CryptoUtil.hashPassword(enteredPassword)) + ToastUtils.showToastInUiThread(context, context.getString(R.string.password_set)) ok?.invoke(enteredPassword) } else { if (sp.contains(preference)) { sp.remove(preference) + ToastUtils.showToastInUiThread(context, context.getString(R.string.password_cleared)) clear?.invoke() } else { + ToastUtils.showToastInUiThread(context, context.getString(R.string.password_not_changed)) cancel?.invoke() } } - ToastUtils.showToastInUiThread(context, context.getString(R.string.password_set)) + } .setNegativeButton(context.getString(R.string.cancel) ) { dialog, _ -> diff --git a/app/src/main/res/values/protection.xml b/app/src/main/res/values/protection.xml index 8b025f68cb..b044b9628e 100644 --- a/app/src/main/res/values/protection.xml +++ b/app/src/main/res/values/protection.xml @@ -18,6 +18,7 @@ Password set! Password not set Password not changed + Password cleared! Enter password here master_password From 3617885819e9ffc1321c8d92fa4ee985b2ef065b Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Thu, 26 Mar 2020 22:43:49 +0100 Subject: [PATCH 04/39] Fix WearFragment --- .../androidaps/dependencyInjection/FragmentsModule.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt index dbd9cdf424..63ed16fb4c 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt @@ -28,6 +28,7 @@ import info.nightscout.androidaps.plugins.general.overview.OverviewFragment import info.nightscout.androidaps.plugins.general.overview.dialogs.EditQuickWizardDialog import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorFragment import info.nightscout.androidaps.plugins.general.tidepool.TidepoolFragment +import info.nightscout.androidaps.plugins.general.wear.WearFragment import info.nightscout.androidaps.plugins.insulin.InsulinFragment import info.nightscout.androidaps.plugins.profile.local.LocalProfileFragment import info.nightscout.androidaps.plugins.profile.ns.NSProfileFragment @@ -71,6 +72,7 @@ abstract class FragmentsModule { @ContributesAndroidInjector abstract fun contributesNSProfileFragment(): NSProfileFragment @ContributesAndroidInjector abstract fun contributesNSClientFragment(): NSClientFragment @ContributesAndroidInjector abstract fun contributesSmsCommunicatorFragment(): SmsCommunicatorFragment + @ContributesAndroidInjector abstract fun contributesWearFragment(): WearFragment @ContributesAndroidInjector abstract fun contributesTidepoolFragment(): TidepoolFragment @ContributesAndroidInjector abstract fun contributesTreatmentsFragment(): TreatmentsFragment From 7e61c5bc61ac0f51f3057aea02cd32e096e2a28a Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Fri, 27 Mar 2020 20:29:36 +0100 Subject: [PATCH 05/39] New Crowdin translations (#2530) * New translations objectives.xml (French) * New translations objectives.xml (Swedish) * New translations strings.xml (Swedish) * New translations strings.xml (Czech) --- app/src/main/res/values-cs-rCZ/strings.xml | 2 +- app/src/main/res/values-fr-rFR/objectives.xml | 1 + app/src/main/res/values-sv-rSE/objectives.xml | 1 + app/src/main/res/values-sv-rSE/strings.xml | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index b89be5648a..d879db8f49 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -280,7 +280,7 @@ DanaR profil DIA [h] Celková doba aktivity inzulínu - Chyba při nastavení dočasného bazálu + Chyba při nastavení bazálního pprofilu Načíst Nahrávám E bolus diff --git a/app/src/main/res/values-fr-rFR/objectives.xml b/app/src/main/res/values-fr-rFR/objectives.xml index 37d434b2d3..58f83d4a9c 100644 --- a/app/src/main/res/values-fr-rFR/objectives.xml +++ b/app/src/main/res/values-fr-rFR/objectives.xml @@ -35,6 +35,7 @@ Affichage du contenu du plugin Boucle Modification de l\'échelle du graphique par un appui long sur la courbe de glycémie Entrer + Si vous avez au moins 3 mois d\'expérience de boucle fermée avec d\'autres systèmes, vous pourriez avoir droit à un code permettant d\'ignorer les objectifs. Voir https://androidaps.readthedocs.io/en/latest/CROWDIN/fr/Usage/Objectives.html#ignorer-les-objectifs pour plus de détails. Code accepté Code invalide Prouver ses connaissances diff --git a/app/src/main/res/values-sv-rSE/objectives.xml b/app/src/main/res/values-sv-rSE/objectives.xml index f9c5cd13ff..94b2ba5549 100644 --- a/app/src/main/res/values-sv-rSE/objectives.xml +++ b/app/src/main/res/values-sv-rSE/objectives.xml @@ -35,6 +35,7 @@ Visa innehållet i insticksprogrammet \"Loop\" Testa skala om BG-grafen genom att trycka och hålla in fingret på den Enter + Om du har minst 3 månaders erfarenhet av closed loop med andra system kan du kvalificera dig för en kod för att hoppa över mål. Se https://androidaps.readthedocs.io/en/latest/EN/Usage/Objectives.html#skip-objectives för mer info. Koden godkänd Koden är felaktig Bevisa dina kunskaper diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 7e1ec0c8ed..4fada83e89 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -1454,4 +1454,5 @@ Eversense-appen. SMB utförd Basalförändring begärd Basalförändring utförd + Pumpvarningar Insight From c4776492768d80daacb15fdbfa0a49adaa6c50fe Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Fri, 27 Mar 2020 21:32:47 +0100 Subject: [PATCH 06/39] Fix wear preferences, expand single preference by default --- .../activities/MyPreferenceFragment.kt | 54 ++++++++++--------- app/src/main/res/xml/pref_wear.xml | 9 ++-- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt index 122ec8d5f0..a7e15ea2fa 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/MyPreferenceFragment.kt @@ -50,7 +50,6 @@ import info.nightscout.androidaps.plugins.source.EversensePlugin import info.nightscout.androidaps.plugins.source.GlimpPlugin import info.nightscout.androidaps.plugins.source.PoctechPlugin import info.nightscout.androidaps.plugins.source.TomatoPlugin -import info.nightscout.androidaps.utils.CryptoUtil import info.nightscout.androidaps.utils.OKDialog.show import info.nightscout.androidaps.utils.SafeParse import info.nightscout.androidaps.utils.protection.PasswordCheck @@ -189,7 +188,7 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang addPreferencesFromResource(R.xml.pref_datachoices, rootKey) addPreferencesFromResourceIfEnabled(maintenancePlugin, rootKey) } - initSummary(preferenceScreen) + initSummary(preferenceScreen, pluginId != -1) for (plugin in pluginStore.plugins) { plugin.preprocessPreferences(this) } @@ -287,22 +286,27 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang } if (pref is Preference) { - if ((pref.getKey() != null) && (pref.getKey().contains("_password"))) { - if (sp.getString(pref.getKey(), "").startsWith("hmac:")) { - pref.setSummary("******") + if ((pref.key != null) && (pref.key.contains("_password"))) { + if (sp.getString(pref.key, "").startsWith("hmac:")) { + pref.summary = "******" } else { - pref.setSummary(resourceHelper.gs(R.string.password_not_set)) + pref.summary = resourceHelper.gs(R.string.password_not_set) } } } pref?.let { adjustUnitDependentPrefs(it) } } - private fun initSummary(p: Preference) { + private fun initSummary(p: Preference, isSinglePreference: Boolean) { p.isIconSpaceReserved = false // remove extra spacing on left after migration to androidx + // expand single plugin preference by default + if (p is PreferenceScreen && isSinglePreference) { + if (p.size > 0 && p.getPreference(0) is PreferenceCategory) + (p.getPreference(0) as PreferenceCategory).initialExpandedChildrenCount = Int.MAX_VALUE + } if (p is PreferenceGroup) { for (i in 0 until p.preferenceCount) { - initSummary(p.getPreference(i)) + initSummary(p.getPreference(i), isSinglePreference) } } else { updatePrefSummary(p) @@ -313,22 +317,24 @@ class MyPreferenceFragment : PreferenceFragmentCompat(), OnSharedPreferenceChang // to hash password while it is saved and never have to show it, even hashed override fun onPreferenceTreeClick(preference: Preference?): Boolean { - if (preference != null) { - if (preference.key == resourceHelper.gs(R.string.key_master_password)) { - passwordCheck.setPassword(this.context!!, R.string.master_password, R.string.key_master_password) - return true; - } - if (preference.key == resourceHelper.gs(R.string.key_settings_password)) { - passwordCheck.setPassword(this.context!!, R.string.settings_password, R.string.key_settings_password) - return true; - } - if (preference.key == resourceHelper.gs(R.string.key_bolus_password)) { - passwordCheck.setPassword(this.context!!, R.string.bolus_password, R.string.key_bolus_password) - return true; - } - if (preference.key == resourceHelper.gs(R.string.key_application_password)) { - passwordCheck.setPassword(this.context!!, R.string.application_password, R.string.key_application_password) - return true; + context?.let { context -> + if (preference != null) { + if (preference.key == resourceHelper.gs(R.string.key_master_password)) { + passwordCheck.setPassword(context, R.string.master_password, R.string.key_master_password) + return true + } + if (preference.key == resourceHelper.gs(R.string.key_settings_password)) { + passwordCheck.setPassword(context, R.string.settings_password, R.string.key_settings_password) + return true + } + if (preference.key == resourceHelper.gs(R.string.key_bolus_password)) { + passwordCheck.setPassword(context, R.string.bolus_password, R.string.key_bolus_password) + return true + } + if (preference.key == resourceHelper.gs(R.string.key_application_password)) { + passwordCheck.setPassword(context, R.string.application_password, R.string.key_application_password) + return true + } } } return super.onPreferenceTreeClick(preference) diff --git a/app/src/main/res/xml/pref_wear.xml b/app/src/main/res/xml/pref_wear.xml index a18d77061b..855ae4a9af 100644 --- a/app/src/main/res/xml/pref_wear.xml +++ b/app/src/main/res/xml/pref_wear.xml @@ -15,8 +15,7 @@ + android:title="@string/wear_wizard_settings"> + android:title="@string/wear_display_settings"> + android:title="@string/wear_general_settings"> Date: Fri, 27 Mar 2020 22:05:14 +0100 Subject: [PATCH 07/39] fix displaying overview buttons in pump suspend mode --- .../general/overview/OverviewFragment.java | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java index a192aa8526..651005a2df 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java @@ -1248,37 +1248,34 @@ public class OverviewFragment extends DaggerFragment implements View.OnClickList quickWizardButton.setVisibility(View.GONE); // **** Various treatment buttons **** - if (carbsButton != null) { - if (sp.getBoolean(R.string.key_show_carbs_button, true) - && (!activePlugin.getActivePump().getPumpDescription().storesCarbInfo || - (pump.isInitialized() && !pump.isSuspended()))) { + if ((!activePlugin.getActivePump().getPumpDescription().storesCarbInfo || + (pump.isInitialized() && !pump.isSuspended())) && carbsButton != null) { + if (sp.getBoolean(R.string.key_show_carbs_button, true)) { carbsButton.setVisibility(View.VISIBLE); } else { carbsButton.setVisibility(View.GONE); } } - if (pump.isInitialized() && !pump.isSuspended()) { - if (treatmentButton != null) { - if (sp.getBoolean(R.string.key_show_treatment_button, false)) { - treatmentButton.setVisibility(View.VISIBLE); - } else { - treatmentButton.setVisibility(View.GONE); - } + if (pump.isInitialized() && !pump.isSuspended() && treatmentButton != null) { + if (sp.getBoolean(R.string.key_show_treatment_button, false)) { + treatmentButton.setVisibility(View.VISIBLE); + } else { + treatmentButton.setVisibility(View.GONE); } - if (pump.isInitialized() && !pump.isSuspended() && wizardButton != null) { - if (sp.getBoolean(R.string.key_show_wizard_button, true)) { - wizardButton.setVisibility(View.VISIBLE); - } else { - wizardButton.setVisibility(View.GONE); - } + } + if (pump.isInitialized() && !pump.isSuspended() && wizardButton != null) { + if (sp.getBoolean(R.string.key_show_wizard_button, true)) { + wizardButton.setVisibility(View.VISIBLE); + } else { + wizardButton.setVisibility(View.GONE); } - if (pump.isInitialized() && !pump.isSuspended() && insulinButton != null) { - if (sp.getBoolean(R.string.key_show_insulin_button, true)) { - insulinButton.setVisibility(View.VISIBLE); - } else { - insulinButton.setVisibility(View.GONE); - } + } + if (pump.isInitialized() && !pump.isSuspended() && insulinButton != null) { + if (sp.getBoolean(R.string.key_show_insulin_button, true)) { + insulinButton.setVisibility(View.VISIBLE); + } else { + insulinButton.setVisibility(View.GONE); } } From a5a5f048ea052a85190c32b46f01d3349510f992 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Fri, 27 Mar 2020 22:30:08 +0100 Subject: [PATCH 08/39] move buttons visibility processing to extra function --- .../general/overview/OverviewFragment.java | 103 ++++++++++-------- 1 file changed, 58 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java index 651005a2df..eef9521d09 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java @@ -1008,6 +1008,63 @@ public class OverviewFragment extends DaggerFragment implements View.OnClickList } } + private void processInsulinCarbsButtonsVisibility() { + BgReading lastBG = iobCobCalculatorPlugin.lastBg(); + + final PumpInterface pump = activePlugin.getActivePump(); + + final Profile profile = profileFunction.getProfile(); + final String profileName = profileFunction.getProfileName(); + + // QuickWizard button + QuickWizardEntry quickWizardEntry = quickWizard.getActive(); + if (quickWizardEntry != null && lastBG != null && profile != null && pump.isInitialized() && !pump.isSuspended()) { + quickWizardButton.setVisibility(View.VISIBLE); + String text = quickWizardEntry.buttonText() + "\n" + DecimalFormatter.to0Decimal(quickWizardEntry.carbs()) + "g"; + BolusWizard wizard = quickWizardEntry.doCalc(profile, profileName, lastBG, false); + text += " " + DecimalFormatter.toPumpSupportedBolus(wizard.getCalculatedTotalInsulin(), pump) + "U"; + quickWizardButton.setText(text); + if (wizard.getCalculatedTotalInsulin() <= 0) + quickWizardButton.setVisibility(View.GONE); + } else + quickWizardButton.setVisibility(View.GONE); + + // **** Various treatment buttons **** + if (carbsButton != null) { + if ((!activePlugin.getActivePump().getPumpDescription().storesCarbInfo || (pump.isInitialized() && !pump.isSuspended())) && + profile != null && + sp.getBoolean(R.string.key_show_carbs_button, true)) + carbsButton.setVisibility(View.VISIBLE); + else + carbsButton.setVisibility(View.GONE); + } + + if (treatmentButton != null) { + if (pump.isInitialized() && !pump.isSuspended() && + profile != null && + sp.getBoolean(R.string.key_show_treatment_button, false)) + treatmentButton.setVisibility(View.VISIBLE); + else + treatmentButton.setVisibility(View.GONE); + } + if (wizardButton != null) { + if (pump.isInitialized() && !pump.isSuspended() && + profile != null && + sp.getBoolean(R.string.key_show_wizard_button, true)) + wizardButton.setVisibility(View.VISIBLE); + else + wizardButton.setVisibility(View.GONE); + } + if (insulinButton != null) { + if (pump.isInitialized() && !pump.isSuspended() && + profile != null && + sp.getBoolean(R.string.key_show_insulin_button, true)) + insulinButton.setVisibility(View.VISIBLE); + else + insulinButton.setVisibility(View.GONE); + } + } + private void scheduleUpdateGUI(final String from) { class UpdateRunnable implements Runnable { public void run() { @@ -1060,7 +1117,6 @@ public class OverviewFragment extends DaggerFragment implements View.OnClickList final Profile profile = profileFunction.getProfile(); if (profile == null) return; - final String profileName = profileFunction.getProfileName(); final String units = profileFunction.getUnits(); final double lowLine = defaultValueHelper.determineLowLine(); @@ -1234,50 +1290,7 @@ public class OverviewFragment extends DaggerFragment implements View.OnClickList activeProfileView.setTextColor(resourceHelper.gc(R.color.ribbonTextDefault)); } - // QuickWizard button - QuickWizardEntry quickWizardEntry = quickWizard.getActive(); - if (quickWizardEntry != null && lastBG != null && pump.isInitialized() && !pump.isSuspended()) { - quickWizardButton.setVisibility(View.VISIBLE); - String text = quickWizardEntry.buttonText() + "\n" + DecimalFormatter.to0Decimal(quickWizardEntry.carbs()) + "g"; - BolusWizard wizard = quickWizardEntry.doCalc(profile, profileName, lastBG, false); - text += " " + DecimalFormatter.toPumpSupportedBolus(wizard.getCalculatedTotalInsulin(), pump) + "U"; - quickWizardButton.setText(text); - if (wizard.getCalculatedTotalInsulin() <= 0) - quickWizardButton.setVisibility(View.GONE); - } else - quickWizardButton.setVisibility(View.GONE); - - // **** Various treatment buttons **** - if ((!activePlugin.getActivePump().getPumpDescription().storesCarbInfo || - (pump.isInitialized() && !pump.isSuspended())) && carbsButton != null) { - if (sp.getBoolean(R.string.key_show_carbs_button, true)) { - carbsButton.setVisibility(View.VISIBLE); - } else { - carbsButton.setVisibility(View.GONE); - } - } - - if (pump.isInitialized() && !pump.isSuspended() && treatmentButton != null) { - if (sp.getBoolean(R.string.key_show_treatment_button, false)) { - treatmentButton.setVisibility(View.VISIBLE); - } else { - treatmentButton.setVisibility(View.GONE); - } - } - if (pump.isInitialized() && !pump.isSuspended() && wizardButton != null) { - if (sp.getBoolean(R.string.key_show_wizard_button, true)) { - wizardButton.setVisibility(View.VISIBLE); - } else { - wizardButton.setVisibility(View.GONE); - } - } - if (pump.isInitialized() && !pump.isSuspended() && insulinButton != null) { - if (sp.getBoolean(R.string.key_show_insulin_button, true)) { - insulinButton.setVisibility(View.VISIBLE); - } else { - insulinButton.setVisibility(View.GONE); - } - } + processInsulinCarbsButtonsVisibility(); // **** BG value **** if (lastBG == null) { //left this here as it seems you want to exit at this point if it is null... From f25e871a91baf79fb257d63269b7b9fd4c784554 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sun, 29 Mar 2020 23:38:58 +0200 Subject: [PATCH 09/39] improve leap day behavior --- .../nightscout/androidaps/data/Profile.java | 9 +++++--- .../general/automation/elements/InputTime.kt | 2 +- .../automation/elements/InputTimeRange.kt | 2 +- .../triggers/TriggerRecurringTime.kt | 2 +- .../automation/triggers/TriggerTimeRange.kt | 2 +- .../smsCommunicator/SmsCommunicatorPlugin.kt | 6 ++--- .../nightscout/androidaps/utils/DateUtil.java | 22 +++++++++++++++++++ .../androidaps/utils/MidnightTime.java | 11 ++++++++++ .../androidaps/utils/stats/TddCalculator.kt | 2 +- .../androidaps/utils/stats/TirCalculator.kt | 2 +- .../triggers/TriggerTimeRangeTest.kt | 4 ++-- 11 files changed, 49 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/data/Profile.java b/app/src/main/java/info/nightscout/androidaps/data/Profile.java index bf570f08a7..b6f22aae07 100644 --- a/app/src/main/java/info/nightscout/androidaps/data/Profile.java +++ b/app/src/main/java/info/nightscout/androidaps/data/Profile.java @@ -2,6 +2,7 @@ package info.nightscout.androidaps.data; import androidx.collection.LongSparseArray; +import org.joda.time.DateTime; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -607,13 +608,15 @@ public class Profile { } public static int secondsFromMidnight() { - long passed = DateUtil.now() - MidnightTime.calc(); + // long passed = DateUtil.now() - MidnightTime.calc(); + long passed = new DateTime().getMillisOfDay(); return (int) (passed / 1000); } public static int secondsFromMidnight(long date) { - long midnight = MidnightTime.calc(date); - long passed = date - midnight; + //long midnight = MidnightTime.calc(date); + //long passed = date - midnight; + long passed = new DateTime(date).getMillisOfDay(); return (int) (passed / 1000); } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputTime.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputTime.kt index 34bc74560c..01a7854e35 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputTime.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputTime.kt @@ -57,7 +57,7 @@ class InputTime(injector: HasAndroidInjector) : Element(injector) { root.addView(l) } - private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calc() + T.mins(minutesSinceMidnight.toLong()).msecs() + private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calcPlusMinutes(minutesSinceMidnight) private fun getMinSinceMidnight(time: Long): Int = Profile.secondsFromMidnight(time) / 60 } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputTimeRange.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputTimeRange.kt index 18cf98fc73..4775990d16 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputTimeRange.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/InputTimeRange.kt @@ -80,7 +80,7 @@ class InputTimeRange(injector: HasAndroidInjector) : Element(injector) { root.addView(l) } - private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calc() + T.mins(minutesSinceMidnight.toLong()).msecs() + private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calcPlusMinutes(minutesSinceMidnight) private fun getMinSinceMidnight(time: Long): Int = Profile.secondsFromMidnight(time) / 60 } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerRecurringTime.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerRecurringTime.kt index 11dd1e0475..8982a12669 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerRecurringTime.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerRecurringTime.kt @@ -93,7 +93,7 @@ class TriggerRecurringTime(injector: HasAndroidInjector) : Trigger(injector) { override fun duplicate(): Trigger = TriggerRecurringTime(injector, this) - private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calc() + T.mins(minutesSinceMidnight.toLong()).msecs() + private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calcPlusMinutes(minutesSinceMidnight) private fun getMinSinceMidnight(time: Long): Int = Profile.secondsFromMidnight(time) / 60 diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTimeRange.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTimeRange.kt index 46edd9a0b2..2c565fa733 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTimeRange.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTimeRange.kt @@ -76,7 +76,7 @@ class TriggerTimeRange(injector: HasAndroidInjector) : Trigger(injector) { override fun duplicate(): Trigger = TriggerTimeRange(injector, range.start, range.end) - private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calc() + T.mins(minutesSinceMidnight.toLong()).msecs() + private fun toMills(minutesSinceMidnight: Int): Long = MidnightTime.calcPlusMinutes(minutesSinceMidnight) private fun getMinSinceMidnight(time: Long): Int = Profile.secondsFromMidnight(time) / 60 diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.kt index 78a0bf0ffc..350e5f08db 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/smsCommunicator/SmsCommunicatorPlugin.kt @@ -703,13 +703,11 @@ class SmsCommunicatorPlugin @Inject constructor( var grams = SafeParse.stringToInt(splitted[1]) var time = DateUtil.now() if (splitted.size > 2) { - val seconds = DateUtil.toSeconds(splitted[2].toUpperCase(Locale.getDefault())) - val midnight = MidnightTime.calc() - if (seconds == 0 && (!splitted[2].startsWith("00:00") || !splitted[2].startsWith("12:00"))) { + time = DateUtil.toTodayTime(splitted[2].toUpperCase(Locale.getDefault())) + if (time == 0L) { sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat))) return } - time = midnight + T.secs(seconds.toLong()).msecs() } grams = constraintChecker.applyCarbsConstraints(Constraint(grams)).value() if (grams == 0) sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.wrongformat))) diff --git a/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java b/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java index 7d2790990f..82fabc52b7 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/DateUtil.java @@ -108,6 +108,28 @@ public class DateUtil { return retval; } + public static long toTodayTime(String hh_colon_mm) { + Pattern p = Pattern.compile("(\\d+):(\\d+)( a.m.| p.m.| AM| PM|AM|PM|)"); + Matcher m = p.matcher(hh_colon_mm); + long retval = 0; + + if (m.find()) { + int hours = SafeParse.stringToInt(m.group(1)); + int minutes = SafeParse.stringToInt(m.group(2)); + if ((m.group(3).equals(" a.m.") || m.group(3).equals(" AM") || m.group(3).equals("AM")) && m.group(1).equals("12")) + hours -= 12; + if ((m.group(3).equals(" p.m.") || m.group(3).equals(" PM") || m.group(3).equals("PM")) && !(m.group(1).equals("12"))) + hours += 12; + Calendar c = Calendar.getInstance(); + c.set(Calendar.HOUR_OF_DAY, hours); + c.set(Calendar.MINUTE, minutes); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + retval = c.getTimeInMillis(); + } + return retval; + } + public static String dateString(Date date) { DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT); return df.format(date); diff --git a/app/src/main/java/info/nightscout/androidaps/utils/MidnightTime.java b/app/src/main/java/info/nightscout/androidaps/utils/MidnightTime.java index 235bb9c6e9..501f6147e7 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/MidnightTime.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/MidnightTime.java @@ -21,6 +21,17 @@ public class MidnightTime { return c.getTimeInMillis(); } + public static long calcPlusMinutes(int minutes) { + int h = minutes / 60; + int m = minutes % 60; + Calendar c = Calendar.getInstance(); + c.set(Calendar.HOUR_OF_DAY, h); + c.set(Calendar.MINUTE, m); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + return c.getTimeInMillis(); + } + public static long calc(long time) { Long m; synchronized (times) { diff --git a/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt b/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt index 0444513952..247d4c22d4 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt @@ -40,7 +40,7 @@ class TddCalculator @Inject constructor( fun calculate(days: Long): LongSparseArray { val range = T.days(days + 1).msecs() - val startTime = MidnightTime.calc(DateUtil.now()) - T.days(days).msecs() + val startTime = MidnightTime.calc(DateUtil.now() - T.days(days).msecs()) val endTime = MidnightTime.calc(DateUtil.now()) initializeData(range) diff --git a/app/src/main/java/info/nightscout/androidaps/utils/stats/TirCalculator.kt b/app/src/main/java/info/nightscout/androidaps/utils/stats/TirCalculator.kt index b8d29af3d5..f1f13cbf0d 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/stats/TirCalculator.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/stats/TirCalculator.kt @@ -23,7 +23,7 @@ class TirCalculator @Inject constructor( fun calculate(days: Long, lowMgdl: Double, highMgdl: Double): LongSparseArray { if (lowMgdl < 39) throw RuntimeException("Low below 39") if (lowMgdl > highMgdl) throw RuntimeException("Low > High") - val startTime = MidnightTime.calc(DateUtil.now()) - T.days(days).msecs() + val startTime = MidnightTime.calc(DateUtil.now() - T.days(days).msecs()) val endTime = MidnightTime.calc(DateUtil.now()) val bgReadings = MainApp.getDbHelper().getBgreadingsDataFromTime(startTime, endTime, true) diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTimeRangeTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTimeRangeTest.kt index fc1dbbf690..dba3ddce80 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTimeRangeTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTimeRangeTest.kt @@ -22,9 +22,9 @@ class TriggerTimeRangeTest : TriggerTestBase() { @Before fun mock() { - var realNow = System.currentTimeMillis() PowerMockito.mockStatic(DateUtil::class.java) - PowerMockito.`when`(DateUtil.now()).thenReturn(now.toLong() * 60000 + MidnightTime.calc(realNow)) + val nowMills = MidnightTime.calcPlusMinutes(now) + PowerMockito.`when`(DateUtil.now()).thenReturn(nowMills) } @Test From d1ee12fafc819af11cc3552ce080322a68df5655 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Sun, 29 Mar 2020 23:52:24 +0200 Subject: [PATCH 10/39] replace deprecated calls --- .../plugins/configBuilder/PluginStore.kt | 1 + .../general/tidepool/TidepoolPlugin.kt | 5 ++-- .../general/tidepool/comm/InfoInterceptor.kt | 24 ++++++------------- .../general/tidepool/comm/TidepoolUploader.kt | 2 +- .../tidepool/events/EventTidepoolStatus.kt | 11 --------- .../general/tidepool/utils/RateLimit.kt | 17 ++++++------- 6 files changed, 21 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt index 30014d38e3..0bce3e34eb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt @@ -190,6 +190,7 @@ class PluginStore @Inject constructor( */ private fun determineActivePlugin(pluginsInCategory: ArrayList, pluginType: PluginType): T? { + @Suppress("UNCHECKED_CAST") val activePlugin = getTheOneEnabledInArray(pluginsInCategory, pluginType) as T? if (activePlugin != null) { setFragmentVisiblities((activePlugin as PluginBase).name, pluginsInCategory, pluginType) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt index ce535e41fd..e21f67957d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt @@ -50,7 +50,8 @@ class TidepoolPlugin @Inject constructor( private val fabricPrivacy: FabricPrivacy, private val tidepoolUploader: TidepoolUploader, private val uploadChunk: UploadChunk, - private val sp: SP + private val sp: SP, + private val rateLimit: RateLimit ) : PluginBase(PluginDescription() .mainType(PluginType.GENERAL) .pluginName(R.string.tidepool) @@ -103,7 +104,7 @@ class TidepoolPlugin @Inject constructor( if (isEnabled(PluginType.GENERAL) && (!sp.getBoolean(R.string.key_tidepool_only_while_charging, false) || ChargingStateReceiver.isCharging()) && (!sp.getBoolean(R.string.key_tidepool_only_while_unmetered, false) || NetworkChangeReceiver.isWifiConnected()) - && RateLimit.rateLimit("tidepool-new-data-upload", T.mins(4).secs().toInt())) + && rateLimit.rateLimit("tidepool-new-data-upload", T.mins(4).secs().toInt())) doUpload() }, { fabricPrivacy.logException(it) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/InfoInterceptor.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/InfoInterceptor.kt index c1a9f55dc6..59268f49b0 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/InfoInterceptor.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/InfoInterceptor.kt @@ -1,32 +1,22 @@ package info.nightscout.androidaps.plugins.general.tidepool.comm -import info.nightscout.androidaps.logging.L -import info.nightscout.androidaps.logging.StacktraceLoggerWrapper +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag import okhttp3.Interceptor import okhttp3.Response import okio.Buffer -import org.slf4j.LoggerFactory import java.io.IOException -class InfoInterceptor(tag: String) : Interceptor { - - private val log = StacktraceLoggerWrapper.getLogger(L.TIDEPOOL) - private var tag = "interceptor" - - init { - this.tag = tag - } +class InfoInterceptor(val tag: String = "interceptor", val aapsLogger: AAPSLogger) : Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() request.body?.let { - if (L.isEnabled(L.TIDEPOOL)) { - log.debug("Interceptor Body size: " + it.contentLength()) - val requestBuffer = Buffer() - it.writeTo(requestBuffer) - log.debug("Interceptor Body: " + requestBuffer.readUtf8()) - } + aapsLogger.debug(LTag.TIDEPOOL, "Interceptor Body size: " + it.contentLength()) + val requestBuffer = Buffer() + it.writeTo(requestBuffer) + aapsLogger.debug(LTag.TIDEPOOL, "Interceptor Body: " + requestBuffer.readUtf8()) } return chain.proceed(request) } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt index a6d89f8971..51c16af342 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/comm/TidepoolUploader.kt @@ -68,7 +68,7 @@ class TidepoolUploader @Inject constructor( val client = OkHttpClient.Builder() .addInterceptor(httpLoggingInterceptor) - .addInterceptor(InfoInterceptor(TidepoolUploader::class.java.name)) + .addInterceptor(InfoInterceptor(TidepoolUploader::class.java.name, aapsLogger)) .build() retrofit = Retrofit.Builder() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolStatus.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolStatus.kt index bbb86c45b8..aff358ba08 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolStatus.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/events/EventTidepoolStatus.kt @@ -1,23 +1,13 @@ package info.nightscout.androidaps.plugins.general.tidepool.events import info.nightscout.androidaps.events.Event -import info.nightscout.androidaps.logging.L -import info.nightscout.androidaps.logging.StacktraceLoggerWrapper import info.nightscout.androidaps.utils.DateUtil -import org.slf4j.LoggerFactory import java.text.SimpleDateFormat import java.util.* class EventTidepoolStatus(val status: String) : Event() { - private val log = StacktraceLoggerWrapper.getLogger(L.TIDEPOOL) - var date: Long = DateUtil.now() - init { - if (L.isEnabled(L.TIDEPOOL)) - log.debug("New status: $status") - } - private var timeFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault()) fun toPreparedHtml(): StringBuilder { @@ -29,5 +19,4 @@ class EventTidepoolStatus(val status: String) : Event() { stringBuilder.append("
") return stringBuilder } - } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/RateLimit.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/RateLimit.kt index 6f23a3f9c1..9e48d0b90f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/RateLimit.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/utils/RateLimit.kt @@ -1,26 +1,27 @@ package info.nightscout.androidaps.plugins.general.tidepool.utils -import info.nightscout.androidaps.logging.L -import info.nightscout.androidaps.logging.StacktraceLoggerWrapper +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.T -import org.slf4j.LoggerFactory import java.util.* +import javax.inject.Inject +import javax.inject.Singleton -object RateLimit { +@Singleton +class RateLimit @Inject constructor( + val aapsLogger: AAPSLogger +) { private val rateLimits = HashMap() - private val log = StacktraceLoggerWrapper.getLogger(L.TIDEPOOL) - // return true if below rate limit @Synchronized fun rateLimit(name: String, seconds: Int): Boolean { // check if over limit rateLimits[name]?.let { if (DateUtil.now() - it < T.secs(seconds.toLong()).msecs()) { - if (L.isEnabled(L.TIDEPOOL)) - log.debug("$name rate limited: $seconds seconds") + aapsLogger.debug(LTag.TIDEPOOL, "$name rate limited: $seconds seconds") return false } } From ddd91109124ae1684b6139c5713e87149ee226b4 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Mon, 30 Mar 2020 00:57:33 +0200 Subject: [PATCH 11/39] LocaleHelper dependency remove --- .../nightscout/androidaps/MainActivity.java | 1 - .../activities/NoSplashAppCompatActivity.kt | 2 +- .../nightscout/androidaps/utils/LocaleHelper.kt | 17 ++++++++++------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/MainActivity.java b/app/src/main/java/info/nightscout/androidaps/MainActivity.java index 0d65cbb834..f6a250dc4a 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/MainActivity.java @@ -92,7 +92,6 @@ public class MainActivity extends NoSplashAppCompatActivity { @Override public void onCreate(Bundle savedInstanceState) { - AndroidInjection.inject(this); super.onCreate(savedInstanceState); Iconify.with(new FontAwesomeModule()); diff --git a/app/src/main/java/info/nightscout/androidaps/activities/NoSplashAppCompatActivity.kt b/app/src/main/java/info/nightscout/androidaps/activities/NoSplashAppCompatActivity.kt index b4ac7eff51..aaf85c6adf 100644 --- a/app/src/main/java/info/nightscout/androidaps/activities/NoSplashAppCompatActivity.kt +++ b/app/src/main/java/info/nightscout/androidaps/activities/NoSplashAppCompatActivity.kt @@ -10,8 +10,8 @@ import info.nightscout.androidaps.utils.LocaleHelper open class NoSplashAppCompatActivity : DaggerAppCompatActivity() { public override fun onCreate(savedInstanceState: Bundle?) { - setTheme(R.style.AppTheme_NoActionBar) super.onCreate(savedInstanceState) + setTheme(R.style.AppTheme_NoActionBar) } public override fun attachBaseContext(newBase: Context) { diff --git a/app/src/main/java/info/nightscout/androidaps/utils/LocaleHelper.kt b/app/src/main/java/info/nightscout/androidaps/utils/LocaleHelper.kt index 447d076d93..62da88a15d 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/LocaleHelper.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/LocaleHelper.kt @@ -4,16 +4,19 @@ import android.content.Context import android.content.ContextWrapper import android.os.Build import android.os.LocaleList +import androidx.preference.PreferenceManager import info.nightscout.androidaps.R import java.util.* - object LocaleHelper { - fun currentLanguage(): String = - SP.getString(R.string.key_language, Locale.getDefault().language) + private fun currentLanguage(context: Context): String = + PreferenceManager.getDefaultSharedPreferences(context).getString(context.getString(R.string.key_language), "en") + ?: "en" + // injection not possible because of use in attachBaseContext + //SP.getString(R.string.key_language, Locale.getDefault().language) - private fun currentLocale(): Locale { - val language = currentLanguage() + private fun currentLocale(context: Context): Locale { + val language = currentLanguage(context) var locale = Locale(language) if (language.contains("_")) { // language with country like pt_BR defined in arrays.xml @@ -26,7 +29,7 @@ object LocaleHelper { @Suppress("DEPRECATION") fun update(context: Context) { - val locale = currentLocale() + val locale = currentLocale(context) Locale.setDefault(locale) val resources = context.resources val configuration = resources.configuration @@ -39,7 +42,7 @@ object LocaleHelper { fun wrap(ctx: Context): ContextWrapper { val res = ctx.resources val configuration = res.configuration - val newLocale = currentLocale() + val newLocale = currentLocale(ctx) val context = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { configuration.setLocale(newLocale) val localeList = LocaleList(newLocale) From 71be72cd03669b3a8a80777bd37d33fb0e77c8f6 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Mon, 30 Mar 2020 09:40:04 +0200 Subject: [PATCH 12/39] Logging cleanup --- .../nightscout/androidaps/RealPumpTest.kt | 6 +- .../info/nightscout/androidaps/logging/L.kt | 86 ++----------------- .../nightscout/androidaps/logging/LTag.kt | 30 +++++++ .../plugins/aps/loop/DeviceStatus.java | 2 +- .../plugins/aps/loop/LoopPlugin.java | 3 +- .../openAPSMA/DetermineBasalAdapterMAJS.java | 3 +- .../iob/iobCobCalculator/AutosensResult.java | 7 +- .../iobCobCalculator/IobCobOref1Thread.java | 20 ++--- .../iob/iobCobCalculator/IobCobThread.java | 21 ++--- 9 files changed, 60 insertions(+), 118 deletions(-) create mode 100644 app/src/main/java/info/nightscout/androidaps/logging/LTag.kt diff --git a/app/src/androidTest/java/info/nightscout/androidaps/RealPumpTest.kt b/app/src/androidTest/java/info/nightscout/androidaps/RealPumpTest.kt index c188d59963..a327989975 100644 --- a/app/src/androidTest/java/info/nightscout/androidaps/RealPumpTest.kt +++ b/app/src/androidTest/java/info/nightscout/androidaps/RealPumpTest.kt @@ -37,8 +37,6 @@ import javax.inject.Inject @RunWith(AndroidJUnit4::class) class RealPumpTest { - private val log = LoggerFactory.getLogger(L.CORE) - companion object { const val R_PASSWORD = 1234 const val R_SERIAL = "PBB00013LR_P" @@ -120,12 +118,12 @@ class RealPumpTest { preparePlugins() while (!pump.isInitialized) { - log.debug("Waiting for initialization") + //log.debug("Waiting for initialization") SystemClock.sleep(1000) } while (true) { - log.debug("Tick") + //log.debug("Tick") SystemClock.sleep(1000) } } diff --git a/app/src/main/java/info/nightscout/androidaps/logging/L.kt b/app/src/main/java/info/nightscout/androidaps/logging/L.kt index ad13015378..4ae358ba02 100644 --- a/app/src/main/java/info/nightscout/androidaps/logging/L.kt +++ b/app/src/main/java/info/nightscout/androidaps/logging/L.kt @@ -7,59 +7,18 @@ object L { private var logElements: MutableList = ArrayList() const val CORE = "CORE" - const val AUTOSENS = "AUTOSENS" - const val AUTOMATION = "AUTOMATION" - const val EVENTS = "EVENTS" - const val GLUCOSE = "GLUCOSE" const val BGSOURCE = "BGSOURCE" - const val OVERVIEW = "OVERVIEW" - const val NOTIFICATION = "NOTIFICATION" const val DATASERVICE = "DATASERVICE" const val DATABASE = "DATABASE" const val DATAFOOD = "DATAFOOD" const val DATATREATMENTS = "DATATREATMENTS" const val NSCLIENT = "NSCLIENT" - const val TIDEPOOL = "TIDEPOOL" - const val CONSTRAINTS = "CONSTRAINTS" const val PUMP = "PUMP" - const val PUMPQUEUE = "PUMPQUEUE" const val PUMPCOMM = "PUMPCOMM" const val PUMPBTCOMM = "PUMPBTCOMM" - const val APS = "APS" - const val PROFILE = "PROFILE" - const val CONFIGBUILDER = "CONFIGBUILDER" - const val UI = "UI" - const val LOCATION = "LOCATION" - const val SMS = "SMS" - const val WEAR = "WEAR" init { - logElements.add(LogElement(APS, defaultValue = true)) - logElements.add(LogElement(AUTOMATION, defaultValue = true)) - logElements.add(LogElement(AUTOSENS, defaultValue = false)) - logElements.add(LogElement(BGSOURCE, defaultValue = true)) - logElements.add(LogElement(GLUCOSE, defaultValue = false)) - logElements.add(LogElement(CONFIGBUILDER, defaultValue = false)) - logElements.add(LogElement(CONSTRAINTS, defaultValue = true)) - logElements.add(LogElement(CORE, defaultValue = true)) - logElements.add(LogElement(DATABASE, defaultValue = true)) - logElements.add(LogElement(DATAFOOD, false)) - logElements.add(LogElement(DATASERVICE, true)) - logElements.add(LogElement(DATATREATMENTS, true)) - logElements.add(LogElement(EVENTS, false, requiresRestart = true)) - logElements.add(LogElement(LOCATION, true)) - logElements.add(LogElement(NOTIFICATION, true)) - logElements.add(LogElement(NSCLIENT, true)) - logElements.add(LogElement(TIDEPOOL, true)) - logElements.add(LogElement(OVERVIEW, true)) - logElements.add(LogElement(PROFILE, true)) - logElements.add(LogElement(PUMP, true)) - logElements.add(LogElement(PUMPBTCOMM, false)) - logElements.add(LogElement(PUMPCOMM, true)) - logElements.add(LogElement(PUMPQUEUE, true)) - logElements.add(LogElement(SMS, true)) - logElements.add(LogElement(UI, true)) - logElements.add(LogElement(WEAR, true)) + LTag.values().forEach { logElements.add(LogElement(it)) } } private fun findByName(name: String): LogElement { @@ -90,16 +49,10 @@ object L { var enabled: Boolean private var requiresRestart = false - internal constructor(name: String, defaultValue: Boolean) { - this.name = name - this.defaultValue = defaultValue - enabled = SP.getBoolean(getSPName(), defaultValue) - } - - internal constructor(name: String, defaultValue: Boolean, requiresRestart: Boolean) { - this.name = name - this.defaultValue = defaultValue - this.requiresRestart = requiresRestart + internal constructor(tag:LTag) { + this.name = tag.tag + this.defaultValue = tag.defaultValue + this.requiresRestart = tag.requiresRestart enabled = SP.getBoolean(getSPName(), defaultValue) } @@ -120,33 +73,4 @@ object L { enable(defaultValue) } } -} - -enum class LTag(val tag: String) { - CORE("CORE"), - AUTOSENS("AUTOSENS"), - AUTOMATION("AUTOMATION"), - EVENTS("EVENTS"), - GLUCOSE("GLUCOSE"), - BGSOURCE("BGSOURCE"), - OVERVIEW("OVERVIEW"), - NOTIFICATION("NOTIFICATION"), - DATASERVICE("DATASERVICE"), - DATABASE("DATABASE"), - DATAFOOD("DATAFOOD"), - DATATREATMENTS("DATATREATMENTS"), - NSCLIENT("NSCLIENT"), - TIDEPOOL("TIDEPOOL"), - CONSTRAINTS("CONSTRAINTS"), - PUMP("PUMP"), - PUMPQUEUE("PUMPQUEUE"), - PUMPCOMM("PUMPCOMM"), - PUMPBTCOMM("PUMPBTCOMM"), - APS("APS"), - PROFILE("PROFILE"), - CONFIGBUILDER("CONFIGBUILDER"), - UI("UI"), - LOCATION("LOCATION"), - WEAR("WEAR"), - SMS("SMS"), } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/logging/LTag.kt b/app/src/main/java/info/nightscout/androidaps/logging/LTag.kt new file mode 100644 index 0000000000..775a789eb9 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/logging/LTag.kt @@ -0,0 +1,30 @@ +package info.nightscout.androidaps.logging + +enum class LTag(val tag: String, val defaultValue : Boolean = false, val requiresRestart: Boolean = false) { + CORE("CORE", defaultValue = false), + APS("APS", defaultValue = false), + AUTOSENS("AUTOSENS"), + AUTOMATION("AUTOMATION", defaultValue = false), + BGSOURCE("BGSOURCE", defaultValue = false), + CONFIGBUILDER("CONFIGBUILDER"), + CONSTRAINTS("CONSTRAINTS", defaultValue = false), + DATABASE("DATABASE", defaultValue = false), + DATAFOOD("DATAFOOD"), + DATASERVICE("DATASERVICE", defaultValue = false), + DATATREATMENTS("DATATREATMENTS", defaultValue = false), + EVENTS("EVENTS", defaultValue = false, requiresRestart = true), + GLUCOSE("GLUCOSE"), + LOCATION("LOCATION", defaultValue = false), + NOTIFICATION("NOTIFICATION", defaultValue = false), + NSCLIENT("NSCLIENT", defaultValue = false), + OVERVIEW("OVERVIEW", defaultValue = false), + PUMP("PUMP", defaultValue = false), + PUMPBTCOMM("PUMPBTCOMM"), + PUMPCOMM("PUMPCOMM", defaultValue = false), + PUMPQUEUE("PUMPQUEUE", defaultValue = false), + PROFILE("PROFILE", defaultValue = false), + SMS("SMS", defaultValue = false), + TIDEPOOL("TIDEPOOL"), + UI("UI", defaultValue = false), + WEAR("WEAR") +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/DeviceStatus.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/DeviceStatus.java index 5222926a9e..42f25cce8f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/DeviceStatus.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/DeviceStatus.java @@ -366,7 +366,7 @@ import info.nightscout.androidaps.logging.StacktraceLoggerWrapper; */ public class DeviceStatus { - private static Logger log = StacktraceLoggerWrapper.getLogger(L.APS); + private static Logger log = StacktraceLoggerWrapper.getLogger(L.NSCLIENT); public String device = null; public JSONObject pump = null; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java index 4493d51275..79c3f5cd33 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/loop/LoopPlugin.java @@ -334,8 +334,7 @@ public class LoopPlugin extends PluginBase { Profile profile = profileFunction.getProfile(); if (profile == null || !profileFunction.isProfileValid("Loop")) { - if (L.isEnabled(L.APS)) - getAapsLogger().debug(LTag.APS, resourceHelper.gs(R.string.noprofileselected)); + getAapsLogger().debug(LTag.APS, resourceHelper.gs(R.string.noprofileselected)); rxBus.send(new EventLoopSetLastRunGui(resourceHelper.gs(R.string.noprofileselected))); return; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/DetermineBasalAdapterMAJS.java b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/DetermineBasalAdapterMAJS.java index cc32ba807b..c7733d4eb7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/DetermineBasalAdapterMAJS.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/aps/openAPSMA/DetermineBasalAdapterMAJS.java @@ -109,8 +109,7 @@ public class DetermineBasalAdapterMAJS { // Parse the jsResult object to a JSON-String String result = NativeJSON.stringify(rhino, scope, jsResult, null, null).toString(); - if (L.isEnabled(L.APS)) - aapsLogger.debug(LTag.APS, "Result: " + result); + aapsLogger.debug(LTag.APS, "Result: " + result); try { determineBasalResultMA = new DetermineBasalResultMA(injector, jsResult, new JSONObject(result)); } catch (JSONException e) { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/AutosensResult.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/AutosensResult.java index 3f478cc478..e1debef726 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/AutosensResult.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/AutosensResult.java @@ -2,17 +2,14 @@ package info.nightscout.androidaps.plugins.iob.iobCobCalculator; import org.json.JSONException; import org.json.JSONObject; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.logging.StacktraceLoggerWrapper; +import info.nightscout.androidaps.logging.LTag; /** * Created by mike on 06.01.2017. */ public class AutosensResult { - private static Logger log = StacktraceLoggerWrapper.getLogger(L.AUTOSENS); //default values to show when autosens algorithm is not called public double ratio = 1d; @@ -30,7 +27,7 @@ public class AutosensResult { ret.put("sensResult", sensResult); ret.put("ratio", ratio); } catch (JSONException e) { - log.error("Unhandled exception", e); + LoggerFactory.getLogger(LTag.CORE.getTag()).error("Unhandled exception", e); } return ret; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java index c3d6685538..505b9a799f 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobOref1Thread.java @@ -196,17 +196,15 @@ public class IobCobOref1Thread extends Thread { try { for (; past < 12; past++) { AutosensData ad = autosensDataTable.valueAt(initialIndex + past); - if (L.isEnabled(L.AUTOSENS)) { - aapsLogger.debug(">>>>> past=" + past + " ad=" + (ad != null ? ad.toString() : null)); - if (ad == null) { - aapsLogger.debug(autosensDataTable.toString()); - aapsLogger.debug(bucketed_data.toString()); - aapsLogger.debug(iobCobCalculatorPlugin.getBgReadings().toString()); - Notification notification = new Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW); - rxBus.send(new EventNewNotification(notification)); - sp.putBoolean("log_AUTOSENS", true); - break; - } + aapsLogger.debug(LTag.AUTOSENS, ">>>>> past=" + past + " ad=" + (ad != null ? ad.toString() : null)); + if (ad == null) { + aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString()); + aapsLogger.debug(LTag.AUTOSENS, bucketed_data.toString()); + aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.getBgReadings().toString()); + Notification notification = new Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW); + rxBus.send(new EventNewNotification(notification)); + sp.putBoolean("log_AUTOSENS", true); + break; } // let it here crash on NPE to get more data as i cannot reproduce this bug double deviationSlope = (ad.avgDeviation - avgDeviation) / (ad.time - bgTime) * 1000 * 60 * 5; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java index 950add8cbe..6e30d776b2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/iob/iobCobCalculator/IobCobThread.java @@ -21,7 +21,6 @@ import info.nightscout.androidaps.events.Event; import info.nightscout.androidaps.interfaces.ActivePluginProvider; import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.logging.LTag; import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults; import info.nightscout.androidaps.plugins.bus.RxBusWrapper; @@ -193,17 +192,15 @@ public class IobCobThread extends Thread { try { for (; past < 12; past++) { AutosensData ad = autosensDataTable.valueAt(initialIndex + past); - if (L.isEnabled(L.AUTOSENS)) { - aapsLogger.debug(">>>>> past=" + past + " ad=" + (ad != null ? ad.toString() : null)); - if (ad == null) { - aapsLogger.debug(autosensDataTable.toString()); - aapsLogger.debug(bucketed_data.toString()); - aapsLogger.debug(iobCobCalculatorPlugin.getBgReadings().toString()); - Notification notification = new Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW); - rxBus.send(new EventNewNotification(notification)); - sp.putBoolean("log_AUTOSENS", true); - break; - } + aapsLogger.debug(LTag.AUTOSENS, ">>>>> past=" + past + " ad=" + (ad != null ? ad.toString() : null)); + if (ad == null) { + aapsLogger.debug(LTag.AUTOSENS, autosensDataTable.toString()); + aapsLogger.debug(LTag.AUTOSENS, bucketed_data.toString()); + aapsLogger.debug(LTag.AUTOSENS, iobCobCalculatorPlugin.getBgReadings().toString()); + Notification notification = new Notification(Notification.SENDLOGFILES, resourceHelper.gs(R.string.sendlogfiles), Notification.LOW); + rxBus.send(new EventNewNotification(notification)); + sp.putBoolean("log_AUTOSENS", true); + break; } // let it here crash on NPE to get more data as i cannot reproduce this bug double deviationSlope = (ad.avgDeviation - avgDeviation) / (ad.time - bgTime) * 1000 * 60 * 5; From 23946f6c2a026a264d0afe68b27916be8691e38e Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Mon, 30 Mar 2020 10:10:50 +0200 Subject: [PATCH 13/39] Remove deprecation --- .../java/info/nightscout/androidaps/logging/L.kt | 12 ++++++++---- .../info/nightscout/androidaps/utils/OKDialog.kt | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/logging/L.kt b/app/src/main/java/info/nightscout/androidaps/logging/L.kt index 4ae358ba02..2411e76c5e 100644 --- a/app/src/main/java/info/nightscout/androidaps/logging/L.kt +++ b/app/src/main/java/info/nightscout/androidaps/logging/L.kt @@ -1,6 +1,7 @@ package info.nightscout.androidaps.logging -import info.nightscout.androidaps.utils.SP +import androidx.preference.PreferenceManager +import info.nightscout.androidaps.MainApp import java.util.* object L { @@ -49,11 +50,13 @@ object L { var enabled: Boolean private var requiresRestart = false - internal constructor(tag:LTag) { + internal constructor(tag: LTag) { this.name = tag.tag this.defaultValue = tag.defaultValue this.requiresRestart = tag.requiresRestart - enabled = SP.getBoolean(getSPName(), defaultValue) + //TODO: remove after getting rid of old logging style "if (L.isEnabled(...))" + @Suppress("DEPRECATION") + enabled = PreferenceManager.getDefaultSharedPreferences(MainApp.instance()).getBoolean(getSPName(), defaultValue) } internal constructor(defaultValue: Boolean) { @@ -66,7 +69,8 @@ object L { fun enable(enabled: Boolean) { this.enabled = enabled - SP.putBoolean(getSPName(), enabled) + @Suppress("DEPRECATION") + PreferenceManager.getDefaultSharedPreferences(MainApp.instance()).edit().putBoolean(getSPName(), enabled).apply() } fun resetToDefault() { diff --git a/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt b/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt index 4ce0071900..b3f8393862 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt @@ -39,6 +39,7 @@ object OKDialog { } fun runOnUiThread(theRunnable: Runnable?) { + @Suppress("DEPRECATION" val mainHandler = Handler(MainApp.instance().applicationContext.mainLooper) theRunnable?.let { mainHandler.post(it) } } From caae6ab0785642335eb3fd67a8f0f44c0895136f Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Mon, 30 Mar 2020 10:13:26 +0200 Subject: [PATCH 14/39] typo --- app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt b/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt index b3f8393862..285b3aaa10 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt @@ -39,7 +39,7 @@ object OKDialog { } fun runOnUiThread(theRunnable: Runnable?) { - @Suppress("DEPRECATION" + @Suppress("DEPRECATION") val mainHandler = Handler(MainApp.instance().applicationContext.mainLooper) theRunnable?.let { mainHandler.post(it) } } From 969f713914b2703fc796bbcfe896108f8ffa88bc Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Mon, 30 Mar 2020 13:14:24 +0200 Subject: [PATCH 15/39] AndroidPermission refactor --- .../nightscout/androidaps/MainActivity.java | 11 +- .../androidaps/setupwizard/SWDefinition.kt | 99 +++++------ .../setupwizard/elements/SWFragment.kt | 4 +- .../androidaps/utils/AndroidPermission.java | 155 ----------------- .../androidaps/utils/AndroidPermission.kt | 163 ++++++++++++++++++ 5 files changed, 216 insertions(+), 216 deletions(-) delete mode 100644 app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.java create mode 100644 app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.kt diff --git a/app/src/main/java/info/nightscout/androidaps/MainActivity.java b/app/src/main/java/info/nightscout/androidaps/MainActivity.java index f6a250dc4a..b0c09bc44d 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/MainActivity.java @@ -78,6 +78,7 @@ public class MainActivity extends NoSplashAppCompatActivity { @Inject AAPSLogger aapsLogger; @Inject RxBusWrapper rxBus; + @Inject AndroidPermission androidPermission; @Inject SP sp; @Inject ResourceHelper resourceHelper; @Inject VersionCheckerUtils versionCheckerUtils; @@ -161,12 +162,12 @@ public class MainActivity extends NoSplashAppCompatActivity { startActivity(intent); } - AndroidPermission.notifyForStoragePermission(this); - AndroidPermission.notifyForBatteryOptimizationPermission(this); + androidPermission.notifyForStoragePermission(this); + androidPermission.notifyForBatteryOptimizationPermission(this); if (Config.PUMPDRIVERS) { - AndroidPermission.notifyForLocationPermissions(this); - AndroidPermission.notifyForSMSPermissions(this, smsCommunicatorPlugin); - AndroidPermission.notifyForSystemWindowPermissions(this); + androidPermission.notifyForLocationPermissions(this); + androidPermission.notifyForSMSPermissions(this, smsCommunicatorPlugin); + androidPermission.notifyForSystemWindowPermissions(this); } } diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt index 940635d555..5164e8388e 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt @@ -1,12 +1,12 @@ package info.nightscout.androidaps.setupwizard import android.Manifest +import android.content.Context import android.content.Intent import androidx.appcompat.app.AppCompatActivity import dagger.android.HasAndroidInjector import info.nightscout.androidaps.Config import info.nightscout.androidaps.Constants -import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.R import info.nightscout.androidaps.activities.PreferencesActivity import info.nightscout.androidaps.dialogs.ProfileSwitchDialog @@ -46,7 +46,7 @@ import javax.inject.Singleton class SWDefinition @Inject constructor( injector: HasAndroidInjector, private val rxBus: RxBusWrapper, - private val mainApp: MainApp, + private val context: Context, resourceHelper: ResourceHelper, private val sp: SP, private val profileFunction: ProfileFunction, @@ -58,10 +58,11 @@ class SWDefinition @Inject constructor( private val loopPlugin: LoopPlugin, private val nsClientPlugin: NSClientPlugin, private val nsProfilePlugin: NSProfilePlugin, - private val protectionCheck: ProtectionCheck + private val protectionCheck: ProtectionCheck, + private val androidPermission: AndroidPermission ) { - var activity: AppCompatActivity? = null + lateinit var activity: AppCompatActivity private val screens: MutableList = ArrayList() fun getScreens(): List { @@ -83,7 +84,7 @@ class SWDefinition @Inject constructor( .preferenceId(R.string.key_language).label(R.string.language) .comment(R.string.setupwizard_language_prompt)) .validator(SWValidator { - update(mainApp) + update(context) sp.contains(R.string.key_language) }) private val screenEula = SWScreen(injector, R.string.end_user_license_agreement) @@ -127,10 +128,10 @@ class SWDefinition @Inject constructor( .add(SWBreak(injector)) .add(SWButton(injector) .text(R.string.askforpermission) - .visibility(SWValidator { AndroidPermission.permissionNotGranted(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) }) - .action(Runnable { AndroidPermission.askForPermission(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, AndroidPermission.CASE_BATTERY) })) - .visibility(SWValidator { AndroidPermission.permissionNotGranted(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) }) - .validator(SWValidator { !AndroidPermission.permissionNotGranted(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) }) + .visibility(SWValidator { androidPermission.permissionNotGranted(context, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) }) + .action(Runnable { androidPermission.askForPermission(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, AndroidPermission.CASE_BATTERY) })) + .visibility(SWValidator { androidPermission.permissionNotGranted(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) }) + .validator(SWValidator { !androidPermission.permissionNotGranted(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) }) private val screenPermissionBt = SWScreen(injector, R.string.permission) .skippable(false) .add(SWInfotext(injector) @@ -138,10 +139,10 @@ class SWDefinition @Inject constructor( .add(SWBreak(injector)) .add(SWButton(injector) .text(R.string.askforpermission) - .visibility(SWValidator { AndroidPermission.permissionNotGranted(activity, Manifest.permission.ACCESS_FINE_LOCATION) }) - .action(Runnable { AndroidPermission.askForPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION, AndroidPermission.CASE_LOCATION) })) - .visibility(SWValidator { AndroidPermission.permissionNotGranted(activity, Manifest.permission.ACCESS_FINE_LOCATION) }) - .validator(SWValidator { !AndroidPermission.permissionNotGranted(activity, Manifest.permission.ACCESS_FINE_LOCATION) }) + .visibility(SWValidator { androidPermission.permissionNotGranted(activity, Manifest.permission.ACCESS_FINE_LOCATION) }) + .action(Runnable { androidPermission.askForPermission(activity, Manifest.permission.ACCESS_FINE_LOCATION, AndroidPermission.CASE_LOCATION) })) + .visibility(SWValidator { androidPermission.permissionNotGranted(activity, Manifest.permission.ACCESS_FINE_LOCATION) }) + .validator(SWValidator { !androidPermission.permissionNotGranted(activity, Manifest.permission.ACCESS_FINE_LOCATION) }) private val screenPermissionStore = SWScreen(injector, R.string.permission) .skippable(false) .add(SWInfotext(injector) @@ -149,10 +150,10 @@ class SWDefinition @Inject constructor( .add(SWBreak(injector)) .add(SWButton(injector) .text(R.string.askforpermission) - .visibility(SWValidator { AndroidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) }) - .action(Runnable { AndroidPermission.askForPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, AndroidPermission.CASE_STORAGE) })) - .visibility(SWValidator { AndroidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) }) - .validator(SWValidator { !AndroidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) }) + .visibility(SWValidator { androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) }) + .action(Runnable { androidPermission.askForPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, AndroidPermission.CASE_STORAGE) })) + .visibility(SWValidator { androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) }) + .validator(SWValidator { !androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) }) private val screenImport = SWScreen(injector, R.string.nav_import) .add(SWInfotext(injector) .label(R.string.storedsettingsfound)) @@ -160,7 +161,7 @@ class SWDefinition @Inject constructor( .add(SWButton(injector) .text(R.string.nav_import) .action(Runnable { ImportExportPrefs.importSharedPreferences(activity) })) - .visibility(SWValidator { ImportExportPrefs.file.exists() && !AndroidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) }) + .visibility(SWValidator { ImportExportPrefs.file.exists() && !androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) }) private val screenNsClient = SWScreen(injector, R.string.nsclientinternal_title) .skippable(true) .add(SWInfotext(injector) @@ -219,13 +220,11 @@ class SWDefinition @Inject constructor( .text(R.string.insulinsourcesetup) .action(Runnable { val plugin = activePlugin.activeInsulin as PluginBase - activity?.let { activity -> - protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, Runnable { - val i = Intent(activity, PreferencesActivity::class.java) - i.putExtra("id", plugin.preferencesId) - activity.startActivity(i) - }, null) - } + protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, Runnable { + val i = Intent(activity, PreferencesActivity::class.java) + i.putExtra("id", plugin.preferencesId) + activity.startActivity(i) + }, null) }) .visibility(SWValidator { (activePlugin.activeInsulin as PluginBase).preferencesId > 0 })) private val screenBgSource = SWScreen(injector, R.string.configbuilder_bgsource) @@ -238,13 +237,11 @@ class SWDefinition @Inject constructor( .text(R.string.bgsourcesetup) .action(Runnable { val plugin = activePlugin.activeBgSource as PluginBase - activity?.let { activity -> - protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, Runnable { - val i = Intent(activity, PreferencesActivity::class.java) - i.putExtra("id", plugin.preferencesId) - activity.startActivity(i) - }, null) - } + protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, Runnable { + val i = Intent(activity, PreferencesActivity::class.java) + i.putExtra("id", plugin.preferencesId) + activity.startActivity(i) + }, null) }) .visibility(SWValidator { (activePlugin.activeBgSource as PluginBase).preferencesId > 0 })) private val screenProfile = SWScreen(injector, R.string.configbuilder_profile) @@ -275,7 +272,7 @@ class SWDefinition @Inject constructor( .label(R.string.profileswitch_ismissing)) .add(SWButton(injector) .text(R.string.doprofileswitch) - .action(Runnable { ProfileSwitchDialog().show(activity!!.supportFragmentManager, "SetupWizard") })) + .action(Runnable { ProfileSwitchDialog().show(activity.supportFragmentManager, "SetupWizard") })) .validator(SWValidator { profileFunction.getProfile() != null }) .visibility(SWValidator { profileFunction.getProfile() == null }) private val screenPump = SWScreen(injector, R.string.configbuilder_pump) @@ -288,13 +285,11 @@ class SWDefinition @Inject constructor( .text(R.string.pumpsetup) .action(Runnable { val plugin = activePlugin.activePump as PluginBase - activity?.let { activity -> - protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, Runnable { - val i = Intent(activity, PreferencesActivity::class.java) - i.putExtra("id", plugin.preferencesId) - activity.startActivity(i) - }, null) - } + protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, Runnable { + val i = Intent(activity, PreferencesActivity::class.java) + i.putExtra("id", plugin.preferencesId) + activity.startActivity(i) + }, null) }) .visibility(SWValidator { (activePlugin.activePump as PluginBase).preferencesId > 0 })) .add(SWButton(injector) @@ -317,13 +312,11 @@ class SWDefinition @Inject constructor( .text(R.string.apssetup) .action(Runnable { val plugin = activePlugin.activeAPS as PluginBase - activity?.let { activity -> - protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, Runnable { - val i = Intent(activity, PreferencesActivity::class.java) - i.putExtra("id", plugin.preferencesId) - activity.startActivity(i) - }, null) - } + protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, Runnable { + val i = Intent(activity, PreferencesActivity::class.java) + i.putExtra("id", plugin.preferencesId) + activity.startActivity(i) + }, null) }) .visibility(SWValidator { (activePlugin.activeAPS as PluginBase).preferencesId > 0 })) .visibility(SWValidator { Config.APS }) @@ -367,13 +360,11 @@ class SWDefinition @Inject constructor( .text(R.string.sensitivitysetup) .action(Runnable { val plugin = activePlugin.activeSensitivity as PluginBase - activity?.let { activity -> - protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, Runnable { - val i = Intent(activity, PreferencesActivity::class.java) - i.putExtra("id", plugin.preferencesId) - activity.startActivity(i) - }, null) - } + protectionCheck.queryProtection(activity, ProtectionCheck.Protection.PREFERENCES, Runnable { + val i = Intent(activity, PreferencesActivity::class.java) + i.putExtra("id", plugin.preferencesId) + activity.startActivity(i) + }, null) }) .visibility(SWValidator { (activePlugin.activeSensitivity as PluginBase).preferencesId > 0 })) private val getScreenObjectives = SWScreen(injector, R.string.objectives) diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWFragment.kt b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWFragment.kt index 72ac956251..d90890f068 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/elements/SWFragment.kt @@ -5,7 +5,7 @@ import androidx.fragment.app.Fragment import dagger.android.HasAndroidInjector import info.nightscout.androidaps.setupwizard.SWDefinition -class SWFragment(injecto:HasAndroidInjector, private var definition: SWDefinition) : SWItem(injecto, Type.FRAGMENT) { +class SWFragment(injector:HasAndroidInjector, private var definition: SWDefinition) : SWItem(injector, Type.FRAGMENT) { lateinit var fragment: Fragment fun add(fragment: Fragment): SWFragment { @@ -14,6 +14,6 @@ class SWFragment(injecto:HasAndroidInjector, private var definition: SWDefinitio } override fun generateDialog(layout: LinearLayout) { - definition.activity?.supportFragmentManager?.beginTransaction()?.add(layout.id, fragment, fragment.tag)?.commit() + definition.activity.supportFragmentManager.beginTransaction().add(layout.id, fragment, fragment.tag).commit() } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.java b/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.java deleted file mode 100644 index 2770e26ed3..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.java +++ /dev/null @@ -1,155 +0,0 @@ -package info.nightscout.androidaps.utils; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Build; -import android.os.PowerManager; -import android.provider.Settings; - -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.interfaces.PluginType; -import info.nightscout.androidaps.plugins.bus.RxBus; -import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; -import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; -import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; -import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction; -import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin; - -public class AndroidPermission { - - public static final int CASE_STORAGE = 0x1; - public static final int CASE_SMS = 0x2; - public static final int CASE_LOCATION = 0x3; - public static final int CASE_BATTERY = 0x4; - public static final int CASE_PHONE_STATE = 0x5; - public static final int CASE_SYSTEM_WINDOW = 0x6; - - private static boolean permission_battery_optimization_failed = false; - - @SuppressLint("BatteryLife") - private static void askForPermission(Activity activity, String[] permission, Integer requestCode) { - boolean test = false; - boolean testBattery = false; - for (String s : permission) { - test = test || (ContextCompat.checkSelfPermission(activity, s) != PackageManager.PERMISSION_GRANTED); - if (s.equals(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) { - PowerManager powerManager = (PowerManager) activity.getSystemService(Context.POWER_SERVICE); - String packageName = activity.getPackageName(); - testBattery = testBattery || !powerManager.isIgnoringBatteryOptimizations(packageName); - } - } - if (test) { - ActivityCompat.requestPermissions(activity, permission, requestCode); - } - if (testBattery) { - try { - Intent i = new Intent(); - i.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - i.setData(Uri.parse("package:" + activity.getPackageName())); - activity.startActivityForResult(i, CASE_BATTERY); - } catch (ActivityNotFoundException e) { - permission_battery_optimization_failed = true; - OKDialog.show(activity, MainApp.gs(R.string.permission), MainApp.gs(R.string.alert_dialog_permission_battery_optimization_failed), activity::recreate); - } - } - } - - public static void askForPermission(Activity activity, String permission, Integer requestCode) { - String[] permissions = {permission}; - askForPermission(activity, permissions, requestCode); - } - - public static boolean permissionNotGranted(Context context, String permission) { - boolean selfCheck = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; - if (permission.equals(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) { - if (!permission_battery_optimization_failed) { - PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - String packageName = context.getPackageName(); - selfCheck = selfCheck && powerManager.isIgnoringBatteryOptimizations(packageName); - } - } - return !selfCheck; - } - - public static synchronized void notifyForSMSPermissions(Activity activity, SmsCommunicatorPlugin smsCommunicatorPlugin) { - if (smsCommunicatorPlugin.isEnabled(PluginType.GENERAL)) { - if (permissionNotGranted(activity, Manifest.permission.RECEIVE_SMS)) { - NotificationWithAction notification = new NotificationWithAction(MainApp.instance(), Notification.PERMISSION_SMS, MainApp.gs(R.string.smscommunicator_missingsmspermission), Notification.URGENT); - notification.action(R.string.request, () -> AndroidPermission.askForPermission(activity, new String[]{Manifest.permission.RECEIVE_SMS, - Manifest.permission.SEND_SMS, - Manifest.permission.RECEIVE_MMS}, AndroidPermission.CASE_SMS)); - RxBus.Companion.getINSTANCE().send(new EventNewNotification(notification)); - } else - RxBus.Companion.getINSTANCE().send(new EventDismissNotification(Notification.PERMISSION_SMS)); - // Following is a bug in Android 8 - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) { - if (permissionNotGranted(activity, Manifest.permission.READ_PHONE_STATE)) { - NotificationWithAction notification = new NotificationWithAction(MainApp.instance(), Notification.PERMISSION_PHONESTATE, MainApp.gs(R.string.smscommunicator_missingphonestatepermission), Notification.URGENT); - notification.action(R.string.request, () -> - AndroidPermission.askForPermission(activity, new String[]{Manifest.permission.READ_PHONE_STATE}, AndroidPermission.CASE_PHONE_STATE)); - RxBus.Companion.getINSTANCE().send(new EventNewNotification(notification)); - } else - RxBus.Companion.getINSTANCE().send(new EventDismissNotification(Notification.PERMISSION_PHONESTATE)); - } - } - } - - public static synchronized void notifyForBatteryOptimizationPermission(Activity activity) { - if (permissionNotGranted(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) { - NotificationWithAction notification = new NotificationWithAction(MainApp.instance(), Notification.PERMISSION_BATTERY, String.format(MainApp.gs(R.string.needwhitelisting), MainApp.gs(R.string.app_name)), Notification.URGENT); - notification.action(R.string.request, () -> AndroidPermission.askForPermission(activity, new String[]{Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS}, AndroidPermission.CASE_BATTERY)); - RxBus.Companion.getINSTANCE().send(new EventNewNotification(notification)); - } else - RxBus.Companion.getINSTANCE().send(new EventDismissNotification(Notification.PERMISSION_BATTERY)); - } - - public static synchronized void notifyForStoragePermission(Activity activity) { - if (permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - NotificationWithAction notification = new NotificationWithAction(MainApp.instance(), Notification.PERMISSION_STORAGE, MainApp.gs(R.string.needstoragepermission), Notification.URGENT); - notification.action(R.string.request, () -> AndroidPermission.askForPermission(activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE}, AndroidPermission.CASE_STORAGE)); - RxBus.Companion.getINSTANCE().send(new EventNewNotification(notification)); - } else - RxBus.Companion.getINSTANCE().send(new EventDismissNotification(Notification.PERMISSION_STORAGE)); - } - - public static synchronized void notifyForLocationPermissions(Activity activity) { - if (permissionNotGranted(activity, Manifest.permission.ACCESS_FINE_LOCATION)) { - NotificationWithAction notification = new NotificationWithAction(MainApp.instance(), Notification.PERMISSION_LOCATION, MainApp.gs(R.string.needlocationpermission), Notification.URGENT); - notification.action(R.string.request, () -> AndroidPermission.askForPermission(activity, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, AndroidPermission.CASE_LOCATION)); - RxBus.Companion.getINSTANCE().send(new EventNewNotification(notification)); - } else - RxBus.Companion.getINSTANCE().send(new EventDismissNotification(Notification.PERMISSION_LOCATION)); - } - - public static synchronized void notifyForSystemWindowPermissions(Activity activity) { - // Check if Android Q or higher - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { - if (!Settings.canDrawOverlays(activity)) { - NotificationWithAction notification = new NotificationWithAction(MainApp.instance(), Notification.PERMISSION_SYSTEM_WINDOW, MainApp.gs(R.string.needsystemwindowpermission), Notification.URGENT); - notification.action(R.string.request, () -> { - // Check if Android Q or higher - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { - // Show alert dialog to the user saying a separate permission is needed - // Launch the settings activity if the user prefers - Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - Uri.parse("package:" + activity.getPackageName())); - activity.startActivity(intent); - } - }); - RxBus.Companion.getINSTANCE().send(new EventNewNotification(notification)); - } else - RxBus.Companion.getINSTANCE().send(new EventDismissNotification(Notification.PERMISSION_SYSTEM_WINDOW)); - } - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.kt b/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.kt new file mode 100644 index 0000000000..583aa2bf25 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/AndroidPermission.kt @@ -0,0 +1,163 @@ +package info.nightscout.androidaps.utils + +import android.Manifest +import android.annotation.SuppressLint +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import android.os.PowerManager +import android.provider.Settings +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.R +import info.nightscout.androidaps.interfaces.PluginType +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification +import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification +import info.nightscout.androidaps.plugins.general.overview.notifications.Notification +import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction +import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin +import info.nightscout.androidaps.utils.OKDialog.show +import info.nightscout.androidaps.utils.resources.ResourceHelper +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AndroidPermission @Inject constructor( + val resourceHelper: ResourceHelper, + val rxBus: RxBusWrapper, + val injector: HasAndroidInjector +) { + + companion object { + const val CASE_STORAGE = 0x1 + const val CASE_SMS = 0x2 + const val CASE_LOCATION = 0x3 + const val CASE_BATTERY = 0x4 + const val CASE_PHONE_STATE = 0x5 + const val CASE_SYSTEM_WINDOW = 0x6 + } + + private var permission_battery_optimization_failed = false + + @SuppressLint("BatteryLife") + private fun askForPermission(activity: Activity, permission: Array, requestCode: Int) { + var test = false + var testBattery = false + for (s in permission) { + test = test || ContextCompat.checkSelfPermission(activity, s) != PackageManager.PERMISSION_GRANTED + if (s == Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) { + val powerManager = activity.getSystemService(Context.POWER_SERVICE) as PowerManager + val packageName = activity.packageName + testBattery = testBattery || !powerManager.isIgnoringBatteryOptimizations(packageName) + } + } + if (test) { + ActivityCompat.requestPermissions(activity, permission, requestCode) + } + if (testBattery) { + try { + val i = Intent() + i.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS + i.data = Uri.parse("package:" + activity.packageName) + activity.startActivityForResult(i, CASE_BATTERY) + } catch (e: ActivityNotFoundException) { + permission_battery_optimization_failed = true + show(activity, resourceHelper.gs(R.string.permission), resourceHelper.gs(R.string.alert_dialog_permission_battery_optimization_failed), Runnable { activity.recreate() }) + } + } + } + + fun askForPermission(activity: Activity, permission: String, requestCode: Int) { + val permissions = arrayOf(permission) + askForPermission(activity, permissions, requestCode) + } + + fun permissionNotGranted(context: Context, permission: String): Boolean { + var selfCheck = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED + if (permission == Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) { + if (!permission_battery_optimization_failed) { + val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager + val packageName = context.packageName + selfCheck = selfCheck && powerManager.isIgnoringBatteryOptimizations(packageName) + } + } + return !selfCheck + } + + @Synchronized + fun notifyForSMSPermissions(activity: Activity, smsCommunicatorPlugin: SmsCommunicatorPlugin) { + if (smsCommunicatorPlugin.isEnabled(PluginType.GENERAL)) { + if (permissionNotGranted(activity, Manifest.permission.RECEIVE_SMS)) { + val notification = NotificationWithAction(injector, Notification.PERMISSION_SMS, resourceHelper.gs(R.string.smscommunicator_missingsmspermission), Notification.URGENT) + notification.action(R.string.request, Runnable { + askForPermission(activity, arrayOf(Manifest.permission.RECEIVE_SMS, + Manifest.permission.SEND_SMS, + Manifest.permission.RECEIVE_MMS), CASE_SMS) + }) + rxBus.send(EventNewNotification(notification)) + } else rxBus.send(EventDismissNotification(Notification.PERMISSION_SMS)) + // Following is a bug in Android 8 + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) { + if (permissionNotGranted(activity, Manifest.permission.READ_PHONE_STATE)) { + val notification = NotificationWithAction(injector, Notification.PERMISSION_PHONESTATE, resourceHelper.gs(R.string.smscommunicator_missingphonestatepermission), Notification.URGENT) + notification.action(R.string.request, Runnable { askForPermission(activity, arrayOf(Manifest.permission.READ_PHONE_STATE), CASE_PHONE_STATE) }) + rxBus.send(EventNewNotification(notification)) + } else rxBus.send(EventDismissNotification(Notification.PERMISSION_PHONESTATE)) + } + } + } + + @Synchronized + fun notifyForBatteryOptimizationPermission(activity: Activity) { + if (permissionNotGranted(activity, Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) { + val notification = NotificationWithAction(injector, Notification.PERMISSION_BATTERY, String.format(resourceHelper.gs(R.string.needwhitelisting), resourceHelper.gs(R.string.app_name)), Notification.URGENT) + notification.action(R.string.request, Runnable { askForPermission(activity, arrayOf(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS), CASE_BATTERY) }) + rxBus.send(EventNewNotification(notification)) + } else rxBus.send(EventDismissNotification(Notification.PERMISSION_BATTERY)) + } + + @Synchronized fun notifyForStoragePermission(activity: Activity) { + if (permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + val notification = NotificationWithAction(injector, Notification.PERMISSION_STORAGE, resourceHelper.gs(R.string.needstoragepermission), Notification.URGENT) + notification.action(R.string.request, Runnable { + askForPermission(activity, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE), CASE_STORAGE) + }) + rxBus.send(EventNewNotification(notification)) + } else rxBus.send(EventDismissNotification(Notification.PERMISSION_STORAGE)) + } + + @Synchronized fun notifyForLocationPermissions(activity: Activity) { + if (permissionNotGranted(activity, Manifest.permission.ACCESS_FINE_LOCATION)) { + val notification = NotificationWithAction(injector, Notification.PERMISSION_LOCATION, resourceHelper.gs(R.string.needlocationpermission), Notification.URGENT) + notification.action(R.string.request, Runnable { askForPermission(activity, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), CASE_LOCATION) }) + rxBus.send(EventNewNotification(notification)) + } else rxBus.send(EventDismissNotification(Notification.PERMISSION_LOCATION)) + } + + @Synchronized fun notifyForSystemWindowPermissions(activity: Activity) { + // Check if Android Q or higher + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + if (!Settings.canDrawOverlays(activity)) { + val notification = NotificationWithAction(injector, Notification.PERMISSION_SYSTEM_WINDOW, resourceHelper.gs(R.string.needsystemwindowpermission), Notification.URGENT) + notification.action(R.string.request, Runnable { + // Check if Android Q or higher + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + // Show alert dialog to the user saying a separate permission is needed + // Launch the settings activity if the user prefers + val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + activity.packageName)) + activity.startActivity(intent) + } + }) + rxBus.send(EventNewNotification(notification)) + } else rxBus.send(EventDismissNotification(Notification.PERMISSION_SYSTEM_WINDOW)) + } + } +} \ No newline at end of file From b95f88295691bdebf4a5b3799d6c6a6f200ecb0c Mon Sep 17 00:00:00 2001 From: Roumen Georgiev Date: Mon, 30 Mar 2020 15:53:12 +0300 Subject: [PATCH 16/39] initial work --- .../general/automation/AutomationPlugin.kt | 3 +- .../automation/elements/DropdownMenu.java | 95 ++++++++ .../automation/triggers/TriggerBTDevice.java | 227 ++++++++++++++++++ app/src/main/res/values/strings.xml | 2 + .../triggers/TriggerBTDeviceTest.java | 124 ++++++++++ 5 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/DropdownMenu.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBTDevice.java create mode 100644 app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBTDeviceTest.java diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt index 0d220c2dd4..63606806e4 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/AutomationPlugin.kt @@ -228,7 +228,8 @@ object AutomationPlugin : PluginBase(PluginDescription() TriggerLocation(), TriggerAutosensValue(), TriggerBolusAgo(), - TriggerPumpLastConnection() + TriggerPumpLastConnection(), + TriggerBTDevice() ) } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/DropdownMenu.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/DropdownMenu.java new file mode 100644 index 0000000000..072660ccd5 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/elements/DropdownMenu.java @@ -0,0 +1,95 @@ +package info.nightscout.androidaps.plugins.general.automation.elements; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.LinearLayout; +import android.widget.Spinner; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.logging.L; + +public class DropdownMenu extends Element { + private static Logger log = LoggerFactory.getLogger(L.AUTOMATION); + private ArrayList itemList; + private String selected; + + public DropdownMenu(String name) { + super(); + this.selected = name; + } + + public DropdownMenu(DropdownMenu another) { + super(); + selected = another.getValue(); + } + + + @Override + public void addToLayout(LinearLayout root) { + if (itemList == null) { + log.error("ItemList is empty!"); + itemList = new ArrayList<>(); + } + ArrayAdapter adapter = new ArrayAdapter<>(root.getContext(), + R.layout.spinner_centered, itemList); + Spinner spinner = new Spinner(root.getContext()); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + LinearLayout.LayoutParams spinnerParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ); + spinnerParams.setMargins(0, MainApp.dpToPx(4), 0, MainApp.dpToPx(4)); + spinner.setLayoutParams(spinnerParams); + spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + setValue(itemList.get(position).toString()); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + spinner.setSelection(0); + LinearLayout l = new LinearLayout(root.getContext()); + l.setOrientation(LinearLayout.VERTICAL); + l.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + l.addView(spinner); + root.addView(l); + } + + public DropdownMenu setValue(String name) { + this.selected = name; + return this; + } + + public String getValue() { + return selected; + } + + public void setList(ArrayList values){ + if (itemList == null) + itemList = new ArrayList<>(); + log.debug("values size is "+values.size()); + itemList = new ArrayList<>(values); + log.debug("items size is "+itemList.size()); + } + + // For testing only + public void add(String item){ + if (itemList == null) + itemList = new ArrayList<>(); + itemList.add(item); + log.debug("Added " + item + "("+itemList.size()+")"); + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBTDevice.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBTDevice.java new file mode 100644 index 0000000000..24dafada91 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBTDevice.java @@ -0,0 +1,227 @@ +package info.nightscout.androidaps.plugins.general.automation.triggers; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.widget.LinearLayout; + +import androidx.fragment.app.FragmentManager; + +import com.google.common.base.Optional; + +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Set; + +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.general.automation.elements.ComparatorExists; +import info.nightscout.androidaps.plugins.general.automation.elements.InputString; +import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder; +import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabel; +import info.nightscout.androidaps.plugins.general.automation.elements.DropdownMenu; +import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.JsonHelper; +import info.nightscout.androidaps.utils.T; + +public class TriggerBTDevice extends Trigger { + private static Logger log = LoggerFactory.getLogger(L.AUTOMATION); + + private InputString deviceName = new InputString(); + DropdownMenu listOfDevices = new DropdownMenu(""); + ComparatorExists comparator = new ComparatorExists(); + private boolean connectedToDevice = false; + + public TriggerBTDevice() { + super(); + } + + private TriggerBTDevice(TriggerBTDevice TriggerBTDevice) { + super(); + deviceName.setValue(TriggerBTDevice.deviceName.getValue()); + comparator = new ComparatorExists(TriggerBTDevice.comparator); + connectedToDevice = TriggerBTDevice.connectedToDevice; + listOfDevices.setList(devicesPaired()); + lastRun = TriggerBTDevice.lastRun; + } + + public ComparatorExists getComparator() { + return comparator; + } + + @Override + public synchronized boolean shouldRun() { + log.debug("Connected "+connectedToDevice+"! Time left "+ (5 - T.msecs(DateUtil.now()-lastRun).mins())); + if (lastRun > DateUtil.now() - T.mins(5).msecs()) + return false; + + checkConnected(); + + if (connectedToDevice && comparator.getValue() == ComparatorExists.Compare.EXISTS) { + if (L.isEnabled(L.AUTOMATION)) + log.debug("Ready for execution: " + friendlyDescription()); + return true; + } + + return false; + } + + @Override + public synchronized String toJSON() { + JSONObject o = new JSONObject(); + try { + o.put("type", TriggerBTDevice.class.getName()); + JSONObject data = new JSONObject(); + data.put("lastRun", lastRun); + data.put("comparator", comparator.getValue().toString()); + if (!deviceName.getValue().equals("")) + data.put("name", deviceName.getValue()); + else + data.put("name", listOfDevices.getValue()); + + o.put("data", data); + } catch (JSONException e) { + log.error("Unhandled exception", e); + } + log.debug("JSON saved "+o.toString()); + return o.toString(); + } + + @Override + Trigger fromJSON(String data) { + try { + JSONObject d = new JSONObject(data); + lastRun = JsonHelper.safeGetLong(d, "lastRun"); + deviceName.setValue(JsonHelper.safeGetString(d, "name")); + listOfDevices.setList(devicesPaired()); + comparator.setValue(ComparatorExists.Compare.valueOf(JsonHelper.safeGetString(d, "comparator"))); + } catch (Exception e) { + log.error("Unhandled exception", e); + } + return this; + } + + @Override + public int friendlyName() { + return R.string.btdevice; + } + + @Override + public String friendlyDescription() { + return MainApp.gs(R.string.btdevicecompared, deviceName.getValue(), MainApp.gs(comparator.getValue().getStringRes())); + } + + @Override + public Optional icon() { + return Optional.of(R.drawable.ic_bluetooth_white_48dp); + } + + @Override + public Trigger duplicate() { + return new TriggerBTDevice(this); + } + + TriggerBTDevice lastRun(long lastRun) { + this.lastRun = lastRun; + return this; + } + + public TriggerBTDevice comparator(ComparatorExists.Compare compare) { + this.comparator = new ComparatorExists().setValue(compare); + return this; + } + + @Override + public void generateDialog(LinearLayout root, FragmentManager fragmentManager) { + ArrayList pairedDevices = devicesPaired(); + if (pairedDevices.size() == 0) { + // No list of paired devices comming from BT adapter -> show a text input + new LayoutBuilder() + .add(new StaticLabel(R.string.btdevice)) + .add(deviceName) + .add(comparator) + .build(root); + } else { + listOfDevices.setList(pairedDevices); + new LayoutBuilder() + .add(new StaticLabel(R.string.btdevice)) + .add(listOfDevices) + .add(comparator) + .build(root); + } + } + + // Get the list of paired BT devices to use in dropdown menu + private ArrayList devicesPaired(){ + BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + ArrayList s = new ArrayList<>(); + if (mBluetoothAdapter == null) + return s; + Set pairedDevices = mBluetoothAdapter.getBondedDevices(); + + for(BluetoothDevice bt : pairedDevices) { + s.add(bt.getName()); + } + return s; + } + + public void checkConnected() { + BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + if (mBluetoothAdapter == null) + return; + + int state = mBluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET); // Checks only for connected HEADSET, no other type of BT devices + if (state != BluetoothProfile.STATE_CONNECTED) { + connectedToDevice = false; + return; + } + try + { + Context context = MainApp.instance().getApplicationContext(); + mBluetoothAdapter.getProfileProxy(context, serviceListener, BluetoothProfile.STATE_CONNECTED); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + private BluetoothProfile.ServiceListener serviceListener = new BluetoothProfile.ServiceListener() + { + @Override + public void onServiceDisconnected(int profile) + { } + + @Override + public void onServiceConnected(int profile, BluetoothProfile proxy) + { + + for (BluetoothDevice device : proxy.getConnectedDevices()) + { + connectedToDevice = deviceName.getValue().equals(device.getName()); + } + + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(profile, proxy); + } + }; + + public void setDeviceName(String newName) { + deviceName.setValue(newName); + } + + public String getDeviceName() { + return deviceName.getValue(); + } + + public void setConnectedState(boolean newstate){ + this.connectedToDevice = newstate; + } + +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a4c79cb63b..49dbd3cc61 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1390,6 +1390,8 @@ exists not exists Temp target %1$s + Bluetooth connection to device %1$s %2$s + Connection to Bluetooth device WiFi SSID %1$s %2$s Autosens %1$s %2$s %% Autosens % diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBTDeviceTest.java b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBTDeviceTest.java new file mode 100644 index 0000000000..ddd7a04805 --- /dev/null +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerBTDeviceTest.java @@ -0,0 +1,124 @@ +package info.nightscout.androidaps.plugins.general.automation.triggers; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothProfile; + +import com.google.common.base.Optional; + +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import info.AAPSMocker; +import info.nightscout.androidaps.MainApp; +import info.nightscout.androidaps.R; +import info.nightscout.androidaps.logging.L; +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctions; +import info.nightscout.androidaps.plugins.general.automation.elements.ComparatorExists; +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; +import info.nightscout.androidaps.utils.DateUtil; +import info.nightscout.androidaps.utils.SP; + +import static org.mockito.ArgumentMatchers.any; +import static org.powermock.api.mockito.PowerMockito.when; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({MainApp.class, ProfileFunctions.class, DateUtil.class, IobCobCalculatorPlugin.class, SP.class, L.class, BluetoothAdapter.class, BluetoothProfile.class}) +public class TriggerBTDeviceTest { + + long now = 1514766900000L; + String someName = "Headset"; + String btJson = "{\"data\":{\"comparator\":\"EXISTS\",\"lastRun\":0,\"name\":\"Headset\"},\"type\":\"info.nightscout.androidaps.plugins.general.automation.triggers.TriggerBTDevice\"}"; + + @Before + public void mock() { + AAPSMocker.mockMainApp(); + AAPSMocker.mockProfileFunctions(); + AAPSMocker.mockSP(); + AAPSMocker.mockL(); + + PowerMockito.mockStatic(DateUtil.class); + when(DateUtil.now()).thenReturn(now); + when(SP.getInt(any(), any())).thenReturn(48); + + + } + + @Test + public void comparator() { + TriggerBTDevice t = new TriggerBTDevice().comparator(ComparatorExists.Compare.EXISTS); + Assert.assertEquals(t.comparator.getValue(), ComparatorExists.Compare.EXISTS); + } + + @Test + public void shouldRun() { + BluetoothAdapter btAdapter = PowerMockito.mock(BluetoothAdapter.class); + PowerMockito.mockStatic(BluetoothAdapter.class); + PowerMockito.mockStatic(BluetoothProfile.class); + TriggerBTDevice t = new TriggerBTDevice().comparator(ComparatorExists.Compare.EXISTS); + Assert.assertFalse(t.shouldRun()); // no bluetooth adapter + when(BluetoothAdapter.getDefaultAdapter()).thenReturn(btAdapter); + when(btAdapter.isEnabled()).thenReturn(true); + Assert.assertFalse(t.shouldRun()); // no device connected + when(btAdapter.getProfileConnectionState(BluetoothProfile.HEADSET)).thenReturn(BluetoothProfile.STATE_CONNECTED); + t.setDeviceName(someName); + Assert.assertFalse(t.shouldRun()); // no mocked connection + t.setConnectedState(true); + Assert.assertTrue(t.shouldRun()); + + } + + @Test + public void toJSON() { + TriggerBTDevice t = new TriggerBTDevice().comparator(ComparatorExists.Compare.EXISTS); + t.setDeviceName(someName); + Assert.assertEquals(btJson, t.toJSON()); + } + + @Test + public void fromJSON() throws JSONException { + TriggerBTDevice t = new TriggerBTDevice().comparator(ComparatorExists.Compare.EXISTS); + t.setDeviceName(someName); + TriggerBTDevice t2 = (TriggerBTDevice) Trigger.instantiate(new JSONObject(t.toJSON())); + Assert.assertEquals(ComparatorExists.Compare.EXISTS, t2.getComparator().getValue()); + Assert.assertEquals("Headset", t2.getDeviceName()); + } + + @Test + public void friendlyName() { + } + + @Test + public void friendlyDescription() { + } + + @Test + public void icon() { + Assert.assertEquals(Optional.of(R.drawable.ic_bluetooth_white_48dp), new TriggerBTDevice().icon()); + } + + @Test + public void duplicate() { + TriggerBTDevice t = new TriggerBTDevice().comparator(ComparatorExists.Compare.EXISTS); + t.setDeviceName(someName); + TriggerBTDevice t1 = (TriggerBTDevice) t.duplicate(); + Assert.assertEquals("Headset", t1.getDeviceName()); + Assert.assertEquals(ComparatorExists.Compare.EXISTS, t.getComparator().getValue()); + } + + @Test + public void lastRun() { + TriggerBTDevice t = new TriggerBTDevice().lastRun(now); + Assert.assertEquals(now, t.lastRun); + } + + @Test + public void generateDialog() { + } +} \ No newline at end of file From 2aea4864098a2f39091a16a8fdfe04770b43a98b Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 31 Mar 2020 09:20:58 +0200 Subject: [PATCH 17/39] NetworkChangeReceiver refactor --- .../dependencyInjection/ReceiversModule.kt | 2 + .../androidaps/events/EventNetworkChange.kt | 2 + .../events/EventPreferenceChange.kt | 10 --- .../objectives/ObjectivesFragment.kt | 11 +-- .../automation/triggers/TriggerWifiSsid.kt | 6 +- .../general/nsclient/NSClientPlugin.java | 6 +- .../nsclient/NsClientReceiverDelegate.java | 75 +++++++++++------ .../general/tidepool/TidepoolPlugin.kt | 7 +- .../receivers/NetworkChangeReceiver.java | 83 ------------------- .../receivers/NetworkChangeReceiver.kt | 59 +++++++++++++ .../receivers/ReceiverStatusStore.kt | 23 +++++ .../{SmsReceiver.java => SmsReceiver.kt} | 4 +- .../automation/triggers/TriggerTestBase.kt | 15 ++++ .../triggers/TriggerWifiSsidTest.kt | 4 +- .../nsclient/NsClientReceiverDelegateTest.kt | 56 ++++++++----- 15 files changed, 207 insertions(+), 156 deletions(-) delete mode 100644 app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java create mode 100644 app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/receivers/ReceiverStatusStore.kt rename app/src/main/java/info/nightscout/androidaps/receivers/{SmsReceiver.java => SmsReceiver.kt} (70%) diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt index 20e320c428..08b3711a6c 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt @@ -4,6 +4,7 @@ import dagger.Module import dagger.android.ContributesAndroidInjector import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkBluetoothStateReceiver import info.nightscout.androidaps.receivers.KeepAliveReceiver +import info.nightscout.androidaps.receivers.NetworkChangeReceiver import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver @Module @@ -11,6 +12,7 @@ import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver abstract class ReceiversModule { @ContributesAndroidInjector abstract fun contributesKeepAliveReceiver(): KeepAliveReceiver + @ContributesAndroidInjector abstract fun contributesNetworkChangeReceiver(): NetworkChangeReceiver @ContributesAndroidInjector abstract fun contributesTimeDateOrTZChangeReceiver(): TimeDateOrTZChangeReceiver @ContributesAndroidInjector abstract fun contributesRileyLinkBluetoothStateReceiver(): RileyLinkBluetoothStateReceiver } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventNetworkChange.kt b/app/src/main/java/info/nightscout/androidaps/events/EventNetworkChange.kt index 62c8bdd13e..87599a9373 100644 --- a/app/src/main/java/info/nightscout/androidaps/events/EventNetworkChange.kt +++ b/app/src/main/java/info/nightscout/androidaps/events/EventNetworkChange.kt @@ -6,9 +6,11 @@ class EventNetworkChange : Event() { var mobileConnected = false var wifiConnected = false + var vpnConnected = false var ssid = "" var roaming = false + var metered = false fun connectedSsid(): String { return StringUtils.removeSurroundingQuotes(ssid) diff --git a/app/src/main/java/info/nightscout/androidaps/events/EventPreferenceChange.kt b/app/src/main/java/info/nightscout/androidaps/events/EventPreferenceChange.kt index 830667c6b6..1f7856b7cd 100644 --- a/app/src/main/java/info/nightscout/androidaps/events/EventPreferenceChange.kt +++ b/app/src/main/java/info/nightscout/androidaps/events/EventPreferenceChange.kt @@ -15,17 +15,7 @@ class EventPreferenceChange : Event { changedKey = resourceHelper.gs(resourceID) } - @Deprecated("use injected version") - constructor(resources: Resources, id: Int) { - changedKey == resources.getString(id) - } - fun isChanged(resourceHelper: ResourceHelper, id: Int): Boolean { return changedKey == resourceHelper.gs(id) } - - @Deprecated("use injected version") - fun isChanged(resources: Resources, id: Int): Boolean { - return changedKey == resources.getString(id) - } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt index 909ceac2b4..44f82fb97e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesFragment.kt @@ -26,7 +26,7 @@ import info.nightscout.androidaps.plugins.constraints.objectives.dialogs.NtpProg import info.nightscout.androidaps.plugins.constraints.objectives.events.EventNtpStatus import info.nightscout.androidaps.plugins.constraints.objectives.events.EventObjectivesUpdateGui import info.nightscout.androidaps.plugins.constraints.objectives.objectives.Objective.ExamTask -import info.nightscout.androidaps.receivers.NetworkChangeReceiver +import info.nightscout.androidaps.receivers.ReceiverStatusStore import info.nightscout.androidaps.setupwizard.events.EventSWUpdate import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy @@ -48,6 +48,7 @@ class ObjectivesFragment : DaggerFragment() { @Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var objectivesPlugin: ObjectivesPlugin + @Inject lateinit var receiverStatusStore: ReceiverStatusStore private val objectivesAdapter = ObjectivesAdapter() private val handler = Handler(Looper.getMainLooper()) @@ -223,7 +224,7 @@ class ObjectivesFragment : DaggerFragment() { holder.accomplished.text = resourceHelper.gs(R.string.accomplished, DateUtil.dateAndTimeString(objective.accomplishedOn)) holder.accomplished.setTextColor(-0x3e3e3f) holder.verify.setOnClickListener { - NetworkChangeReceiver.grabNetworkStatus(context) + receiverStatusStore.updateNetworkStatus() if (objectives_fake.isChecked) { objective.accomplishedOn = DateUtil.now() scrollToCurrentObjective() @@ -257,12 +258,12 @@ class ObjectivesFragment : DaggerFragment() { rxBus.send(EventNtpStatus(resourceHelper.gs(R.string.failedretrievetime), 99)) } } - }, NetworkChangeReceiver.isConnected()) + }, receiverStatusStore.isConnected) }.start() } } holder.start.setOnClickListener { - NetworkChangeReceiver.grabNetworkStatus(context) + receiverStatusStore.updateNetworkStatus() if (objectives_fake.isChecked) { objective.startedOn = DateUtil.now() scrollToCurrentObjective() @@ -292,7 +293,7 @@ class ObjectivesFragment : DaggerFragment() { rxBus.send(EventNtpStatus(resourceHelper.gs(R.string.failedretrievetime), 99)) } } - }, NetworkChangeReceiver.isConnected()) + }, receiverStatusStore.isConnected) }.start() } holder.unStart.setOnClickListener { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerWifiSsid.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerWifiSsid.kt index 341a8fdf68..cef73c1339 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerWifiSsid.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerWifiSsid.kt @@ -11,10 +11,14 @@ import info.nightscout.androidaps.plugins.general.automation.elements.LabelWithE import info.nightscout.androidaps.plugins.general.automation.elements.LayoutBuilder import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabel import info.nightscout.androidaps.receivers.NetworkChangeReceiver +import info.nightscout.androidaps.receivers.ReceiverStatusStore import info.nightscout.androidaps.utils.JsonHelper import org.json.JSONObject +import javax.inject.Inject class TriggerWifiSsid(injector: HasAndroidInjector) : Trigger(injector) { + @Inject lateinit var receiverStatusStore: ReceiverStatusStore + var ssid = InputString(injector) var comparator = Comparator(injector) @@ -39,7 +43,7 @@ class TriggerWifiSsid(injector: HasAndroidInjector) : Trigger(injector) { } override fun shouldRun(): Boolean { - val eventNetworkChange = NetworkChangeReceiver.getLastEvent() ?: return false + val eventNetworkChange = receiverStatusStore.lastNetworkEvent ?: return false if (!eventNetworkChange.wifiConnected && comparator.value == Comparator.Compare.IS_NOT_AVAILABLE) { aapsLogger.debug(LTag.AUTOMATION, "Ready for execution: " + friendlyDescription()) return true diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.java index 30a0291059..8c6480ea86 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NSClientPlugin.java @@ -71,7 +71,7 @@ public class NSClientPlugin extends PluginBase { public NSClientService nsClientService = null; - private NsClientReceiverDelegate nsClientReceiverDelegate = new NsClientReceiverDelegate(); + private NsClientReceiverDelegate nsClientReceiverDelegate; @Inject public NSClientPlugin( @@ -80,7 +80,8 @@ public class NSClientPlugin extends PluginBase { RxBusWrapper rxBus, ResourceHelper resourceHelper, Context context, - SP sp + SP sp, + NsClientReceiverDelegate nsClientReceiverDelegate ) { super(new PluginDescription() .mainType(PluginType.GENERAL) @@ -97,6 +98,7 @@ public class NSClientPlugin extends PluginBase { this.resourceHelper = resourceHelper; this.context = context; this.sp = sp; + this.nsClientReceiverDelegate = nsClientReceiverDelegate; if (Config.NSCLIENT) { getPluginDescription().alwaysEnabled(true).visibleByDefault(true); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.java index 2b104008c2..35454f1406 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.java @@ -1,50 +1,71 @@ package info.nightscout.androidaps.plugins.general.nsclient; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; -import android.net.wifi.WifiManager; -import info.nightscout.androidaps.MainApp; +import javax.inject.Inject; +import javax.inject.Singleton; + import info.nightscout.androidaps.R; import info.nightscout.androidaps.events.EventChargingState; import info.nightscout.androidaps.events.EventNetworkChange; import info.nightscout.androidaps.events.EventPreferenceChange; -import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.logging.AAPSLogger; +import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.receivers.ChargingStateReceiver; import info.nightscout.androidaps.receivers.NetworkChangeReceiver; -import info.nightscout.androidaps.utils.SP; +import info.nightscout.androidaps.receivers.ReceiverStatusStore; +import info.nightscout.androidaps.utils.resources.ResourceHelper; +import info.nightscout.androidaps.utils.sharedPreferences.SP; +@Singleton class NsClientReceiverDelegate { private boolean allowedChargingState = true; private boolean allowedNetworkState = true; boolean allowed = true; - void grabReceiversState() { - Context context = MainApp.instance().getApplicationContext(); + private AAPSLogger aapsLogger; + private Context context; + private RxBusWrapper rxBus; + private ResourceHelper resourceHelper; + private SP sp; + private ReceiverStatusStore receiverStatusStore; - EventNetworkChange event = NetworkChangeReceiver.grabNetworkStatus(context); - if (event != null) RxBus.Companion.getINSTANCE().send(event); + @Inject + public NsClientReceiverDelegate( + AAPSLogger aapsLogger, + Context context, + RxBusWrapper rxBus, + ResourceHelper resourceHelper, + SP sp, + ReceiverStatusStore receiverStatusStore + ) { + this.aapsLogger = aapsLogger; + this.context = context; + this.rxBus = rxBus; + this.resourceHelper = resourceHelper; + this.sp = sp; + this.receiverStatusStore = receiverStatusStore; + } + + void grabReceiversState() { + + receiverStatusStore.updateNetworkStatus(); EventChargingState eventChargingState = ChargingStateReceiver.grabChargingState(context); - if (eventChargingState != null) RxBus.Companion.getINSTANCE().send(eventChargingState); + rxBus.send(eventChargingState); } void onStatusEvent(EventPreferenceChange ev) { - if (ev.isChanged(MainApp.resources(), R.string.key_ns_wifionly) || - ev.isChanged(MainApp.resources(), R.string.key_ns_wifi_ssids) || - ev.isChanged(MainApp.resources(), R.string.key_ns_allowroaming) + if (ev.isChanged(resourceHelper, R.string.key_ns_wifionly) || + ev.isChanged(resourceHelper, R.string.key_ns_wifi_ssids) || + ev.isChanged(resourceHelper, R.string.key_ns_allowroaming) ) { - EventNetworkChange event = NetworkChangeReceiver.grabNetworkStatus(MainApp.instance().getApplicationContext()); - if (event != null) - RxBus.Companion.getINSTANCE().send(event); - } else if (ev.isChanged(MainApp.resources(), R.string.key_ns_chargingonly)) { - EventChargingState event = ChargingStateReceiver.grabChargingState(MainApp.instance().getApplicationContext()); - if (event != null) - RxBus.Companion.getINSTANCE().send(event); + receiverStatusStore.updateNetworkStatus(); + } else if (ev.isChanged(resourceHelper, R.string.key_ns_chargingonly)) { + EventChargingState event = ChargingStateReceiver.grabChargingState(context); + rxBus.send(event); } } @@ -70,12 +91,12 @@ class NsClientReceiverDelegate { boolean newAllowedState = allowedChargingState && allowedNetworkState; if (newAllowedState != allowed) { allowed = newAllowedState; - RxBus.Companion.getINSTANCE().send(new EventPreferenceChange(MainApp.gs(R.string.key_nsclientinternal_paused))); + rxBus.send(new EventPreferenceChange(resourceHelper.gs(R.string.key_nsclientinternal_paused))); } } boolean calculateStatus(final EventChargingState ev) { - boolean chargingOnly = SP.getBoolean(R.string.key_ns_chargingonly, false); + boolean chargingOnly = sp.getBoolean(R.string.key_ns_chargingonly, false); boolean newAllowedState = true; if (!ev.isCharging() && chargingOnly) { @@ -86,9 +107,9 @@ class NsClientReceiverDelegate { } boolean calculateStatus(final EventNetworkChange ev) { - boolean wifiOnly = SP.getBoolean(R.string.key_ns_wifionly, false); - String allowedSSIDs = SP.getString(R.string.key_ns_wifi_ssids, ""); - boolean allowRoaming = SP.getBoolean(R.string.key_ns_allowroaming, true); + boolean wifiOnly = sp.getBoolean(R.string.key_ns_wifionly, false); + String allowedSSIDs = sp.getString(R.string.key_ns_wifi_ssids, ""); + boolean allowRoaming = sp.getBoolean(R.string.key_ns_allowroaming, true); boolean newAllowedState = true; diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt index e21f67957d..9d1a66c29e 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt @@ -26,7 +26,7 @@ import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolS import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolUpdateGUI import info.nightscout.androidaps.plugins.general.tidepool.utils.RateLimit import info.nightscout.androidaps.receivers.ChargingStateReceiver -import info.nightscout.androidaps.receivers.NetworkChangeReceiver +import info.nightscout.androidaps.receivers.ReceiverStatusStore import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.HtmlHelper import info.nightscout.androidaps.utils.T @@ -51,7 +51,8 @@ class TidepoolPlugin @Inject constructor( private val tidepoolUploader: TidepoolUploader, private val uploadChunk: UploadChunk, private val sp: SP, - private val rateLimit: RateLimit + private val rateLimit: RateLimit, + private val receiverStatusStore: ReceiverStatusStore ) : PluginBase(PluginDescription() .mainType(PluginType.GENERAL) .pluginName(R.string.tidepool) @@ -103,7 +104,7 @@ class TidepoolPlugin @Inject constructor( uploadChunk.setLastEnd(bgReading.date) if (isEnabled(PluginType.GENERAL) && (!sp.getBoolean(R.string.key_tidepool_only_while_charging, false) || ChargingStateReceiver.isCharging()) - && (!sp.getBoolean(R.string.key_tidepool_only_while_unmetered, false) || NetworkChangeReceiver.isWifiConnected()) + && (!sp.getBoolean(R.string.key_tidepool_only_while_unmetered, false) || receiverStatusStore.isWifiConnected) && rateLimit.rateLimit("tidepool-new-data-upload", T.mins(4).secs().toInt())) doUpload() }, { diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java deleted file mode 100644 index b5931ccc9a..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.java +++ /dev/null @@ -1,83 +0,0 @@ -package info.nightscout.androidaps.receivers; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.wifi.SupplicantState; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; -import androidx.annotation.Nullable; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.events.EventNetworkChange; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.logging.StacktraceLoggerWrapper; -import info.nightscout.androidaps.plugins.bus.RxBus; - -public class NetworkChangeReceiver extends BroadcastReceiver { - - private static Logger log = StacktraceLoggerWrapper.getLogger(L.CORE); - - private static EventNetworkChange lastEvent = null; - - @Override - public void onReceive(final Context context, final Intent intent) { - EventNetworkChange event = grabNetworkStatus(context); - if (event != null) - RxBus.Companion.getINSTANCE().send(event); - } - - @Nullable - public static EventNetworkChange grabNetworkStatus(final Context context) { - EventNetworkChange event = new EventNetworkChange(); - - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (cm == null) return null; - NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); - - if (activeNetwork != null) { - if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI && activeNetwork.isConnected()) { - event.setWifiConnected(true); - WifiManager wifiManager = (WifiManager) MainApp.instance().getApplicationContext().getSystemService(Context.WIFI_SERVICE); - if (wifiManager != null) { - WifiInfo wifiInfo = wifiManager.getConnectionInfo(); - if (wifiInfo.getSupplicantState() == SupplicantState.COMPLETED) { - event.setSsid(wifiInfo.getSSID()); - } - if (L.isEnabled(L.CORE)) - log.debug("NETCHANGE: Wifi connected. SSID: " + event.connectedSsid()); - } - } - - if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { - event.setMobileConnected(true); - event.setRoaming(activeNetwork.isRoaming()); - if (L.isEnabled(L.CORE)) - log.debug("NETCHANGE: Mobile connected. Roaming: " + event.getRoaming()); - } - } else { - if (L.isEnabled(L.CORE)) - log.debug("NETCHANGE: Disconnected."); - } - - lastEvent = event; - return event; - } - - public static boolean isWifiConnected() { - return lastEvent != null && lastEvent.getWifiConnected(); - } - - public static boolean isConnected() { - return lastEvent != null && (lastEvent.getWifiConnected() || lastEvent.getMobileConnected()); - } - - public static EventNetworkChange getLastEvent() { - return lastEvent; - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.kt new file mode 100644 index 0000000000..52a8894079 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/receivers/NetworkChangeReceiver.kt @@ -0,0 +1,59 @@ +package info.nightscout.androidaps.receivers + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.wifi.SupplicantState +import android.net.wifi.WifiManager +import dagger.android.DaggerBroadcastReceiver +import info.nightscout.androidaps.events.EventNetworkChange +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import javax.inject.Inject + +class NetworkChangeReceiver : DaggerBroadcastReceiver() { + @Inject lateinit var rxBus: RxBusWrapper + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var receiverStatusStore: ReceiverStatusStore + + override fun onReceive(context: Context, intent: Intent) { + super.onReceive(context, intent) + rxBus.send(grabNetworkStatus(context, aapsLogger)) + } + + fun grabNetworkStatus(context: Context, aapsLogger: AAPSLogger): EventNetworkChange { + val event = EventNetworkChange() + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val networks: Array = cm.allNetworks + networks.forEach { + val capabilities = cm.getNetworkCapabilities(it) + event.wifiConnected = event.wifiConnected || (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) + event.mobileConnected = event.mobileConnected || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + event.vpnConnected = event.vpnConnected || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) + // if (event.vpnConnected) aapsLogger.debug(LTag.CORE, "NETCHANGE: VPN connected.") + if (event.wifiConnected) { + val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + val wifiInfo = wifiManager.connectionInfo + if (wifiInfo.supplicantState == SupplicantState.COMPLETED) { + event.ssid = wifiInfo.ssid + // aapsLogger.debug(LTag.CORE, "NETCHANGE: Wifi connected. SSID: ${event.connectedSsid()}") + } + } + if (event.mobileConnected) { + event.mobileConnected = true + event.roaming = event.roaming || !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING) + event.metered = event.metered || !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) + aapsLogger.debug(LTag.CORE, "NETCHANGE: Mobile connected. Roaming: ${event.roaming} Metered: ${event.metered}") + } + // aapsLogger.info(LTag.CORE, "Network: $it") + } + + aapsLogger.debug(LTag.CORE, event.toString()) + receiverStatusStore.lastNetworkEvent = event + return event + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/ReceiverStatusStore.kt b/app/src/main/java/info/nightscout/androidaps/receivers/ReceiverStatusStore.kt new file mode 100644 index 0000000000..97df6107a7 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/receivers/ReceiverStatusStore.kt @@ -0,0 +1,23 @@ +package info.nightscout.androidaps.receivers + +import android.content.Context +import android.content.Intent +import info.nightscout.androidaps.events.EventNetworkChange +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ReceiverStatusStore @Inject constructor(val context: Context) { + + var lastNetworkEvent: EventNetworkChange? = null + + val isWifiConnected: Boolean + get() = lastNetworkEvent?.wifiConnected ?: false + + val isConnected: Boolean + get() = lastNetworkEvent?.wifiConnected ?: false || lastNetworkEvent?.mobileConnected ?: false + + fun updateNetworkStatus() { + context.sendBroadcast(Intent(context, NetworkChangeReceiver::class.java)) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/SmsReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/SmsReceiver.kt similarity index 70% rename from app/src/main/java/info/nightscout/androidaps/receivers/SmsReceiver.java rename to app/src/main/java/info/nightscout/androidaps/receivers/SmsReceiver.kt index f527375349..ed5b1201cf 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/SmsReceiver.java +++ b/app/src/main/java/info/nightscout/androidaps/receivers/SmsReceiver.kt @@ -1,7 +1,7 @@ -package info.nightscout.androidaps.receivers; +package info.nightscout.androidaps.receivers /** * Forward received SMS intents. This is a separate class, because unlike local broadcasts handled by DataReceiver, * receiving SMS requires a special permission in the manifest, which necessitates a separate receiver. */ -public class SmsReceiver extends DataReceiver {} +class SmsReceiver : DataReceiver() \ No newline at end of file diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTestBase.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTestBase.kt index 221a1989ab..dc3e043b03 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTestBase.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTestBase.kt @@ -1,9 +1,11 @@ package info.nightscout.androidaps.plugins.general.automation.triggers +import android.content.Context import dagger.android.AndroidInjector import dagger.android.HasAndroidInjector import info.nightscout.androidaps.TestBase import info.nightscout.androidaps.TestBaseWithProfile +import info.nightscout.androidaps.events.EventNetworkChange import info.nightscout.androidaps.interfaces.ActivePluginProvider import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.plugins.bus.RxBusWrapper @@ -13,9 +15,11 @@ import info.nightscout.androidaps.plugins.general.automation.elements.StaticLabe import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import info.nightscout.androidaps.receivers.ReceiverStatusStore import info.nightscout.androidaps.services.LastLocationDataContainer import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.sharedPreferences.SP +import org.junit.Before import org.mockito.Mock import org.powermock.core.classloader.annotations.PrepareForTest @@ -27,6 +31,14 @@ open class TriggerTestBase : TestBaseWithProfile() { @Mock lateinit var locationDataContainer: LastLocationDataContainer @Mock lateinit var activePlugin: ActivePluginProvider @Mock lateinit var iobCobCalculatorPlugin: IobCobCalculatorPlugin + @Mock lateinit var context: Context + + lateinit var receiverStatusStore: ReceiverStatusStore + + @Before + fun prepareMock1() { + receiverStatusStore = ReceiverStatusStore(context) + } var injector: HasAndroidInjector = HasAndroidInjector { AndroidInjector { @@ -44,6 +56,9 @@ open class TriggerTestBase : TestBaseWithProfile() { if (it is TriggerBg) { it.profileFunction = profileFunction } + if (it is TriggerWifiSsid) { + it.receiverStatusStore = receiverStatusStore + } if (it is InputBg) { it.profileFunction = profileFunction } diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerWifiSsidTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerWifiSsidTest.kt index 7412701d8e..7dcdac3ea4 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerWifiSsidTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerWifiSsidTest.kt @@ -30,7 +30,7 @@ class TriggerWifiSsidTest : TriggerTestBase() { @Test fun shouldRunTest() { val e = EventNetworkChange() - PowerMockito.`when`(NetworkChangeReceiver.getLastEvent()).thenReturn(e) + receiverStatusStore.lastNetworkEvent = e var t: TriggerWifiSsid = TriggerWifiSsid(injector).setValue("aSSID").comparator(Comparator.Compare.IS_EQUAL) e.wifiConnected = false Assert.assertFalse(t.shouldRun()) @@ -45,7 +45,7 @@ class TriggerWifiSsidTest : TriggerTestBase() { Assert.assertTrue(t.shouldRun()) // no network data - PowerMockito.`when`(NetworkChangeReceiver.getLastEvent()).thenReturn(null) + receiverStatusStore.lastNetworkEvent = null Assert.assertFalse(t.shouldRun()) } diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegateTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegateTest.kt index de55bcaaec..c7d49cdfa5 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegateTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegateTest.kt @@ -6,7 +6,11 @@ import info.nightscout.androidaps.R import info.nightscout.androidaps.TestBase import info.nightscout.androidaps.events.EventChargingState import info.nightscout.androidaps.events.EventNetworkChange -import info.nightscout.androidaps.utils.SP +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.receivers.ReceiverStatusStore +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP import org.junit.Assert import org.junit.Before import org.junit.Test @@ -16,6 +20,7 @@ import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyLong import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.`when` import org.mockito.Mockito.mock @@ -27,30 +32,39 @@ import org.powermock.modules.junit4.PowerMockRunner @PrepareForTest(MainApp::class, SP::class, Context::class) class NsClientReceiverDelegateTest : TestBase() { + @Mock lateinit var aapsLogger: AAPSLogger + @Mock lateinit var context: Context + @Mock lateinit var sp: SP + @Mock lateinit var resourceHelper: ResourceHelper + + lateinit var receiverStatusStore : ReceiverStatusStore + val rxBus: RxBusWrapper = RxBusWrapper() + private var sut: NsClientReceiverDelegate? = null @Before fun prepare() { + receiverStatusStore = ReceiverStatusStore(context) System.setProperty("disableFirebase", "true") PowerMockito.mockStatic(MainApp::class.java) val mainApp: MainApp = mock(MainApp::class.java) `when`(MainApp.instance()).thenReturn(mainApp) PowerMockito.mockStatic(SP::class.java) - `when`(SP.getLong(anyInt(), anyLong())).thenReturn(0L) - `when`(SP.getBoolean(anyInt(), anyBoolean())).thenReturn(false) - `when`(SP.getInt(anyInt(), anyInt())).thenReturn(0) - `when`(SP.getString(anyInt(), anyString())).thenReturn("") + `when`(sp.getLong(anyInt(), anyLong())).thenReturn(0L) + `when`(sp.getBoolean(anyInt(), anyBoolean())).thenReturn(false) + `when`(sp.getInt(anyInt(), anyInt())).thenReturn(0) + `when`(sp.getString(anyInt(), anyString())).thenReturn("") - sut = NsClientReceiverDelegate() + sut = NsClientReceiverDelegate(aapsLogger, context, rxBus, resourceHelper, sp, receiverStatusStore) } @Test fun testCalculateStatusChargingState() { PowerMockito.mockStatic(SP::class.java) - Mockito.`when`(SP.getBoolean(ArgumentMatchers.anyInt(), ArgumentMatchers.anyBoolean())).thenReturn(false) + Mockito.`when`(sp.getBoolean(ArgumentMatchers.anyInt(), ArgumentMatchers.anyBoolean())).thenReturn(false) var ev = EventChargingState(true) Assert.assertTrue(sut!!.calculateStatus(ev)) ev = EventChargingState(false) Assert.assertTrue(sut!!.calculateStatus(ev)) - Mockito.`when`(SP.getBoolean(ArgumentMatchers.anyInt(), ArgumentMatchers.anyBoolean())).thenReturn(true) + Mockito.`when`(sp.getBoolean(ArgumentMatchers.anyInt(), ArgumentMatchers.anyBoolean())).thenReturn(true) ev = EventChargingState(true) Assert.assertTrue(sut!!.calculateStatus(ev)) ev = EventChargingState(false) @@ -61,15 +75,15 @@ class NsClientReceiverDelegateTest : TestBase() { PowerMockito.mockStatic(SP::class.java) // wifiOnly = false // allowRoaming = false as well - Mockito.`when`(SP.getBoolean(ArgumentMatchers.anyInt(), ArgumentMatchers.anyBoolean())).thenReturn(false) - Mockito.`when`(SP.getString(ArgumentMatchers.anyInt(), ArgumentMatchers.anyString())).thenReturn("") + Mockito.`when`(sp.getBoolean(ArgumentMatchers.anyInt(), ArgumentMatchers.anyBoolean())).thenReturn(false) + Mockito.`when`(sp.getString(ArgumentMatchers.anyInt(), ArgumentMatchers.anyString())).thenReturn("") val ev = EventNetworkChange() ev.ssid = "" ev.mobileConnected = true ev.wifiConnected = true Assert.assertTrue(sut!!.calculateStatus(ev)) ev.ssid = "test" - Mockito.`when`(SP.getString(ArgumentMatchers.anyInt(), ArgumentMatchers.anyString())).thenReturn("\"test\"") + Mockito.`when`(sp.getString(ArgumentMatchers.anyInt(), ArgumentMatchers.anyString())).thenReturn("\"test\"") Assert.assertTrue(sut!!.calculateStatus(ev)) ev.ssid = "\"test\"" Assert.assertTrue(sut!!.calculateStatus(ev)) @@ -78,7 +92,7 @@ class NsClientReceiverDelegateTest : TestBase() { // wifiOnly = true // allowRoaming = true as well - Mockito.`when`(SP.getBoolean(ArgumentMatchers.anyInt(), ArgumentMatchers.anyBoolean())).thenReturn(true) + Mockito.`when`(sp.getBoolean(ArgumentMatchers.anyInt(), ArgumentMatchers.anyBoolean())).thenReturn(true) ev.wifiConnected = true Assert.assertTrue(sut!!.calculateStatus(ev)) ev.wifiConnected = false @@ -86,39 +100,39 @@ class NsClientReceiverDelegateTest : TestBase() { // wifiOnly = false // allowRoaming = false as well - Mockito.`when`(SP.getBoolean(ArgumentMatchers.anyInt(), ArgumentMatchers.anyBoolean())).thenReturn(false) + Mockito.`when`(sp.getBoolean(ArgumentMatchers.anyInt(), ArgumentMatchers.anyBoolean())).thenReturn(false) ev.wifiConnected = false ev.roaming = true Assert.assertTrue(!sut!!.calculateStatus(ev)) // wifiOnly = false // allowRoaming = true - Mockito.`when`(SP.getBoolean(R.string.key_ns_wifionly, false)).thenReturn(false) - Mockito.`when`(SP.getBoolean(R.string.key_ns_allowroaming, true)).thenReturn(true) + Mockito.`when`(sp.getBoolean(R.string.key_ns_wifionly, false)).thenReturn(false) + Mockito.`when`(sp.getBoolean(R.string.key_ns_allowroaming, true)).thenReturn(true) ev.wifiConnected = false ev.roaming = true Assert.assertTrue(sut!!.calculateStatus(ev)) // wifiOnly = true // allowRoaming = true - Mockito.`when`(SP.getBoolean(R.string.key_ns_wifionly, false)).thenReturn(true) - Mockito.`when`(SP.getBoolean(R.string.key_ns_allowroaming, true)).thenReturn(true) + Mockito.`when`(sp.getBoolean(R.string.key_ns_wifionly, false)).thenReturn(true) + Mockito.`when`(sp.getBoolean(R.string.key_ns_allowroaming, true)).thenReturn(true) ev.wifiConnected = false ev.roaming = true Assert.assertTrue(!sut!!.calculateStatus(ev)) // wifiOnly = true // allowRoaming = true - Mockito.`when`(SP.getBoolean(R.string.key_ns_wifionly, false)).thenReturn(true) - Mockito.`when`(SP.getBoolean(R.string.key_ns_allowroaming, true)).thenReturn(true) + Mockito.`when`(sp.getBoolean(R.string.key_ns_wifionly, false)).thenReturn(true) + Mockito.`when`(sp.getBoolean(R.string.key_ns_allowroaming, true)).thenReturn(true) ev.wifiConnected = true ev.roaming = true Assert.assertTrue(sut!!.calculateStatus(ev)) // wifiOnly = false // allowRoaming = false - Mockito.`when`(SP.getBoolean(R.string.key_ns_wifionly, false)).thenReturn(false) - Mockito.`when`(SP.getBoolean(R.string.key_ns_allowroaming, true)).thenReturn(false) + Mockito.`when`(sp.getBoolean(R.string.key_ns_wifionly, false)).thenReturn(false) + Mockito.`when`(sp.getBoolean(R.string.key_ns_allowroaming, true)).thenReturn(false) ev.wifiConnected = true ev.roaming = true Assert.assertTrue(sut!!.calculateStatus(ev)) From 51d363d4671fa684cb67128bf3b239e0187017c8 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 31 Mar 2020 09:48:46 +0200 Subject: [PATCH 18/39] TimeDateReceiver cleanup --- .../info/nightscout/androidaps/MainApp.java | 42 +++++++++---------- .../receivers/TimeDateOrTZChangeReceiver.kt | 12 +----- 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index cfa6594374..2d601689d7 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -114,9 +114,6 @@ public class MainApp extends DaggerApplication { static DatabaseHelper sDatabaseHelper = null; - DataReceiver dataReceiver = new DataReceiver(); - TimeDateOrTZChangeReceiver timeDateOrTZChangeReceiver; - private String CHANNEL_ID = "AndroidAPS-Ongoing"; // TODO: move to OngoingNotificationProvider (and dagger) private int ONGOING_NOTIFICATION_ID = 4711; // TODO: move to OngoingNotificationProvider (and dagger) private Notification notification; // TODO: move to OngoingNotificationProvider (and dagger) @@ -330,23 +327,28 @@ public class MainApp extends DaggerApplication { } private void registerLocalBroadcastReceiver() { - LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); - lbm.registerReceiver(dataReceiver, new IntentFilter(Intents.ACTION_NEW_TREATMENT)); - lbm.registerReceiver(dataReceiver, new IntentFilter(Intents.ACTION_CHANGED_TREATMENT)); - lbm.registerReceiver(dataReceiver, new IntentFilter(Intents.ACTION_REMOVED_TREATMENT)); - lbm.registerReceiver(dataReceiver, new IntentFilter(Intents.ACTION_NEW_SGV)); - lbm.registerReceiver(dataReceiver, new IntentFilter(Intents.ACTION_NEW_PROFILE)); - lbm.registerReceiver(dataReceiver, new IntentFilter(Intents.ACTION_NEW_MBG)); - lbm.registerReceiver(dataReceiver, new IntentFilter(Intents.ACTION_NEW_CAL)); + IntentFilter filter = new IntentFilter(); + filter.addAction(Intents.ACTION_NEW_TREATMENT); + filter.addAction(Intents.ACTION_CHANGED_TREATMENT); + filter.addAction(Intents.ACTION_REMOVED_TREATMENT); + filter.addAction(Intents.ACTION_NEW_SGV); + filter.addAction(Intents.ACTION_NEW_PROFILE); + filter.addAction(Intents.ACTION_NEW_MBG); + filter.addAction(Intents.ACTION_NEW_CAL); + LocalBroadcastManager.getInstance(this).registerReceiver(new DataReceiver(), filter); - this.timeDateOrTZChangeReceiver = new TimeDateOrTZChangeReceiver(); - this.timeDateOrTZChangeReceiver.registerBroadcasts(this); + filter = new IntentFilter(); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_DATE_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + registerReceiver(new TimeDateOrTZChangeReceiver(), filter); + + filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + registerReceiver(new NetworkChangeReceiver(), filter); - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); - intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); - registerReceiver(new NetworkChangeReceiver(), intentFilter); registerReceiver(new ChargingStateReceiver(), new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); } @@ -421,11 +423,7 @@ public class MainApp extends DaggerApplication { @Override public void onTerminate() { - aapsLogger.debug(LTag.CORE, "onTerminate"); - - if (timeDateOrTZChangeReceiver != null) - unregisterReceiver(timeDateOrTZChangeReceiver); unregisterActivityLifecycleCallbacks(activityMonitor); keepAliveManager.cancelAlarm(this); super.onTerminate(); diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/TimeDateOrTZChangeReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/TimeDateOrTZChangeReceiver.kt index 14ace4c893..3aa1c2122d 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/TimeDateOrTZChangeReceiver.kt +++ b/app/src/main/java/info/nightscout/androidaps/receivers/TimeDateOrTZChangeReceiver.kt @@ -1,9 +1,7 @@ package info.nightscout.androidaps.receivers -import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.content.IntentFilter import dagger.android.DaggerBroadcastReceiver import info.nightscout.androidaps.interfaces.ActivePluginProvider import info.nightscout.androidaps.interfaces.PumpInterface @@ -18,19 +16,11 @@ class TimeDateOrTZChangeReceiver : DaggerBroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { super.onReceive(context, intent) val action = intent.action - val activePump: PumpInterface = activePlugin.getActivePump() + val activePump: PumpInterface = activePlugin.activePump aapsLogger.debug(LTag.PUMP, "Date, Time and/or TimeZone changed.") if (action != null) { aapsLogger.debug(LTag.PUMP, "Date, Time and/or TimeZone changed. Notifying pump driver.") activePump.timeDateOrTimeZoneChanged() } } - - fun registerBroadcasts(context: Context) { - val filter = IntentFilter() - filter.addAction(Intent.ACTION_TIME_CHANGED) - filter.addAction(Intent.ACTION_DATE_CHANGED) - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED) - context.registerReceiver(this, filter) - } } \ No newline at end of file From 533433afd8421d6697605d6c4321b41063a1100f Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 31 Mar 2020 10:10:48 +0200 Subject: [PATCH 19/39] DataService refactor --- .../dependencyInjection/ReceiversModule.kt | 4 ++- .../androidaps/receivers/DataReceiver.java | 25 ------------------- .../androidaps/receivers/DataReceiver.kt | 22 ++++++++++++++++ 3 files changed, 25 insertions(+), 26 deletions(-) delete mode 100644 app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.java create mode 100644 app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt index 08b3711a6c..9427d5555a 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt @@ -3,6 +3,7 @@ package info.nightscout.androidaps.dependencyInjection import dagger.Module import dagger.android.ContributesAndroidInjector import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkBluetoothStateReceiver +import info.nightscout.androidaps.receivers.DataReceiver import info.nightscout.androidaps.receivers.KeepAliveReceiver import info.nightscout.androidaps.receivers.NetworkChangeReceiver import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver @@ -11,8 +12,9 @@ import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver @Suppress("unused") abstract class ReceiversModule { + @ContributesAndroidInjector abstract fun contributesDataReceiver(): DataReceiver @ContributesAndroidInjector abstract fun contributesKeepAliveReceiver(): KeepAliveReceiver @ContributesAndroidInjector abstract fun contributesNetworkChangeReceiver(): NetworkChangeReceiver - @ContributesAndroidInjector abstract fun contributesTimeDateOrTZChangeReceiver(): TimeDateOrTZChangeReceiver @ContributesAndroidInjector abstract fun contributesRileyLinkBluetoothStateReceiver(): RileyLinkBluetoothStateReceiver + @ContributesAndroidInjector abstract fun contributesTimeDateOrTZChangeReceiver(): TimeDateOrTZChangeReceiver } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.java deleted file mode 100644 index 48b5d479f7..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.java +++ /dev/null @@ -1,25 +0,0 @@ -package info.nightscout.androidaps.receivers; - -import android.content.Context; -import android.content.Intent; -import androidx.legacy.content.WakefulBroadcastReceiver; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.logging.StacktraceLoggerWrapper; -import info.nightscout.androidaps.services.DataService; - -public class DataReceiver extends WakefulBroadcastReceiver { - private static Logger log = StacktraceLoggerWrapper.getLogger(L.DATASERVICE); - - @Override - public void onReceive(Context context, Intent intent) { - if (L.isEnabled(L.DATASERVICE)) - log.debug("onReceive " + intent); - startWakefulService(context, new Intent(context, DataService.class) - .setAction(intent.getAction()) - .putExtras(intent)); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt new file mode 100644 index 0000000000..f8fb246f22 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt @@ -0,0 +1,22 @@ +package info.nightscout.androidaps.receivers + +import android.content.Context +import android.content.Intent +import androidx.legacy.content.WakefulBroadcastReceiver +import dagger.android.AndroidInjection +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.services.DataService +import javax.inject.Inject + +open class DataReceiver : WakefulBroadcastReceiver() { + @Inject lateinit var aapsLogger: AAPSLogger + + override fun onReceive(context: Context, intent: Intent) { + AndroidInjection.inject(this, context) + aapsLogger.debug(LTag.DATASERVICE, "onReceive $intent") + startWakefulService(context, Intent(context, DataService::class.java) + .setAction(intent.action) + .putExtras(intent)) + } +} \ No newline at end of file From 416f5b7cabd9304283a48f131d6890104b1b5ab7 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 31 Mar 2020 10:18:53 +0200 Subject: [PATCH 20/39] AutoStartReceiver refactor --- .../receivers/AutoStartReceiver.java | 21 ------------------- .../androidaps/receivers/AutoStartReceiver.kt | 18 ++++++++++++++++ .../androidaps/receivers/DataReceiver.kt | 2 ++ 3 files changed, 20 insertions(+), 21 deletions(-) delete mode 100644 app/src/main/java/info/nightscout/androidaps/receivers/AutoStartReceiver.java create mode 100644 app/src/main/java/info/nightscout/androidaps/receivers/AutoStartReceiver.kt diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/AutoStartReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/AutoStartReceiver.java deleted file mode 100644 index 8abfb22953..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/receivers/AutoStartReceiver.java +++ /dev/null @@ -1,21 +0,0 @@ -package info.nightscout.androidaps.receivers; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.Build; - -import info.nightscout.androidaps.plugins.general.persistentNotification.DummyService; - -public class AutoStartReceiver extends BroadcastReceiver { - public AutoStartReceiver() { - } - - @Override - public void onReceive(Context context, Intent intent) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - context.startForegroundService(new Intent(context, DummyService.class)); - else - context.startService(new Intent(context, DummyService.class)); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/AutoStartReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/AutoStartReceiver.kt new file mode 100644 index 0000000000..7b2eb03f96 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/receivers/AutoStartReceiver.kt @@ -0,0 +1,18 @@ +package info.nightscout.androidaps.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.os.Build +import info.nightscout.androidaps.plugins.general.persistentNotification.DummyService + +class AutoStartReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_BOOT_COMPLETED) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + context.startForegroundService(Intent(context, DummyService::class.java)) + else + context.startService(Intent(context, DummyService::class.java)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt index f8fb246f22..484e19a1d5 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt +++ b/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt @@ -9,6 +9,8 @@ import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.services.DataService import javax.inject.Inject +// We are not ready to switch to JobScheduler +@Suppress("DEPRECATION") open class DataReceiver : WakefulBroadcastReceiver() { @Inject lateinit var aapsLogger: AAPSLogger From 7e6501bdeddca99fe35ff71f903cec20932bc603 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 31 Mar 2020 15:49:57 +0200 Subject: [PATCH 21/39] Logging in tests --- .../androidaps/logging/AAPSLoggerTest.kt | 60 +++++++++++++++++++ .../nightscout/androidaps/logging/LTag.kt | 42 ++++++------- .../info/nightscout/androidaps/TestBase.kt | 4 ++ .../androidaps/TestBaseWithProfile.kt | 2 +- .../androidaps/data/QuickWizardTest.kt | 1 - .../nightscout/androidaps/db/BgReadingTest.kt | 1 - .../androidaps/interfaces/ConstraintTest.kt | 2 - .../plugins/aps/loop/LoopPluginTest.kt | 1 - .../configBuilder/ConfigBuilderPluginTest.kt | 1 - .../dstHelper/DstHelperPluginTest.kt | 1 - .../objectives/ObjectivesPluginTest.kt | 1 - .../SignatureVerifierPluginTest.kt | 1 - .../storage/StorageConstraintPluginTest.kt | 1 - .../VersionCheckerUtilsKtTest.kt | 1 - .../general/automation/AutomationEventTest.kt | 1 - .../actions/ActionNotificationTest.kt | 1 - .../maintenance/MaintenancePluginTest.kt | 1 - .../nsclient/NsClientReceiverDelegateTest.kt | 1 - .../smsCommunicator/AuthRequestTest.kt | 1 - .../insulin/InsulinOrefFreePeakPluginTest.kt | 1 - .../GlucoseStatusTest.kt | 1 - .../IobCobCalculatorPluginTest.kt | 1 - .../plugins/pump/combo/ComboPluginTest.kt | 1 - .../bolusInfo/DetailedBolusInfoStorageTest.kt | 2 - .../plugins/pump/danaR/comm/DanaRTestBase.kt | 1 - .../pump/virtual/VirtualPumpPluginUTest.kt | 1 - .../AbstractSensitivityPluginTest.kt | 1 - .../plugins/source/GlimpPluginTest.kt | 1 - .../plugins/source/MM640GPluginTest.kt | 1 - .../plugins/source/NSClientPluginTest.kt | 1 - .../plugins/source/XdripPluginTest.kt | 1 - .../utils/wizard/BolusWizardTest.kt | 1 - 32 files changed, 86 insertions(+), 52 deletions(-) create mode 100644 app/src/main/java/info/nightscout/androidaps/logging/AAPSLoggerTest.kt diff --git a/app/src/main/java/info/nightscout/androidaps/logging/AAPSLoggerTest.kt b/app/src/main/java/info/nightscout/androidaps/logging/AAPSLoggerTest.kt new file mode 100644 index 0000000000..0503db143f --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/logging/AAPSLoggerTest.kt @@ -0,0 +1,60 @@ +package info.nightscout.androidaps.logging + +/** + * Created by adrian on 2019-12-27. + */ + +class AAPSLoggerTest : AAPSLogger { + + override fun debug(message: String) { + println("DEBUG: $message") + } + + override fun debug(enable: Boolean, tag: LTag, message: String) { + println("DEBUG: " + message) + } + + override fun debug(tag: LTag, message: String) { + println("DEBUG: : " + tag.tag + " " + message) + } + + override fun debug(tag: LTag, format: String, vararg arguments: Any?) { + println("DEBUG: : " + tag.tag + " " + String.format(format, arguments)) + } + + override fun warn(tag: LTag, message: String) { + println("WARN: " + tag.tag + " " + message) + } + + override fun info(tag: LTag, message: String) { + println("INFO: " + tag.tag + " " + message) + } + + override fun info(tag: LTag, format: String, vararg arguments: Any?) { + println("INFO: : " + tag.tag + " " + String.format(format, arguments)) + } + + override fun error(tag: LTag, message: String) { + println("ERROR: " + tag.tag + " " + message) + } + + override fun error(message: String) { + println("ERROR: " + message) + } + + override fun error(message: String, throwable: Throwable) { + println("ERROR: " + message + " " + throwable) + } + + override fun error(format: String, vararg arguments: Any?) { + println("ERROR: : " + String.format(format, arguments)) + } + + override fun error(tag: LTag, message: String, throwable: Throwable) { + println("ERROR: " + tag.tag + " " + message + " " + throwable) + } + + override fun error(tag: LTag, format: String, vararg arguments: Any?) { + println("ERROR: : " + tag.tag + " " + String.format(format, arguments)) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/logging/LTag.kt b/app/src/main/java/info/nightscout/androidaps/logging/LTag.kt index 775a789eb9..fc88377d6b 100644 --- a/app/src/main/java/info/nightscout/androidaps/logging/LTag.kt +++ b/app/src/main/java/info/nightscout/androidaps/logging/LTag.kt @@ -1,30 +1,30 @@ package info.nightscout.androidaps.logging -enum class LTag(val tag: String, val defaultValue : Boolean = false, val requiresRestart: Boolean = false) { - CORE("CORE", defaultValue = false), - APS("APS", defaultValue = false), - AUTOSENS("AUTOSENS"), - AUTOMATION("AUTOMATION", defaultValue = false), - BGSOURCE("BGSOURCE", defaultValue = false), +enum class LTag(val tag: String, val defaultValue : Boolean = true, val requiresRestart: Boolean = false) { + CORE("CORE"), + APS("APS"), + AUTOSENS("AUTOSENS", defaultValue = false), + AUTOMATION("AUTOMATION"), + BGSOURCE("BGSOURCE"), CONFIGBUILDER("CONFIGBUILDER"), - CONSTRAINTS("CONSTRAINTS", defaultValue = false), - DATABASE("DATABASE", defaultValue = false), - DATAFOOD("DATAFOOD"), - DATASERVICE("DATASERVICE", defaultValue = false), - DATATREATMENTS("DATATREATMENTS", defaultValue = false), + CONSTRAINTS("CONSTRAINTS"), + DATABASE("DATABASE"), + DATAFOOD("DATAFOOD", defaultValue = false), + DATASERVICE("DATASERVICE"), + DATATREATMENTS("DATATREATMENTS"), EVENTS("EVENTS", defaultValue = false, requiresRestart = true), GLUCOSE("GLUCOSE"), - LOCATION("LOCATION", defaultValue = false), - NOTIFICATION("NOTIFICATION", defaultValue = false), - NSCLIENT("NSCLIENT", defaultValue = false), + LOCATION("LOCATION"), + NOTIFICATION("NOTIFICATION"), + NSCLIENT("NSCLIENT"), OVERVIEW("OVERVIEW", defaultValue = false), - PUMP("PUMP", defaultValue = false), - PUMPBTCOMM("PUMPBTCOMM"), - PUMPCOMM("PUMPCOMM", defaultValue = false), - PUMPQUEUE("PUMPQUEUE", defaultValue = false), - PROFILE("PROFILE", defaultValue = false), - SMS("SMS", defaultValue = false), + PUMP("PUMP"), + PUMPBTCOMM("PUMPBTCOMM", defaultValue = false), + PUMPCOMM("PUMPCOMM"), + PUMPQUEUE("PUMPQUEUE"), + PROFILE("PROFILE"), + SMS("SMS"), TIDEPOOL("TIDEPOOL"), UI("UI", defaultValue = false), - WEAR("WEAR") + WEAR("WEAR", defaultValue = false) } \ No newline at end of file diff --git a/app/src/test/java/info/nightscout/androidaps/TestBase.kt b/app/src/test/java/info/nightscout/androidaps/TestBase.kt index 9455dea294..278a74d081 100644 --- a/app/src/test/java/info/nightscout/androidaps/TestBase.kt +++ b/app/src/test/java/info/nightscout/androidaps/TestBase.kt @@ -4,6 +4,7 @@ import dagger.android.AndroidInjector import dagger.android.HasAndroidInjector import info.nightscout.androidaps.data.Profile import info.nightscout.androidaps.data.ProfileStore +import info.nightscout.androidaps.logging.AAPSLoggerTest import org.json.JSONObject import org.junit.Before import org.junit.Rule @@ -13,6 +14,9 @@ import org.mockito.junit.MockitoRule import java.util.* open class TestBase { + + val aapsLogger = AAPSLoggerTest() + // Add a JUnit rule that will setup the @Mock annotated vars and log. // Another possibility would be to add `MockitoAnnotations.initMocks(this) to the setup method. @get:Rule diff --git a/app/src/test/java/info/nightscout/androidaps/TestBaseWithProfile.kt b/app/src/test/java/info/nightscout/androidaps/TestBaseWithProfile.kt index 65cf2aba05..d36c2f961b 100644 --- a/app/src/test/java/info/nightscout/androidaps/TestBaseWithProfile.kt +++ b/app/src/test/java/info/nightscout/androidaps/TestBaseWithProfile.kt @@ -7,6 +7,7 @@ import info.nightscout.androidaps.data.ProfileStore import info.nightscout.androidaps.db.ProfileSwitch import info.nightscout.androidaps.interfaces.ActivePluginProvider import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.AAPSLoggerTest import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.utils.FabricPrivacy @@ -18,7 +19,6 @@ import org.powermock.core.classloader.annotations.PrepareForTest @PrepareForTest(FabricPrivacy::class) open class TestBaseWithProfile : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var activePluginProvider: ActivePluginProvider @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var treatmentsPlugin: TreatmentsPlugin diff --git a/app/src/test/java/info/nightscout/androidaps/data/QuickWizardTest.kt b/app/src/test/java/info/nightscout/androidaps/data/QuickWizardTest.kt index 6019ba9926..69a166dc86 100644 --- a/app/src/test/java/info/nightscout/androidaps/data/QuickWizardTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/data/QuickWizardTest.kt @@ -26,7 +26,6 @@ import org.powermock.modules.junit4.PowerMockRunner @PrepareForTest(Profile::class) class QuickWizardTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var sp: SP @Mock lateinit var profileFunction: ProfileFunction @Mock lateinit var treatmentsPlugin: TreatmentsPlugin diff --git a/app/src/test/java/info/nightscout/androidaps/db/BgReadingTest.kt b/app/src/test/java/info/nightscout/androidaps/db/BgReadingTest.kt index abdce35c42..7f55f458d4 100644 --- a/app/src/test/java/info/nightscout/androidaps/db/BgReadingTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/db/BgReadingTest.kt @@ -30,7 +30,6 @@ import java.util.logging.Logger @PrepareForTest(MainApp::class, Logger::class, L::class, SP::class, GlucoseStatus::class) class BgReadingTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var defaultValueHelper: DefaultValueHelper @Mock lateinit var profileFunction: ProfileFunction @Mock lateinit var resourceHelper: ResourceHelper diff --git a/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintTest.kt b/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintTest.kt index 2a58b2ff58..a41d7c1630 100644 --- a/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/interfaces/ConstraintTest.kt @@ -19,8 +19,6 @@ import org.powermock.modules.junit4.PowerMockRunner @PrepareForTest(MainApp::class, SP::class) class ConstraintTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger - @Test fun doTests() { val b = Constraint(true) Assert.assertEquals(true, b.value()) diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/aps/loop/LoopPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/aps/loop/LoopPluginTest.kt index 7366b9ac77..804f68a91f 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/aps/loop/LoopPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/aps/loop/LoopPluginTest.kt @@ -33,7 +33,6 @@ import org.powermock.modules.junit4.PowerMockRunner @PrepareForTest(ConstraintChecker::class, VirtualPumpPlugin::class) class LoopPluginTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var sp: SP private val rxBus: RxBusWrapper = RxBusWrapper() @Mock lateinit var constraintChecker: ConstraintChecker diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderPluginTest.kt index 6477826fd4..ac412d2f02 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/configBuilder/ConfigBuilderPluginTest.kt @@ -25,7 +25,6 @@ class ConfigBuilderPluginTest : TestBase() { @Mock lateinit var treatmentsPlugin: Lazy @Mock lateinit var sp: SP - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var commandQueue: CommandQueueProvider @Mock lateinit var activePlugin: ActivePluginProvider diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPluginTest.kt index 554ce97596..d1f147d691 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/constraints/dstHelper/DstHelperPluginTest.kt @@ -22,7 +22,6 @@ import java.util.* @RunWith(PowerMockRunner::class) class DstHelperPluginTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var sp: SP @Mock lateinit var activePlugin: ActivePluginProvider diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPluginTest.kt index 7417fd5ec8..b4dc882882 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/constraints/objectives/ObjectivesPluginTest.kt @@ -22,7 +22,6 @@ import org.powermock.modules.junit4.PowerMockRunner @RunWith(PowerMockRunner::class) class ObjectivesPluginTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var activePlugin: ActivePluginProvider @Mock lateinit var sp: SP diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/constraints/signatureVerifier/SignatureVerifierPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/constraints/signatureVerifier/SignatureVerifierPluginTest.kt index d9a2acea04..706c663802 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/constraints/signatureVerifier/SignatureVerifierPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/constraints/signatureVerifier/SignatureVerifierPluginTest.kt @@ -13,7 +13,6 @@ import org.junit.Test import org.mockito.Mock class SignatureVerifierPluginTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var sp: SP @Mock lateinit var context: Context diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/constraints/storage/StorageConstraintPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/constraints/storage/StorageConstraintPluginTest.kt index 556802036b..31a960bcc3 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/constraints/storage/StorageConstraintPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/constraints/storage/StorageConstraintPluginTest.kt @@ -20,7 +20,6 @@ import org.powermock.modules.junit4.PowerMockRunner @RunWith(PowerMockRunner::class) class StorageConstraintPluginTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var resourceHelper: ResourceHelper private val rxBusWrapper = RxBusWrapper() diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/constraints/versionChecker/VersionCheckerUtilsKtTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/constraints/versionChecker/VersionCheckerUtilsKtTest.kt index aa7a4d4d34..17a9c171d0 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/constraints/versionChecker/VersionCheckerUtilsKtTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/constraints/versionChecker/VersionCheckerUtilsKtTest.kt @@ -15,7 +15,6 @@ class VersionCheckerUtilsKtTest : TestBase() { lateinit var versionCheckerUtils: VersionCheckerUtils - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var sp: SP @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var context: Context diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/AutomationEventTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/AutomationEventTest.kt index d8a9f5b584..363bf83b21 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/AutomationEventTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/AutomationEventTest.kt @@ -21,7 +21,6 @@ import org.powermock.modules.junit4.PowerMockRunner @RunWith(PowerMockRunner::class) class AutomationEventTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var loopPlugin: LoopPlugin @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var configBuilderPlugin: ConfigBuilderPlugin diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionNotificationTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionNotificationTest.kt index b9d9c35900..a2422d44d8 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionNotificationTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/actions/ActionNotificationTest.kt @@ -27,7 +27,6 @@ import org.powermock.modules.junit4.PowerMockRunner @PrepareForTest(NSUpload::class, RxBusWrapper::class) class ActionNotificationTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var rxBus: RxBusWrapper diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePluginTest.kt index 3405d5bb84..e66c001aee 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePluginTest.kt @@ -26,7 +26,6 @@ class MaintenancePluginTest : TestBase() { @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var sp: SP @Mock lateinit var nsSettingsStatus: NSSettingsStatus - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var buildHelper: BuildHelper lateinit var sut: MaintenancePlugin diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegateTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegateTest.kt index c7d49cdfa5..436d8481e7 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegateTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegateTest.kt @@ -32,7 +32,6 @@ import org.powermock.modules.junit4.PowerMockRunner @PrepareForTest(MainApp::class, SP::class, Context::class) class NsClientReceiverDelegateTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var context: Context @Mock lateinit var sp: SP @Mock lateinit var resourceHelper: ResourceHelper diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/AuthRequestTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/AuthRequestTest.kt index 90ea068d4d..83fb044221 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/AuthRequestTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/smsCommunicator/AuthRequestTest.kt @@ -26,7 +26,6 @@ import org.powermock.modules.junit4.PowerMockRunner @PrepareForTest(SmsCommunicatorPlugin::class, DateUtil::class, OneTimePassword::class) class AuthRequestTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var smsCommunicatorPlugin: SmsCommunicatorPlugin @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var otp: OneTimePassword diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefFreePeakPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefFreePeakPluginTest.kt index ce466847cc..e34407fcfe 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefFreePeakPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/insulin/InsulinOrefFreePeakPluginTest.kt @@ -30,7 +30,6 @@ class InsulinOrefFreePeakPluginTest : TestBase() { @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var rxBus: RxBusWrapper @Mock lateinit var profileFunction: ProfileFunction - @Mock lateinit var aapsLogger: AAPSLogger private var injector: HasAndroidInjector = HasAndroidInjector { AndroidInjector { diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculatorPlugin/GlucoseStatusTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculatorPlugin/GlucoseStatusTest.kt index 1cd079684f..d0a5f7ebeb 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculatorPlugin/GlucoseStatusTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculatorPlugin/GlucoseStatusTest.kt @@ -30,7 +30,6 @@ import java.util.* @PrepareForTest(IobCobCalculatorPlugin::class, DateUtil::class) class GlucoseStatusTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var iobCobCalculatorPlugin: IobCobCalculatorPlugin val injector = HasAndroidInjector { diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculatorPlugin/IobCobCalculatorPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculatorPlugin/IobCobCalculatorPluginTest.kt index 942bf37bf6..a21990f406 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculatorPlugin/IobCobCalculatorPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/iob/iobCobCalculatorPlugin/IobCobCalculatorPluginTest.kt @@ -32,7 +32,6 @@ import java.util.* @PrepareForTest(FabricPrivacy::class) class IobCobCalculatorPluginTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var sp: SP private val rxBus: RxBusWrapper = RxBusWrapper() @Mock lateinit var resourceHelper: ResourceHelper diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/pump/combo/ComboPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/pump/combo/ComboPluginTest.kt index d1f0616fe3..6b349711f0 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/pump/combo/ComboPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/pump/combo/ComboPluginTest.kt @@ -33,7 +33,6 @@ import org.powermock.modules.junit4.PowerMockRunner @PrepareForTest(MainApp::class, ConfigBuilderPlugin::class, ConstraintChecker::class, Context::class, CommandQueue::class) class ComboPluginTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var profileFunction: ProfileFunction @Mock lateinit var constraintChecker: ConstraintChecker diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/pump/common/bolusInfo/DetailedBolusInfoStorageTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/pump/common/bolusInfo/DetailedBolusInfoStorageTest.kt index e81010d078..28117e5583 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/pump/common/bolusInfo/DetailedBolusInfoStorageTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/pump/common/bolusInfo/DetailedBolusInfoStorageTest.kt @@ -14,8 +14,6 @@ import org.powermock.modules.junit4.PowerMockRunner @RunWith(PowerMockRunner::class) class DetailedBolusInfoStorageTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger - private val info1 = DetailedBolusInfo() private val info2 = DetailedBolusInfo() private val info3 = DetailedBolusInfo() diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaR/comm/DanaRTestBase.kt b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaR/comm/DanaRTestBase.kt index 9a1ed6b487..20c5bf6038 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaR/comm/DanaRTestBase.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/pump/danaR/comm/DanaRTestBase.kt @@ -10,7 +10,6 @@ import org.mockito.Mock open class DanaRTestBase : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var sp: SP @Mock lateinit var injector: HasAndroidInjector diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPluginUTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPluginUTest.kt index ed9ec5aa14..7a8c732c62 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPluginUTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPluginUTest.kt @@ -26,7 +26,6 @@ import org.powermock.modules.junit4.PowerMockRunner @PrepareForTest(FabricPrivacy::class) class VirtualPumpPluginUTest : TestBase() { - @Mock lateinit var aapsLogger: AAPSLogger val rxBus = RxBusWrapper() @Mock lateinit var fabricPrivacy: FabricPrivacy @Mock lateinit var resourceHelper: ResourceHelper diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/sensitivity/AbstractSensitivityPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/sensitivity/AbstractSensitivityPluginTest.kt index 4daa3f1b6c..000a718c01 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/sensitivity/AbstractSensitivityPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/sensitivity/AbstractSensitivityPluginTest.kt @@ -19,7 +19,6 @@ import org.powermock.modules.junit4.PowerMockRunner class AbstractSensitivityPluginTest : TestBase() { @Mock lateinit var pluginDescription: PluginDescription - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var sp: SP diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/source/GlimpPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/source/GlimpPluginTest.kt index 00d91d53c7..2fcd7b9a96 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/source/GlimpPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/source/GlimpPluginTest.kt @@ -13,7 +13,6 @@ import org.mockito.Mock class GlimpPluginTest : TestBase() { private lateinit var glimpPlugin: GlimpPlugin - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var resourceHelper: ResourceHelper @Before diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/source/MM640GPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/source/MM640GPluginTest.kt index a535ff0b9d..f80d5db9e0 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/source/MM640GPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/source/MM640GPluginTest.kt @@ -17,7 +17,6 @@ class MM640GPluginTest : TestBase() { private lateinit var mM640gPlugin: MM640gPlugin - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var resourceHelper: ResourceHelper @Before diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/source/NSClientPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/source/NSClientPluginTest.kt index e26a904a62..3cbdb53a7e 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/source/NSClientPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/source/NSClientPluginTest.kt @@ -18,7 +18,6 @@ class NSClientPluginTest : TestBase() { private lateinit var nsClientSourcePlugin: NSClientSourcePlugin - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var sp: SP diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/source/XdripPluginTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/source/XdripPluginTest.kt index 19fb35ecf3..ed1dc9c91d 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/source/XdripPluginTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/source/XdripPluginTest.kt @@ -17,7 +17,6 @@ class XdripPluginTest : TestBase() { private lateinit var xdripPlugin: XdripPlugin - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var resourceHelper: ResourceHelper @Before diff --git a/app/src/test/java/info/nightscout/androidaps/utils/wizard/BolusWizardTest.kt b/app/src/test/java/info/nightscout/androidaps/utils/wizard/BolusWizardTest.kt index 625ab928cc..e44b95c950 100644 --- a/app/src/test/java/info/nightscout/androidaps/utils/wizard/BolusWizardTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/utils/wizard/BolusWizardTest.kt @@ -37,7 +37,6 @@ class BolusWizardTest : TestBase() { private val PUMP_BOLUS_STEP = 0.1 - @Mock lateinit var aapsLogger: AAPSLogger @Mock lateinit var resourceHelper: ResourceHelper @Mock lateinit var profileFunction: ProfileFunction @Mock lateinit var constraintChecker: ConstraintChecker From e755ea4662f8be8df2be670b9db6faeee3c4ea52 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 31 Mar 2020 15:55:30 +0200 Subject: [PATCH 22/39] Remove deprecation warning --- .../java/info/nightscout/androidaps/receivers/DataReceiver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt index 484e19a1d5..b5fd362e57 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt +++ b/app/src/main/java/info/nightscout/androidaps/receivers/DataReceiver.kt @@ -2,7 +2,7 @@ package info.nightscout.androidaps.receivers import android.content.Context import android.content.Intent -import androidx.legacy.content.WakefulBroadcastReceiver +import androidx.legacy.content.* import dagger.android.AndroidInjection import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag From e5d7ffb53f078b19a711d5ba48278b72027a251d Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 31 Mar 2020 16:09:49 +0200 Subject: [PATCH 23/39] DismissNotificationService refactor --- .../dependencyInjection/ServicesModule.kt | 2 + .../DismissNotificationService.java | 38 -------------- .../DismissNotificationService.kt | 15 ++++++ .../notifications/NotificationStore.kt | 50 +++++++++++-------- 4 files changed, 46 insertions(+), 59 deletions(-) delete mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/DismissNotificationService.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/DismissNotificationService.kt diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ServicesModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ServicesModule.kt index 5bf16440f9..c43a8dfb7d 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ServicesModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ServicesModule.kt @@ -3,6 +3,7 @@ package info.nightscout.androidaps.dependencyInjection import dagger.Module import dagger.android.ContributesAndroidInjector import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService +import info.nightscout.androidaps.plugins.general.overview.notifications.DismissNotificationService import info.nightscout.androidaps.plugins.general.persistentNotification.DummyService import info.nightscout.androidaps.plugins.general.wear.wearintegration.WatchUpdaterService import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkService @@ -25,6 +26,7 @@ abstract class ServicesModule { @ContributesAndroidInjector abstract fun contributesAbstractDanaRExecutionService(): AbstractDanaRExecutionService @ContributesAndroidInjector abstract fun contributesAlarmSoundService(): AlarmSoundService @ContributesAndroidInjector abstract fun contributesDataService(): DataService + @ContributesAndroidInjector abstract fun contributesDismissNotificationService(): DismissNotificationService @ContributesAndroidInjector abstract fun contributesDummyService(): DummyService @ContributesAndroidInjector abstract fun contributesLocationService(): LocationService @ContributesAndroidInjector abstract fun contributesNSClientService(): NSClientService diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/DismissNotificationService.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/DismissNotificationService.java deleted file mode 100644 index 7491b8abd6..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/DismissNotificationService.java +++ /dev/null @@ -1,38 +0,0 @@ -package info.nightscout.androidaps.plugins.general.overview.notifications; - -import android.app.IntentService; -import android.app.PendingIntent; -import android.content.Intent; - -import androidx.annotation.Nullable; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.plugins.bus.RxBus; -import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; - -public class DismissNotificationService extends IntentService { - - /** - * Creates an IntentService. Invoked by your subclass's constructor. - * - * @param name Used to name the worker thread, important only for debugging. - */ - public DismissNotificationService(String name) { - super(name); - } - - public DismissNotificationService() { - super("DismissNotificationService"); - } - - @Override - protected void onHandleIntent(@Nullable Intent intent) { - RxBus.Companion.getINSTANCE().send(new EventDismissNotification(intent.getIntExtra("alertID", -1))); - } - - public static PendingIntent deleteIntent(int id) { - Intent intent = new Intent(MainApp.instance(), DismissNotificationService.class); - intent.putExtra("alertID", id); - return PendingIntent.getService(MainApp.instance(), id, intent, PendingIntent.FLAG_UPDATE_CURRENT); - } -} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/DismissNotificationService.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/DismissNotificationService.kt new file mode 100644 index 0000000000..3d4e2d0600 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/DismissNotificationService.kt @@ -0,0 +1,15 @@ +package info.nightscout.androidaps.plugins.general.overview.notifications + +import android.content.Intent +import dagger.android.DaggerIntentService +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification +import javax.inject.Inject + +class DismissNotificationService : DaggerIntentService(DismissNotificationService::class.simpleName) { + @Inject lateinit var rxBus: RxBusWrapper + + override fun onHandleIntent(intent: Intent) { + rxBus.send(EventDismissNotification(intent.getIntExtra("alertID", -1))) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.kt index 43d4327450..a14f33e33c 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/notifications/NotificationStore.kt @@ -3,6 +3,7 @@ package info.nightscout.androidaps.plugins.general.overview.notifications import android.annotation.SuppressLint import android.app.NotificationChannel import android.app.NotificationManager +import android.app.PendingIntent import android.content.Context import android.content.Intent import android.media.AudioManager @@ -16,7 +17,6 @@ import android.widget.TextView import androidx.cardview.widget.CardView import androidx.core.app.NotificationCompat import androidx.recyclerview.widget.RecyclerView -import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.R import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag @@ -36,7 +36,7 @@ class NotificationStore @Inject constructor( private val sp: SP, private val rxBus: RxBusWrapper, private val resourceHelper: ResourceHelper, - private val mainApp: MainApp + private val context: Context ) { var store: MutableList = ArrayList() @@ -66,15 +66,15 @@ class NotificationStore @Inject constructor( if (sp.getBoolean(R.string.key_raise_notifications_as_android_notifications, false) && n !is NotificationWithAction) { raiseSystemNotification(n) if (usesChannels && n.soundId != null && n.soundId != 0) { - val alarm = Intent(mainApp, AlarmSoundService::class.java) + val alarm = Intent(context, AlarmSoundService::class.java) alarm.putExtra("soundid", n.soundId) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) mainApp.startForegroundService(alarm) else mainApp.startService(alarm) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) context.startForegroundService(alarm) else context.startService(alarm) } } else { if (n.soundId != null && n.soundId != 0) { - val alarm = Intent(mainApp, AlarmSoundService::class.java) + val alarm = Intent(context, AlarmSoundService::class.java) alarm.putExtra("soundid", n.soundId) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) mainApp.startForegroundService(alarm) else mainApp.startService(alarm) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) context.startForegroundService(alarm) else context.startService(alarm) } } Collections.sort(store, NotificationComparator()) @@ -85,8 +85,8 @@ class NotificationStore @Inject constructor( for (i in store.indices) { if (store[i].id == id) { if (store[i].soundId != null) { - val alarm = Intent(mainApp, AlarmSoundService::class.java) - mainApp.stopService(alarm) + val alarm = Intent(context, AlarmSoundService::class.java) + context.stopService(alarm) } store.removeAt(i) return true @@ -108,16 +108,16 @@ class NotificationStore @Inject constructor( } private fun raiseSystemNotification(n: Notification) { - val mgr = mainApp.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val mgr = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val largeIcon = resourceHelper.decodeResource(resourceHelper.getIcon()) val smallIcon = resourceHelper.getNotificationIcon() val sound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM) - val notificationBuilder = NotificationCompat.Builder(mainApp, CHANNEL_ID) + val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(smallIcon) .setLargeIcon(largeIcon) .setContentText(n.text) .setPriority(NotificationCompat.PRIORITY_MAX) - .setDeleteIntent(DismissNotificationService.deleteIntent(n.id)) + .setDeleteIntent(deleteIntent(n.id)) if (n.level == Notification.URGENT) { notificationBuilder.setVibrate(longArrayOf(1000, 1000, 1000, 1000)) .setContentTitle(resourceHelper.gs(R.string.urgent_alarm)) @@ -129,10 +129,16 @@ class NotificationStore @Inject constructor( mgr.notify(n.id, notificationBuilder.build()) } + private fun deleteIntent(id: Int): PendingIntent { + val intent = Intent(context, DismissNotificationService::class.java) + intent.putExtra("alertID", id) + return PendingIntent.getService(context, id, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { usesChannels = true - val mNotificationManager = mainApp.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val mNotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager @SuppressLint("WrongConstant") val channel = NotificationChannel(CHANNEL_ID, CHANNEL_ID, NotificationManager.IMPORTANCE_HIGH) @@ -159,17 +165,19 @@ class NotificationStore @Inject constructor( clone.addAll(store) return clone } -/* - private fun unSnooze() { - if (sp.getBoolean(R.string.key_nsalarm_staledata, false)) { - val notification = Notification(Notification.NSALARM, resourceHelper.gs(R.string.nsalarm_staledata), Notification.URGENT) - sp.putLong(R.string.key_snoozedTo, System.currentTimeMillis()) - add(notification) - aapsLogger.debug(LTag.NOTIFICATION, "Snoozed to current time and added back notification!") + + /* + private fun unSnooze() { + if (sp.getBoolean(R.string.key_nsalarm_staledata, false)) { + val notification = Notification(Notification.NSALARM, resourceHelper.gs(R.string.nsalarm_staledata), Notification.URGENT) + sp.putLong(R.string.key_snoozedTo, System.currentTimeMillis()) + add(notification) + aapsLogger.debug(LTag.NOTIFICATION, "Snoozed to current time and added back notification!") + } } - } -*/ + */ inner class NotificationRecyclerViewAdapter internal constructor(private val notificationsList: List) : RecyclerView.Adapter() { + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): NotificationsViewHolder { val v = LayoutInflater.from(viewGroup.context).inflate(R.layout.overview_notification_item, viewGroup, false) return NotificationsViewHolder(v) From 6b80645e06cb32dffcc6ae871e11fb3c04d4d20b Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 31 Mar 2020 20:22:32 +0200 Subject: [PATCH 24/39] ChargingStateReceiver refactor --- .../info/nightscout/androidaps/MainApp.java | 8 +++- .../dependencyInjection/ReceiversModule.kt | 2 + .../nsclient/NsClientReceiverDelegate.java | 18 +------- .../general/tidepool/TidepoolPlugin.kt | 3 +- .../receivers/ChargingStateReceiver.java | 45 ------------------- .../receivers/ChargingStateReceiver.kt | 34 ++++++++++++++ .../receivers/ReceiverStatusStore.kt | 13 +++++- .../nsclient/NsClientReceiverDelegateTest.kt | 13 ++---- 8 files changed, 60 insertions(+), 76 deletions(-) delete mode 100644 app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java create mode 100644 app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.kt diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 2d601689d7..6270ac2ffb 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -96,6 +96,7 @@ import info.nightscout.androidaps.receivers.ChargingStateReceiver; import info.nightscout.androidaps.receivers.DataReceiver; import info.nightscout.androidaps.receivers.KeepAliveReceiver; import info.nightscout.androidaps.receivers.NetworkChangeReceiver; +import info.nightscout.androidaps.receivers.ReceiverStatusStore; import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver; import info.nightscout.androidaps.services.Intents; import info.nightscout.androidaps.utils.ActivityMonitor; @@ -121,6 +122,7 @@ public class MainApp extends DaggerApplication { @Inject PluginStore pluginStore; @Inject public HasAndroidInjector injector; @Inject AAPSLogger aapsLogger; + @Inject ReceiverStatusStore receiverStatusStore; @Inject ActivityMonitor activityMonitor; @Inject FabricPrivacy fabricPrivacy; @Inject ResourceHelper resourceHelper; @@ -349,7 +351,11 @@ public class MainApp extends DaggerApplication { filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); registerReceiver(new NetworkChangeReceiver(), filter); - registerReceiver(new ChargingStateReceiver(), new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + filter = new IntentFilter(); + filter.addAction(Intent.ACTION_POWER_CONNECTED); + filter.addAction(Intent.ACTION_POWER_DISCONNECTED); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + registerReceiver(new ChargingStateReceiver(), filter); } @Deprecated diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt index 9427d5555a..6c6890fa74 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ReceiversModule.kt @@ -3,6 +3,7 @@ package info.nightscout.androidaps.dependencyInjection import dagger.Module import dagger.android.ContributesAndroidInjector import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkBluetoothStateReceiver +import info.nightscout.androidaps.receivers.ChargingStateReceiver import info.nightscout.androidaps.receivers.DataReceiver import info.nightscout.androidaps.receivers.KeepAliveReceiver import info.nightscout.androidaps.receivers.NetworkChangeReceiver @@ -12,6 +13,7 @@ import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver @Suppress("unused") abstract class ReceiversModule { + @ContributesAndroidInjector abstract fun contributesChargingStateReceiver(): ChargingStateReceiver @ContributesAndroidInjector abstract fun contributesDataReceiver(): DataReceiver @ContributesAndroidInjector abstract fun contributesKeepAliveReceiver(): KeepAliveReceiver @ContributesAndroidInjector abstract fun contributesNetworkChangeReceiver(): NetworkChangeReceiver diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.java index 35454f1406..3bc2bde171 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegate.java @@ -1,7 +1,5 @@ package info.nightscout.androidaps.plugins.general.nsclient; -import android.content.Context; - import javax.inject.Inject; import javax.inject.Singleton; @@ -9,10 +7,7 @@ import info.nightscout.androidaps.R; import info.nightscout.androidaps.events.EventChargingState; import info.nightscout.androidaps.events.EventNetworkChange; import info.nightscout.androidaps.events.EventPreferenceChange; -import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.receivers.ChargingStateReceiver; -import info.nightscout.androidaps.receivers.NetworkChangeReceiver; import info.nightscout.androidaps.receivers.ReceiverStatusStore; import info.nightscout.androidaps.utils.resources.ResourceHelper; import info.nightscout.androidaps.utils.sharedPreferences.SP; @@ -24,8 +19,6 @@ class NsClientReceiverDelegate { private boolean allowedNetworkState = true; boolean allowed = true; - private AAPSLogger aapsLogger; - private Context context; private RxBusWrapper rxBus; private ResourceHelper resourceHelper; private SP sp; @@ -33,15 +26,11 @@ class NsClientReceiverDelegate { @Inject public NsClientReceiverDelegate( - AAPSLogger aapsLogger, - Context context, RxBusWrapper rxBus, ResourceHelper resourceHelper, SP sp, ReceiverStatusStore receiverStatusStore ) { - this.aapsLogger = aapsLogger; - this.context = context; this.rxBus = rxBus; this.resourceHelper = resourceHelper; this.sp = sp; @@ -51,10 +40,6 @@ class NsClientReceiverDelegate { void grabReceiversState() { receiverStatusStore.updateNetworkStatus(); - - EventChargingState eventChargingState = ChargingStateReceiver.grabChargingState(context); - rxBus.send(eventChargingState); - } void onStatusEvent(EventPreferenceChange ev) { @@ -64,8 +49,7 @@ class NsClientReceiverDelegate { ) { receiverStatusStore.updateNetworkStatus(); } else if (ev.isChanged(resourceHelper, R.string.key_ns_chargingonly)) { - EventChargingState event = ChargingStateReceiver.grabChargingState(context); - rxBus.send(event); + receiverStatusStore.broadcastChargingState(); } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt index 9d1a66c29e..701221e361 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/tidepool/TidepoolPlugin.kt @@ -25,7 +25,6 @@ import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolR import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolStatus import info.nightscout.androidaps.plugins.general.tidepool.events.EventTidepoolUpdateGUI import info.nightscout.androidaps.plugins.general.tidepool.utils.RateLimit -import info.nightscout.androidaps.receivers.ChargingStateReceiver import info.nightscout.androidaps.receivers.ReceiverStatusStore import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.HtmlHelper @@ -103,7 +102,7 @@ class TidepoolPlugin @Inject constructor( if (bgReading!!.date < uploadChunk.getLastEnd()) uploadChunk.setLastEnd(bgReading.date) if (isEnabled(PluginType.GENERAL) - && (!sp.getBoolean(R.string.key_tidepool_only_while_charging, false) || ChargingStateReceiver.isCharging()) + && (!sp.getBoolean(R.string.key_tidepool_only_while_charging, false) || receiverStatusStore.isCharging) && (!sp.getBoolean(R.string.key_tidepool_only_while_unmetered, false) || receiverStatusStore.isWifiConnected) && rateLimit.rateLimit("tidepool-new-data-upload", T.mins(4).secs().toInt())) doUpload() diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java b/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java deleted file mode 100644 index bf8e199c58..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.java +++ /dev/null @@ -1,45 +0,0 @@ -package info.nightscout.androidaps.receivers; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.BatteryManager; - -import info.nightscout.androidaps.events.EventChargingState; -import info.nightscout.androidaps.plugins.bus.RxBus; - -public class ChargingStateReceiver extends BroadcastReceiver { - - private static EventChargingState lastEvent; - - @Override - public void onReceive(Context context, Intent intent) { - EventChargingState event = grabChargingState(context); - - if (event != null) - RxBus.Companion.getINSTANCE().send(event); - lastEvent = event; - } - - public static EventChargingState grabChargingState(Context context) { - BatteryManager bm = (BatteryManager) context.getSystemService(Context.BATTERY_SERVICE); - - if (bm == null) - return new EventChargingState(false); - - int status = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_STATUS); - boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING - || status == BatteryManager.BATTERY_STATUS_FULL; - - EventChargingState event = new EventChargingState(isCharging); - return event; - } - - static public boolean isCharging() { - return lastEvent != null && lastEvent.isCharging(); - } - - static public EventChargingState getLastEvent() { - return lastEvent; - } -} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.kt new file mode 100644 index 0000000000..f6456fc749 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/receivers/ChargingStateReceiver.kt @@ -0,0 +1,34 @@ +package info.nightscout.androidaps.receivers + +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.BatteryManager +import dagger.android.DaggerBroadcastReceiver +import info.nightscout.androidaps.events.EventChargingState +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import javax.inject.Inject + +class ChargingStateReceiver : DaggerBroadcastReceiver() { + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var rxBus: RxBusWrapper + @Inject lateinit var receiverStatusStore: ReceiverStatusStore + + override fun onReceive(context: Context, intent: Intent) { + super.onReceive(context, intent) + rxBus.send(grabChargingState(context)) + aapsLogger.debug(LTag.CORE, receiverStatusStore.lastChargingEvent!!.toString()) + } + + private fun grabChargingState(context: Context): EventChargingState { + val batteryStatus: Intent? = IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { iFilter -> + context.registerReceiver(null, iFilter) + } + val status: Int = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1 + val isCharging: Boolean = status == BatteryManager.BATTERY_STATUS_CHARGING + || status == BatteryManager.BATTERY_STATUS_FULL + return EventChargingState(isCharging).also { receiverStatusStore.lastChargingEvent = it } + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/ReceiverStatusStore.kt b/app/src/main/java/info/nightscout/androidaps/receivers/ReceiverStatusStore.kt index 97df6107a7..a55b95f650 100644 --- a/app/src/main/java/info/nightscout/androidaps/receivers/ReceiverStatusStore.kt +++ b/app/src/main/java/info/nightscout/androidaps/receivers/ReceiverStatusStore.kt @@ -2,12 +2,14 @@ package info.nightscout.androidaps.receivers import android.content.Context import android.content.Intent +import info.nightscout.androidaps.events.EventChargingState import info.nightscout.androidaps.events.EventNetworkChange +import info.nightscout.androidaps.plugins.bus.RxBusWrapper import javax.inject.Inject import javax.inject.Singleton @Singleton -class ReceiverStatusStore @Inject constructor(val context: Context) { +class ReceiverStatusStore @Inject constructor(val context: Context, val rxBus: RxBusWrapper) { var lastNetworkEvent: EventNetworkChange? = null @@ -20,4 +22,13 @@ class ReceiverStatusStore @Inject constructor(val context: Context) { fun updateNetworkStatus() { context.sendBroadcast(Intent(context, NetworkChangeReceiver::class.java)) } + + var lastChargingEvent: EventChargingState? = null + + val isCharging: Boolean + get() = lastChargingEvent?.isCharging ?: false + + fun broadcastChargingState() { + lastChargingEvent?.let { rxBus.send(it) } + } } \ No newline at end of file diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegateTest.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegateTest.kt index 436d8481e7..7388178df6 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegateTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/nsclient/NsClientReceiverDelegateTest.kt @@ -6,7 +6,6 @@ import info.nightscout.androidaps.R import info.nightscout.androidaps.TestBase import info.nightscout.androidaps.events.EventChargingState import info.nightscout.androidaps.events.EventNetworkChange -import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.receivers.ReceiverStatusStore import info.nightscout.androidaps.utils.resources.ResourceHelper @@ -23,7 +22,6 @@ import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.`when` -import org.mockito.Mockito.mock import org.powermock.api.mockito.PowerMockito import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner @@ -36,24 +34,19 @@ class NsClientReceiverDelegateTest : TestBase() { @Mock lateinit var sp: SP @Mock lateinit var resourceHelper: ResourceHelper - lateinit var receiverStatusStore : ReceiverStatusStore + lateinit var receiverStatusStore: ReceiverStatusStore val rxBus: RxBusWrapper = RxBusWrapper() private var sut: NsClientReceiverDelegate? = null @Before fun prepare() { - receiverStatusStore = ReceiverStatusStore(context) - System.setProperty("disableFirebase", "true") - PowerMockito.mockStatic(MainApp::class.java) - val mainApp: MainApp = mock(MainApp::class.java) - `when`(MainApp.instance()).thenReturn(mainApp) - PowerMockito.mockStatic(SP::class.java) + receiverStatusStore = ReceiverStatusStore(context, rxBus) `when`(sp.getLong(anyInt(), anyLong())).thenReturn(0L) `when`(sp.getBoolean(anyInt(), anyBoolean())).thenReturn(false) `when`(sp.getInt(anyInt(), anyInt())).thenReturn(0) `when`(sp.getString(anyInt(), anyString())).thenReturn("") - sut = NsClientReceiverDelegate(aapsLogger, context, rxBus, resourceHelper, sp, receiverStatusStore) + sut = NsClientReceiverDelegate(rxBus, resourceHelper, sp, receiverStatusStore) } @Test fun testCalculateStatusChargingState() { From 5b539fd906037b7dbf486d1b6f4508c3d2435ac6 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 31 Mar 2020 21:16:18 +0200 Subject: [PATCH 25/39] remove unused code --- app/build.gradle | 11 ---- app/src/test/java/android/util/Log.java | 59 ------------------- .../automation/triggers/TriggerTestBase.kt | 2 +- 3 files changed, 1 insertion(+), 71 deletions(-) delete mode 100644 app/src/test/java/android/util/Log.java diff --git a/app/build.gradle b/app/build.gradle index adc28fb221..693bf61ccd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -125,12 +125,6 @@ android { jvmTarget = '1.8' } lintOptions { - // TODO remove once wear dependency com.google.android.gms:play-services-wearable:7.3.0 - // has been upgraded (requiring significant code changes), which currently fails release - // build with a deprecation warning - // abortOnError false - // (disabled entirely to avoid reports on the error, which would still be displayed - // and it's easy to overlook that it's ignored) checkReleaseBuilds false disable 'MissingTranslation' disable 'ExtraTranslation' @@ -300,11 +294,6 @@ dependencies { } testImplementation "org.skyscreamer:jsonassert:1.5.0" testImplementation "org.hamcrest:hamcrest-all:1.3" -/* - testImplementation("uk.org.lidalia:slf4j-test:1.2.0") { - exclude group: "com.google.guava", module: "guava" - } -*/ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" diff --git a/app/src/test/java/android/util/Log.java b/app/src/test/java/android/util/Log.java deleted file mode 100644 index bc2777f297..0000000000 --- a/app/src/test/java/android/util/Log.java +++ /dev/null @@ -1,59 +0,0 @@ -package android.util; - -import java.time.LocalDateTime; - -/** - * Created by andy on 3/10/19. - */ - -public class Log { - - // 03-10 13:44:42.847 12790-12888/info.nightscout.androidaps D/MedtronicHistoryData: - - static boolean isLoggingEnabled = false; - - - public static void setLoggingEnabled(boolean enabled) { - isLoggingEnabled = enabled; - } - - - private void writeLog(String type, String tag, String message) { - if (isLoggingEnabled) { - LocalDateTime ldt = LocalDateTime.now(); - System.out.println("DEBUG: " + tag + ": " + message); - } - } - - - public static int d(String tag, String msg) { - System.out.println("DEBUG: " + tag + ": " + msg); - return 0; - } - - - public static int v(String tag, String msg) { - System.out.println("VERBOSE: " + tag + ": " + msg); - return 0; - } - - - public static int i(String tag, String msg) { - System.out.println("INFO: " + tag + ": " + msg); - return 0; - } - - - public static int w(String tag, String msg) { - System.out.println("WARN: " + tag + ": " + msg); - return 0; - } - - - public static int e(String tag, String msg) { - System.out.println("ERROR: " + tag + ": " + msg); - return 0; - } - - // add other methods if required... -} diff --git a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTestBase.kt b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTestBase.kt index dc3e043b03..7de0a96001 100644 --- a/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTestBase.kt +++ b/app/src/test/java/info/nightscout/androidaps/plugins/general/automation/triggers/TriggerTestBase.kt @@ -37,7 +37,7 @@ open class TriggerTestBase : TestBaseWithProfile() { @Before fun prepareMock1() { - receiverStatusStore = ReceiverStatusStore(context) + receiverStatusStore = ReceiverStatusStore(context, rxBus) } var injector: HasAndroidInjector = HasAndroidInjector { From 56d0d2850d632029e2c68414b8160852acceb226 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Tue, 31 Mar 2020 21:54:49 +0200 Subject: [PATCH 26/39] remove some dependencies --- .../plugins/pump/medtronic/MedtronicFragment.kt | 14 +++++++++++--- .../pump/medtronic/comm/ui/MedtronicUIComm.java | 11 ++--------- .../service/RileyLinkMedtronicService.java | 5 ----- .../plugins/pump/medtronic/util/MedtronicUtil.java | 11 ----------- .../plugins/treatments/TreatmentsPlugin.java | 3 ++- 5 files changed, 15 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicFragment.kt index ad1d1054f6..05ff1cf463 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicFragment.kt @@ -37,6 +37,7 @@ import info.nightscout.androidaps.queue.Callback import info.nightscout.androidaps.queue.events.EventQueueChanged import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.OKDialog import info.nightscout.androidaps.utils.SetWarnColor import info.nightscout.androidaps.utils.T import info.nightscout.androidaps.utils.extensions.plusAssign @@ -86,13 +87,13 @@ class MedtronicFragment : DaggerFragment() { if (MedtronicUtil.getPumpStatus().verifyConfiguration()) { startActivity(Intent(context, MedtronicHistoryActivity::class.java)) } else { - MedtronicUtil.displayNotConfiguredDialog(context) + displayNotConfiguredDialog() } } medtronic_refresh.setOnClickListener { if (!MedtronicUtil.getPumpStatus().verifyConfiguration()) { - MedtronicUtil.displayNotConfiguredDialog(context) + displayNotConfiguredDialog() } else { medtronic_refresh.isEnabled = false medtronicPumpPlugin.resetStatusState() @@ -108,7 +109,7 @@ class MedtronicFragment : DaggerFragment() { if (MedtronicUtil.getPumpStatus().verifyConfiguration()) { startActivity(Intent(context, RileyLinkStatusActivity::class.java)) } else { - MedtronicUtil.displayNotConfiguredDialog(context) + displayNotConfiguredDialog() } } } @@ -247,6 +248,13 @@ class MedtronicFragment : DaggerFragment() { } } + private fun displayNotConfiguredDialog() { + context?.let { + OKDialog.show(it, resourceHelper.gs(R.string.combo_warning), + resourceHelper.gs(R.string.medtronic_error_operation_not_possible_no_configuration), null) + } + } + // GUI functions @Synchronized fun updateGUI() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUIComm.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUIComm.java index b2a5c97233..b4cebbb1cf 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUIComm.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/comm/ui/MedtronicUIComm.java @@ -1,7 +1,6 @@ package info.nightscout.androidaps.plugins.pump.medtronic.comm.ui; import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.logging.LTag; import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.RileyLinkConst; @@ -43,8 +42,7 @@ public class MedtronicUIComm { public synchronized MedtronicUITask executeCommand(MedtronicCommandType commandType, Object... parameters) { - if (isLogEnabled()) - aapsLogger.warn(LTag.PUMP, "Execute Command: " + commandType.name()); + aapsLogger.warn(LTag.PUMP, "Execute Command: " + commandType.name()); MedtronicUITask task = new MedtronicUITask(commandType, parameters); @@ -78,7 +76,7 @@ public class MedtronicUIComm { // } // } - if (!task.isReceived() && isLogEnabled()) { + if (!task.isReceived()) { aapsLogger.warn(LTag.PUMP, "Reply not received for " + commandType); } @@ -112,9 +110,4 @@ public class MedtronicUIComm { public void startTunning() { RileyLinkUtil.sendBroadcastMessage(RileyLinkConst.IPC.MSG_PUMP_tunePump); } - - private boolean isLogEnabled() { - return L.isEnabled(L.PUMP); - } - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/service/RileyLinkMedtronicService.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/service/RileyLinkMedtronicService.java index 84beb5871d..c3e403f723 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/service/RileyLinkMedtronicService.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/service/RileyLinkMedtronicService.java @@ -64,11 +64,6 @@ public class RileyLinkMedtronicService extends RileyLinkService { } - public static MedtronicCommunicationManager getCommunicationManager() { - return instance.medtronicCommunicationManager; - } - - @Override public void onConfigurationChanged(Configuration newConfig) { aapsLogger.warn(LTag.PUMPCOMM, "onConfigurationChanged"); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicUtil.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicUtil.java index 2ac9d4ef56..7f02a42d21 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicUtil.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/util/MedtronicUtil.java @@ -1,13 +1,10 @@ package info.nightscout.androidaps.plugins.pump.medtronic.util; -import android.content.Context; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.joda.time.LocalTime; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -15,8 +12,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; import info.nightscout.androidaps.interfaces.PluginType; import info.nightscout.androidaps.logging.L; import info.nightscout.androidaps.logging.StacktraceLoggerWrapper; @@ -41,7 +36,6 @@ import info.nightscout.androidaps.plugins.pump.medtronic.defs.PumpDeviceState; import info.nightscout.androidaps.plugins.pump.medtronic.driver.MedtronicPumpStatus; import info.nightscout.androidaps.plugins.pump.medtronic.events.EventMedtronicDeviceStatusChange; import info.nightscout.androidaps.plugins.pump.medtronic.service.RileyLinkMedtronicService; -import info.nightscout.androidaps.utils.OKDialog; import info.nightscout.androidaps.utils.resources.ResourceHelper; /** @@ -532,9 +526,4 @@ public class MedtronicUtil extends RileyLinkUtil { } - public static void displayNotConfiguredDialog(Context context) { - OKDialog.show(context, MainApp.gs(R.string.combo_warning), - MainApp.gs(R.string.medtronic_error_operation_not_possible_no_configuration), null); - } - } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java index 6615e47e22..9704d82f43 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java @@ -54,6 +54,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult; +import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin; import info.nightscout.androidaps.plugins.pump.medtronic.data.MedtronicHistoryData; import info.nightscout.androidaps.plugins.pump.medtronic.util.MedtronicUtil; import info.nightscout.androidaps.utils.DateUtil; @@ -571,7 +572,7 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface // return true if new record is created @Override public boolean addToHistoryTreatment(DetailedBolusInfo detailedBolusInfo, boolean allowUpdate) { - boolean medtronicPump = MedtronicUtil.isMedtronicPump(); + boolean medtronicPump = activePlugin.getActivePump() instanceof MedtronicPumpPlugin; getAapsLogger().debug(MedtronicHistoryData.doubleBolusDebug, LTag.DATATREATMENTS, "DoubleBolusDebug: addToHistoryTreatment::isMedtronicPump={} " + medtronicPump); From bc5a089c5f89dd5a2467bd08924fa9e147ea4f28 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Wed, 1 Apr 2020 09:07:36 +0200 Subject: [PATCH 27/39] GraphData refactor --- .../historyBrowser/HistoryBrowseActivity.java | 2 +- .../general/overview/OverviewFragment.java | 2 +- .../general/overview/graphData/GraphData.java | 726 ------------------ .../general/overview/graphData/GraphData.kt | 569 ++++++++++++++ .../graphExtensions/ScaledDataPoint.java | 6 + 5 files changed, 577 insertions(+), 728 deletions(-) delete mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt diff --git a/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.java b/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.java index 3c081e3309..6a47e3749b 100644 --- a/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.java +++ b/app/src/main/java/info/nightscout/androidaps/historyBrowser/HistoryBrowseActivity.java @@ -303,7 +303,7 @@ public class HistoryBrowseActivity extends NoSplashAppCompatActivity { // add basal data if (pump.getPumpDescription().isTempBasalCapable && showBasal) { - graphData.addBasals(fromTime, toTime, lowLine / graphData.maxY / 1.2d); + graphData.addBasals(fromTime, toTime, lowLine / graphData.getMaxY() / 1.2d); } // **** NOW line **** diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java index eef9521d09..a3d86716bb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/OverviewFragment.java @@ -1463,7 +1463,7 @@ public class OverviewFragment extends DaggerFragment implements View.OnClickList // add basal data if (pump.getPumpDescription().isTempBasalCapable && sp.getBoolean("showbasals", true)) { - graphData.addBasals(fromTime, now, lowLine / graphData.maxY / 1.2d); + graphData.addBasals(fromTime, now, lowLine / graphData.getMaxY() / 1.2d); } // add target line diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.java deleted file mode 100644 index bcb11363c8..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.java +++ /dev/null @@ -1,726 +0,0 @@ -package info.nightscout.androidaps.plugins.general.overview.graphData; - -import android.graphics.Color; -import android.graphics.DashPathEffect; -import android.graphics.Paint; - -import com.jjoe64.graphview.GraphView; -import com.jjoe64.graphview.series.BarGraphSeries; -import com.jjoe64.graphview.series.DataPoint; -import com.jjoe64.graphview.series.LineGraphSeries; -import com.jjoe64.graphview.series.Series; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.inject.Inject; - -import dagger.android.HasAndroidInjector; -import info.nightscout.androidaps.Constants; -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.data.IobTotal; -import info.nightscout.androidaps.data.Profile; -import info.nightscout.androidaps.db.BgReading; -import info.nightscout.androidaps.db.CareportalEvent; -import info.nightscout.androidaps.db.ExtendedBolus; -import info.nightscout.androidaps.db.ProfileSwitch; -import info.nightscout.androidaps.db.TempTarget; -import info.nightscout.androidaps.interfaces.ActivePluginProvider; -import info.nightscout.androidaps.interfaces.TreatmentsInterface; -import info.nightscout.androidaps.logging.AAPSLogger; -import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.aps.loop.APSResult; -import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; -import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults; -import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.AreaGraphSeries; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DataPointWithLabelInterface; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.DoubleDataPoint; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.FixedLineGraphSeries; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.PointsWithLabelGraphSeries; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.Scale; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.ScaledDataPoint; -import info.nightscout.androidaps.plugins.general.overview.graphExtensions.TimeAsXAxisLabelFormatter; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensData; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.BasalData; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.plugins.treatments.Treatment; -import info.nightscout.androidaps.utils.DecimalFormatter; -import info.nightscout.androidaps.utils.Round; -import info.nightscout.androidaps.utils.resources.ResourceHelper; - -/** - * Created by mike on 18.10.2017. - */ - -public class GraphData { - - @Inject AAPSLogger aapsLogger; - @Inject ProfileFunction profileFunction; - @Inject ResourceHelper resourceHelper; - @Inject ActivePluginProvider activePlugin; - - private GraphView graph; - public double maxY = Double.MIN_VALUE; - private double minY = Double.MAX_VALUE; - private List bgReadingsArray; - private String units; - private List series = new ArrayList<>(); - private TreatmentsInterface treatmentsPlugin; - - - private IobCobCalculatorPlugin iobCobCalculatorPlugin; // Cannot be injected: HistoryBrowser - - public GraphData(HasAndroidInjector injector, GraphView graph, IobCobCalculatorPlugin iobCobCalculatorPlugin) { - injector.androidInjector().inject(this); - units = profileFunction.getUnits(); - this.graph = graph; - this.iobCobCalculatorPlugin = iobCobCalculatorPlugin; - treatmentsPlugin = activePlugin.getActiveTreatments(); - } - - public void addBgReadings(long fromTime, long toTime, double lowLine, double highLine, List predictions) { - double maxBgValue = Double.MIN_VALUE; - //bgReadingsArray = MainApp.getDbHelper().getBgreadingsDataFromTime(fromTime, true); - bgReadingsArray = iobCobCalculatorPlugin.getBgReadings(); - List bgListArray = new ArrayList<>(); - - if (bgReadingsArray == null || bgReadingsArray.size() == 0) { - aapsLogger.debug(LTag.OVERVIEW, "No BG data."); - maxY = 10; - minY = 0; - return; - } - - for (BgReading bg : bgReadingsArray) { - if (bg.date < fromTime || bg.date > toTime) continue; - if (bg.value > maxBgValue) maxBgValue = bg.value; - bgListArray.add(bg); - } - if (predictions != null) { - Collections.sort(predictions, (o1, o2) -> Double.compare(o1.getX(), o2.getX())); - for (BgReading prediction : predictions) { - if (prediction.value >= 40) - bgListArray.add(prediction); - } - } - - maxBgValue = Profile.fromMgdlToUnits(maxBgValue, units); - maxBgValue = units.equals(Constants.MGDL) ? Round.roundTo(maxBgValue, 40d) + 80 : Round.roundTo(maxBgValue, 2d) + 4; - if (highLine > maxBgValue) maxBgValue = highLine; - int numOfVertLines = units.equals(Constants.MGDL) ? (int) (maxBgValue / 40 + 1) : (int) (maxBgValue / 2 + 1); - - DataPointWithLabelInterface[] bg = new DataPointWithLabelInterface[bgListArray.size()]; - bg = bgListArray.toArray(bg); - - - maxY = maxBgValue; - minY = 0; - // set manual y bounds to have nice steps - graph.getGridLabelRenderer().setNumVerticalLabels(numOfVertLines); - - addSeries(new PointsWithLabelGraphSeries<>(bg)); - } - - public void addInRangeArea(long fromTime, long toTime, double lowLine, double highLine) { - AreaGraphSeries inRangeAreaSeries; - - DoubleDataPoint[] inRangeAreaDataPoints = new DoubleDataPoint[]{ - new DoubleDataPoint(fromTime, lowLine, highLine), - new DoubleDataPoint(toTime, lowLine, highLine) - }; - inRangeAreaSeries = new AreaGraphSeries<>(inRangeAreaDataPoints); - inRangeAreaSeries.setColor(0); - inRangeAreaSeries.setDrawBackground(true); - inRangeAreaSeries.setBackgroundColor(resourceHelper.gc(R.color.inrangebackground)); - - addSeries(inRangeAreaSeries); - } - - // scale in % of vertical size (like 0.3) - public void addBasals(long fromTime, long toTime, double scale) { - LineGraphSeries basalsLineSeries; - LineGraphSeries absoluteBasalsLineSeries; - LineGraphSeries baseBasalsSeries; - LineGraphSeries tempBasalsSeries; - - double maxBasalValueFound = 0d; - Scale basalScale = new Scale(); - - List baseBasalArray = new ArrayList<>(); - List tempBasalArray = new ArrayList<>(); - List basalLineArray = new ArrayList<>(); - List absoluteBasalLineArray = new ArrayList<>(); - double lastLineBasal = 0; - double lastAbsoluteLineBasal = -1; - double lastBaseBasal = 0; - double lastTempBasal = 0; - for (long time = fromTime; time < toTime; time += 60 * 1000L) { - Profile profile = profileFunction.getProfile(time); - if (profile == null) continue; - BasalData basalData = iobCobCalculatorPlugin.getBasalData(profile, time); - double baseBasalValue = basalData.basal; - double absoluteLineValue = baseBasalValue; - double tempBasalValue = 0; - double basal = 0d; - if (basalData.isTempBasalRunning) { - absoluteLineValue = tempBasalValue = basalData.tempBasalAbsolute; - if (tempBasalValue != lastTempBasal) { - tempBasalArray.add(new ScaledDataPoint(time, lastTempBasal, basalScale)); - tempBasalArray.add(new ScaledDataPoint(time, basal = tempBasalValue, basalScale)); - } - if (lastBaseBasal != 0d) { - baseBasalArray.add(new ScaledDataPoint(time, lastBaseBasal, basalScale)); - baseBasalArray.add(new ScaledDataPoint(time, 0d, basalScale)); - lastBaseBasal = 0d; - } - } else { - if (baseBasalValue != lastBaseBasal) { - baseBasalArray.add(new ScaledDataPoint(time, lastBaseBasal, basalScale)); - baseBasalArray.add(new ScaledDataPoint(time, basal = baseBasalValue, basalScale)); - lastBaseBasal = baseBasalValue; - } - if (lastTempBasal != 0) { - tempBasalArray.add(new ScaledDataPoint(time, lastTempBasal, basalScale)); - tempBasalArray.add(new ScaledDataPoint(time, 0d, basalScale)); - } - } - - if (baseBasalValue != lastLineBasal) { - basalLineArray.add(new ScaledDataPoint(time, lastLineBasal, basalScale)); - basalLineArray.add(new ScaledDataPoint(time, baseBasalValue, basalScale)); - } - if (absoluteLineValue != lastAbsoluteLineBasal) { - absoluteBasalLineArray.add(new ScaledDataPoint(time, lastAbsoluteLineBasal, basalScale)); - absoluteBasalLineArray.add(new ScaledDataPoint(time, basal, basalScale)); - } - - lastAbsoluteLineBasal = absoluteLineValue; - lastLineBasal = baseBasalValue; - lastTempBasal = tempBasalValue; - maxBasalValueFound = Math.max(maxBasalValueFound, Math.max(tempBasalValue, baseBasalValue)); - } - - basalLineArray.add(new ScaledDataPoint(toTime, lastLineBasal, basalScale)); - baseBasalArray.add(new ScaledDataPoint(toTime, lastBaseBasal, basalScale)); - tempBasalArray.add(new ScaledDataPoint(toTime, lastTempBasal, basalScale)); - absoluteBasalLineArray.add(new ScaledDataPoint(toTime, lastAbsoluteLineBasal, basalScale)); - - ScaledDataPoint[] baseBasal = new ScaledDataPoint[baseBasalArray.size()]; - baseBasal = baseBasalArray.toArray(baseBasal); - baseBasalsSeries = new LineGraphSeries<>(baseBasal); - baseBasalsSeries.setDrawBackground(true); - baseBasalsSeries.setBackgroundColor(resourceHelper.gc(R.color.basebasal)); - baseBasalsSeries.setThickness(0); - - ScaledDataPoint[] tempBasal = new ScaledDataPoint[tempBasalArray.size()]; - tempBasal = tempBasalArray.toArray(tempBasal); - tempBasalsSeries = new LineGraphSeries<>(tempBasal); - tempBasalsSeries.setDrawBackground(true); - tempBasalsSeries.setBackgroundColor(resourceHelper.gc(R.color.tempbasal)); - tempBasalsSeries.setThickness(0); - - ScaledDataPoint[] basalLine = new ScaledDataPoint[basalLineArray.size()]; - basalLine = basalLineArray.toArray(basalLine); - basalsLineSeries = new LineGraphSeries<>(basalLine); - Paint paint = new Paint(); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(resourceHelper.getDisplayMetrics().scaledDensity * 2); - paint.setPathEffect(new DashPathEffect(new float[]{2, 4}, 0)); - paint.setColor(resourceHelper.gc(R.color.basal)); - basalsLineSeries.setCustomPaint(paint); - - ScaledDataPoint[] absoluteBasalLine = new ScaledDataPoint[absoluteBasalLineArray.size()]; - absoluteBasalLine = absoluteBasalLineArray.toArray(absoluteBasalLine); - absoluteBasalsLineSeries = new LineGraphSeries<>(absoluteBasalLine); - Paint absolutePaint = new Paint(); - absolutePaint.setStyle(Paint.Style.STROKE); - absolutePaint.setStrokeWidth(resourceHelper.getDisplayMetrics().scaledDensity * 2); - absolutePaint.setColor(resourceHelper.gc(R.color.basal)); - absoluteBasalsLineSeries.setCustomPaint(absolutePaint); - - basalScale.setMultiplier(maxY * scale / maxBasalValueFound); - - addSeries(baseBasalsSeries); - addSeries(tempBasalsSeries); - addSeries(basalsLineSeries); - addSeries(absoluteBasalsLineSeries); - } - - public void addTargetLine(long fromTime, long toTime, Profile profile, LoopPlugin.LastRun lastRun) { - LineGraphSeries targetsSeries; - - Scale targetsScale = new Scale(); - targetsScale.setMultiplier(1); - - List targetsSeriesArray = new ArrayList<>(); - double lastTarget = -1; - - if (lastRun != null && lastRun.constraintsProcessed != null) { - APSResult apsResult = lastRun.constraintsProcessed; - long latestPredictionsTime = apsResult.getLatestPredictionsTime(); - if (latestPredictionsTime > toTime) { - toTime = latestPredictionsTime; - } - } - - for (long time = fromTime; time < toTime; time += 5 * 60 * 1000L) { - TempTarget tt = treatmentsPlugin.getTempTargetFromHistory(time); - double value; - if (tt == null) { - value = Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, profileFunction.getUnits()); - } else { - value = Profile.fromMgdlToUnits(tt.target(), profileFunction.getUnits()); - } - if (lastTarget != value) { - if (lastTarget != -1) - targetsSeriesArray.add(new DataPoint(time, lastTarget)); - targetsSeriesArray.add(new DataPoint(time, value)); - } - lastTarget = value; - } - targetsSeriesArray.add(new DataPoint(toTime, lastTarget)); - - DataPoint[] targets = new DataPoint[targetsSeriesArray.size()]; - targets = targetsSeriesArray.toArray(targets); - targetsSeries = new LineGraphSeries<>(targets); - targetsSeries.setDrawBackground(false); - targetsSeries.setColor(resourceHelper.gc(R.color.tempTargetBackground)); - targetsSeries.setThickness(2); - - addSeries(targetsSeries); - } - - public void addTreatments(long fromTime, long endTime) { - List filteredTreatments = new ArrayList<>(); - - List treatments = treatmentsPlugin.getTreatmentsFromHistory(); - - for (int tx = 0; tx < treatments.size(); tx++) { - Treatment t = treatments.get(tx); - if (t.getX() < fromTime || t.getX() > endTime) continue; - if (t.isSMB && !t.isValid) continue; - t.setY(getNearestBg((long) t.getX())); - filteredTreatments.add(t); - } - - // ProfileSwitch - List profileSwitches = treatmentsPlugin.getProfileSwitchesFromHistory().getList(); - - for (int tx = 0; tx < profileSwitches.size(); tx++) { - DataPointWithLabelInterface t = profileSwitches.get(tx); - if (t.getX() < fromTime || t.getX() > endTime) continue; - filteredTreatments.add(t); - } - - // Extended bolus - if (!activePlugin.getActivePump().isFakingTempsByExtendedBoluses()) { - List extendedBoluses = treatmentsPlugin.getExtendedBolusesFromHistory().getList(); - - for (int tx = 0; tx < extendedBoluses.size(); tx++) { - DataPointWithLabelInterface t = extendedBoluses.get(tx); - if (t.getX() + t.getDuration() < fromTime || t.getX() > endTime) continue; - if (t.getDuration() == 0) continue; - t.setY(getNearestBg((long) t.getX())); - filteredTreatments.add(t); - } - } - - // Careportal - List careportalEvents = MainApp.getDbHelper().getCareportalEventsFromTime(fromTime - 6 * 60 * 60 * 1000, true); - - for (int tx = 0; tx < careportalEvents.size(); tx++) { - DataPointWithLabelInterface t = careportalEvents.get(tx); - if (t.getX() + t.getDuration() < fromTime || t.getX() > endTime) continue; - t.setY(getNearestBg((long) t.getX())); - filteredTreatments.add(t); - } - - DataPointWithLabelInterface[] treatmentsArray = new DataPointWithLabelInterface[filteredTreatments.size()]; - treatmentsArray = filteredTreatments.toArray(treatmentsArray); - addSeries(new PointsWithLabelGraphSeries<>(treatmentsArray)); - } - - private double getNearestBg(long date) { - if (bgReadingsArray == null) - return Profile.fromMgdlToUnits(100, units); - for (int r = 0; r < bgReadingsArray.size(); r++) { - BgReading reading = bgReadingsArray.get(r); - if (reading.date > date) continue; - return Profile.fromMgdlToUnits(reading.value, units); - } - return bgReadingsArray.size() > 0 - ? Profile.fromMgdlToUnits(bgReadingsArray.get(0).value, units) : Profile.fromMgdlToUnits(100, units); - } - - public void addActivity(long fromTime, long toTime, boolean useForScale, double scale) { - FixedLineGraphSeries actSeriesHist; - List actArrayHist = new ArrayList<>(); - FixedLineGraphSeries actSeriesPred; - List actArrayPred = new ArrayList<>(); - - double now = System.currentTimeMillis(); - Scale actScale = new Scale(); - IobTotal total; - double maxIAValue = 0; - - for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - Profile profile = profileFunction.getProfile(time); - double act; - if (profile == null) continue; - total = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile); - act = total.activity; - - if (time <= now) - actArrayHist.add(new ScaledDataPoint(time, act, actScale)); - else - actArrayPred.add(new ScaledDataPoint(time, act, actScale)); - - maxIAValue = Math.max(maxIAValue, Math.abs(act)); - } - - ScaledDataPoint[] actData = new ScaledDataPoint[actArrayHist.size()]; - actData = actArrayHist.toArray(actData); - actSeriesHist = new FixedLineGraphSeries<>(actData); - actSeriesHist.setDrawBackground(false); - actSeriesHist.setColor(resourceHelper.gc(R.color.activity)); - actSeriesHist.setThickness(3); - - addSeries(actSeriesHist); - - actData = new ScaledDataPoint[actArrayPred.size()]; - actData = actArrayPred.toArray(actData); - actSeriesPred = new FixedLineGraphSeries<>(actData); - - Paint paint = new Paint(); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(3); - paint.setPathEffect(new DashPathEffect(new float[]{4, 4}, 0)); - paint.setColor(resourceHelper.gc(R.color.activity)); - actSeriesPred.setCustomPaint(paint); - - if (useForScale) { - maxY = maxIAValue; - minY = -maxIAValue; - } - actScale.setMultiplier(maxY * scale / maxIAValue); - - addSeries(actSeriesPred); - } - - // scale in % of vertical size (like 0.3) - public void addIob(long fromTime, long toTime, boolean useForScale, double scale, boolean showPrediction) { - FixedLineGraphSeries iobSeries; - List iobArray = new ArrayList<>(); - Double maxIobValueFound = Double.MIN_VALUE; - double lastIob = 0; - Scale iobScale = new Scale(); - - for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - Profile profile = profileFunction.getProfile(time); - double iob = 0d; - if (profile != null) - iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile).iob; - if (Math.abs(lastIob - iob) > 0.02) { - if (Math.abs(lastIob - iob) > 0.2) - iobArray.add(new ScaledDataPoint(time, lastIob, iobScale)); - iobArray.add(new ScaledDataPoint(time, iob, iobScale)); - maxIobValueFound = Math.max(maxIobValueFound, Math.abs(iob)); - lastIob = iob; - } - } - - ScaledDataPoint[] iobData = new ScaledDataPoint[iobArray.size()]; - iobData = iobArray.toArray(iobData); - iobSeries = new FixedLineGraphSeries<>(iobData); - iobSeries.setDrawBackground(true); - iobSeries.setBackgroundColor(0x80FFFFFF & resourceHelper.gc(R.color.iob)); //50% - iobSeries.setColor(resourceHelper.gc(R.color.iob)); - iobSeries.setThickness(3); - - if (showPrediction) { - AutosensResult lastAutosensResult; - AutosensData autosensData = iobCobCalculatorPlugin.getLastAutosensDataSynchronized("GraphData"); - if (autosensData == null) - lastAutosensResult = new AutosensResult(); - else - lastAutosensResult = autosensData.autosensResult; - boolean isTempTarget = treatmentsPlugin.getTempTargetFromHistory(System.currentTimeMillis()) != null; - - List iobPred = new ArrayList<>(); - IobTotal[] iobPredArray = iobCobCalculatorPlugin.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget); - for (IobTotal i : iobPredArray) { - iobPred.add(i.setColor(resourceHelper.gc(R.color.iobPredAS))); - maxIobValueFound = Math.max(maxIobValueFound, Math.abs(i.iob)); - } - DataPointWithLabelInterface[] iobp = new DataPointWithLabelInterface[iobPred.size()]; - iobp = iobPred.toArray(iobp); - addSeries(new PointsWithLabelGraphSeries<>(iobp)); - - - List iobPred2 = new ArrayList<>(); - IobTotal[] iobPredArray2 = iobCobCalculatorPlugin.calculateIobArrayForSMB(new AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget); - for (IobTotal i : iobPredArray2) { - iobPred2.add(i.setColor(resourceHelper.gc(R.color.iobPred))); - maxIobValueFound = Math.max(maxIobValueFound, Math.abs(i.iob)); - } - DataPointWithLabelInterface[] iobp2 = new DataPointWithLabelInterface[iobPred2.size()]; - iobp2 = iobPred2.toArray(iobp2); - addSeries(new PointsWithLabelGraphSeries<>(iobp2)); - - aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray)); - aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(1) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray2)); - } - - if (useForScale) { - maxY = maxIobValueFound; - minY = -maxIobValueFound; - } - - iobScale.setMultiplier(maxY * scale / maxIobValueFound); - - addSeries(iobSeries); - } - - // scale in % of vertical size (like 0.3) - public void addCob(long fromTime, long toTime, boolean useForScale, double scale) { - List minFailoverActiveList = new ArrayList<>(); - FixedLineGraphSeries cobSeries; - List cobArray = new ArrayList<>(); - Double maxCobValueFound = 0d; - int lastCob = 0; - Scale cobScale = new Scale(); - - for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - AutosensData autosensData = iobCobCalculatorPlugin.getAutosensData(time); - if (autosensData != null) { - int cob = (int) autosensData.cob; - if (cob != lastCob) { - if (autosensData.carbsFromBolus > 0) - cobArray.add(new ScaledDataPoint(time, lastCob, cobScale)); - cobArray.add(new ScaledDataPoint(time, cob, cobScale)); - maxCobValueFound = Math.max(maxCobValueFound, cob); - lastCob = cob; - } - if (autosensData.failoverToMinAbsorbtionRate) { - autosensData.setScale(cobScale); - autosensData.setChartTime(time); - minFailoverActiveList.add(autosensData); - } - } - } - - // COB - ScaledDataPoint[] cobData = new ScaledDataPoint[cobArray.size()]; - cobData = cobArray.toArray(cobData); - cobSeries = new FixedLineGraphSeries<>(cobData); - cobSeries.setDrawBackground(true); - cobSeries.setBackgroundColor(0x80FFFFFF & resourceHelper.gc(R.color.cob)); //50% - cobSeries.setColor(resourceHelper.gc(R.color.cob)); - cobSeries.setThickness(3); - - if (useForScale) { - maxY = maxCobValueFound; - minY = 0; - } - - cobScale.setMultiplier(maxY * scale / maxCobValueFound); - - addSeries(cobSeries); - - DataPointWithLabelInterface[] minFailover = new DataPointWithLabelInterface[minFailoverActiveList.size()]; - minFailover = minFailoverActiveList.toArray(minFailover); - addSeries(new PointsWithLabelGraphSeries<>(minFailover)); - } - - // scale in % of vertical size (like 0.3) - public void addDeviations(long fromTime, long toTime, boolean useForScale, double scale) { - class DeviationDataPoint extends ScaledDataPoint { - public int color; - - private DeviationDataPoint(double x, double y, int color, Scale scale) { - super(x, y, scale); - this.color = color; - } - } - - BarGraphSeries devSeries; - List devArray = new ArrayList<>(); - Double maxDevValueFound = 0d; - Scale devScale = new Scale(); - - for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - AutosensData autosensData = iobCobCalculatorPlugin.getAutosensData(time); - if (autosensData != null) { - int color = resourceHelper.gc(R.color.deviationblack); // "=" - if (autosensData.type.equals("") || autosensData.type.equals("non-meal")) { - if (autosensData.pastSensitivity.equals("C")) - color = resourceHelper.gc(R.color.deviationgrey); - if (autosensData.pastSensitivity.equals("+")) - color = resourceHelper.gc(R.color.deviationgreen); - if (autosensData.pastSensitivity.equals("-")) - color = resourceHelper.gc(R.color.deviationred); - } else if (autosensData.type.equals("uam")) { - color = resourceHelper.gc(R.color.uam); - } else if (autosensData.type.equals("csf")) { - color = resourceHelper.gc(R.color.deviationgrey); - } - devArray.add(new DeviationDataPoint(time, autosensData.deviation, color, devScale)); - maxDevValueFound = Math.max(maxDevValueFound, Math.abs(autosensData.deviation)); - } - } - - // DEVIATIONS - DeviationDataPoint[] devData = new DeviationDataPoint[devArray.size()]; - devData = devArray.toArray(devData); - devSeries = new BarGraphSeries<>(devData); - devSeries.setValueDependentColor(data -> data.color); - - if (useForScale) { - maxY = maxDevValueFound; - minY = -maxY; - } - - devScale.setMultiplier(maxY * scale / maxDevValueFound); - - addSeries(devSeries); - } - - // scale in % of vertical size (like 0.3) - public void addRatio(long fromTime, long toTime, boolean useForScale, double scale) { - LineGraphSeries ratioSeries; - List ratioArray = new ArrayList<>(); - double maxRatioValueFound = Double.MIN_VALUE; - double minRatioValueFound = Double.MAX_VALUE; - Scale ratioScale = new Scale(); - - for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - AutosensData autosensData = iobCobCalculatorPlugin.getAutosensData(time); - if (autosensData != null) { - ratioArray.add(new ScaledDataPoint(time, autosensData.autosensResult.ratio - 1, ratioScale)); - maxRatioValueFound = Math.max(maxRatioValueFound, autosensData.autosensResult.ratio - 1); - minRatioValueFound = Math.min(minRatioValueFound, autosensData.autosensResult.ratio - 1); - } - } - - // RATIOS - ScaledDataPoint[] ratioData = new ScaledDataPoint[ratioArray.size()]; - ratioData = ratioArray.toArray(ratioData); - ratioSeries = new LineGraphSeries<>(ratioData); - ratioSeries.setColor(resourceHelper.gc(R.color.ratio)); - ratioSeries.setThickness(3); - - if (useForScale) { - maxY = Math.max(maxRatioValueFound, Math.abs(minRatioValueFound)); - minY = -maxY; - } - - ratioScale.setMultiplier(maxY * scale / Math.max(maxRatioValueFound, Math.abs(minRatioValueFound))); - - addSeries(ratioSeries); - } - - // scale in % of vertical size (like 0.3) - public void addDeviationSlope(long fromTime, long toTime, boolean useForScale, double scale) { - LineGraphSeries dsMaxSeries; - LineGraphSeries dsMinSeries; - List dsMaxArray = new ArrayList<>(); - List dsMinArray = new ArrayList<>(); - double maxFromMaxValueFound = 0d; - double maxFromMinValueFound = 0d; - Scale dsMaxScale = new Scale(); - Scale dsMinScale = new Scale(); - - for (long time = fromTime; time <= toTime; time += 5 * 60 * 1000L) { - AutosensData autosensData = iobCobCalculatorPlugin.getAutosensData(time); - if (autosensData != null) { - dsMaxArray.add(new ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, dsMaxScale)); - dsMinArray.add(new ScaledDataPoint(time, autosensData.slopeFromMinDeviation, dsMinScale)); - maxFromMaxValueFound = Math.max(maxFromMaxValueFound, Math.abs(autosensData.slopeFromMaxDeviation)); - maxFromMinValueFound = Math.max(maxFromMinValueFound, Math.abs(autosensData.slopeFromMinDeviation)); - } - } - - // Slopes - ScaledDataPoint[] ratioMaxData = new ScaledDataPoint[dsMaxArray.size()]; - ratioMaxData = dsMaxArray.toArray(ratioMaxData); - dsMaxSeries = new LineGraphSeries<>(ratioMaxData); - dsMaxSeries.setColor(resourceHelper.gc(R.color.devslopepos)); - dsMaxSeries.setThickness(3); - - ScaledDataPoint[] ratioMinData = new ScaledDataPoint[dsMinArray.size()]; - ratioMinData = dsMinArray.toArray(ratioMinData); - dsMinSeries = new LineGraphSeries<>(ratioMinData); - dsMinSeries.setColor(resourceHelper.gc(R.color.devslopeneg)); - dsMinSeries.setThickness(3); - - if (useForScale) { - maxY = Math.max(maxFromMaxValueFound, maxFromMinValueFound); - minY = -maxY; - } - - dsMaxScale.setMultiplier(maxY * scale / maxFromMaxValueFound); - dsMinScale.setMultiplier(maxY * scale / maxFromMinValueFound); - - addSeries(dsMaxSeries); - addSeries(dsMinSeries); - } - - // scale in % of vertical size (like 0.3) - public void addNowLine(long now) { - LineGraphSeries seriesNow; - DataPoint[] nowPoints = new DataPoint[]{ - new DataPoint(now, 0), - new DataPoint(now, maxY) - }; - - seriesNow = new LineGraphSeries<>(nowPoints); - seriesNow.setDrawDataPoints(false); - // custom paint to make a dotted line - Paint paint = new Paint(); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(2); - paint.setPathEffect(new DashPathEffect(new float[]{10, 20}, 0)); - paint.setColor(Color.WHITE); - seriesNow.setCustomPaint(paint); - - addSeries(seriesNow); - } - - public void formatAxis(long fromTime, long endTime) { - graph.getViewport().setMaxX(endTime); - graph.getViewport().setMinX(fromTime); - graph.getViewport().setXAxisBoundsManual(true); - graph.getGridLabelRenderer().setLabelFormatter(new TimeAsXAxisLabelFormatter("HH")); - graph.getGridLabelRenderer().setNumHorizontalLabels(7); // only 7 because of the space - } - - private void addSeries(Series s) { - series.add(s); - } - - public void performUpdate() { - // clear old data - graph.getSeries().clear(); - - // add precalculated series - for (Series s : series) { - if (!s.isEmpty()) { - s.onGraphViewAttached(graph); - graph.getSeries().add(s); - } - } - - double step = 1d; - if (maxY < 1) step = 0.1d; - graph.getViewport().setMaxY(Round.ceilTo(maxY, step)); - graph.getViewport().setMinY(Round.floorTo(minY, step)); - graph.getViewport().setYAxisBoundsManual(true); - - // draw it - graph.onDataChanged(false, false); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt new file mode 100644 index 0000000000..fc7d1eb2a1 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphData/GraphData.kt @@ -0,0 +1,569 @@ +package info.nightscout.androidaps.plugins.general.overview.graphData + +import android.graphics.Color +import android.graphics.DashPathEffect +import android.graphics.Paint +import com.jjoe64.graphview.GraphView +import com.jjoe64.graphview.series.BarGraphSeries +import com.jjoe64.graphview.series.DataPoint +import com.jjoe64.graphview.series.LineGraphSeries +import com.jjoe64.graphview.series.Series +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.MainApp +import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.IobTotal +import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.db.BgReading +import info.nightscout.androidaps.interfaces.ActivePluginProvider +import info.nightscout.androidaps.interfaces.TreatmentsInterface +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin.LastRun +import info.nightscout.androidaps.plugins.aps.openAPSSMB.SMBDefaults +import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction +import info.nightscout.androidaps.plugins.general.overview.graphExtensions.* +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin +import info.nightscout.androidaps.utils.DecimalFormatter +import info.nightscout.androidaps.utils.Round +import info.nightscout.androidaps.utils.resources.ResourceHelper +import java.util.* +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +class GraphData(injector: HasAndroidInjector, private val graph: GraphView, private val iobCobCalculatorPlugin: IobCobCalculatorPlugin) { + + // IobCobCalculatorPlugin Cannot be injected: HistoryBrowser + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var resourceHelper: ResourceHelper + @Inject lateinit var activePlugin: ActivePluginProvider + + private val treatmentsPlugin: TreatmentsInterface + + var maxY = Double.MIN_VALUE + private var minY = Double.MAX_VALUE + private var bgReadingsArray: List? = null + private val units: String + private val series: MutableList> = ArrayList() + + init { + injector.androidInjector().inject(this) + units = profileFunction.getUnits() + treatmentsPlugin = activePlugin.activeTreatments + } + + @Suppress("UNUSED_PARAMETER") + fun addBgReadings(fromTime: Long, toTime: Long, lowLine: Double, highLine: Double, predictions: MutableList?) { + var maxBgValue = Double.MIN_VALUE + bgReadingsArray = iobCobCalculatorPlugin.bgReadings + if (bgReadingsArray?.isEmpty() != false) { + aapsLogger.debug(LTag.OVERVIEW, "No BG data.") + maxY = 10.0 + minY = 0.0 + return + } + val bgListArray: MutableList = ArrayList() + for (bg in bgReadingsArray!!) { + if (bg.date < fromTime || bg.date > toTime) continue + if (bg.value > maxBgValue) maxBgValue = bg.value + bgListArray.add(bg) + } + if (predictions != null) { + predictions.sortWith(Comparator { o1: BgReading, o2: BgReading -> o1.x.compareTo(o2.x) }) + for (prediction in predictions) if (prediction.value >= 40) bgListArray.add(prediction) + } + maxBgValue = Profile.fromMgdlToUnits(maxBgValue, units) + maxBgValue = if (units == Constants.MGDL) Round.roundTo(maxBgValue, 40.0) + 80 else Round.roundTo(maxBgValue, 2.0) + 4 + if (highLine > maxBgValue) maxBgValue = highLine + val numOfVerticalLines = if (units == Constants.MGDL) (maxBgValue / 40 + 1).toInt() else (maxBgValue / 2 + 1).toInt() + maxY = maxBgValue + minY = 0.0 + // set manual y bounds to have nice steps + graph.gridLabelRenderer.numVerticalLabels = numOfVerticalLines + addSeries(PointsWithLabelGraphSeries(Array(bgListArray.size) { i -> bgListArray[i] })) + } + + fun addInRangeArea(fromTime: Long, toTime: Long, lowLine: Double, highLine: Double) { + val inRangeAreaSeries: AreaGraphSeries + val inRangeAreaDataPoints = arrayOf( + DoubleDataPoint(fromTime.toDouble(), lowLine, highLine), + DoubleDataPoint(toTime.toDouble(), lowLine, highLine) + ) + inRangeAreaSeries = AreaGraphSeries(inRangeAreaDataPoints) + inRangeAreaSeries.color = 0 + inRangeAreaSeries.isDrawBackground = true + inRangeAreaSeries.backgroundColor = resourceHelper.gc(R.color.inrangebackground) + addSeries(inRangeAreaSeries) + } + + // scale in % of vertical size (like 0.3) + fun addBasals(fromTime: Long, toTime: Long, scale: Double) { + var maxBasalValueFound = 0.0 + val basalScale = Scale() + val baseBasalArray: MutableList = ArrayList() + val tempBasalArray: MutableList = ArrayList() + val basalLineArray: MutableList = ArrayList() + val absoluteBasalLineArray: MutableList = ArrayList() + var lastLineBasal = 0.0 + var lastAbsoluteLineBasal = -1.0 + var lastBaseBasal = 0.0 + var lastTempBasal = 0.0 + var time = fromTime + while (time < toTime) { + val profile = profileFunction.getProfile(time) + if (profile == null) { + time += 60 * 1000L + continue + } + val basalData = iobCobCalculatorPlugin.getBasalData(profile, time) + val baseBasalValue = basalData.basal + var absoluteLineValue = baseBasalValue + var tempBasalValue = 0.0 + var basal = 0.0 + if (basalData.isTempBasalRunning) { + tempBasalValue = basalData.tempBasalAbsolute + absoluteLineValue = tempBasalValue + if (tempBasalValue != lastTempBasal) { + tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale)) + tempBasalArray.add(ScaledDataPoint(time, tempBasalValue.also { basal = it }, basalScale)) + } + if (lastBaseBasal != 0.0) { + baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale)) + baseBasalArray.add(ScaledDataPoint(time, 0.0, basalScale)) + lastBaseBasal = 0.0 + } + } else { + if (baseBasalValue != lastBaseBasal) { + baseBasalArray.add(ScaledDataPoint(time, lastBaseBasal, basalScale)) + baseBasalArray.add(ScaledDataPoint(time, baseBasalValue.also { basal = it }, basalScale)) + lastBaseBasal = baseBasalValue + } + if (lastTempBasal != 0.0) { + tempBasalArray.add(ScaledDataPoint(time, lastTempBasal, basalScale)) + tempBasalArray.add(ScaledDataPoint(time, 0.0, basalScale)) + } + } + if (baseBasalValue != lastLineBasal) { + basalLineArray.add(ScaledDataPoint(time, lastLineBasal, basalScale)) + basalLineArray.add(ScaledDataPoint(time, baseBasalValue, basalScale)) + } + if (absoluteLineValue != lastAbsoluteLineBasal) { + absoluteBasalLineArray.add(ScaledDataPoint(time, lastAbsoluteLineBasal, basalScale)) + absoluteBasalLineArray.add(ScaledDataPoint(time, basal, basalScale)) + } + lastAbsoluteLineBasal = absoluteLineValue + lastLineBasal = baseBasalValue + lastTempBasal = tempBasalValue + maxBasalValueFound = max(maxBasalValueFound, max(tempBasalValue, baseBasalValue)) + time += 60 * 1000L + } + + // final points + basalLineArray.add(ScaledDataPoint(toTime, lastLineBasal, basalScale)) + baseBasalArray.add(ScaledDataPoint(toTime, lastBaseBasal, basalScale)) + tempBasalArray.add(ScaledDataPoint(toTime, lastTempBasal, basalScale)) + absoluteBasalLineArray.add(ScaledDataPoint(toTime, lastAbsoluteLineBasal, basalScale)) + + // create series + addSeries(LineGraphSeries(Array(baseBasalArray.size) { i -> baseBasalArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = resourceHelper.gc(R.color.basebasal) + it.thickness = 0 + }) + addSeries(LineGraphSeries(Array(tempBasalArray.size) { i -> tempBasalArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = resourceHelper.gc(R.color.tempbasal) + it.thickness = 0 + }) + addSeries(LineGraphSeries(Array(basalLineArray.size) { i -> basalLineArray[i] }).also { + it.setCustomPaint(Paint().also { paint -> + paint.style = Paint.Style.STROKE + paint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2 + paint.pathEffect = DashPathEffect(floatArrayOf(2f, 4f), 0f) + paint.color = resourceHelper.gc(R.color.basal) + }) + }) + addSeries(LineGraphSeries(Array(absoluteBasalLineArray.size) { i -> absoluteBasalLineArray[i] }).also { + it.setCustomPaint(Paint().also { absolutePaint -> + absolutePaint.style = Paint.Style.STROKE + absolutePaint.strokeWidth = resourceHelper.getDisplayMetrics().scaledDensity * 2 + absolutePaint.color = resourceHelper.gc(R.color.basal) + }) + }) + basalScale.setMultiplier(maxY * scale / maxBasalValueFound) + } + + fun addTargetLine(fromTime: Long, toTimeParam: Long, profile: Profile, lastRun: LastRun?) { + var toTime = toTimeParam + val targetsSeriesArray: MutableList = ArrayList() + var lastTarget = -1.0 + lastRun?.constraintsProcessed?.let { toTime = max(it.latestPredictionsTime, toTime) } + var time = fromTime + while (time < toTime) { + val tt = treatmentsPlugin.getTempTargetFromHistory(time) + var value: Double + value = if (tt == null) { + Profile.fromMgdlToUnits((profile.getTargetLowMgdl(time) + profile.getTargetHighMgdl(time)) / 2, units) + } else { + Profile.fromMgdlToUnits(tt.target(), units) + } + if (lastTarget != value) { + if (lastTarget != -1.0) targetsSeriesArray.add(DataPoint(time.toDouble(), lastTarget)) + targetsSeriesArray.add(DataPoint(time.toDouble(), value)) + } + lastTarget = value + time += 5 * 60 * 1000L + } + // final point + targetsSeriesArray.add(DataPoint(toTime.toDouble(), lastTarget)) + // create series + addSeries(LineGraphSeries(Array(targetsSeriesArray.size) { i -> targetsSeriesArray[i] }).also { + it.isDrawBackground = false + it.color = resourceHelper.gc(R.color.tempTargetBackground) + it.thickness = 2 + }) + } + + fun addTreatments(fromTime: Long, endTime: Long) { + val filteredTreatments: MutableList = ArrayList() + val treatments = treatmentsPlugin.treatmentsFromHistory + for (tx in treatments.indices) { + val t = treatments[tx] + if (t.x < fromTime || t.x > endTime) continue + if (t.isSMB && !t.isValid) continue + t.y = getNearestBg(t.x.toLong()) + filteredTreatments.add(t) + } + + // ProfileSwitch + val profileSwitches = treatmentsPlugin.profileSwitchesFromHistory.list + for (tx in profileSwitches.indices) { + val t: DataPointWithLabelInterface = profileSwitches[tx] + if (t.x < fromTime || t.x > endTime) continue + filteredTreatments.add(t) + } + + // Extended bolus + if (!activePlugin.activePump.isFakingTempsByExtendedBoluses) { + val extendedBoluses = treatmentsPlugin.extendedBolusesFromHistory.list + for (tx in extendedBoluses.indices) { + val t: DataPointWithLabelInterface = extendedBoluses[tx] + if (t.x + t.duration < fromTime || t.x > endTime) continue + if (t.duration == 0L) continue + t.y = getNearestBg(t.x.toLong()) + filteredTreatments.add(t) + } + } + + // Careportal + val careportalEvents = MainApp.getDbHelper().getCareportalEventsFromTime(fromTime - 6 * 60 * 60 * 1000, true) + for (tx in careportalEvents.indices) { + val t: DataPointWithLabelInterface = careportalEvents[tx] + if (t.x + t.duration < fromTime || t.x > endTime) continue + t.y = getNearestBg(t.x.toLong()) + filteredTreatments.add(t) + } + addSeries(PointsWithLabelGraphSeries(Array(filteredTreatments.size) { i -> filteredTreatments[i] })) + } + + private fun getNearestBg(date: Long): Double { + bgReadingsArray?.let { bgReadingsArray -> + for (r in bgReadingsArray.indices) { + val reading = bgReadingsArray[r] + if (reading.date > date) continue + return Profile.fromMgdlToUnits(reading.value, units) + } + return if (bgReadingsArray.isNotEmpty()) Profile.fromMgdlToUnits(bgReadingsArray[0].value, units) else Profile.fromMgdlToUnits(100.0, units) + } ?: return Profile.fromMgdlToUnits(100.0, units) + } + + fun addActivity(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { + val actArrayHist: MutableList = ArrayList() + val actArrayPred: MutableList = ArrayList() + val now = System.currentTimeMillis().toDouble() + val actScale = Scale() + var total: IobTotal + var maxIAValue = 0.0 + var time = fromTime + while (time <= toTime) { + val profile = profileFunction.getProfile(time) + if (profile == null) { + time += 5 * 60 * 1000L + continue + } + total = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile) + val act: Double = total.activity + if (time <= now) actArrayHist.add(ScaledDataPoint(time, act, actScale)) else actArrayPred.add(ScaledDataPoint(time, act, actScale)) + maxIAValue = max(maxIAValue, abs(act)) + time += 5 * 60 * 1000L + } + addSeries(FixedLineGraphSeries(Array(actArrayHist.size) { i -> actArrayHist[i] }).also { + it.isDrawBackground = false + it.color = resourceHelper.gc(R.color.activity) + it.thickness = 3 + }) + addSeries(FixedLineGraphSeries(Array(actArrayPred.size) { i -> actArrayPred[i] }).also { + it.setCustomPaint(Paint().also { paint -> + paint.style = Paint.Style.STROKE + paint.strokeWidth = 3f + paint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f) + paint.color = resourceHelper.gc(R.color.activity) + }) + }) + if (useForScale) { + maxY = maxIAValue + minY = -maxIAValue + } + actScale.setMultiplier(maxY * scale / maxIAValue) + } + + // scale in % of vertical size (like 0.3) + fun addIob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double, showPrediction: Boolean) { + val iobSeries: FixedLineGraphSeries + val iobArray: MutableList = ArrayList() + var maxIobValueFound = Double.MIN_VALUE + var lastIob = 0.0 + val iobScale = Scale() + var time = fromTime + while (time <= toTime) { + val profile = profileFunction.getProfile(time) + var iob = 0.0 + if (profile != null) iob = iobCobCalculatorPlugin.calculateFromTreatmentsAndTempsSynchronized(time, profile).iob + if (abs(lastIob - iob) > 0.02) { + if (abs(lastIob - iob) > 0.2) iobArray.add(ScaledDataPoint(time, lastIob, iobScale)) + iobArray.add(ScaledDataPoint(time, iob, iobScale)) + maxIobValueFound = max(maxIobValueFound, abs(iob)) + lastIob = iob + } + time += 5 * 60 * 1000L + } + iobSeries = FixedLineGraphSeries(Array(iobArray.size) { i -> iobArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.iob) //50% + it.color = resourceHelper.gc(R.color.iob) + it.thickness = 3 + } + if (showPrediction) { + val autosensData = iobCobCalculatorPlugin.getLastAutosensDataSynchronized("GraphData") + val lastAutosensResult = autosensData?.autosensResult ?: AutosensResult() + val isTempTarget = treatmentsPlugin.getTempTargetFromHistory(System.currentTimeMillis()) != null + val iobPred: MutableList = ArrayList() + val iobPredArray = iobCobCalculatorPlugin.calculateIobArrayForSMB(lastAutosensResult, SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) + for (i in iobPredArray) { + iobPred.add(i.setColor(resourceHelper.gc(R.color.iobPredAS))) + maxIobValueFound = max(maxIobValueFound, abs(i.iob)) + } + addSeries(PointsWithLabelGraphSeries(Array(iobPred.size) { i -> iobPred[i] })) + val iobPred2: MutableList = ArrayList() + val iobPredArray2 = iobCobCalculatorPlugin.calculateIobArrayForSMB(AutosensResult(), SMBDefaults.exercise_mode, SMBDefaults.half_basal_exercise_target, isTempTarget) + for (i in iobPredArray2) { + iobPred2.add(i.setColor(resourceHelper.gc(R.color.iobPred))) + maxIobValueFound = max(maxIobValueFound, abs(i.iob)) + } + addSeries(PointsWithLabelGraphSeries(Array(iobPred2.size) { i -> iobPred2[i] })) + aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(lastAutosensResult.ratio) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray)) + aapsLogger.debug(LTag.AUTOSENS, "IOB pred for AS=" + DecimalFormatter.to2Decimal(1.0) + ": " + iobCobCalculatorPlugin.iobArrayToString(iobPredArray2)) + } + if (useForScale) { + maxY = maxIobValueFound + minY = -maxIobValueFound + } + iobScale.setMultiplier(maxY * scale / maxIobValueFound) + addSeries(iobSeries) + } + + // scale in % of vertical size (like 0.3) + fun addCob(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { + val minFailOverActiveList: MutableList = ArrayList() + val cobArray: MutableList = ArrayList() + var maxCobValueFound = 0.0 + var lastCob = 0 + val cobScale = Scale() + var time = fromTime + while (time <= toTime) { + iobCobCalculatorPlugin.getAutosensData(time)?.let { autosensData -> + val cob = autosensData.cob.toInt() + if (cob != lastCob) { + if (autosensData.carbsFromBolus > 0) cobArray.add(ScaledDataPoint(time, lastCob.toDouble(), cobScale)) + cobArray.add(ScaledDataPoint(time, cob.toDouble(), cobScale)) + maxCobValueFound = max(maxCobValueFound, cob.toDouble()) + lastCob = cob + } + if (autosensData.failoverToMinAbsorbtionRate) { + autosensData.setScale(cobScale) + autosensData.setChartTime(time) + minFailOverActiveList.add(autosensData) + } + } + time += 5 * 60 * 1000L + } + + // COB + addSeries(FixedLineGraphSeries(Array(cobArray.size) { i -> cobArray[i] }).also { + it.isDrawBackground = true + it.backgroundColor = -0x7f000001 and resourceHelper.gc(R.color.cob) //50% + it.color = resourceHelper.gc(R.color.cob) + it.thickness = 3 + }) + if (useForScale) { + maxY = maxCobValueFound + minY = 0.0 + } + cobScale.setMultiplier(maxY * scale / maxCobValueFound) + addSeries(PointsWithLabelGraphSeries(Array(minFailOverActiveList.size) { i -> minFailOverActiveList[i] })) + } + + // scale in % of vertical size (like 0.3) + fun addDeviations(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { + class DeviationDataPoint(x: Double, y: Double, var color: Int, scale: Scale) : ScaledDataPoint(x, y, scale) + + val devArray: MutableList = ArrayList() + var maxDevValueFound = 0.0 + val devScale = Scale() + var time = fromTime + while (time <= toTime) { + iobCobCalculatorPlugin.getAutosensData(time)?.let { autosensData -> + var color = resourceHelper.gc(R.color.deviationblack) // "=" + if (autosensData.type == "" || autosensData.type == "non-meal") { + if (autosensData.pastSensitivity == "C") color = resourceHelper.gc(R.color.deviationgrey) + if (autosensData.pastSensitivity == "+") color = resourceHelper.gc(R.color.deviationgreen) + if (autosensData.pastSensitivity == "-") color = resourceHelper.gc(R.color.deviationred) + } else if (autosensData.type == "uam") { + color = resourceHelper.gc(R.color.uam) + } else if (autosensData.type == "csf") { + color = resourceHelper.gc(R.color.deviationgrey) + } + devArray.add(DeviationDataPoint(time.toDouble(), autosensData.deviation, color, devScale)) + maxDevValueFound = max(maxDevValueFound, abs(autosensData.deviation)) + } + time += 5 * 60 * 1000L + } + + // DEVIATIONS + addSeries(BarGraphSeries(Array(devArray.size) { i -> devArray[i] }).also { + it.setValueDependentColor { data: DeviationDataPoint -> data.color } + }) + if (useForScale) { + maxY = maxDevValueFound + minY = -maxY + } + devScale.setMultiplier(maxY * scale / maxDevValueFound) + } + + // scale in % of vertical size (like 0.3) + fun addRatio(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { + val ratioArray: MutableList = ArrayList() + var maxRatioValueFound = Double.MIN_VALUE + var minRatioValueFound = Double.MAX_VALUE + val ratioScale = Scale() + var time = fromTime + while (time <= toTime) { + iobCobCalculatorPlugin.getAutosensData(time)?.let { autosensData -> + ratioArray.add(ScaledDataPoint(time, autosensData.autosensResult.ratio - 1, ratioScale)) + maxRatioValueFound = max(maxRatioValueFound, autosensData.autosensResult.ratio - 1) + minRatioValueFound = min(minRatioValueFound, autosensData.autosensResult.ratio - 1) + } + time += 5 * 60 * 1000L + } + + // RATIOS + addSeries(LineGraphSeries(Array(ratioArray.size) { i -> ratioArray[i] }).also { + it.color = resourceHelper.gc(R.color.ratio) + it.thickness = 3 + }) + if (useForScale) { + maxY = max(maxRatioValueFound, abs(minRatioValueFound)) + minY = -maxY + } + ratioScale.setMultiplier(maxY * scale / max(maxRatioValueFound, abs(minRatioValueFound))) + } + + // scale in % of vertical size (like 0.3) + fun addDeviationSlope(fromTime: Long, toTime: Long, useForScale: Boolean, scale: Double) { + val dsMaxArray: MutableList = ArrayList() + val dsMinArray: MutableList = ArrayList() + var maxFromMaxValueFound = 0.0 + var maxFromMinValueFound = 0.0 + val dsMaxScale = Scale() + val dsMinScale = Scale() + var time = fromTime + while (time <= toTime) { + iobCobCalculatorPlugin.getAutosensData(time)?.let { autosensData -> + dsMaxArray.add(ScaledDataPoint(time, autosensData.slopeFromMaxDeviation, dsMaxScale)) + dsMinArray.add(ScaledDataPoint(time, autosensData.slopeFromMinDeviation, dsMinScale)) + maxFromMaxValueFound = max(maxFromMaxValueFound, abs(autosensData.slopeFromMaxDeviation)) + maxFromMinValueFound = max(maxFromMinValueFound, abs(autosensData.slopeFromMinDeviation)) + } + time += 5 * 60 * 1000L + } + + // Slopes + addSeries(LineGraphSeries(Array(dsMaxArray.size) { i -> dsMaxArray[i] }).also { + it.color = resourceHelper.gc(R.color.devslopepos) + it.thickness = 3 + }) + addSeries(LineGraphSeries(Array(dsMinArray.size) { i -> dsMinArray[i] }).also { + it.color = resourceHelper.gc(R.color.devslopeneg) + it.thickness = 3 + }) + if (useForScale) { + maxY = max(maxFromMaxValueFound, maxFromMinValueFound) + minY = -maxY + } + dsMaxScale.setMultiplier(maxY * scale / maxFromMaxValueFound) + dsMinScale.setMultiplier(maxY * scale / maxFromMinValueFound) + } + + // scale in % of vertical size (like 0.3) + fun addNowLine(now: Long) { + val nowPoints = arrayOf( + DataPoint(now.toDouble(), 0.0), + DataPoint(now.toDouble(), maxY) + ) + addSeries(LineGraphSeries(nowPoints).also { + it.isDrawDataPoints = false + // custom paint to make a dotted line + it.setCustomPaint(Paint().also { paint -> + paint.style = Paint.Style.STROKE + paint.strokeWidth = 2f + paint.pathEffect = DashPathEffect(floatArrayOf(10f, 20f), 0f) + paint.color = Color.WHITE + }) + }) + } + + fun formatAxis(fromTime: Long, endTime: Long) { + graph.viewport.setMaxX(endTime.toDouble()) + graph.viewport.setMinX(fromTime.toDouble()) + graph.viewport.isXAxisBoundsManual = true + graph.gridLabelRenderer.labelFormatter = TimeAsXAxisLabelFormatter("HH") + graph.gridLabelRenderer.numHorizontalLabels = 7 // only 7 because of the space + } + + private fun addSeries(s: Series<*>) = series.add(s) + + fun performUpdate() { + // clear old data + graph.series.clear() + + // add pre calculated series + for (s in series) { + if (!s.isEmpty) { + s.onGraphViewAttached(graph) + graph.series.add(s) + } + } + var step = 1.0 + if (maxY < 1) step = 0.1 + graph.viewport.setMaxY(Round.ceilTo(maxY, step)) + graph.viewport.setMinY(Round.floorTo(minY, step)) + graph.viewport.isYAxisBoundsManual = true + + // draw it + graph.onDataChanged(false, false) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ScaledDataPoint.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ScaledDataPoint.java index ac63790a71..8a00446092 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ScaledDataPoint.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/overview/graphExtensions/ScaledDataPoint.java @@ -23,6 +23,12 @@ public class ScaledDataPoint implements DataPointInterface, Serializable { this.scale = scale; } + public ScaledDataPoint(long x, double y, Scale scale) { + this.x=x; + this.y=y; + this.scale = scale; + } + public ScaledDataPoint(Date x, double y, Scale scale) { this.x = x.getTime(); this.y = y; From 7b4174afe0428e6046dd7ea4b8f48cea6a01af71 Mon Sep 17 00:00:00 2001 From: Milos Kozak Date: Wed, 1 Apr 2020 09:08:18 +0200 Subject: [PATCH 28/39] gradle update --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7ffc201be4..dba467c04a 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { maven { url 'https://maven.fabric.io/public' } } dependencies { - classpath 'com.android.tools.build:gradle:3.6.1' + classpath 'com.android.tools.build:gradle:3.6.2' classpath 'com.google.gms:google-services:4.3.3' classpath 'io.fabric.tools:gradle:1.31.2' From f4b7f642c91cb42e33e0426c5cefa6a39e3d83a3 Mon Sep 17 00:00:00 2001 From: TebbeUbben Date: Wed, 25 Mar 2020 23:51:04 +0100 Subject: [PATCH 29/39] Register plugins with Dagger --- .../info/nightscout/androidaps/MainApp.java | 165 +------- .../dependencyInjection/AppModule.kt | 17 +- .../dependencyInjection/PluginsModule.kt | 372 ++++++++++++++++++ .../plugins/configBuilder/PluginStore.kt | 11 +- 4 files changed, 396 insertions(+), 169 deletions(-) create mode 100644 app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.java b/app/src/main/java/info/nightscout/androidaps/MainApp.java index 6270ac2ffb..03e802a43c 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.java +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.java @@ -25,6 +25,8 @@ import net.danlew.android.joda.JodaTimeAndroid; import org.json.JSONException; +import java.util.List; + import javax.inject.Inject; import dagger.android.AndroidInjector; @@ -33,65 +35,14 @@ import dagger.android.HasAndroidInjector; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.db.DatabaseHelper; import info.nightscout.androidaps.dependencyInjection.DaggerAppComponent; +import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.logging.LTag; -import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin; -import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin; -import info.nightscout.androidaps.plugins.aps.openAPSMA.OpenAPSMAPlugin; -import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin; import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.configBuilder.PluginStore; import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction; -import info.nightscout.androidaps.plugins.constraints.dstHelper.DstHelperPlugin; -import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin; -import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin; -import info.nightscout.androidaps.plugins.constraints.signatureVerifier.SignatureVerifierPlugin; -import info.nightscout.androidaps.plugins.constraints.storage.StorageConstraintPlugin; -import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerPlugin; import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils; -import info.nightscout.androidaps.plugins.general.actions.ActionsPlugin; -import info.nightscout.androidaps.plugins.general.automation.AutomationPlugin; -import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin; -import info.nightscout.androidaps.plugins.general.dataBroadcaster.DataBroadcastPlugin; -import info.nightscout.androidaps.plugins.general.food.FoodPlugin; -import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin; -import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; -import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin; -import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin; -import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin; -import info.nightscout.androidaps.plugins.general.tidepool.TidepoolPlugin; -import info.nightscout.androidaps.plugins.general.wear.WearPlugin; -import info.nightscout.androidaps.plugins.general.xdripStatusline.StatusLinePlugin; -import info.nightscout.androidaps.plugins.insulin.InsulinOrefFreePeakPlugin; -import info.nightscout.androidaps.plugins.insulin.InsulinOrefRapidActingPlugin; -import info.nightscout.androidaps.plugins.insulin.InsulinOrefUltraRapidActingPlugin; -import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin; -import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin; -import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin; -import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin; -import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin; -import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin; -import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin; -import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin; -import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin; -import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin; -import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin; -import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin; -import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin; -import info.nightscout.androidaps.plugins.source.DexcomPlugin; -import info.nightscout.androidaps.plugins.source.EversensePlugin; -import info.nightscout.androidaps.plugins.source.GlimpPlugin; -import info.nightscout.androidaps.plugins.source.MM640gPlugin; -import info.nightscout.androidaps.plugins.source.NSClientSourcePlugin; -import info.nightscout.androidaps.plugins.source.PoctechPlugin; -import info.nightscout.androidaps.plugins.source.RandomBgPlugin; -import info.nightscout.androidaps.plugins.source.TomatoPlugin; -import info.nightscout.androidaps.plugins.source.XdripPlugin; -import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.receivers.ChargingStateReceiver; import info.nightscout.androidaps.receivers.DataReceiver; import info.nightscout.androidaps.receivers.KeepAliveReceiver; @@ -130,60 +81,9 @@ public class MainApp extends DaggerApplication { @Inject SP sp; @Inject ProfileFunction profileFunction; - @Inject ActionsPlugin actionsPlugin; - @Inject AutomationPlugin automationPlugin; - @Inject ComboPlugin comboPlugin; - @Inject CareportalPlugin careportalPlugin; @Inject ConfigBuilderPlugin configBuilderPlugin; - @Inject DanaRPlugin danaRPlugin; - @Inject DanaRSPlugin danaRSPlugin; - @Inject DanaRv2Plugin danaRv2Plugin; - @Inject DanaRKoreanPlugin danaRKoreanPlugin; - @Inject DataBroadcastPlugin dataBroadcastPlugin; - @Inject DstHelperPlugin dstHelperPlugin; - @Inject FoodPlugin foodPlugin; - @Inject InsulinOrefFreePeakPlugin insulinOrefFreePeakPlugin; - @Inject InsulinOrefRapidActingPlugin insulinOrefRapidActingPlugin; - @Inject InsulinOrefUltraRapidActingPlugin insulinOrefUltraRapidActingPlugin; - @Inject IobCobCalculatorPlugin iobCobCalculatorPlugin; - @Inject LocalInsightPlugin localInsightPlugin; - @Inject LocalProfilePlugin localProfilePlugin; - @Inject LoopPlugin loopPlugin; - @Inject MedtronicPumpPlugin medtronicPumpPlugin; - @Inject MDIPlugin mdiPlugin; - @Inject NSProfilePlugin nsProfilePlugin; - @Inject ObjectivesPlugin objectivesPlugin; - @Inject SafetyPlugin safetyPlugin; - @Inject SmsCommunicatorPlugin smsCommunicatorPlugin; - @Inject OpenAPSMAPlugin openAPSMAPlugin; - @Inject OpenAPSAMAPlugin openAPSAMAPlugin; - @Inject OpenAPSSMBPlugin openAPSSMBPlugin; - @Inject OverviewPlugin overviewPlugin; - @Inject PersistentNotificationPlugin persistentNotificationPlugin; - @Inject RandomBgPlugin randomBgPlugin; - @Inject SensitivityOref1Plugin sensitivityOref1Plugin; - @Inject SensitivityAAPSPlugin sensitivityAAPSPlugin; - @Inject SensitivityOref0Plugin sensitivityOref0Plugin; - @Inject SensitivityWeightedAveragePlugin sensitivityWeightedAveragePlugin; - @Inject SignatureVerifierPlugin signatureVerifierPlugin; - @Inject StorageConstraintPlugin storageConstraintPlugin; - @Inject DexcomPlugin dexcomPlugin; - @Inject EversensePlugin eversensePlugin; - @Inject GlimpPlugin glimpPlugin; - @Inject MaintenancePlugin maintenancePlugin; - @Inject MM640gPlugin mM640GPlugin; - @Inject NSClientPlugin nsClientPlugin; - @Inject NSClientSourcePlugin nSClientSourcePlugin; - @Inject PoctechPlugin poctechPlugin; - @Inject TomatoPlugin tomatoPlugin; - @Inject XdripPlugin xdripPlugin; - @Inject StatusLinePlugin statusLinePlugin; - @Inject TidepoolPlugin tidepoolPlugin; - @Inject TreatmentsPlugin treatmentsPlugin; - @Inject VirtualPumpPlugin virtualPumpPlugin; - @Inject VersionCheckerPlugin versionCheckerPlugin; - @Inject WearPlugin wearPlugin; @Inject KeepAliveReceiver.KeepAliveManager keepAliveManager; + @Inject List plugins; @Override public void onCreate() { @@ -231,62 +131,7 @@ public class MainApp extends DaggerApplication { versionCheckersUtils.triggerCheckVersion(); // Register all tabs in app here - pluginStore.add(overviewPlugin); - pluginStore.add(iobCobCalculatorPlugin); - if (!Config.NSCLIENT) pluginStore.add(actionsPlugin); - pluginStore.add(insulinOrefRapidActingPlugin); - pluginStore.add(insulinOrefUltraRapidActingPlugin); - pluginStore.add(insulinOrefFreePeakPlugin); - pluginStore.add(sensitivityOref0Plugin); - pluginStore.add(sensitivityAAPSPlugin); - pluginStore.add(sensitivityWeightedAveragePlugin); - pluginStore.add(sensitivityOref1Plugin); - if (Config.PUMPDRIVERS) pluginStore.add(danaRPlugin); - if (Config.PUMPDRIVERS) pluginStore.add(danaRKoreanPlugin); - if (Config.PUMPDRIVERS) pluginStore.add(danaRv2Plugin); - if (Config.PUMPDRIVERS) pluginStore.add(danaRSPlugin); - if (Config.PUMPDRIVERS) pluginStore.add(localInsightPlugin); - if (Config.PUMPDRIVERS) pluginStore.add(comboPlugin); - if (Config.PUMPDRIVERS) pluginStore.add(medtronicPumpPlugin); - if (!Config.NSCLIENT) pluginStore.add(mdiPlugin); - if (!Config.NSCLIENT) pluginStore.add(virtualPumpPlugin); - if (Config.NSCLIENT) pluginStore.add(careportalPlugin); - if (Config.APS) pluginStore.add(loopPlugin); - if (Config.APS) pluginStore.add(openAPSMAPlugin); - if (Config.APS) pluginStore.add(openAPSAMAPlugin); - if (Config.APS) pluginStore.add(openAPSSMBPlugin); - pluginStore.add(nsProfilePlugin); - if (!Config.NSCLIENT) pluginStore.add(localProfilePlugin); - pluginStore.add(treatmentsPlugin); - if (!Config.NSCLIENT) pluginStore.add(safetyPlugin); - if (!Config.NSCLIENT) pluginStore.add(versionCheckerPlugin); - if (Config.APS) pluginStore.add(storageConstraintPlugin); - if (Config.APS) pluginStore.add(signatureVerifierPlugin); - if (Config.APS) pluginStore.add(objectivesPlugin); - pluginStore.add(xdripPlugin); - pluginStore.add(nSClientSourcePlugin); - pluginStore.add(mM640GPlugin); - pluginStore.add(glimpPlugin); - pluginStore.add(dexcomPlugin); - pluginStore.add(poctechPlugin); - pluginStore.add(tomatoPlugin); - pluginStore.add(eversensePlugin); - pluginStore.add(randomBgPlugin); - if (!Config.NSCLIENT) pluginStore.add(smsCommunicatorPlugin); - pluginStore.add(foodPlugin); - - pluginStore.add(wearPlugin); - pluginStore.add(statusLinePlugin); - pluginStore.add(persistentNotificationPlugin); - pluginStore.add(nsClientPlugin); -// if (engineeringMode) pluginsList.add(tidepoolPlugin); - pluginStore.add(maintenancePlugin); - pluginStore.add(automationPlugin); - pluginStore.add(dstHelperPlugin); - pluginStore.add(dataBroadcastPlugin); - - pluginStore.add(configBuilderPlugin); - + pluginStore.setPlugins(plugins); configBuilderPlugin.initialize(); NSUpload.uploadAppStart(); diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt index 28429bf870..511f216463 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt @@ -3,10 +3,12 @@ package info.nightscout.androidaps.dependencyInjection import android.content.Context import androidx.preference.PreferenceManager import dagger.Binds +import dagger.Lazy import dagger.Module import dagger.Provides import dagger.android.ContributesAndroidInjector import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.Config import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.data.Profile import info.nightscout.androidaps.data.ProfileStore @@ -15,6 +17,7 @@ import info.nightscout.androidaps.db.BgReading import info.nightscout.androidaps.db.ProfileSwitch import info.nightscout.androidaps.interfaces.ActivePluginProvider import info.nightscout.androidaps.interfaces.CommandQueueProvider +import info.nightscout.androidaps.interfaces.PluginBase import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLoggerProduction import info.nightscout.androidaps.plugins.aps.loop.APSResult @@ -55,7 +58,7 @@ import info.nightscout.androidaps.utils.wizard.BolusWizard import info.nightscout.androidaps.utils.wizard.QuickWizardEntry import javax.inject.Singleton -@Module(includes = [AppModule.AppBindings::class]) +@Module(includes = [AppModule.AppBindings::class, PluginsModule::class]) open class AppModule { @Provides @@ -88,6 +91,18 @@ open class AppModule { */ } + @Provides + fun providesPlugins(@PluginsModule.AllConfigs allConfigs: Map<@JvmSuppressWildcards Int, @JvmSuppressWildcards PluginBase>, + @PluginsModule.PumpDriver pumpDrivers: Lazy>, + @PluginsModule.NotNSClient notNsClient: Lazy>, + @PluginsModule.APS aps: Lazy>): List<@JvmSuppressWildcards PluginBase> { + val plugins = allConfigs.toMutableMap() + if (Config.PUMPDRIVERS) plugins += pumpDrivers.get() + if (Config.APS) plugins += aps.get() + if (!Config.NSCLIENT) plugins += notNsClient.get() + return plugins.toList().sortedBy { it.first }.map { it.second } + } + @Module interface AppBindings { diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt new file mode 100644 index 0000000000..2a9c45d361 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PluginsModule.kt @@ -0,0 +1,372 @@ +package info.nightscout.androidaps.dependencyInjection + +import dagger.Binds +import dagger.Lazy +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntKey +import dagger.multibindings.IntoMap +import dagger.multibindings.IntoSet +import info.nightscout.androidaps.Config +import info.nightscout.androidaps.interfaces.PluginBase +import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin +import info.nightscout.androidaps.plugins.aps.openAPSAMA.OpenAPSAMAPlugin +import info.nightscout.androidaps.plugins.aps.openAPSMA.OpenAPSMAPlugin +import info.nightscout.androidaps.plugins.aps.openAPSSMB.OpenAPSSMBPlugin +import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin +import info.nightscout.androidaps.plugins.constraints.dstHelper.DstHelperPlugin +import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin +import info.nightscout.androidaps.plugins.constraints.safety.SafetyPlugin +import info.nightscout.androidaps.plugins.constraints.signatureVerifier.SignatureVerifierPlugin +import info.nightscout.androidaps.plugins.constraints.storage.StorageConstraintPlugin +import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerPlugin +import info.nightscout.androidaps.plugins.general.actions.ActionsPlugin +import info.nightscout.androidaps.plugins.general.careportal.CareportalPlugin +import info.nightscout.androidaps.plugins.general.dataBroadcaster.DataBroadcastPlugin +import info.nightscout.androidaps.plugins.general.food.FoodPlugin +import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin +import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin +import info.nightscout.androidaps.plugins.general.overview.OverviewPlugin +import info.nightscout.androidaps.plugins.general.persistentNotification.PersistentNotificationPlugin +import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin +import info.nightscout.androidaps.plugins.general.wear.WearPlugin +import info.nightscout.androidaps.plugins.general.xdripStatusline.StatusLinePlugin +import info.nightscout.androidaps.plugins.insulin.InsulinOrefFreePeakPlugin +import info.nightscout.androidaps.plugins.insulin.InsulinOrefRapidActingPlugin +import info.nightscout.androidaps.plugins.insulin.InsulinOrefUltraRapidActingPlugin +import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorPlugin +import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin +import info.nightscout.androidaps.plugins.profile.ns.NSProfilePlugin +import info.nightscout.androidaps.plugins.pump.combo.ComboPlugin +import info.nightscout.androidaps.plugins.pump.danaR.DanaRPlugin +import info.nightscout.androidaps.plugins.pump.danaRKorean.DanaRKoreanPlugin +import info.nightscout.androidaps.plugins.pump.danaRS.DanaRSPlugin +import info.nightscout.androidaps.plugins.pump.danaRv2.DanaRv2Plugin +import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin +import info.nightscout.androidaps.plugins.pump.mdi.MDIPlugin +import info.nightscout.androidaps.plugins.pump.medtronic.MedtronicPumpPlugin +import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityAAPSPlugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref0Plugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityOref1Plugin +import info.nightscout.androidaps.plugins.sensitivity.SensitivityWeightedAveragePlugin +import info.nightscout.androidaps.plugins.source.* +import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin +import javax.inject.Qualifier + +@Module +abstract class PluginsModule { + + @Binds + @AllConfigs + @IntoMap + @IntKey(0) + abstract fun bindOverviewPlugin(plugin: OverviewPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(10) + abstract fun bindIobCobCalculatorPlugin(plugin: IobCobCalculatorPlugin): PluginBase + + @Binds + @NotNSClient + @IntoMap + @IntKey(20) + abstract fun bindActionsPlugin(plugin: ActionsPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(30) + abstract fun bindInsulinOrefRapidActingPlugin(plugin: InsulinOrefRapidActingPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(40) + abstract fun bindInsulinOrefUltraRapidActingPlugin(plugin: InsulinOrefUltraRapidActingPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(50) + abstract fun bindInsulinOrefFreePeakPlugin(plugin: InsulinOrefFreePeakPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(60) + abstract fun bindSensitivityOref0Plugin(plugin: SensitivityOref0Plugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(70) + abstract fun bindSensitivityAAPSPlugin(plugin: SensitivityAAPSPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(80) + abstract fun bindSensitivityWeightedAveragePlugin(plugin: SensitivityWeightedAveragePlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(90) + abstract fun bindSensitivityOref1Plugin(plugin: SensitivityOref1Plugin): PluginBase + + @Binds + @PumpDriver + @IntoMap + @IntKey(100) + abstract fun bindDanaRPlugin(plugin: DanaRPlugin): PluginBase + + @Binds + @PumpDriver + @IntoMap + @IntKey(110) + abstract fun bindDanaRKoreanPlugin(plugin: DanaRKoreanPlugin): PluginBase + + @Binds + @PumpDriver + @IntoMap + @IntKey(120) + abstract fun bindDanaRv2Plugin(plugin: DanaRv2Plugin): PluginBase + + @Binds + @PumpDriver + @IntoMap + @IntKey(130) + abstract fun bindDanaRSPlugin(plugin: DanaRSPlugin): PluginBase + + @Binds + @PumpDriver + @IntoMap + @IntKey(140) + abstract fun bindLocalInsightPlugin(plugin: LocalInsightPlugin): PluginBase + + @Binds + @PumpDriver + @IntoMap + @IntKey(150) + abstract fun bindComboPlugin(plugin: ComboPlugin): PluginBase + + @Binds + @PumpDriver + @IntoMap + @IntKey(160) + abstract fun bindMedtronicPumpPlugin(plugin: MedtronicPumpPlugin): PluginBase + + @Binds + @NotNSClient + @IntoMap + @IntKey(170) + abstract fun bindMDIPlugin(plugin: MDIPlugin): PluginBase + + @Binds + @NotNSClient + @IntoMap + @IntKey(180) + abstract fun bindVirtualPumpPlugin(plugin: VirtualPumpPlugin): PluginBase + + @Binds + @NotNSClient + @IntoMap + @IntKey(190) + abstract fun bindCareportalPlugin(plugin: CareportalPlugin): PluginBase + + @Binds + @APS + @IntoMap + @IntKey(200) + abstract fun bindLoopPlugin(plugin: LoopPlugin): PluginBase + + @Binds + @APS + @IntoMap + @IntKey(210) + abstract fun bindOpenAPSMAPlugin(plugin: OpenAPSMAPlugin): PluginBase + + @Binds + @APS + @IntoMap + @IntKey(220) + abstract fun bindOpenAPSAMAPlugin(plugin: OpenAPSAMAPlugin): PluginBase + + @Binds + @APS + @IntoMap + @IntKey(230) + abstract fun bindOpenAPSSMBPlugin(plugin: OpenAPSSMBPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(240) + abstract fun bindNSProfilePlugin(plugin: NSProfilePlugin): PluginBase + + @Binds + @NotNSClient + @IntoMap + @IntKey(250) + abstract fun bindLocalProfilePlugin(plugin: LocalProfilePlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(260) + abstract fun bindTreatmentsPlugin(plugin: TreatmentsPlugin): PluginBase + + @Binds + @NotNSClient + @IntoSet + abstract fun bindSafetyPlugin(plugin: SafetyPlugin): PluginBase + + @Binds + @NotNSClient + @IntoMap + @IntKey(270) + abstract fun bindVersionCheckerPlugin(plugin: VersionCheckerPlugin): PluginBase + + @Binds + @NotNSClient + @IntoMap + @IntKey(280) + abstract fun bindSmsCommunicatorPlugin(plugin: SmsCommunicatorPlugin): PluginBase + + + @Binds + @APS + @IntoMap + @IntKey(290) + abstract fun bindStorageConstraintPlugin(plugin: StorageConstraintPlugin): PluginBase + + @Binds + @APS + @IntoMap + @IntKey(300) + abstract fun bindSignatureVerifierPlugin(plugin: SignatureVerifierPlugin): PluginBase + + @Binds + @APS + @IntoMap + @IntKey(310) + abstract fun bindObjectivesPlugin(plugin: ObjectivesPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(320) + abstract fun bindFoodPlugin(plugin: FoodPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(330) + abstract fun bindWearPlugin(plugin: WearPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(340) + abstract fun bindStatusLinePlugin(plugin: StatusLinePlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(350) + abstract fun bindPersistentNotificationPlugin(plugin: PersistentNotificationPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(360) + abstract fun bindNSClientPlugin(plugin: NSClientPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(370) + abstract fun bindMaintenancePlugin(plugin: MaintenancePlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(380) + abstract fun bindDstHelperPlugin(plugin: DstHelperPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(390) + abstract fun bindDataBroadcastPlugin(plugin: DataBroadcastPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(400) + abstract fun bindXdripPlugin(plugin: XdripPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(410) + abstract fun bindNSClientSourcePlugin(plugin: NSClientSourcePlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(420) + abstract fun bindMM640gPlugin(plugin: MM640gPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(430) + abstract fun bindGlimpPlugin(plugin: GlimpPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(440) + abstract fun bindDexcomPlugin(plugin: DexcomPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(450) + abstract fun bindPoctechPlugin(plugin: PoctechPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(460) + abstract fun bindTomatoPlugin(plugin: TomatoPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(470) + abstract fun bindRandomBgPlugin(plugin: RandomBgPlugin): PluginBase + + @Binds + @AllConfigs + @IntoMap + @IntKey(480) + abstract fun bindConfigBuilderPlugin(plugin: ConfigBuilderPlugin): PluginBase + + @Qualifier + annotation class AllConfigs + + @Qualifier + annotation class PumpDriver + + @Qualifier + annotation class NotNSClient + + @Qualifier + annotation class APS + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt index 0bce3e34eb..31692b93bb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/configBuilder/PluginStore.kt @@ -6,6 +6,7 @@ import info.nightscout.androidaps.logging.LTag import java.util.* import javax.inject.Inject import javax.inject.Singleton +import kotlin.collections.ArrayList @Singleton class PluginStore @Inject constructor( @@ -26,8 +27,7 @@ class PluginStore @Inject constructor( return pluginStore!! } } - - var plugins = ArrayList() + lateinit var plugins: List<@JvmSuppressWildcards PluginBase> private var activeBgSource: BgSourceInterface? = null private var activePump: PumpInterface? = null @@ -41,11 +41,6 @@ class PluginStore @Inject constructor( verifySelectionInCategories() } - fun add(pluginBase: PluginBase): ActivePluginProvider { - plugins.add(pluginBase) - return this - } - fun getDefaultPlugin(type: PluginType): PluginBase { for (p in plugins) if (p.getType() == type && p.isDefault()) return p @@ -243,6 +238,6 @@ class PluginStore @Inject constructor( override fun getActiveTreatments(): TreatmentsInterface = activeTreatments ?: checkNotNull(activeTreatments) { "No treatments selected" } - override fun getPluginsList(): ArrayList = plugins + override fun getPluginsList(): ArrayList = ArrayList(plugins) } \ No newline at end of file From fde84207aba863726a07fa79cd6e4644b7a4f6b3 Mon Sep 17 00:00:00 2001 From: dlvoy Date: Fri, 21 Feb 2020 08:26:58 +0100 Subject: [PATCH 30/39] Preferences Encryption - export as JSON --- .../dependencyInjection/AppModule.kt | 3 + .../maintenance/ImportExportPrefs.java | 129 ---------------- .../general/maintenance/ImportExportPrefs.kt | 142 ++++++++++++++++++ .../maintenance/MaintenanceFragment.kt | 9 +- .../maintenance/formats/ClassicPrefsFormat.kt | 54 +++++++ .../formats/EncryptedPrefsFormat.kt | 58 +++++++ .../maintenance/formats/PrefsFormat.kt | 26 ++++ .../androidaps/setupwizard/SWDefinition.kt | 5 +- 8 files changed, 291 insertions(+), 135 deletions(-) delete mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.java create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/ClassicPrefsFormat.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/EncryptedPrefsFormat.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/PrefsFormat.kt diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt index 511f216463..cc75cf16be 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt @@ -36,6 +36,7 @@ import info.nightscout.androidaps.plugins.general.automation.actions.* import info.nightscout.androidaps.plugins.general.automation.elements.* import info.nightscout.androidaps.plugins.general.automation.triggers.* import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData +import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction import info.nightscout.androidaps.plugins.general.smsCommunicator.AuthRequest import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensData @@ -249,6 +250,8 @@ open class AppModule { @ContributesAndroidInjector fun graphDataInjector(): GraphData + @ContributesAndroidInjector fun importExportPrefsInjector(): ImportExportPrefs + @Binds fun bindContext(mainApp: MainApp): Context @Binds fun bindInjector(mainApp: MainApp): HasAndroidInjector diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.java b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.java deleted file mode 100644 index 59c8e87144..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.java +++ /dev/null @@ -1,129 +0,0 @@ -package info.nightscout.androidaps.plugins.general.maintenance; - -import android.Manifest; -import android.app.Activity; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.os.Environment; -import androidx.preference.PreferenceManager; - -import androidx.core.content.ContextCompat; -import androidx.fragment.app.Fragment; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Map; - -import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.R; -import info.nightscout.androidaps.events.EventAppExit; -import info.nightscout.androidaps.logging.L; -import info.nightscout.androidaps.logging.StacktraceLoggerWrapper; -import info.nightscout.androidaps.plugins.bus.RxBus; -import info.nightscout.androidaps.utils.OKDialog; -import info.nightscout.androidaps.utils.SP; -import info.nightscout.androidaps.utils.ToastUtils; - -/** - * Created by mike on 03.07.2016. - */ - -public class ImportExportPrefs { - private static Logger log = StacktraceLoggerWrapper.getLogger(L.CORE); - private static File path = new File(Environment.getExternalStorageDirectory().toString()); - static public final File file = new File(path, MainApp.gs(R.string.app_name) + "Preferences"); - - private static final int REQUEST_EXTERNAL_STORAGE = 1; - private static String[] PERMISSIONS_STORAGE = { - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - }; - - static void verifyStoragePermissions(Fragment fragment) { - int permission = ContextCompat.checkSelfPermission(fragment.getContext(), - Manifest.permission.WRITE_EXTERNAL_STORAGE); - - if (permission != PackageManager.PERMISSION_GRANTED) { - // We don't have permission so prompt the user - fragment.requestPermissions(PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE); - } - - } - - static void exportSharedPreferences(final Fragment f) { - exportSharedPreferences(f.getContext()); - } - - private static void exportSharedPreferences(final Context context) { - OKDialog.showConfirmation(context, MainApp.gs(R.string.maintenance), MainApp.gs(R.string.export_to) + " " + file + " ?", () -> { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - try { - FileWriter fw = new FileWriter(file); - PrintWriter pw = new PrintWriter(fw); - Map prefsMap = prefs.getAll(); - for (Map.Entry entry : prefsMap.entrySet()) { - pw.println(entry.getKey() + "::" + entry.getValue().toString()); - } - pw.close(); - fw.close(); - ToastUtils.showToastInUiThread(context, MainApp.gs(R.string.exported)); - } catch (FileNotFoundException e) { - ToastUtils.showToastInUiThread(context, MainApp.gs(R.string.filenotfound) + " " + file); - log.error("Unhandled exception", e); - } catch (IOException e) { - log.error("Unhandled exception", e); - } - }); - } - - static void importSharedPreferences(final Fragment fragment) { - importSharedPreferences(fragment.getContext()); - } - - public static void importSharedPreferences(final Context context) { - OKDialog.showConfirmation(context, MainApp.gs(R.string.maintenance), MainApp.gs(R.string.import_from) + " " + file + " ?", () -> { - String line; - String[] lineParts; - try { - SP.clear(); - - BufferedReader reader = new BufferedReader(new FileReader(file)); - while ((line = reader.readLine()) != null) { - lineParts = line.split("::"); - if (lineParts.length == 2) { - if (lineParts[1].equals("true") || lineParts[1].equals("false")) { - SP.putBoolean(lineParts[0], Boolean.parseBoolean(lineParts[1])); - } else { - SP.putString(lineParts[0], lineParts[1]); - } - } - } - reader.close(); - SP.putBoolean(R.string.key_setupwizard_processed, true); - OKDialog.show(context, MainApp.gs(R.string.setting_imported), MainApp.gs(R.string.restartingapp), () -> { - log.debug("Exiting"); - RxBus.Companion.getINSTANCE().send(new EventAppExit()); - if (context instanceof Activity) { - ((Activity) context).finish(); - } - System.runFinalization(); - System.exit(0); - }); - } catch (FileNotFoundException e) { - ToastUtils.showToastInUiThread(context, MainApp.gs(R.string.filenotfound) + " " + file); - log.error("Unhandled exception", e); - } catch (IOException e) { - log.error("Unhandled exception", e); - } - }); - } -} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt new file mode 100644 index 0000000000..ede8d79bc5 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt @@ -0,0 +1,142 @@ +package info.nightscout.androidaps.plugins.general.maintenance + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import android.os.Environment +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import info.nightscout.androidaps.R +import info.nightscout.androidaps.events.EventAppExit +import info.nightscout.androidaps.logging.AAPSLogger +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.bus.RxBusWrapper +import info.nightscout.androidaps.plugins.general.maintenance.formats.* +import info.nightscout.androidaps.utils.OKDialog.show +import info.nightscout.androidaps.utils.OKDialog.showConfirmation +import info.nightscout.androidaps.utils.ToastUtils +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.sharedPreferences.SP +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Created by mike on 03.07.2016. + */ + +private const val REQUEST_EXTERNAL_STORAGE = 1 +private val PERMISSIONS_STORAGE = arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE +) + +@Singleton +class ImportExportPrefs @Inject constructor ( + private var log: AAPSLogger, + private val resourceHelper: ResourceHelper, + private val sp : SP, + private val rxBus: RxBusWrapper +) +{ + + val TAG = LTag.CORE + + private val path = File(Environment.getExternalStorageDirectory().toString()) + + private val file = File(path, resourceHelper.gs(R.string.app_name) + "Preferences") + private val encFile = File(path, resourceHelper.gs(R.string.app_name) + "Preferences.json") + + fun prefsImportFile() : File { + return if (encFile.exists()) encFile else file + } + + fun prefsFileExists() : Boolean { + return encFile.exists() || file.exists() + } + + fun exportSharedPreferences(f: Fragment) { + exportSharedPreferences(f.context) + } + + fun verifyStoragePermissions(fragment: Fragment) { + val permission = ContextCompat.checkSelfPermission(fragment.context!!, + Manifest.permission.WRITE_EXTERNAL_STORAGE) + if (permission != PackageManager.PERMISSION_GRANTED) { + // We don't have permission so prompt the user + fragment.requestPermissions(PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE) + } + } + + private fun exportSharedPreferences(context: Context?) { + showConfirmation(context!!, resourceHelper.gs(R.string.maintenance), resourceHelper.gs(R.string.export_to) + " " + encFile + " ?", Runnable { + try { + val entries: MutableMap = mutableMapOf() + for ((key, value) in sp.getAll()) { + entries[key] = value.toString() + } + + val prefs = Prefs(entries, mapOf()) + + ClassicPrefsFormat.savePreferences(file, prefs) + EncryptedPrefsFormat.savePreferences(encFile, prefs) + + ToastUtils.showToastInUiThread(context, resourceHelper.gs(R.string.exported)) + } catch (e: FileNotFoundException) { + ToastUtils.showToastInUiThread(context, resourceHelper.gs(R.string.filenotfound) + " " + encFile) + log.error(TAG,"Unhandled exception", e) + } catch (e: IOException) { + log.error(TAG,"Unhandled exception", e) + } + }) + } + + fun importSharedPreferences(fragment: Fragment) { + importSharedPreferences(fragment.context) + } + + fun importSharedPreferences(context: Context?) { + + val importFile = prefsImportFile() + + showConfirmation(context!!, resourceHelper.gs(R.string.maintenance), resourceHelper.gs(R.string.import_from) + " " + importFile + " ?", Runnable { + + val format : PrefsFormat = if (encFile.exists()) EncryptedPrefsFormat else ClassicPrefsFormat + + try { + val prefs = format.loadPreferences(importFile) + + sp.clear() + for ((key, value) in prefs.values) { + if (value == "true" || value == "false") { + sp.putBoolean(key, value.toBoolean()) + } else { + sp.putString(key, value) + } + } + + sp.putBoolean(R.string.key_setupwizard_processed, true) + show(context, resourceHelper.gs(R.string.setting_imported), resourceHelper.gs(R.string.restartingapp), Runnable { + log.debug(TAG,"Exiting") + rxBus.send(EventAppExit()) + if (context is Activity) { + context.finish() + } + System.runFinalization() + System.exit(0) + }) + + + } catch (e: PrefFileNotFoundError) { + ToastUtils.showToastInUiThread(context, resourceHelper.gs(R.string.filenotfound) + " " + importFile) + log.error(TAG,"Unhandled exception", e) + } catch (e: PrefIOError) { + log.error(TAG,"Unhandled exception", e) + } + + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt index 7de5d303ed..2e42294fc1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenanceFragment.kt @@ -23,6 +23,7 @@ class MaintenanceFragment : DaggerFragment() { @Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var treatmentsPlugin: TreatmentsPlugin @Inject lateinit var foodPlugin: FoodPlugin + @Inject lateinit var importExportPrefs: ImportExportPrefs override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.maintenance_fragment, container, false) @@ -45,13 +46,13 @@ class MaintenanceFragment : DaggerFragment() { } nav_export.setOnClickListener { // start activity for checking permissions... - ImportExportPrefs.verifyStoragePermissions(this) - ImportExportPrefs.exportSharedPreferences(this) + importExportPrefs.verifyStoragePermissions(this) + importExportPrefs.exportSharedPreferences(this) } nav_import.setOnClickListener { // start activity for checking permissions... - ImportExportPrefs.verifyStoragePermissions(this) - ImportExportPrefs.importSharedPreferences(this) + importExportPrefs.verifyStoragePermissions(this) + importExportPrefs.importSharedPreferences(this) } nav_logsettings.setOnClickListener { startActivity(Intent(activity, LogSettingActivity::class.java)) } } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/ClassicPrefsFormat.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/ClassicPrefsFormat.kt new file mode 100644 index 0000000000..c71775c33c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/ClassicPrefsFormat.kt @@ -0,0 +1,54 @@ +package info.nightscout.androidaps.plugins.general.maintenance.formats + +import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs +import java.io.* + + +object ClassicPrefsFormat : PrefsFormat { + + const val FORMAT_KEY = "old" + + override fun savePreferences(file:File, prefs: Prefs) { + try { + val fw = FileWriter(file) + val pw = PrintWriter(fw) + for ((key, value) in prefs.values) { + pw.println(key + "::" + value) + } + pw.close() + fw.close() + } catch (e: FileNotFoundException) { + throw PrefFileNotFoundError(file.absolutePath) + } catch (e: IOException) { + throw PrefIOError(file.absolutePath) + } + } + + override fun loadPreferences(file:File): Prefs { + var line: String + var lineParts: Array + val entries: MutableMap = mutableMapOf() + val metadata: MutableMap = mutableMapOf() + try { + val reader = BufferedReader(FileReader(file)) + while (reader.readLine().also { line = it } != null) { + lineParts = line.split("::").toTypedArray() + if (lineParts.size == 2) { + entries[lineParts[0]] = lineParts[1] + } + } + reader.close() + + metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(FORMAT_KEY, PrefsStatus.WARN) + + return Prefs(entries, metadata) + + } catch (e: FileNotFoundException) { + throw PrefFileNotFoundError(file.absolutePath) + } catch (e: IOException) { + throw PrefIOError(file.absolutePath) + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/EncryptedPrefsFormat.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/EncryptedPrefsFormat.kt new file mode 100644 index 0000000000..7e32029c46 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/EncryptedPrefsFormat.kt @@ -0,0 +1,58 @@ +package info.nightscout.androidaps.plugins.general.maintenance.formats + +import org.json.JSONException +import org.json.JSONObject +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException + +object EncryptedPrefsFormat : PrefsFormat { + + const val FORMAT_KEY = "new_v1" + + override fun savePreferences(file:File, prefs: Prefs) { + + val container = JSONObject() + + try { + for ((key, value) in prefs.values) { + container.put(key, value) + } + + file.writeText(container.toString(2)); + + } catch (e: FileNotFoundException) { + throw PrefFileNotFoundError(file.absolutePath) + } catch (e: IOException) { + throw PrefIOError(file.absolutePath) + } + } + + override fun loadPreferences(file:File): Prefs { + + val entries: MutableMap = mutableMapOf() + val metadata: MutableMap = mutableMapOf() + try { + + val jsonBody = file.readText() + val container = JSONObject(jsonBody) + + for (key in container.keys()) { + entries.put(key, container[key].toString()) + } + + metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(FORMAT_KEY, PrefsStatus.OK) + + return Prefs(entries, metadata) + + } catch (e: FileNotFoundException) { + throw PrefFileNotFoundError(file.absolutePath) + } catch (e: IOException) { + throw PrefIOError(file.absolutePath) + } catch (e: JSONException){ + throw PrefFormatError("Mallformed preferences JSON file: "+e) + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/PrefsFormat.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/PrefsFormat.kt new file mode 100644 index 0000000000..d8beb30786 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/PrefsFormat.kt @@ -0,0 +1,26 @@ +package info.nightscout.androidaps.plugins.general.maintenance.formats + +import java.io.File + +enum class PrefsMetadataKey(val key: String) { + FILE_FORMAT("fileFormat") +} + +data class PrefMetadata(var value : String, var status : PrefsStatus) + +data class Prefs(val values : Map, val metadata : Map) + +interface PrefsFormat { + fun savePreferences(file: File, prefs: Prefs) + fun loadPreferences(file: File) : Prefs +} + +enum class PrefsStatus { + OK, + WARN, + ERROR +} + +class PrefFileNotFoundError(message: String) : Exception(message) +class PrefIOError(message: String) : Exception(message) +class PrefFormatError(message: String) : Exception(message) \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt index 5164e8388e..3912b48433 100644 --- a/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt +++ b/app/src/main/java/info/nightscout/androidaps/setupwizard/SWDefinition.kt @@ -59,6 +59,7 @@ class SWDefinition @Inject constructor( private val nsClientPlugin: NSClientPlugin, private val nsProfilePlugin: NSProfilePlugin, private val protectionCheck: ProtectionCheck, + private val importExportPrefs: ImportExportPrefs, private val androidPermission: AndroidPermission ) { @@ -160,8 +161,8 @@ class SWDefinition @Inject constructor( .add(SWBreak(injector)) .add(SWButton(injector) .text(R.string.nav_import) - .action(Runnable { ImportExportPrefs.importSharedPreferences(activity) })) - .visibility(SWValidator { ImportExportPrefs.file.exists() && !androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) }) + .action(Runnable { importExportPrefs.importSharedPreferences(activity) })) + .visibility(SWValidator { importExportPrefs.prefsFileExists() && !androidPermission.permissionNotGranted(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) }) private val screenNsClient = SWScreen(injector, R.string.nsclientinternal_title) .skippable(true) .add(SWInfotext(injector) From 1c97f8a720fd970ced021201bdf506a26c2a1165 Mon Sep 17 00:00:00 2001 From: Dominik Dzienia Date: Tue, 11 Feb 2020 19:34:56 +0100 Subject: [PATCH 31/39] Preferences Encryption: - encrypted JSON format support - using master password & password prompt - refactored alerts --- .../dependencyInjection/AppModule.kt | 14 +- .../dependencyInjection/FragmentsModule.kt | 3 + .../general/maintenance/ImportExportPrefs.kt | 286 +++++++++++++++--- .../general/maintenance/MaintenancePlugin.kt | 12 + .../maintenance/formats/ClassicPrefsFormat.kt | 46 +-- .../formats/EncryptedPrefsFormat.kt | 199 +++++++++++- .../maintenance/formats/PrefsFormat.kt | 69 ++++- .../nightscout/androidaps/utils/OKDialog.kt | 66 ++-- .../androidaps/utils/ToastUtils.java | 72 ++++- .../nightscout/androidaps/utils/UIUtils.kt | 6 + .../utils/alertDialogs/AlertDialogHelper.kt | 31 ++ .../alertDialogs/PrefImportSummaryDialog.kt | 140 +++++++++ .../alertDialogs/TwoMessagesAlertDialog.kt | 51 ++++ .../utils/alertDialogs/WarningDialog.kt | 49 +++ .../utils/protection/PasswordCheck.kt | 44 +-- .../androidaps/utils/storage/FileStrorage.kt | 17 ++ .../androidaps/utils/storage/Storage.kt | 11 + .../main/res/drawable/alert_border_error.xml | 15 + .../res/drawable/alert_border_warning.xml | 15 + app/src/main/res/drawable/ic_header_error.xml | 16 + .../main/res/drawable/ic_header_export.xml | 16 + .../main/res/drawable/ic_header_import.xml | 16 + app/src/main/res/drawable/ic_header_key.xml | 16 + app/src/main/res/drawable/ic_header_log.xml | 16 + .../main/res/drawable/ic_header_warning.xml | 16 + app/src/main/res/drawable/ic_key_48dp.xml | 17 -- app/src/main/res/drawable/ic_meta_date.xml | 9 + .../main/res/drawable/ic_meta_encryption.xml | 9 + app/src/main/res/drawable/ic_meta_error.xml | 10 + app/src/main/res/drawable/ic_meta_flavour.xml | 9 + app/src/main/res/drawable/ic_meta_format.xml | 9 + app/src/main/res/drawable/ic_meta_model.xml | 9 + app/src/main/res/drawable/ic_meta_name.xml | 9 + app/src/main/res/drawable/ic_meta_ok.xml | 10 + app/src/main/res/drawable/ic_meta_version.xml | 9 + app/src/main/res/drawable/ic_meta_warning.xml | 10 + app/src/main/res/drawable/ic_toast_check.xml | 10 + .../res/drawable/ic_toast_delete_confirm.xml | 10 + app/src/main/res/drawable/ic_toast_error.xml | 10 + app/src/main/res/drawable/ic_toast_info.xml | 10 + app/src/main/res/drawable/ic_toast_warn.xml | 10 + app/src/main/res/drawable/toast_border_ok.xml | 12 + .../main/res/layout/dialog_alert_custom.xml | 19 +- .../layout/dialog_alert_import_summary.xml | 30 ++ .../res/layout/dialog_alert_two_messages.xml | 19 ++ .../res/layout/import_summary_details.xml | 14 + .../main/res/layout/import_summary_item.xml | 36 +++ app/src/main/res/layout/toast.xml | 32 ++ app/src/main/res/values/attrs.xml | 7 + app/src/main/res/values/colors.xml | 19 ++ app/src/main/res/values/protection.xml | 1 + app/src/main/res/values/strings.xml | 47 +++ app/src/main/res/values/styles.xml | 27 ++ app/src/main/res/xml/pref_maintenance.xml | 4 + .../maintenance/ClassicPrefsFormatTest.kt | 65 ++++ .../maintenance/EncryptedPrefsFormatTest.kt | 226 ++++++++++++++ .../testing/mockers/AAPSMocker.java | 56 ++++ .../testing/mocks/SharedPreferencesMock.java | 158 ++++++++++ .../testing/utils/SingleStringStorage.kt | 26 ++ 59 files changed, 2004 insertions(+), 196 deletions(-) create mode 100644 app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/AlertDialogHelper.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/PrefImportSummaryDialog.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/TwoMessagesAlertDialog.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/WarningDialog.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/utils/storage/FileStrorage.kt create mode 100644 app/src/main/java/info/nightscout/androidaps/utils/storage/Storage.kt create mode 100644 app/src/main/res/drawable/alert_border_error.xml create mode 100644 app/src/main/res/drawable/alert_border_warning.xml create mode 100644 app/src/main/res/drawable/ic_header_error.xml create mode 100644 app/src/main/res/drawable/ic_header_export.xml create mode 100644 app/src/main/res/drawable/ic_header_import.xml create mode 100644 app/src/main/res/drawable/ic_header_key.xml create mode 100644 app/src/main/res/drawable/ic_header_log.xml create mode 100644 app/src/main/res/drawable/ic_header_warning.xml delete mode 100644 app/src/main/res/drawable/ic_key_48dp.xml create mode 100644 app/src/main/res/drawable/ic_meta_date.xml create mode 100644 app/src/main/res/drawable/ic_meta_encryption.xml create mode 100644 app/src/main/res/drawable/ic_meta_error.xml create mode 100644 app/src/main/res/drawable/ic_meta_flavour.xml create mode 100644 app/src/main/res/drawable/ic_meta_format.xml create mode 100644 app/src/main/res/drawable/ic_meta_model.xml create mode 100644 app/src/main/res/drawable/ic_meta_name.xml create mode 100644 app/src/main/res/drawable/ic_meta_ok.xml create mode 100644 app/src/main/res/drawable/ic_meta_version.xml create mode 100644 app/src/main/res/drawable/ic_meta_warning.xml create mode 100644 app/src/main/res/drawable/ic_toast_check.xml create mode 100644 app/src/main/res/drawable/ic_toast_delete_confirm.xml create mode 100644 app/src/main/res/drawable/ic_toast_error.xml create mode 100644 app/src/main/res/drawable/ic_toast_info.xml create mode 100644 app/src/main/res/drawable/ic_toast_warn.xml create mode 100644 app/src/main/res/drawable/toast_border_ok.xml create mode 100644 app/src/main/res/layout/dialog_alert_import_summary.xml create mode 100644 app/src/main/res/layout/dialog_alert_two_messages.xml create mode 100644 app/src/main/res/layout/import_summary_details.xml create mode 100644 app/src/main/res/layout/import_summary_item.xml create mode 100644 app/src/main/res/layout/toast.xml create mode 100644 app/src/main/res/values/attrs.xml create mode 100644 app/src/test/java/info/nightscout/androidaps/plugins/general/maintenance/ClassicPrefsFormatTest.kt create mode 100644 app/src/test/java/info/nightscout/androidaps/plugins/general/maintenance/EncryptedPrefsFormatTest.kt create mode 100644 app/src/test/java/info/nightscout/androidaps/testing/mockers/AAPSMocker.java create mode 100644 app/src/test/java/info/nightscout/androidaps/testing/mocks/SharedPreferencesMock.java create mode 100644 app/src/test/java/info/nightscout/androidaps/testing/utils/SingleStringStorage.kt diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt index cc75cf16be..4a081f85fb 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/AppModule.kt @@ -26,7 +26,6 @@ import info.nightscout.androidaps.plugins.aps.openAPSMA.DetermineBasalResultMA import info.nightscout.androidaps.plugins.aps.openAPSMA.LoggerCallback import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalAdapterSMBJS import info.nightscout.androidaps.plugins.aps.openAPSSMB.DetermineBasalResultSMB -import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin import info.nightscout.androidaps.plugins.configBuilder.PluginStore import info.nightscout.androidaps.plugins.configBuilder.ProfileFunction import info.nightscout.androidaps.plugins.configBuilder.ProfileFunctionImplementation @@ -37,6 +36,8 @@ import info.nightscout.androidaps.plugins.general.automation.elements.* import info.nightscout.androidaps.plugins.general.automation.triggers.* import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs +import info.nightscout.androidaps.plugins.general.maintenance.formats.ClassicPrefsFormat +import info.nightscout.androidaps.plugins.general.maintenance.formats.EncryptedPrefsFormat import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationWithAction import info.nightscout.androidaps.plugins.general.smsCommunicator.AuthRequest import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensData @@ -46,7 +47,6 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobThread import info.nightscout.androidaps.plugins.treatments.Treatment import info.nightscout.androidaps.queue.CommandQueue import info.nightscout.androidaps.queue.commands.* -import info.nightscout.androidaps.setupwizard.SWDefinition import info.nightscout.androidaps.setupwizard.SWEventListener import info.nightscout.androidaps.setupwizard.SWScreen import info.nightscout.androidaps.setupwizard.elements.* @@ -55,6 +55,8 @@ import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelperImplementation import info.nightscout.androidaps.utils.sharedPreferences.SP import info.nightscout.androidaps.utils.sharedPreferences.SPImplementation +import info.nightscout.androidaps.utils.storage.FileStorage +import info.nightscout.androidaps.utils.storage.Storage import info.nightscout.androidaps.utils.wizard.BolusWizard import info.nightscout.androidaps.utils.wizard.QuickWizardEntry import javax.inject.Singleton @@ -104,6 +106,12 @@ open class AppModule { return plugins.toList().sortedBy { it.first }.map { it.second } } + @Provides + @Singleton + fun provideStorage(): Storage { + return FileStorage() + } + @Module interface AppBindings { @@ -251,6 +259,8 @@ open class AppModule { @ContributesAndroidInjector fun graphDataInjector(): GraphData @ContributesAndroidInjector fun importExportPrefsInjector(): ImportExportPrefs + @ContributesAndroidInjector fun encryptedPrefsFormatInjector(): EncryptedPrefsFormat + @ContributesAndroidInjector fun classicPrefsFormatInjector(): ClassicPrefsFormat @Binds fun bindContext(mainApp: MainApp): Context @Binds fun bindInjector(mainApp: MainApp): HasAndroidInjector diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt index 63ed16fb4c..d151912151 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/FragmentsModule.kt @@ -40,6 +40,7 @@ import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpFragment import info.nightscout.androidaps.plugins.source.BGSourceFragment import info.nightscout.androidaps.plugins.treatments.TreatmentsFragment import info.nightscout.androidaps.plugins.treatments.fragments.* +import info.nightscout.androidaps.utils.protection.PasswordCheck @Module @Suppress("unused") @@ -112,4 +113,6 @@ abstract class FragmentsModule { @ContributesAndroidInjector abstract fun contributesTreatmentDialog(): TreatmentDialog @ContributesAndroidInjector abstract fun contributesWizardDialog(): WizardDialog @ContributesAndroidInjector abstract fun contributesWizardInfoDialog(): WizardInfoDialog + + @ContributesAndroidInjector abstract fun contributesPasswordCheck(): PasswordCheck } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt index ede8d79bc5..f5ad429641 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/ImportExportPrefs.kt @@ -2,25 +2,42 @@ package info.nightscout.androidaps.plugins.general.maintenance import android.Manifest import android.app.Activity +import android.bluetooth.BluetoothAdapter import android.content.Context +import android.content.Intent import android.content.pm.PackageManager +import android.os.Build import android.os.Environment +import android.provider.Settings +import androidx.annotation.StringRes import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment +import info.nightscout.androidaps.BuildConfig import info.nightscout.androidaps.R +import info.nightscout.androidaps.activities.PreferencesActivity import info.nightscout.androidaps.events.EventAppExit import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.general.maintenance.formats.* +import info.nightscout.androidaps.plugins.general.smsCommunicator.otp.OneTimePassword +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.OKDialog import info.nightscout.androidaps.utils.OKDialog.show -import info.nightscout.androidaps.utils.OKDialog.showConfirmation import info.nightscout.androidaps.utils.ToastUtils +import info.nightscout.androidaps.utils.alertDialogs.PrefImportSummaryDialog +import info.nightscout.androidaps.utils.alertDialogs.TwoMessagesAlertDialog +import info.nightscout.androidaps.utils.alertDialogs.WarningDialog +import info.nightscout.androidaps.utils.buildHelper.BuildHelper +import info.nightscout.androidaps.utils.protection.PasswordCheck import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.sharedPreferences.SP +import org.joda.time.DateTime +import org.joda.time.Days import java.io.File import java.io.FileNotFoundException import java.io.IOException +import java.util.* import javax.inject.Inject import javax.inject.Singleton @@ -34,14 +51,20 @@ private val PERMISSIONS_STORAGE = arrayOf( Manifest.permission.WRITE_EXTERNAL_STORAGE ) +private const val IMPORT_AGE_NOT_YET_OLD_DAYS = 60 + @Singleton -class ImportExportPrefs @Inject constructor ( +class ImportExportPrefs @Inject constructor( private var log: AAPSLogger, private val resourceHelper: ResourceHelper, - private val sp : SP, - private val rxBus: RxBusWrapper -) -{ + private val sp: SP, + private val buildHelper: BuildHelper, + private val otp: OneTimePassword, + private val rxBus: RxBusWrapper, + private val passwordCheck: PasswordCheck, + private val classicPrefsFormat: ClassicPrefsFormat, + private val encryptedPrefsFormat: EncryptedPrefsFormat +) { val TAG = LTag.CORE @@ -50,16 +73,16 @@ class ImportExportPrefs @Inject constructor ( private val file = File(path, resourceHelper.gs(R.string.app_name) + "Preferences") private val encFile = File(path, resourceHelper.gs(R.string.app_name) + "Preferences.json") - fun prefsImportFile() : File { + fun prefsImportFile(): File { return if (encFile.exists()) encFile else file } - fun prefsFileExists() : Boolean { + fun prefsFileExists(): Boolean { return encFile.exists() || file.exists() } fun exportSharedPreferences(f: Fragment) { - exportSharedPreferences(f.context) + exportSharedPreferences(f.activity) } fun verifyStoragePermissions(fragment: Fragment) { @@ -71,72 +94,247 @@ class ImportExportPrefs @Inject constructor ( } } - private fun exportSharedPreferences(context: Context?) { - showConfirmation(context!!, resourceHelper.gs(R.string.maintenance), resourceHelper.gs(R.string.export_to) + " " + encFile + " ?", Runnable { + private fun prepareMetadata(context: Context?): Map { + + val metadata: MutableMap = mutableMapOf() + + if (context != null) { + metadata[PrefsMetadataKey.DEVICE_NAME] = PrefMetadata(detectUserName(context), PrefsStatus.OK) + } + + metadata[PrefsMetadataKey.CREATED_AT] = PrefMetadata(DateUtil.toISOString(Date()), PrefsStatus.OK) + metadata[PrefsMetadataKey.AAPS_VERSION] = PrefMetadata(BuildConfig.VERSION_NAME, PrefsStatus.OK) + metadata[PrefsMetadataKey.AAPS_FLAVOUR] = PrefMetadata(BuildConfig.FLAVOR, PrefsStatus.OK) + metadata[PrefsMetadataKey.DEVICE_MODEL] = PrefMetadata(getCurrentDeviceModelString(), PrefsStatus.OK) + + if (prefsEncryptionIsDisabled()) { + metadata[PrefsMetadataKey.ENCRYPTION] = PrefMetadata("Disabled", PrefsStatus.DISABLED) + } else { + metadata[PrefsMetadataKey.ENCRYPTION] = PrefMetadata("Enabled", PrefsStatus.OK) + } + + return metadata + } + + private fun detectUserName(context: Context): String { + // based on https://medium.com/@pribble88/how-to-get-an-android-device-nickname-4b4700b3068c + val n1 = Settings.System.getString(context.contentResolver, "bluetooth_name") + val n2 = Settings.Secure.getString(context.contentResolver, "bluetooth_name") + val n3 = BluetoothAdapter.getDefaultAdapter()?.name + val n4 = Settings.System.getString(context.contentResolver, "device_name") + val n5 = Settings.Secure.getString(context.contentResolver, "lock_screen_owner_info") + val n6 = Settings.Global.getString(context.contentResolver, "device_name") + + // name we use for SMS OTP token in communicator + val otpName = otp.name().trim() + val defaultOtpName = resourceHelper.gs(R.string.smscommunicator_default_user_display_name) + + // name we detect from OS + val systemName = n1 ?: n2 ?: n3 ?: n4 ?: n5 ?: n6 ?: defaultOtpName + val name = if (otpName.length > 0 && otpName != defaultOtpName) otpName else systemName + return name + } + + private fun getCurrentDeviceModelString() = + Build.MANUFACTURER + " " + Build.MODEL + " (" + Build.DEVICE + ")" + + private fun prefsEncryptionIsDisabled() = + buildHelper.isEngineeringMode() && !sp.getBoolean(resourceHelper.gs(R.string.key_maintenance_encrypt_exported_prefs), true) + + private fun askForMasterPass(activity: Activity?, @StringRes canceledMsg: Int, then: ((password: String) -> Unit)) { + passwordCheck.queryPassword(activity!!, R.string.master_password, R.string.key_master_password, { password -> + then(password) + }, { + ToastUtils.warnToast(activity, resourceHelper.gs(canceledMsg)) + }) + } + + private fun askForMasterPassIfNeeded(activity: Activity?, @StringRes canceledMsg: Int, then: ((password: String) -> Unit)) { + if (prefsEncryptionIsDisabled()) { + then("") + } else { + askForMasterPass(activity, canceledMsg, then) + } + } + + private fun assureMasterPasswordSet(activity: Activity?, @StringRes wrongPwdTitle: Int): Boolean { + if (!sp.contains(R.string.key_master_password) || (sp.getString(R.string.key_master_password, "") == "")) { + WarningDialog.showWarning(activity!!, + resourceHelper.gs(wrongPwdTitle), + resourceHelper.gs(R.string.master_password_missing, resourceHelper.gs(R.string.configbuilder_general), resourceHelper.gs(R.string.protection)), + R.string.nav_preferences, { + val intent = Intent(activity, PreferencesActivity::class.java).apply { + putExtra("id", R.xml.pref_general) + } + activity.startActivity(intent) + }) + return false + } + return true + } + + private fun askToConfirmExport(activity: Activity?, then: ((password: String) -> Unit)) { + if (!prefsEncryptionIsDisabled() && !assureMasterPasswordSet(activity, R.string.nav_export)) return + + TwoMessagesAlertDialog.showAlert(activity!!, resourceHelper.gs(R.string.nav_export), + resourceHelper.gs(R.string.export_to) + " " + encFile + " ?", + resourceHelper.gs(R.string.password_preferences_encrypt_prompt), { + askForMasterPassIfNeeded(activity, R.string.preferences_export_canceled, then) + }, null, R.drawable.ic_header_export) + } + + private fun askToConfirmImport(activity: Activity?, fileToImport: File, then: ((password: String) -> Unit)) { + + if (encFile.exists()) { + if (!assureMasterPasswordSet(activity, R.string.nav_import)) return + + TwoMessagesAlertDialog.showAlert(activity!!, resourceHelper.gs(R.string.nav_import), + resourceHelper.gs(R.string.import_from) + " " + fileToImport + " ?", + resourceHelper.gs(R.string.password_preferences_decrypt_prompt), { + askForMasterPass(activity, R.string.preferences_import_canceled, then) + }, null, R.drawable.ic_header_import) + + } else { + OKDialog.showConfirmation(activity!!, resourceHelper.gs(R.string.nav_import), + resourceHelper.gs(R.string.import_from) + " " + fileToImport + " ?", + Runnable { then("") }) + } + } + + private fun exportSharedPreferences(activity: Activity?) { + askToConfirmExport(activity) { password -> try { val entries: MutableMap = mutableMapOf() for ((key, value) in sp.getAll()) { entries[key] = value.toString() } - val prefs = Prefs(entries, mapOf()) + val prefs = Prefs(entries, prepareMetadata(activity)) - ClassicPrefsFormat.savePreferences(file, prefs) - EncryptedPrefsFormat.savePreferences(encFile, prefs) + classicPrefsFormat.savePreferences(file, prefs) + encryptedPrefsFormat.savePreferences(encFile, prefs, password) - ToastUtils.showToastInUiThread(context, resourceHelper.gs(R.string.exported)) + ToastUtils.okToast(activity, resourceHelper.gs(R.string.exported)) } catch (e: FileNotFoundException) { - ToastUtils.showToastInUiThread(context, resourceHelper.gs(R.string.filenotfound) + " " + encFile) - log.error(TAG,"Unhandled exception", e) + ToastUtils.errorToast(activity, resourceHelper.gs(R.string.filenotfound) + " " + encFile) + log.error(TAG, "Unhandled exception", e) } catch (e: IOException) { - log.error(TAG,"Unhandled exception", e) + ToastUtils.errorToast(activity, e.message) + log.error(TAG, "Unhandled exception", e) } - }) + } } fun importSharedPreferences(fragment: Fragment) { - importSharedPreferences(fragment.context) + importSharedPreferences(fragment.activity) } - fun importSharedPreferences(context: Context?) { + fun importSharedPreferences(activity: Activity?) { val importFile = prefsImportFile() - showConfirmation(context!!, resourceHelper.gs(R.string.maintenance), resourceHelper.gs(R.string.import_from) + " " + importFile + " ?", Runnable { + askToConfirmImport(activity, importFile) { password -> - val format : PrefsFormat = if (encFile.exists()) EncryptedPrefsFormat else ClassicPrefsFormat + val format: PrefsFormat = if (encFile.exists()) encryptedPrefsFormat else classicPrefsFormat try { - val prefs = format.loadPreferences(importFile) - sp.clear() - for ((key, value) in prefs.values) { - if (value == "true" || value == "false") { - sp.putBoolean(key, value.toBoolean()) + val prefs = format.loadPreferences(importFile, password) + prefs.metadata = checkMetadata(prefs.metadata) + + // import is OK when we do not have errors (warnings are allowed) + val importOk = checkIfImportIsOk(prefs) + + // if at end we allow to import preferences + val importPossible = (importOk || buildHelper.isEngineeringMode()) && (prefs.values.size > 0) + + PrefImportSummaryDialog.showSummary(activity!!, importOk, importPossible, prefs, { + if (importPossible) { + sp.clear() + for ((key, value) in prefs.values) { + if (value == "true" || value == "false") { + sp.putBoolean(key, value.toBoolean()) + } else { + sp.putString(key, value) + } + } + + restartAppAfterImport(activity) } else { - sp.putString(key, value) + // for impossible imports it should not be called + ToastUtils.errorToast(activity, "Cannot import preferences!") } - } - - sp.putBoolean(R.string.key_setupwizard_processed, true) - show(context, resourceHelper.gs(R.string.setting_imported), resourceHelper.gs(R.string.restartingapp), Runnable { - log.debug(TAG,"Exiting") - rxBus.send(EventAppExit()) - if (context is Activity) { - context.finish() - } - System.runFinalization() - System.exit(0) }) - } catch (e: PrefFileNotFoundError) { - ToastUtils.showToastInUiThread(context, resourceHelper.gs(R.string.filenotfound) + " " + importFile) - log.error(TAG,"Unhandled exception", e) + ToastUtils.errorToast(activity, resourceHelper.gs(R.string.filenotfound) + " " + importFile) + log.error(TAG, "Unhandled exception", e) } catch (e: PrefIOError) { - log.error(TAG,"Unhandled exception", e) + log.error(TAG, "Unhandled exception", e) + ToastUtils.errorToast(activity, e.message) } + } + } + // check metadata for known issues, change their status and add info with explanations + private fun checkMetadata(metadata: Map): Map { + val meta = metadata.toMutableMap() + + if (meta.containsKey(PrefsMetadataKey.AAPS_FLAVOUR)) { + val flavourOfPrefs = meta[PrefsMetadataKey.AAPS_FLAVOUR]!!.value + if (meta[PrefsMetadataKey.AAPS_FLAVOUR]!!.value != BuildConfig.FLAVOR) { + meta[PrefsMetadataKey.AAPS_FLAVOUR]!!.status = PrefsStatus.WARN + meta[PrefsMetadataKey.AAPS_FLAVOUR]!!.info = resourceHelper.gs(R.string.metadata_warning_different_flavour, flavourOfPrefs, BuildConfig.FLAVOR) + } + } + + if (meta.containsKey(PrefsMetadataKey.DEVICE_MODEL)) { + if (meta[PrefsMetadataKey.DEVICE_MODEL]!!.value != getCurrentDeviceModelString()) { + meta[PrefsMetadataKey.DEVICE_MODEL]!!.status = PrefsStatus.WARN + meta[PrefsMetadataKey.DEVICE_MODEL]!!.info = resourceHelper.gs(R.string.metadata_warning_different_device) + } + } + + if (meta.containsKey(PrefsMetadataKey.CREATED_AT)) { + try { + val date1 = DateTime.parse(meta[PrefsMetadataKey.CREATED_AT]!!.value); + val date2 = DateTime.now() + + val daysOld = Days.daysBetween(date1.toLocalDate(), date2.toLocalDate()).getDays() + + if (daysOld > IMPORT_AGE_NOT_YET_OLD_DAYS) { + meta[PrefsMetadataKey.CREATED_AT]!!.status = PrefsStatus.WARN + meta[PrefsMetadataKey.CREATED_AT]!!.info = resourceHelper.gs(R.string.metadata_warning_old_export, daysOld.toString()) + } + } catch (e: Exception) { + meta[PrefsMetadataKey.CREATED_AT]!!.status = PrefsStatus.WARN + meta[PrefsMetadataKey.CREATED_AT]!!.info = resourceHelper.gs(R.string.metadata_warning_date_format) + } + } + + return meta + } + + private fun checkIfImportIsOk(prefs: Prefs): Boolean { + var importOk = true + + for ((_, value) in prefs.metadata) { + if (value.status == PrefsStatus.ERROR) + importOk = false; + } + return importOk + } + + private fun restartAppAfterImport(context: Context?) { + sp.putBoolean(R.string.key_setupwizard_processed, true) + show(context!!, resourceHelper.gs(R.string.setting_imported), resourceHelper.gs(R.string.restartingapp), Runnable { + log.debug(TAG, "Exiting") + rxBus.send(EventAppExit()) + if (context is Activity) { + context.finish() + } + System.runFinalization() + System.exit(0) }) } } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt index cd4d333995..4a3e5e83ab 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/MaintenancePlugin.kt @@ -4,6 +4,8 @@ import android.content.Context import android.content.Intent import android.net.Uri import androidx.core.content.FileProvider +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreference import dagger.android.HasAndroidInjector import info.nightscout.androidaps.BuildConfig import info.nightscout.androidaps.Config @@ -16,6 +18,7 @@ import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.sharedPreferences.SP +import info.nightscout.androidaps.utils.textValidator.ValidatingEditTextPreference import java.io.* import java.util.* import java.util.zip.ZipEntry @@ -203,4 +206,13 @@ class MaintenancePlugin @Inject constructor( emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) return emailIntent } + + override fun preprocessPreferences(preferenceFragment: PreferenceFragmentCompat) { + super.preprocessPreferences(preferenceFragment) + val encryptSwitch = preferenceFragment.findPreference(resourceHelper.gs(R.string.key_maintenance_encrypt_exported_prefs)) as SwitchPreference? + ?: return + encryptSwitch.isVisible = buildHelper.isEngineeringMode() + encryptSwitch.isEnabled = buildHelper.isEngineeringMode() + } + } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/ClassicPrefsFormat.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/ClassicPrefsFormat.kt index c71775c33c..4689f549a6 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/ClassicPrefsFormat.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/ClassicPrefsFormat.kt @@ -1,22 +1,30 @@ package info.nightscout.androidaps.plugins.general.maintenance.formats -import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs -import java.io.* +import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.storage.Storage +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException +import javax.inject.Inject +import javax.inject.Singleton +@Singleton +class ClassicPrefsFormat @Inject constructor( + private var resourceHelper: ResourceHelper, + private var storage: Storage +) : PrefsFormat { -object ClassicPrefsFormat : PrefsFormat { + companion object { + val FORMAT_KEY = "aaps_old" + } - const val FORMAT_KEY = "old" - - override fun savePreferences(file:File, prefs: Prefs) { + override fun savePreferences(file: File, prefs: Prefs, masterPassword: String?) { try { - val fw = FileWriter(file) - val pw = PrintWriter(fw) - for ((key, value) in prefs.values) { - pw.println(key + "::" + value) + val contents = prefs.values.entries.joinToString("\n") { entry -> + "${entry.key}::${entry.value}" } - pw.close() - fw.close() + storage.putFileContents(file, contents) } catch (e: FileNotFoundException) { throw PrefFileNotFoundError(file.absolutePath) } catch (e: IOException) { @@ -24,31 +32,29 @@ object ClassicPrefsFormat : PrefsFormat { } } - override fun loadPreferences(file:File): Prefs { - var line: String + override fun loadPreferences(file: File, masterPassword: String?): Prefs { var lineParts: Array val entries: MutableMap = mutableMapOf() val metadata: MutableMap = mutableMapOf() try { - val reader = BufferedReader(FileReader(file)) - while (reader.readLine().also { line = it } != null) { + + val rawLines = storage.getFileContents(file).split("\n") + rawLines.forEach { line -> lineParts = line.split("::").toTypedArray() if (lineParts.size == 2) { entries[lineParts[0]] = lineParts[1] } } - reader.close() - metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(FORMAT_KEY, PrefsStatus.WARN) + metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(FORMAT_KEY, PrefsStatus.WARN, resourceHelper.gs(R.string.metadata_warning_outdated_format)) return Prefs(entries, metadata) - } catch (e: FileNotFoundException) { + } catch (e: FileNotFoundException) { throw PrefFileNotFoundError(file.absolutePath) } catch (e: IOException) { throw PrefIOError(file.absolutePath) } } - } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/EncryptedPrefsFormat.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/EncryptedPrefsFormat.kt index 7e32029c46..a2debad281 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/EncryptedPrefsFormat.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/EncryptedPrefsFormat.kt @@ -1,25 +1,90 @@ package info.nightscout.androidaps.plugins.general.maintenance.formats +import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.CryptoUtil +import info.nightscout.androidaps.utils.hexStringToByteArray +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.androidaps.utils.storage.Storage +import info.nightscout.androidaps.utils.toHex import org.json.JSONException import org.json.JSONObject import java.io.File import java.io.FileNotFoundException import java.io.IOException +import java.util.* +import javax.inject.Inject +import javax.inject.Singleton -object EncryptedPrefsFormat : PrefsFormat { +@Singleton +class EncryptedPrefsFormat @Inject constructor( + private var resourceHelper: ResourceHelper, + private var storage: Storage +) : PrefsFormat { - const val FORMAT_KEY = "new_v1" + companion object { + val FORMAT_KEY_ENC = "aaps_encrypted" + val FORMAT_KEY_NOENC = "aaps_structured" - override fun savePreferences(file:File, prefs: Prefs) { + private val KEY_CONSCIENCE = "if you remove/change this, please make sure you know the consequences!" + } + + override fun savePreferences(file: File, prefs: Prefs, masterPassword: String?) { val container = JSONObject() + val content = JSONObject() + val meta = JSONObject() + + val encStatus = prefs.metadata[PrefsMetadataKey.ENCRYPTION]?.status ?: PrefsStatus.OK + var encrypted = encStatus == PrefsStatus.OK && masterPassword != null try { - for ((key, value) in prefs.values) { - container.put(key, value) + for ((key, value) in prefs.values.toSortedMap()) { + content.put(key, value) } - file.writeText(container.toString(2)); + for ((metaKey, metaEntry) in prefs.metadata) { + if (metaKey == PrefsMetadataKey.FILE_FORMAT) + continue; + if (metaKey == PrefsMetadataKey.ENCRYPTION) + continue; + meta.put(metaKey.key, metaEntry.value) + } + + container.put(PrefsMetadataKey.FILE_FORMAT.key, if (encrypted) FORMAT_KEY_ENC else FORMAT_KEY_NOENC); + container.put("metadata", meta) + + val security = JSONObject() + security.put("file_hash", "--to-be-calculated--") + var encodedContent = "" + + if (encrypted) { + val salt = CryptoUtil.mineSalt() + val rawContent = content.toString() + val contentAttempt = CryptoUtil.encrypt(masterPassword!!, salt, rawContent) + if (contentAttempt != null) { + encodedContent = contentAttempt + security.put("algorithm", "v1") + security.put("salt", salt.toHex()) + security.put("content_hash", CryptoUtil.sha256(rawContent)) + } else { + // fallback when encryption does not work + encrypted = false + } + } + + if (!encrypted) { + security.put("algorithm", "none") + } + + container.put("security", security) + container.put("content", if (encrypted) encodedContent else content) + + var fileContents = container.toString(2) + val fileHash = CryptoUtil.hmac256(fileContents, KEY_CONSCIENCE) + + fileContents = fileContents.replace(Regex("(\\\"file_hash\\\"\\s*\\:\\s*\\\")(--to-be-calculated--)(\\\")"), "$1" + fileHash + "$3") + + storage.putFileContents(file, fileContents) } catch (e: FileNotFoundException) { throw PrefFileNotFoundError(file.absolutePath) @@ -28,31 +93,133 @@ object EncryptedPrefsFormat : PrefsFormat { } } - override fun loadPreferences(file:File): Prefs { + override fun loadPreferences(file: File, masterPassword: String?): Prefs { val entries: MutableMap = mutableMapOf() val metadata: MutableMap = mutableMapOf() + val issues = LinkedList() try { - val jsonBody = file.readText() + val jsonBody = storage.getFileContents(file) + val fileContents = jsonBody.replace(Regex("(?is)(\\\"file_hash\\\"\\s*\\:\\s*\\\")([^\"]*)(\\\")"), "$1--to-be-calculated--$3") + val calculatedFileHash = CryptoUtil.hmac256(fileContents, KEY_CONSCIENCE) val container = JSONObject(jsonBody) - for (key in container.keys()) { - entries.put(key, container[key].toString()) - } + if (container.has(PrefsMetadataKey.FILE_FORMAT.key) && container.has("security") && container.has("content") && container.has("metadata")) { + val fileFormat = container.getString(PrefsMetadataKey.FILE_FORMAT.key) - metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(FORMAT_KEY, PrefsStatus.OK) + if ((fileFormat != FORMAT_KEY_ENC) && (fileFormat != FORMAT_KEY_NOENC)) { + throw PrefFormatError("Unsupported file format: "+fileFormat) + } + + val meta = container.getJSONObject("metadata") + val security = container.getJSONObject("security") + + metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(fileFormat, PrefsStatus.OK) + for (key in meta.keys()) { + val metaKey = PrefsMetadataKey.fromKey(key) + if (metaKey != null) { + metadata[metaKey] = PrefMetadata(meta.getString(key), PrefsStatus.OK) + } + } + + val encrypted = fileFormat == FORMAT_KEY_ENC + var secure: PrefsStatus = PrefsStatus.OK + var decryptedOk = false + var contentJsonObj: JSONObject? = null + var insecurityReason = resourceHelper.gs(R.string.prefdecrypt_settings_tampered) + + if (security.has("file_hash")) { + if (calculatedFileHash != security.getString("file_hash")) { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_modified)) + } + } else { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_missing_file_hash)) + } + + if (encrypted) { + if (security.has("algorithm") && security.get("algorithm") == "v1") { + if (security.has("salt") && security.has("content_hash")) { + + val salt = security.getString("salt").hexStringToByteArray() + val decrypted = CryptoUtil.decrypt(masterPassword!!, salt, container.getString("content")) + + if (decrypted != null) { + try { + val contentHash = CryptoUtil.sha256(decrypted) + + if (contentHash == security.getString("content_hash")) { + contentJsonObj = JSONObject(decrypted) + decryptedOk = true + } else { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_modified)) + } + + } catch (e: JSONException) { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_parsing)) + } + + } else { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_wrong_pass)) + insecurityReason = resourceHelper.gs(R.string.prefdecrypt_wrong_password) + } + + } else { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_wrong_format)) + } + } else { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_wrong_algorithm)) + } + + } else { + + if (secure == PrefsStatus.OK) { + secure = PrefsStatus.WARN + } + + if (!(security.has("algorithm") && security.get("algorithm") == "none")) { + secure = PrefsStatus.ERROR + issues.add(resourceHelper.gs(R.string.prefdecrypt_issue_wrong_algorithm)) + } + + contentJsonObj = container.getJSONObject("content") + decryptedOk = true + } + + if (decryptedOk && contentJsonObj != null) { + for (key in contentJsonObj.keys()) { + entries.put(key, contentJsonObj[key].toString()) + } + } + + val issuesStr: String? = if (issues.size > 0) issues.joinToString("\n") else null + val encryptionDescStr = if (encrypted) { + if (secure == PrefsStatus.OK) resourceHelper.gs(R.string.prefdecrypt_settings_secure) else insecurityReason + } else { + if (secure != PrefsStatus.ERROR) resourceHelper.gs(R.string.prefdecrypt_settings_unencrypted) else resourceHelper.gs(R.string.prefdecrypt_settings_tampered) + } + + metadata[PrefsMetadataKey.ENCRYPTION] = PrefMetadata(encryptionDescStr, secure, issuesStr) + } else { + metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(resourceHelper.gs(R.string.prefdecrypt_wrong_json), PrefsStatus.ERROR) + } return Prefs(entries, metadata) - } catch (e: FileNotFoundException) { + } catch (e: FileNotFoundException) { throw PrefFileNotFoundError(file.absolutePath) } catch (e: IOException) { throw PrefIOError(file.absolutePath) - } catch (e: JSONException){ - throw PrefFormatError("Mallformed preferences JSON file: "+e) + } catch (e: JSONException) { + throw PrefFormatError("Mallformed preferences JSON file: " + e) } } - } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/PrefsFormat.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/PrefsFormat.kt index d8beb30786..a47add0dd7 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/PrefsFormat.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/formats/PrefsFormat.kt @@ -1,26 +1,73 @@ package info.nightscout.androidaps.plugins.general.maintenance.formats +import android.content.Context +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import info.nightscout.androidaps.R import java.io.File -enum class PrefsMetadataKey(val key: String) { - FILE_FORMAT("fileFormat") +enum class PrefsMetadataKey(val key: String, @DrawableRes val icon:Int, @StringRes val label:Int) { + + FILE_FORMAT("format", R.drawable.ic_meta_format, R.string.metadata_label_format), + CREATED_AT("created_at", R.drawable.ic_meta_date, R.string.metadata_label_created_at), + AAPS_VERSION("aaps_version", R.drawable.ic_meta_version, R.string.metadata_label_aaps_version), + AAPS_FLAVOUR("aaps_flavour", R.drawable.ic_meta_flavour, R.string.metadata_label_aaps_flavour), + DEVICE_NAME("device_name", R.drawable.ic_meta_name, R.string.metadata_label_device_name), + DEVICE_MODEL("device_model", R.drawable.ic_meta_model, R.string.metadata_label_device_model), + ENCRYPTION("encryption", R.drawable.ic_meta_encryption, R.string.metadata_label_encryption); + + companion object { + private val keyToEnumMap = HashMap() + + init { + for (value in values()) { + keyToEnumMap.put(value.key, value) + } + } + + fun fromKey(key: String): PrefsMetadataKey? { + if (keyToEnumMap.containsKey(key)) { + return keyToEnumMap.get(key) + } else { + return null + } + } + + + } + + fun formatForDisplay(context: Context, value:String): String { + return when (this) { + FILE_FORMAT -> when (value) { + ClassicPrefsFormat.FORMAT_KEY -> context.getString(R.string.metadata_format_old) + EncryptedPrefsFormat.FORMAT_KEY_ENC -> context.getString(R.string.metadata_format_new) + EncryptedPrefsFormat.FORMAT_KEY_NOENC -> context.getString(R.string.metadata_format_debug) + else -> context.getString(R.string.metadata_format_other) + } + CREATED_AT -> value.replace("T", " ").replace("Z", " (UTC)") + else -> value + } + } + } -data class PrefMetadata(var value : String, var status : PrefsStatus) +data class PrefMetadata(var value : String, var status : PrefsStatus, var info : String? = null) -data class Prefs(val values : Map, val metadata : Map) +data class Prefs(val values : Map, var metadata : Map) interface PrefsFormat { - fun savePreferences(file: File, prefs: Prefs) - fun loadPreferences(file: File) : Prefs + fun savePreferences(file: File, prefs: Prefs, masterPassword: String? = null) + fun loadPreferences(file: File, masterPassword: String? = null) : Prefs } -enum class PrefsStatus { - OK, - WARN, - ERROR +enum class PrefsStatus(@DrawableRes val icon:Int) { + OK(R.drawable.ic_meta_ok), + WARN(R.drawable.ic_meta_warning), + ERROR(R.drawable.ic_meta_error), + UNKNOWN(R.drawable.ic_meta_error), + DISABLED(R.drawable.ic_meta_error) } class PrefFileNotFoundError(message: String) : Exception(message) class PrefIOError(message: String) : Exception(message) -class PrefFormatError(message: String) : Exception(message) \ No newline at end of file +class PrefFormatError(message: String) : Exception(message) diff --git a/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt b/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt index 285b3aaa10..186b81ae36 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/OKDialog.kt @@ -4,17 +4,10 @@ import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.DialogInterface -import android.os.Handler import android.os.SystemClock import android.text.Spanned -import android.view.LayoutInflater -import android.view.View -import android.widget.ImageView -import android.widget.TextView -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.view.ContextThemeWrapper -import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.alertDialogs.AlertDialogHelper object OKDialog { @SuppressLint("InflateParams") @@ -23,11 +16,9 @@ object OKDialog { fun show(context: Context, title: String, message: String, runnable: Runnable? = null) { var notEmptytitle = title if (notEmptytitle.isEmpty()) notEmptytitle = context.getString(R.string.message) - val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = notEmptytitle - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) - AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme)) - .setCustomTitle(titleLayout) + + AlertDialogHelper.Builder(context) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, notEmptytitle)) .setMessage(message) .setPositiveButton(context.getString(R.string.ok)) { dialog: DialogInterface, _: Int -> dialog.dismiss() @@ -38,23 +29,15 @@ object OKDialog { .setCanceledOnTouchOutside(false) } - fun runOnUiThread(theRunnable: Runnable?) { - @Suppress("DEPRECATION") - val mainHandler = Handler(MainApp.instance().applicationContext.mainLooper) - theRunnable?.let { mainHandler.post(it) } - } - @SuppressLint("InflateParams") @JvmStatic @JvmOverloads fun show(activity: Activity, title: String, message: Spanned, runnable: Runnable? = null) { var notEmptytitle = title if (notEmptytitle.isEmpty()) notEmptytitle = activity.getString(R.string.message) - val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = notEmptytitle - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) - AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme)) - .setCustomTitle(titleLayout) + + AlertDialogHelper.Builder(activity) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(activity, notEmptytitle)) .setMessage(message) .setPositiveButton(activity.getString(R.string.ok)) { dialog: DialogInterface, _: Int -> dialog.dismiss() @@ -79,12 +62,9 @@ object OKDialog { @JvmStatic @JvmOverloads fun showConfirmation(activity: Activity, title: String, message: Spanned, ok: Runnable?, cancel: Runnable? = null) { - val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) - AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme)) + AlertDialogHelper.Builder(activity) .setMessage(message) - .setCustomTitle(titleLayout) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(activity, title)) .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int -> dialog.dismiss() SystemClock.sleep(100) @@ -103,12 +83,9 @@ object OKDialog { @SuppressLint("InflateParams") @JvmStatic fun showConfirmation(activity: Activity, title: String, message: String, ok: Runnable?, cancel: Runnable? = null) { - val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) - AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme)) + AlertDialogHelper.Builder(activity) .setMessage(message) - .setCustomTitle(titleLayout) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(activity, title)) .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int -> dialog.dismiss() SystemClock.sleep(100) @@ -133,12 +110,9 @@ object OKDialog { @JvmStatic @JvmOverloads fun showConfirmation(context: Context, title: String, message: Spanned, ok: Runnable?, cancel: Runnable? = null) { - val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) - AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme)) + AlertDialogHelper.Builder(context) .setMessage(message) - .setCustomTitle(titleLayout) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, title)) .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int -> dialog.dismiss() SystemClock.sleep(100) @@ -164,12 +138,9 @@ object OKDialog { @JvmStatic @JvmOverloads fun showConfirmation(context: Context, title: String, message: String, ok: Runnable?, cancel: Runnable? = null) { - val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) - AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme)) + AlertDialogHelper.Builder(context) .setMessage(message) - .setCustomTitle(titleLayout) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, title)) .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int -> dialog.dismiss() SystemClock.sleep(100) @@ -188,12 +159,9 @@ object OKDialog { @JvmStatic @JvmOverloads fun showConfirmation(context: Context, title: String, message: String, ok: DialogInterface.OnClickListener?, cancel: DialogInterface.OnClickListener? = null) { - val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_check_while_48dp) - AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme)) + AlertDialogHelper.Builder(context) .setMessage(message) - .setCustomTitle(titleLayout) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, title)) .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int -> dialog.dismiss() SystemClock.sleep(100) diff --git a/app/src/main/java/info/nightscout/androidaps/utils/ToastUtils.java b/app/src/main/java/info/nightscout/androidaps/utils/ToastUtils.java index 7822e8a80f..26062011f6 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/ToastUtils.java +++ b/app/src/main/java/info/nightscout/androidaps/utils/ToastUtils.java @@ -1,26 +1,94 @@ package info.nightscout.androidaps.utils; +import android.annotation.SuppressLint; import android.content.Context; import android.media.MediaPlayer; import android.os.Handler; import android.os.Looper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.DrawableRes; +import androidx.appcompat.view.ContextThemeWrapper; + import info.nightscout.androidaps.MainApp; -import info.nightscout.androidaps.plugins.bus.RxBus; +import info.nightscout.androidaps.R; import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; public class ToastUtils { + + public static class Long { + + public static void warnToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_warn, false); + } + + public static void infoToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_info,false); + } + + public static void okToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_check,false); + } + + public static void errorToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_error,false); + } + } + public static void showToastInUiThread(final Context ctx, final int stringId) { showToastInUiThread(ctx, MainApp.gs(stringId)); } + public static void warnToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_warn, true); + } + + public static void infoToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_info, true); + } + + public static void okToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_check, true); + } + + public static void errorToast(final Context ctx, final String string) { + graphicalToast(ctx, string, R.drawable.ic_toast_error, true); + } + + public static void graphicalToast(final Context ctx, final String string, @DrawableRes int iconId) { + graphicalToast(ctx, string, iconId, true); + } + + @SuppressLint("InflateParams") + public static void graphicalToast(final Context ctx, final String string, @DrawableRes int iconId, boolean isShort) { + Handler mainThread = new Handler(Looper.getMainLooper()); + mainThread.post(() -> { + View toastRoot =LayoutInflater.from(new ContextThemeWrapper(ctx, R.style.AppTheme)).inflate(R.layout.toast, null); + TextView toastMessage = toastRoot.findViewById(android.R.id.message); + toastMessage.setText(string); + + ImageView toastIcon = toastRoot.findViewById(android.R.id.icon); + toastIcon.setImageResource(iconId); + + Toast toast = new Toast(ctx); + toast.setDuration(isShort ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG); + toast.setView(toastRoot); + toast.show(); + }); + } + public static void showToastInUiThread(final Context ctx, final String string) { Handler mainThread = new Handler(Looper.getMainLooper()); - mainThread.post(() -> Toast.makeText(ctx, string, Toast.LENGTH_SHORT).show()); + mainThread.post(() -> { + Toast.makeText(ctx, string, Toast.LENGTH_SHORT).show(); + }); } public static void showToastInUiThread(final Context ctx, final RxBusWrapper rxBus, diff --git a/app/src/main/java/info/nightscout/androidaps/utils/UIUtils.kt b/app/src/main/java/info/nightscout/androidaps/utils/UIUtils.kt index 99c4a4af31..ad57e519da 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/UIUtils.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/UIUtils.kt @@ -1,6 +1,8 @@ package info.nightscout.androidaps.utils +import android.os.Handler import android.view.View +import info.nightscout.androidaps.MainApp /** * Created by adrian on 2019-12-20. @@ -8,3 +10,7 @@ import android.view.View fun Boolean.toVisibility() = if (this) View.VISIBLE else View.GONE +fun runOnUiThread(theRunnable: Runnable?) { + val mainHandler = Handler(MainApp.instance().applicationContext.mainLooper) + theRunnable?.let { mainHandler.post(it) } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/AlertDialogHelper.kt b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/AlertDialogHelper.kt new file mode 100644 index 0000000000..afc5491652 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/AlertDialogHelper.kt @@ -0,0 +1,31 @@ +package info.nightscout.androidaps.utils.alertDialogs + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.annotation.LayoutRes +import androidx.annotation.StyleRes +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.view.ContextThemeWrapper +import info.nightscout.androidaps.R + +object AlertDialogHelper { + + @Suppress("FunctionName") + fun Builder(context: Context, @StyleRes themeResId: Int = R.style.AppTheme) = + AlertDialog.Builder(ContextThemeWrapper(context, themeResId)) + + fun buildCustomTitle(context: Context, title: String, + @DrawableRes iconResource: Int = R.drawable.ic_check_while_48dp, + @StyleRes themeResId: Int = R.style.AppTheme, + @LayoutRes layoutResource: Int = R.layout.dialog_alert_custom): View? { + val titleLayout = LayoutInflater.from(ContextThemeWrapper(context, themeResId)).inflate(layoutResource, null) + (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = title + (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(iconResource) + return titleLayout + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/PrefImportSummaryDialog.kt b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/PrefImportSummaryDialog.kt new file mode 100644 index 0000000000..db5731f8fc --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/PrefImportSummaryDialog.kt @@ -0,0 +1,140 @@ +package info.nightscout.androidaps.utils.alertDialogs + +import android.annotation.SuppressLint +import android.content.Context +import android.content.DialogInterface +import android.graphics.Color +import android.os.SystemClock +import android.view.LayoutInflater +import android.view.View +import android.webkit.WebView +import android.widget.Button +import android.widget.ImageView +import android.widget.TableLayout +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.annotation.StyleRes +import androidx.appcompat.view.ContextThemeWrapper +import info.nightscout.androidaps.R +import info.nightscout.androidaps.plugins.general.maintenance.formats.Prefs +import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsStatus +import info.nightscout.androidaps.utils.ToastUtils +import info.nightscout.androidaps.utils.runOnUiThread +import java.util.* + +object PrefImportSummaryDialog { + + @SuppressLint("InflateParams") + @JvmStatic + @JvmOverloads + fun showSummary(context: Context, importOk: Boolean, importPossible: Boolean, prefs: Prefs, ok: (() -> Unit)?, cancel: (() -> Unit)? = null) { + + @StyleRes val theme: Int = if (importOk) R.style.AppTheme else { + if (importPossible) R.style.AppThemeWarningDialog else R.style.AppThemeErrorDialog + } + + @StringRes val messageRes: Int = if (importOk) R.string.check_preferences_before_import else { + if (importPossible) R.string.check_preferences_dangerous_import else R.string.check_preferences_cannot_import + } + + @DrawableRes val headerIcon: Int = if (importOk) R.drawable.ic_header_import else { + if (importPossible) R.drawable.ic_header_warning else R.drawable.ic_header_error + } + + val themedCtx = ContextThemeWrapper(context, theme) + + val innerLayout = LayoutInflater.from(themedCtx).inflate(R.layout.dialog_alert_import_summary, null) + val table = (innerLayout.findViewById(R.id.summary_table) as TableLayout) + val detailsBtn = (innerLayout.findViewById(R.id.summary_details_btn) as Button) + + var idx = 0 + val details = LinkedList() + + + for ((metaKey, metaEntry) in prefs.metadata) { + val rowLayout = LayoutInflater.from(themedCtx).inflate(R.layout.import_summary_item, null) + val label = (rowLayout.findViewById(R.id.summary_text) as TextView) + label.setText(metaKey.formatForDisplay(context, metaEntry.value)) + (rowLayout.findViewById(R.id.summary_icon) as ImageView).setImageResource(metaKey.icon) + (rowLayout.findViewById(R.id.status_icon) as ImageView).setImageResource(metaEntry.status.icon) + + if (metaEntry.status == PrefsStatus.WARN) label.setTextColor(themedCtx.getColor(R.color.metadataTextWarning)) + else if (metaEntry.status == PrefsStatus.ERROR) label.setTextColor(themedCtx.getColor(R.color.metadataTextError)) + + if (metaEntry.info != null) { + details.add("${context.getString(metaKey.label)}: ${metaEntry.value}
${metaEntry.info}") + rowLayout.isClickable = true + rowLayout.setOnClickListener { + val msg = "[${context.getString(metaKey.label)}] ${metaEntry.info}" + when (metaEntry.status) { + PrefsStatus.WARN -> ToastUtils.Long.warnToast(context, msg) + PrefsStatus.ERROR -> ToastUtils.Long.errorToast(context, msg) + else -> ToastUtils.Long.infoToast(context, msg) + } + } + } else { + rowLayout.isClickable = true + rowLayout.setOnClickListener { ToastUtils.Long.infoToast(context, context.getString(metaKey.label)) } + } + + table.addView(rowLayout, idx++) + } + + if (details.size > 0) { + detailsBtn.visibility = View.VISIBLE + detailsBtn.setOnClickListener { + val detailsLayout = LayoutInflater.from(context).inflate(R.layout.import_summary_details, null) + val wview = detailsLayout.findViewById(R.id.details_webview) as WebView + wview.loadData("" + details.joinToString("
"), "text/html; charset=utf-8", "utf-8"); + wview.setBackgroundColor(Color.TRANSPARENT); + wview.setLayerType(WebView.LAYER_TYPE_SOFTWARE, null); + + AlertDialogHelper.Builder(context, R.style.AppTheme) + .setCustomTitle(AlertDialogHelper.buildCustomTitle( + context, + context.getString(R.string.check_preferences_details_title), + R.drawable.ic_header_log, + R.style.AppTheme)) + .setView(detailsLayout) + .setPositiveButton(android.R.string.ok) { dialogInner: DialogInterface, _: Int -> + dialogInner.dismiss() + } + .show() + } + } + + val builder = AlertDialogHelper.Builder(context, theme) + .setMessage(context.getString(messageRes)) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, context.getString(R.string.nav_import), headerIcon, theme)) + .setView(innerLayout) + + .setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + if (cancel != null) { + runOnUiThread(Runnable { + cancel() + }) + } + } + + if (importPossible) { + builder.setPositiveButton( + if (importOk) R.string.check_preferences_import_btn else R.string.check_preferences_import_anyway_btn + ) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + if (ok != null) { + runOnUiThread(Runnable { + ok() + }) + } + } + } + + val dialog = builder.show() + dialog.setCanceledOnTouchOutside(false) + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/TwoMessagesAlertDialog.kt b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/TwoMessagesAlertDialog.kt new file mode 100644 index 0000000000..7e8a4d47a6 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/TwoMessagesAlertDialog.kt @@ -0,0 +1,51 @@ +package info.nightscout.androidaps.utils.alertDialogs + +import android.annotation.SuppressLint +import android.content.Context +import android.content.DialogInterface +import android.os.SystemClock +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import androidx.annotation.DrawableRes +import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.runOnUiThread + +object TwoMessagesAlertDialog { + + @SuppressLint("InflateParams") + @JvmStatic + @JvmOverloads + fun showAlert(context: Context, title: String, message: String, secondMessage: String, ok: (() -> Unit)?, cancel: (() -> Unit)? = null, @DrawableRes icon: Int? = null) { + + val secondMessageLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_two_messages, null) + (secondMessageLayout.findViewById(R.id.password_prompt_title) as TextView).text = secondMessage + + val dialog = AlertDialogHelper.Builder(context) + .setMessage(message) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, title, icon + ?: R.drawable.ic_check_while_48dp)) + .setView(secondMessageLayout) + .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + if (ok != null) { + runOnUiThread(Runnable { + ok() + }) + } + } + .setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + if (cancel != null) { + runOnUiThread(Runnable { + cancel() + }) + } + } + .show() + dialog.setCanceledOnTouchOutside(false) + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/WarningDialog.kt b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/WarningDialog.kt new file mode 100644 index 0000000000..cd0654f3bf --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/alertDialogs/WarningDialog.kt @@ -0,0 +1,49 @@ +package info.nightscout.androidaps.utils.alertDialogs + +import android.annotation.SuppressLint +import android.content.Context +import android.content.DialogInterface +import android.os.SystemClock +import androidx.annotation.StringRes +import info.nightscout.androidaps.R +import info.nightscout.androidaps.utils.runOnUiThread + +// if you need error dialog - duplicate to ErrorDialog and make it and use: AppThemeErrorDialog & R.drawable.ic_header_error instead + +object WarningDialog { + + @SuppressLint("InflateParams") + @JvmStatic + @JvmOverloads + fun showWarning(context: Context, title: String, message: String, @StringRes positiveButton: Int = -1, ok: (() -> Unit)? = null, cancel: (() -> Unit)? = null) { + + val builder = AlertDialogHelper.Builder(context, R.style.AppThemeWarningDialog) + .setMessage(message) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, title, R.drawable.ic_header_warning, R.style.AppThemeWarningDialog)) + .setNegativeButton(R.string.dismiss) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + if (cancel != null) { + runOnUiThread(Runnable { + cancel() + }) + } + } + + if (positiveButton != -1) { + builder.setPositiveButton(positiveButton) { dialog: DialogInterface, _: Int -> + dialog.dismiss() + SystemClock.sleep(100) + if (ok != null) { + runOnUiThread(Runnable { + ok() + }) + } + } + } + + val dialog = builder.show() + dialog.setCanceledOnTouchOutside(true) + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt b/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt index 65f1e76e44..1907ac01b5 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/protection/PasswordCheck.kt @@ -1,20 +1,16 @@ package info.nightscout.androidaps.utils.protection import android.annotation.SuppressLint -import android.app.AlertDialog import android.content.Context import android.os.Build import android.view.LayoutInflater import android.view.View import android.widget.EditText -import android.widget.ImageView -import android.widget.TextView import androidx.annotation.StringRes -import androidx.appcompat.view.ContextThemeWrapper -import androidx.fragment.app.FragmentActivity import info.nightscout.androidaps.R import info.nightscout.androidaps.utils.CryptoUtil import info.nightscout.androidaps.utils.ToastUtils +import info.nightscout.androidaps.utils.alertDialogs.AlertDialogHelper import info.nightscout.androidaps.utils.sharedPreferences.SP import javax.inject.Inject import javax.inject.Singleton @@ -26,41 +22,36 @@ val AUTOFILL_HINT_NEW_PASSWORD = "newPassword" class PasswordCheck @Inject constructor(val sp: SP) { @SuppressLint("InflateParams") - fun queryPassword(activity: FragmentActivity, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)?, cancel: (()->Unit)? = null, fail: (()->Unit)? = null) { + fun queryPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)?, cancel: (()->Unit)? = null, fail: (()->Unit)? = null) { val password = sp.getString(preference, "") if (password == "") { ok?.invoke("") return } - val titleLayout = activity.layoutInflater.inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = activity.getString(labelId) - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_key_48dp) - - val promptsView = LayoutInflater.from(activity).inflate(R.layout.passwordprompt, null) - - val alertDialogBuilder = AlertDialog.Builder(ContextThemeWrapper(activity, R.style.AppTheme)) + val promptsView = LayoutInflater.from(context).inflate(R.layout.passwordprompt, null) + val alertDialogBuilder = AlertDialogHelper.Builder(context) alertDialogBuilder.setView(promptsView) val userInput = promptsView.findViewById(R.id.passwordprompt_pass) as EditText if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val autoFillHintPasswordKind = activity.getString(preference) + val autoFillHintPasswordKind = context.getString(preference) userInput.setAutofillHints(View.AUTOFILL_HINT_PASSWORD, "aaps_${autoFillHintPasswordKind}") userInput.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_YES } alertDialogBuilder .setCancelable(false) - .setCustomTitle(titleLayout) - .setPositiveButton(activity.getString(R.string.ok)) { _, _ -> + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, context.getString(labelId), R.drawable.ic_header_key)) + .setPositiveButton(context.getString(R.string.ok)) { _, _ -> val enteredPassword = userInput.text.toString() if (CryptoUtil.checkPassword(enteredPassword, password)) ok?.invoke(enteredPassword) else { - ToastUtils.showToastInUiThread(activity, activity.getString(R.string.wrongpassword)) + ToastUtils.errorToast(context, context.getString(R.string.wrongpassword)) fail?.invoke() } } - .setNegativeButton(activity.getString(R.string.cancel) + .setNegativeButton(context.getString(R.string.cancel) ) { dialog, _ -> cancel?.invoke() dialog.cancel() @@ -72,12 +63,7 @@ class PasswordCheck @Inject constructor(val sp: SP) { @SuppressLint("InflateParams") fun setPassword(context: Context, @StringRes labelId: Int, @StringRes preference: Int, ok: ( (String) -> Unit)? = null, cancel: (()->Unit)? = null, clear: (()->Unit)? = null) { val promptsView = LayoutInflater.from(context).inflate(R.layout.passwordprompt, null) - - val titleLayout = LayoutInflater.from(context).inflate(R.layout.dialog_alert_custom, null) - (titleLayout.findViewById(R.id.alertdialog_title) as TextView).text = context.getText(labelId) - (titleLayout.findViewById(R.id.alertdialog_icon) as ImageView).setImageResource(R.drawable.ic_key_48dp) - - val alertDialogBuilder = AlertDialog.Builder(ContextThemeWrapper(context, R.style.AppTheme)) + val alertDialogBuilder = AlertDialogHelper.Builder(context) alertDialogBuilder.setView(promptsView) val userInput = promptsView.findViewById(R.id.passwordprompt_pass) as EditText @@ -90,20 +76,20 @@ class PasswordCheck @Inject constructor(val sp: SP) { alertDialogBuilder .setCancelable(false) - .setCustomTitle(titleLayout) + .setCustomTitle(AlertDialogHelper.buildCustomTitle(context, context.getString(labelId), R.drawable.ic_header_key)) .setPositiveButton(context.getString(R.string.ok)) { _, _ -> val enteredPassword = userInput.text.toString() if (enteredPassword.isNotEmpty()) { sp.putString(preference, CryptoUtil.hashPassword(enteredPassword)) - ToastUtils.showToastInUiThread(context, context.getString(R.string.password_set)) + ToastUtils.okToast(context, context.getString(R.string.password_set)) ok?.invoke(enteredPassword) } else { if (sp.contains(preference)) { sp.remove(preference) - ToastUtils.showToastInUiThread(context, context.getString(R.string.password_cleared)) + ToastUtils.graphicalToast(context, context.getString(R.string.password_cleared), R.drawable.ic_toast_delete_confirm) clear?.invoke() } else { - ToastUtils.showToastInUiThread(context, context.getString(R.string.password_not_changed)) + ToastUtils.warnToast(context, context.getString(R.string.password_not_changed)) cancel?.invoke() } } @@ -111,7 +97,7 @@ class PasswordCheck @Inject constructor(val sp: SP) { } .setNegativeButton(context.getString(R.string.cancel) ) { dialog, _ -> - ToastUtils.showToastInUiThread(context, context.getString(R.string.password_not_changed)) + ToastUtils.infoToast(context, context.getString(R.string.password_not_changed)) cancel?.invoke() dialog.cancel() } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/storage/FileStrorage.kt b/app/src/main/java/info/nightscout/androidaps/utils/storage/FileStrorage.kt new file mode 100644 index 0000000000..40be1e65c3 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/storage/FileStrorage.kt @@ -0,0 +1,17 @@ +package info.nightscout.androidaps.utils.storage + +import java.io.File +import javax.inject.Singleton + +@Singleton +class FileStorage : Storage { + + override fun getFileContents(file: File): String { + return file.readText() + } + + override fun putFileContents(file: File, contents: String) { + file.writeText(contents) + } + +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/utils/storage/Storage.kt b/app/src/main/java/info/nightscout/androidaps/utils/storage/Storage.kt new file mode 100644 index 0000000000..475cd2675e --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/utils/storage/Storage.kt @@ -0,0 +1,11 @@ +package info.nightscout.androidaps.utils.storage + +import java.io.File + +// This may seems unnecessary abstraction - but it will simplify testing +interface Storage { + + fun getFileContents(file: File) : String + fun putFileContents(file: File, contents: String) + +} diff --git a/app/src/main/res/drawable/alert_border_error.xml b/app/src/main/res/drawable/alert_border_error.xml new file mode 100644 index 0000000000..d1bcae1348 --- /dev/null +++ b/app/src/main/res/drawable/alert_border_error.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/alert_border_warning.xml b/app/src/main/res/drawable/alert_border_warning.xml new file mode 100644 index 0000000000..c73a9517a5 --- /dev/null +++ b/app/src/main/res/drawable/alert_border_warning.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_header_error.xml b/app/src/main/res/drawable/ic_header_error.xml new file mode 100644 index 0000000000..09c29c1558 --- /dev/null +++ b/app/src/main/res/drawable/ic_header_error.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_header_export.xml b/app/src/main/res/drawable/ic_header_export.xml new file mode 100644 index 0000000000..d59103ab07 --- /dev/null +++ b/app/src/main/res/drawable/ic_header_export.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_header_import.xml b/app/src/main/res/drawable/ic_header_import.xml new file mode 100644 index 0000000000..e929fad69b --- /dev/null +++ b/app/src/main/res/drawable/ic_header_import.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_header_key.xml b/app/src/main/res/drawable/ic_header_key.xml new file mode 100644 index 0000000000..416cc50bd3 --- /dev/null +++ b/app/src/main/res/drawable/ic_header_key.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_header_log.xml b/app/src/main/res/drawable/ic_header_log.xml new file mode 100644 index 0000000000..6ef0e68d81 --- /dev/null +++ b/app/src/main/res/drawable/ic_header_log.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_header_warning.xml b/app/src/main/res/drawable/ic_header_warning.xml new file mode 100644 index 0000000000..3d7afd8ef3 --- /dev/null +++ b/app/src/main/res/drawable/ic_header_warning.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_key_48dp.xml b/app/src/main/res/drawable/ic_key_48dp.xml deleted file mode 100644 index 813dc24d00..0000000000 --- a/app/src/main/res/drawable/ic_key_48dp.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_date.xml b/app/src/main/res/drawable/ic_meta_date.xml new file mode 100644 index 0000000000..190392aaa1 --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_date.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_encryption.xml b/app/src/main/res/drawable/ic_meta_encryption.xml new file mode 100644 index 0000000000..597b35a77a --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_encryption.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_error.xml b/app/src/main/res/drawable/ic_meta_error.xml new file mode 100644 index 0000000000..e5d213f774 --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_error.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_flavour.xml b/app/src/main/res/drawable/ic_meta_flavour.xml new file mode 100644 index 0000000000..a7fe7f3471 --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_flavour.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_format.xml b/app/src/main/res/drawable/ic_meta_format.xml new file mode 100644 index 0000000000..2023a4934d --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_format.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_model.xml b/app/src/main/res/drawable/ic_meta_model.xml new file mode 100644 index 0000000000..eca3e69c59 --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_model.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_name.xml b/app/src/main/res/drawable/ic_meta_name.xml new file mode 100644 index 0000000000..ad120e037a --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_name.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_ok.xml b/app/src/main/res/drawable/ic_meta_ok.xml new file mode 100644 index 0000000000..663897c160 --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_ok.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_meta_version.xml b/app/src/main/res/drawable/ic_meta_version.xml new file mode 100644 index 0000000000..ddf95f2fa9 --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_version.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_meta_warning.xml b/app/src/main/res/drawable/ic_meta_warning.xml new file mode 100644 index 0000000000..9cf5ce2c36 --- /dev/null +++ b/app/src/main/res/drawable/ic_meta_warning.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_toast_check.xml b/app/src/main/res/drawable/ic_toast_check.xml new file mode 100644 index 0000000000..26b9f05b49 --- /dev/null +++ b/app/src/main/res/drawable/ic_toast_check.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_toast_delete_confirm.xml b/app/src/main/res/drawable/ic_toast_delete_confirm.xml new file mode 100644 index 0000000000..de6371045a --- /dev/null +++ b/app/src/main/res/drawable/ic_toast_delete_confirm.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_toast_error.xml b/app/src/main/res/drawable/ic_toast_error.xml new file mode 100644 index 0000000000..fb49272c0a --- /dev/null +++ b/app/src/main/res/drawable/ic_toast_error.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_toast_info.xml b/app/src/main/res/drawable/ic_toast_info.xml new file mode 100644 index 0000000000..9d54be6827 --- /dev/null +++ b/app/src/main/res/drawable/ic_toast_info.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_toast_warn.xml b/app/src/main/res/drawable/ic_toast_warn.xml new file mode 100644 index 0000000000..8864b16500 --- /dev/null +++ b/app/src/main/res/drawable/ic_toast_warn.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/toast_border_ok.xml b/app/src/main/res/drawable/toast_border_ok.xml new file mode 100644 index 0000000000..1c62848b31 --- /dev/null +++ b/app/src/main/res/drawable/toast_border_ok.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/app/src/main/res/layout/dialog_alert_custom.xml b/app/src/main/res/layout/dialog_alert_custom.xml index eea5fc6882..f006387d28 100644 --- a/app/src/main/res/layout/dialog_alert_custom.xml +++ b/app/src/main/res/layout/dialog_alert_custom.xml @@ -1,4 +1,8 @@ + + android:layout_height="wrap_content" + android:tint="?dialogTitleIconTint" /> + android:textAppearance="?android:attr/textAppearanceLarge" + android:textColor="?dialogTitleColor" /> + - \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_alert_import_summary.xml b/app/src/main/res/layout/dialog_alert_import_summary.xml new file mode 100644 index 0000000000..fb6e5a0708 --- /dev/null +++ b/app/src/main/res/layout/dialog_alert_import_summary.xml @@ -0,0 +1,30 @@ + + + + + +