refactor import/export to create DaggerAppCompatActivityWithResult

This commit is contained in:
Milos Kozak 2021-01-18 17:36:18 +01:00
parent 3f18f94c79
commit 65a39d91be
45 changed files with 491 additions and 499 deletions

View file

@ -5,11 +5,13 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class Config @Inject constructor(): ConfigInterface{ class Config @Inject constructor() : ConfigInterface {
override val SUPPORTEDNSVERSION = 1002 // 0.10.00 override val SUPPORTEDNSVERSION = 1002 // 0.10.00
override val APS = BuildConfig.FLAVOR == "full" override val APS = BuildConfig.FLAVOR == "full"
override val NSCLIENT = BuildConfig.FLAVOR == "nsclient" || BuildConfig.FLAVOR == "nsclient2" override val NSCLIENT = BuildConfig.FLAVOR == "nsclient" || BuildConfig.FLAVOR == "nsclient2"
override val PUMPCONTROL = BuildConfig.FLAVOR == "pumpcontrol" override val PUMPCONTROL = BuildConfig.FLAVOR == "pumpcontrol"
override val PUMPDRIVERS = BuildConfig.FLAVOR == "full" || BuildConfig.FLAVOR == "pumpcontrol" override val PUMPDRIVERS = BuildConfig.FLAVOR == "full" || BuildConfig.FLAVOR == "pumpcontrol"
override val FLAVOR = BuildConfig.FLAVOR
override val VERSION_NAME = BuildConfig.VERSION_NAME
} }

View file

@ -47,8 +47,6 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.constraints.signatureVerifier.SignatureVerifierPlugin import info.nightscout.androidaps.plugins.constraints.signatureVerifier.SignatureVerifierPlugin
import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs
import info.nightscout.androidaps.plugins.general.maintenance.PrefsFileContract
import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus import info.nightscout.androidaps.plugins.general.nsclient.data.NSSettingsStatus
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
import info.nightscout.androidaps.setupwizard.SetupWizardActivity import info.nightscout.androidaps.setupwizard.SetupWizardActivity
@ -92,18 +90,11 @@ class MainActivity : NoSplashAppCompatActivity() {
@Inject lateinit var constraintChecker: ConstraintChecker @Inject lateinit var constraintChecker: ConstraintChecker
@Inject lateinit var signatureVerifierPlugin: SignatureVerifierPlugin @Inject lateinit var signatureVerifierPlugin: SignatureVerifierPlugin
@Inject lateinit var config: Config @Inject lateinit var config: Config
@Inject lateinit var importExportPrefs: ImportExportPrefs
private lateinit var actionBarDrawerToggle: ActionBarDrawerToggle private lateinit var actionBarDrawerToggle: ActionBarDrawerToggle
private var pluginPreferencesMenuItem: MenuItem? = null private var pluginPreferencesMenuItem: MenuItem? = null
private var menu: Menu? = null private var menu: Menu? = null
val callForPrefFile = registerForActivityResult(PrefsFileContract()) {
it?.let {
importExportPrefs.importSharedPreferences(this, it)
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Iconify.with(FontAwesomeModule()) Iconify.with(FontAwesomeModule())
@ -203,10 +194,9 @@ class MainActivity : NoSplashAppCompatActivity() {
if (p.isEnabled() && p.hasFragment() && !p.isFragmentVisible() && !p.pluginDescription.neverVisible) { if (p.isEnabled() && p.hasFragment() && !p.isFragmentVisible() && !p.pluginDescription.neverVisible) {
val menuItem = menu.add(p.name) val menuItem = menu.add(p.name)
menuItem.isCheckable = true menuItem.isCheckable = true
if(p.menuIcon != -1) { if (p.menuIcon != -1) {
menuItem.setIcon(p.menuIcon) menuItem.setIcon(p.menuIcon)
} else } else {
{
menuItem.setIcon(R.drawable.ic_settings) menuItem.setIcon(R.drawable.ic_settings)
} }
menuItem.setOnMenuItemClickListener { menuItem.setOnMenuItemClickListener {
@ -283,7 +273,7 @@ class MainActivity : NoSplashAppCompatActivity() {
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
this.menu = menu this.menu = menu
menuInflater.inflate(R.menu.menu_main, menu) menuInflater.inflate(R.menu.menu_main, menu)
pluginPreferencesMenuItem = menu.findItem(R.id.nav_plugin_preferences) pluginPreferencesMenuItem = menu.findItem(R.id.nav_plugin_preferences)
setPluginPreferenceMenuName() setPluginPreferenceMenuName()

View file

@ -5,29 +5,20 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import dagger.android.support.DaggerAppCompatActivity
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.interfaces.PluginBase import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.plugins.configBuilder.PluginStore import info.nightscout.androidaps.plugins.configBuilder.PluginStore
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs
import info.nightscout.androidaps.plugins.general.maintenance.PrefsFileContract
import info.nightscout.androidaps.utils.locale.LocaleHelper import info.nightscout.androidaps.utils.locale.LocaleHelper
import info.nightscout.androidaps.utils.protection.ProtectionCheck import info.nightscout.androidaps.utils.protection.ProtectionCheck
import javax.inject.Inject import javax.inject.Inject
class SingleFragmentActivity : DaggerAppCompatActivity() { class SingleFragmentActivity : DaggerAppCompatActivityWithResult() {
@Inject lateinit var pluginStore: PluginStore @Inject lateinit var pluginStore: PluginStore
@Inject lateinit var protectionCheck: ProtectionCheck @Inject lateinit var protectionCheck: ProtectionCheck
@Inject lateinit var importExportPrefs: ImportExportPrefs
private var plugin: PluginBase? = null private var plugin: PluginBase? = null
val callForPrefFile = registerForActivityResult(PrefsFileContract()) {
it?.let {
importExportPrefs.importSharedPreferences(this, it)
}
}
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_single_fragment) setContentView(R.layout.activity_single_fragment)

View file

@ -6,7 +6,6 @@ import info.nightscout.androidaps.MainActivity
import info.nightscout.androidaps.activities.* import info.nightscout.androidaps.activities.*
import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity
import info.nightscout.androidaps.plugins.general.maintenance.activities.LogSettingActivity import info.nightscout.androidaps.plugins.general.maintenance.activities.LogSettingActivity
import info.nightscout.androidaps.plugins.general.maintenance.activities.PrefImportListActivity
import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansLoginActivity import info.nightscout.androidaps.plugins.general.openhumans.OpenHumansLoginActivity
import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity
import info.nightscout.androidaps.plugins.general.smsCommunicator.activities.SmsCommunicatorOtpActivity import info.nightscout.androidaps.plugins.general.smsCommunicator.activities.SmsCommunicatorOtpActivity
@ -40,7 +39,6 @@ abstract class ActivitiesModule {
@ContributesAndroidInjector abstract fun contributesStatsActivity(): StatsActivity @ContributesAndroidInjector abstract fun contributesStatsActivity(): StatsActivity
@ContributesAndroidInjector abstract fun contributesSurveyActivity(): SurveyActivity @ContributesAndroidInjector abstract fun contributesSurveyActivity(): SurveyActivity
@ContributesAndroidInjector abstract fun contributesDefaultProfileActivity(): ProfileHelperActivity @ContributesAndroidInjector abstract fun contributesDefaultProfileActivity(): ProfileHelperActivity
@ContributesAndroidInjector abstract fun contributesPrefImportListActivity(): PrefImportListActivity
@ContributesAndroidInjector abstract fun contributesOpenHumansLoginActivity(): OpenHumansLoginActivity @ContributesAndroidInjector abstract fun contributesOpenHumansLoginActivity(): OpenHumansLoginActivity
} }

View file

@ -12,6 +12,7 @@ import info.nightscout.androidaps.db.DatabaseHelperProvider
import info.nightscout.androidaps.interfaces.* import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
import info.nightscout.androidaps.plugins.configBuilder.PluginStore import info.nightscout.androidaps.plugins.configBuilder.PluginStore
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs
import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
import info.nightscout.androidaps.queue.CommandQueue import info.nightscout.androidaps.queue.CommandQueue
@ -58,5 +59,6 @@ open class AppModule {
@Binds fun bindDatabaseHelperInterface(databaseHelperProvider: DatabaseHelperProvider): DatabaseHelperInterface @Binds fun bindDatabaseHelperInterface(databaseHelperProvider: DatabaseHelperProvider): DatabaseHelperInterface
@Binds fun bindUploadQueueInterface(uploadQueue: UploadQueue): UploadQueueInterface @Binds fun bindUploadQueueInterface(uploadQueue: UploadQueue): UploadQueueInterface
@Binds fun bindNotificationHolderInterface(notificationHolder: NotificationHolder): NotificationHolderInterface @Binds fun bindNotificationHolderInterface(notificationHolder: NotificationHolder): NotificationHolderInterface
@Binds fun bindImportExportPrefsInterface(importExportPrefs: ImportExportPrefs): ImportExportPrefsInterface
} }
} }

View file

@ -2,7 +2,6 @@ package info.nightscout.androidaps.dependencyInjection
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs
import info.nightscout.androidaps.plugins.general.maintenance.PrefFileListProvider import info.nightscout.androidaps.plugins.general.maintenance.PrefFileListProvider
import info.nightscout.androidaps.plugins.general.maintenance.formats.ClassicPrefsFormat import info.nightscout.androidaps.plugins.general.maintenance.formats.ClassicPrefsFormat
import info.nightscout.androidaps.plugins.general.maintenance.formats.EncryptedPrefsFormat import info.nightscout.androidaps.plugins.general.maintenance.formats.EncryptedPrefsFormat
@ -13,7 +12,6 @@ import info.nightscout.androidaps.utils.CryptoUtil
abstract class PreferencesModule { abstract class PreferencesModule {
@ContributesAndroidInjector abstract fun cryptoUtilInjector(): CryptoUtil @ContributesAndroidInjector abstract fun cryptoUtilInjector(): CryptoUtil
@ContributesAndroidInjector abstract fun importExportPrefsInjector(): ImportExportPrefs
@ContributesAndroidInjector abstract fun encryptedPrefsFormatInjector(): EncryptedPrefsFormat @ContributesAndroidInjector abstract fun encryptedPrefsFormatInjector(): EncryptedPrefsFormat
@ContributesAndroidInjector abstract fun classicPrefsFormatInjector(): ClassicPrefsFormat @ContributesAndroidInjector abstract fun classicPrefsFormatInjector(): ClassicPrefsFormat
@ContributesAndroidInjector abstract fun prefImportListProviderInjector(): PrefFileListProvider @ContributesAndroidInjector abstract fun prefImportListProviderInjector(): PrefFileListProvider

View file

@ -1,6 +1,5 @@
package info.nightscout.androidaps.plugins.constraints.objectives.objectives; package info.nightscout.androidaps.plugins.constraints.objectives.objectives;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
import android.text.util.Linkify; import android.text.util.Linkify;
@ -185,9 +184,9 @@ public abstract class Objective {
int days = (int) Math.floor((double) duration / T.days(1).msecs()); int days = (int) Math.floor((double) duration / T.days(1).msecs());
int hours = (int) Math.floor((double) duration / T.hours(1).msecs()); int hours = (int) Math.floor((double) duration / T.hours(1).msecs());
int minutes = (int) Math.floor((double) duration / T.mins(1).msecs()); int minutes = (int) Math.floor((double) duration / T.mins(1).msecs());
if (days > 0) return resourceHelper.gq(R.plurals.objective_days, days, days); if (days > 0) return resourceHelper.gq(R.plurals.days, days, days);
else if (hours > 0) return resourceHelper.gq(R.plurals.objective_hours, hours, hours); else if (hours > 0) return resourceHelper.gq(R.plurals.hours, hours, hours);
else return resourceHelper.gq(R.plurals.objective_minutes, minutes, minutes); else return resourceHelper.gq(R.plurals.minutes, minutes, minutes);
} }
} }

View file

@ -12,16 +12,15 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import info.nightscout.androidaps.BuildConfig import info.nightscout.androidaps.BuildConfig
import info.nightscout.androidaps.MainActivity
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.DaggerAppCompatActivityWithResult
import info.nightscout.androidaps.activities.PreferencesActivity import info.nightscout.androidaps.activities.PreferencesActivity
import info.nightscout.androidaps.activities.SingleFragmentActivity
import info.nightscout.androidaps.events.EventAppExit import info.nightscout.androidaps.events.EventAppExit
import info.nightscout.androidaps.interfaces.ImportExportPrefsInterface
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.maintenance.formats.* import info.nightscout.androidaps.plugins.general.maintenance.formats.*
import info.nightscout.androidaps.setupwizard.SetupWizardActivity
import info.nightscout.androidaps.utils.AndroidPermission import info.nightscout.androidaps.utils.AndroidPermission
import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.ToastUtils import info.nightscout.androidaps.utils.ToastUtils
@ -40,6 +39,7 @@ import java.io.IOException
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.system.exitProcess
/** /**
* Created by mike on 03.07.2016. * Created by mike on 03.07.2016.
@ -57,19 +57,17 @@ class ImportExportPrefs @Inject constructor(
private val classicPrefsFormat: ClassicPrefsFormat, private val classicPrefsFormat: ClassicPrefsFormat,
private val encryptedPrefsFormat: EncryptedPrefsFormat, private val encryptedPrefsFormat: EncryptedPrefsFormat,
private val prefFileList: PrefFileListProvider private val prefFileList: PrefFileListProvider
) { ) : ImportExportPrefsInterface {
val TAG = LTag.CORE override fun prefsFileExists(): Boolean {
fun prefsFileExists(): Boolean {
return prefFileList.listPreferenceFiles().size > 0 return prefFileList.listPreferenceFiles().size > 0
} }
fun exportSharedPreferences(f: Fragment) { override fun exportSharedPreferences(f: Fragment) {
f.activity?.let { exportSharedPreferences(it) } f.activity?.let { exportSharedPreferences(it) }
} }
fun verifyStoragePermissions(fragment: Fragment, onGranted: Runnable) { override fun verifyStoragePermissions(fragment: Fragment, onGranted: Runnable) {
fragment.context?.let { ctx -> fragment.context?.let { ctx ->
val permission = ContextCompat.checkSelfPermission(ctx, val permission = ContextCompat.checkSelfPermission(ctx,
Manifest.permission.WRITE_EXTERNAL_STORAGE) Manifest.permission.WRITE_EXTERNAL_STORAGE)
@ -119,8 +117,7 @@ class ImportExportPrefs @Inject constructor(
// name we detect from OS // name we detect from OS
val systemName = n1 ?: n2 ?: n3 ?: n4 ?: n5 ?: n6 ?: defaultPatientName val systemName = n1 ?: n2 ?: n3 ?: n4 ?: n5 ?: n6 ?: defaultPatientName
val name = if (patientName.isNotEmpty() && patientName != defaultPatientName) patientName else systemName return if (patientName.isNotEmpty() && patientName != defaultPatientName) patientName else systemName
return name
} }
private fun prefsEncryptionIsDisabled() = private fun prefsEncryptionIsDisabled() =
@ -134,6 +131,7 @@ class ImportExportPrefs @Inject constructor(
}) })
} }
@Suppress("SameParameterValue")
private fun askForEncryptionPass(activity: FragmentActivity, @StringRes canceledMsg: Int, @StringRes passwordName: Int, @StringRes passwordExplanation: Int?, private fun askForEncryptionPass(activity: FragmentActivity, @StringRes canceledMsg: Int, @StringRes passwordName: Int, @StringRes passwordExplanation: Int?,
@StringRes passwordWarning: Int?, then: ((password: String) -> Unit)) { @StringRes passwordWarning: Int?, then: ((password: String) -> Unit)) {
passwordCheck.queryAnyPassword(activity, passwordName, R.string.key_master_password, passwordExplanation, passwordWarning, { password -> passwordCheck.queryAnyPassword(activity, passwordName, R.string.key_master_password, passwordExplanation, passwordWarning, { password ->
@ -143,6 +141,7 @@ class ImportExportPrefs @Inject constructor(
}) })
} }
@Suppress("SameParameterValue")
private fun askForMasterPassIfNeeded(activity: FragmentActivity, @StringRes canceledMsg: Int, then: ((password: String) -> Unit)) { private fun askForMasterPassIfNeeded(activity: FragmentActivity, @StringRes canceledMsg: Int, then: ((password: String) -> Unit)) {
if (prefsEncryptionIsDisabled()) { if (prefsEncryptionIsDisabled()) {
then("") then("")
@ -239,49 +238,45 @@ class ImportExportPrefs @Inject constructor(
ToastUtils.okToast(activity, resourceHelper.gs(R.string.exported)) ToastUtils.okToast(activity, resourceHelper.gs(R.string.exported))
} catch (e: FileNotFoundException) { } catch (e: FileNotFoundException) {
ToastUtils.errorToast(activity, resourceHelper.gs(R.string.filenotfound) + " " + newFile) ToastUtils.errorToast(activity, resourceHelper.gs(R.string.filenotfound) + " " + newFile)
log.error(TAG, "Unhandled exception", e) log.error(LTag.CORE, "Unhandled exception", e)
} catch (e: IOException) { } catch (e: IOException) {
ToastUtils.errorToast(activity, e.message) ToastUtils.errorToast(activity, e.message)
log.error(TAG, "Unhandled exception", e) log.error(LTag.CORE, "Unhandled exception", e)
} catch (e: PrefFileNotFoundError) { } catch (e: PrefFileNotFoundError) {
ToastUtils.Long.errorToast(activity, resourceHelper.gs(R.string.preferences_export_canceled) ToastUtils.Long.errorToast(activity, resourceHelper.gs(R.string.preferences_export_canceled)
+ "\n\n" + resourceHelper.gs(R.string.filenotfound) + "\n\n" + resourceHelper.gs(R.string.filenotfound)
+ ": " + e.message + ": " + e.message
+ "\n\n" + resourceHelper.gs(R.string.needstoragepermission)) + "\n\n" + resourceHelper.gs(R.string.needstoragepermission))
log.error(TAG, "File system exception", e) log.error(LTag.CORE, "File system exception", e)
} catch (e: PrefIOError) { } catch (e: PrefIOError) {
ToastUtils.Long.errorToast(activity, resourceHelper.gs(R.string.preferences_export_canceled) ToastUtils.Long.errorToast(activity, resourceHelper.gs(R.string.preferences_export_canceled)
+ "\n\n" + resourceHelper.gs(R.string.needstoragepermission) + "\n\n" + resourceHelper.gs(R.string.needstoragepermission)
+ ": " + e.message) + ": " + e.message)
log.error(TAG, "File system exception", e) log.error(LTag.CORE, "File system exception", e)
} }
} }
} }
fun importSharedPreferences(fragment: Fragment) { override fun importSharedPreferences(fragment: Fragment) {
fragment.activity?.let { fragmentAct -> fragment.activity?.let { fragmentAct ->
importSharedPreferences(fragmentAct) importSharedPreferences(fragmentAct)
} }
} }
fun importSharedPreferences(activity: FragmentActivity) { override fun importSharedPreferences(activity: FragmentActivity) {
try { try {
if (activity is SingleFragmentActivity) if (activity is DaggerAppCompatActivityWithResult)
activity.callForPrefFile.launch(null)
if (activity is MainActivity)
activity.callForPrefFile.launch(null)
if (activity is SetupWizardActivity)
activity.callForPrefFile.launch(null) activity.callForPrefFile.launch(null)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
// this exception happens on some early implementations of ActivityResult contracts // this exception happens on some early implementations of ActivityResult contracts
// when registered and called for the second time // when registered and called for the second time
ToastUtils.errorToast(activity, resourceHelper.gs(R.string.goto_main_try_again)) ToastUtils.errorToast(activity, resourceHelper.gs(R.string.goto_main_try_again))
log.error(TAG, "Internal android framework exception", e) log.error(LTag.CORE, "Internal android framework exception", e)
} }
} }
fun importSharedPreferences(activity: FragmentActivity, importFile: PrefsFile) { override fun importSharedPreferences(activity: FragmentActivity, importFile: PrefsFile) {
askToConfirmImport(activity, importFile) { password -> askToConfirmImport(activity, importFile) { password ->
@ -301,7 +296,7 @@ class ImportExportPrefs @Inject constructor(
promptForDecryptionPasswordIfNeeded(activity, prefsAttempted, importOkAttempted, format, importFile) { prefs, importOk -> promptForDecryptionPasswordIfNeeded(activity, prefsAttempted, importOkAttempted, format, importFile) { prefs, importOk ->
// if at end we allow to import preferences // if at end we allow to import preferences
val importPossible = (importOk || buildHelper.isEngineeringMode()) && (prefs.values.size > 0) val importPossible = (importOk || buildHelper.isEngineeringMode()) && (prefs.values.isNotEmpty())
PrefImportSummaryDialog.showSummary(activity, importOk, importPossible, prefs, { PrefImportSummaryDialog.showSummary(activity, importOk, importPossible, prefs, {
if (importPossible) { if (importPossible) {
@ -325,9 +320,9 @@ class ImportExportPrefs @Inject constructor(
} catch (e: PrefFileNotFoundError) { } catch (e: PrefFileNotFoundError) {
ToastUtils.errorToast(activity, resourceHelper.gs(R.string.filenotfound) + " " + importFile) ToastUtils.errorToast(activity, resourceHelper.gs(R.string.filenotfound) + " " + importFile)
log.error(TAG, "Unhandled exception", e) log.error(LTag.CORE, "Unhandled exception", e)
} catch (e: PrefIOError) { } catch (e: PrefIOError) {
log.error(TAG, "Unhandled exception", e) log.error(LTag.CORE, "Unhandled exception", e)
ToastUtils.errorToast(activity, e.message) ToastUtils.errorToast(activity, e.message)
} }
} }
@ -346,13 +341,13 @@ class ImportExportPrefs @Inject constructor(
private fun restartAppAfterImport(context: Context) { private fun restartAppAfterImport(context: Context) {
sp.putBoolean(R.string.key_setupwizard_processed, true) sp.putBoolean(R.string.key_setupwizard_processed, true)
show(context, resourceHelper.gs(R.string.setting_imported), resourceHelper.gs(R.string.restartingapp), Runnable { show(context, resourceHelper.gs(R.string.setting_imported), resourceHelper.gs(R.string.restartingapp), Runnable {
log.debug(TAG, "Exiting") log.debug(LTag.CORE, "Exiting")
rxBus.send(EventAppExit()) rxBus.send(EventAppExit())
if (context is AppCompatActivity) { if (context is AppCompatActivity) {
context.finish() context.finish()
} }
System.runFinalization() System.runFinalization()
System.exit(0) exitProcess(0)
}) })
} }
} }

View file

@ -8,6 +8,7 @@ import android.view.ViewGroup
import dagger.android.support.DaggerFragment import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.MainApp
import info.nightscout.androidaps.R import info.nightscout.androidaps.R
import info.nightscout.androidaps.interfaces.ImportExportPrefsInterface
import info.nightscout.androidaps.plugins.general.food.FoodPlugin import info.nightscout.androidaps.plugins.general.food.FoodPlugin
import info.nightscout.androidaps.plugins.general.maintenance.activities.LogSettingActivity import info.nightscout.androidaps.plugins.general.maintenance.activities.LogSettingActivity
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin
@ -23,7 +24,7 @@ class MaintenanceFragment : DaggerFragment() {
@Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var treatmentsPlugin: TreatmentsPlugin @Inject lateinit var treatmentsPlugin: TreatmentsPlugin
@Inject lateinit var foodPlugin: FoodPlugin @Inject lateinit var foodPlugin: FoodPlugin
@Inject lateinit var importExportPrefs: ImportExportPrefs @Inject lateinit var importExportPrefs: ImportExportPrefsInterface
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.maintenance_fragment, container, false) return inflater.inflate(R.layout.maintenance_fragment, container, false)

View file

@ -11,6 +11,7 @@ import info.nightscout.androidaps.dialogs.ProfileSwitchDialog
import info.nightscout.androidaps.events.EventPumpStatusChanged import info.nightscout.androidaps.events.EventPumpStatusChanged
import info.nightscout.androidaps.interfaces.ActivePluginProvider import info.nightscout.androidaps.interfaces.ActivePluginProvider
import info.nightscout.androidaps.interfaces.CommandQueueProvider import info.nightscout.androidaps.interfaces.CommandQueueProvider
import info.nightscout.androidaps.interfaces.ImportExportPrefsInterface
import info.nightscout.androidaps.interfaces.PluginType import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin
@ -18,7 +19,6 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin import info.nightscout.androidaps.plugins.configBuilder.ConfigBuilderPlugin
import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesFragment import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesFragment
import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin import info.nightscout.androidaps.plugins.constraints.objectives.ObjectivesPlugin
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs
import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin import info.nightscout.androidaps.plugins.general.nsclient.NSClientPlugin
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus
import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService
@ -55,7 +55,7 @@ class SWDefinition @Inject constructor(
private val loopPlugin: LoopPlugin, private val loopPlugin: LoopPlugin,
private val nsClientPlugin: NSClientPlugin, private val nsClientPlugin: NSClientPlugin,
private val nsProfilePlugin: NSProfilePlugin, private val nsProfilePlugin: NSProfilePlugin,
private val importExportPrefs: ImportExportPrefs, private val importExportPrefs: ImportExportPrefsInterface,
private val androidPermission: AndroidPermission, private val androidPermission: AndroidPermission,
private val cryptoUtil: CryptoUtil, private val cryptoUtil: CryptoUtil,
private val config: Config private val config: Config

View file

@ -14,8 +14,6 @@ import info.nightscout.androidaps.events.EventProfileNeedsUpdate
import info.nightscout.androidaps.events.EventProfileStoreChanged import info.nightscout.androidaps.events.EventProfileStoreChanged
import info.nightscout.androidaps.events.EventPumpStatusChanged import info.nightscout.androidaps.events.EventPumpStatusChanged
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.plugins.general.maintenance.ImportExportPrefs
import info.nightscout.androidaps.plugins.general.maintenance.PrefsFileContract
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientStatus
import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin import info.nightscout.androidaps.plugins.profile.local.LocalProfilePlugin
import info.nightscout.androidaps.plugins.pump.common.events.EventRileyLinkDeviceStatusChange import info.nightscout.androidaps.plugins.pump.common.events.EventRileyLinkDeviceStatusChange
@ -44,7 +42,6 @@ class SetupWizardActivity : NoSplashAppCompatActivity() {
@Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var resourceHelper: ResourceHelper
@Inject lateinit var sp: SP @Inject lateinit var sp: SP
@Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var importExportPrefs: ImportExportPrefs
private val disposable = CompositeDisposable() private val disposable = CompositeDisposable()
private lateinit var screens: List<SWScreen> private lateinit var screens: List<SWScreen>
@ -52,12 +49,6 @@ class SetupWizardActivity : NoSplashAppCompatActivity() {
private val intentMessage = "WIZZARDPAGE" private val intentMessage = "WIZZARDPAGE"
val callForPrefFile = registerForActivityResult(PrefsFileContract()) {
it?.let {
importExportPrefs.importSharedPreferences(this, it)
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
update(applicationContext) update(applicationContext)
@ -213,7 +204,7 @@ class SetupWizardActivity : NoSplashAppCompatActivity() {
if (permissions.isNotEmpty()) { if (permissions.isNotEmpty()) {
if (ActivityCompat.checkSelfPermission(this, permissions[0]) == PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.checkSelfPermission(this, permissions[0]) == PackageManager.PERMISSION_GRANTED) {
when (requestCode) { when (requestCode) {
AndroidPermission.CASE_STORAGE -> //show dialog after permission is granted AndroidPermission.CASE_STORAGE -> //show dialog after permission is granted
show(this, resourceHelper.gs(R.string.permission), resourceHelper.gs(R.string.alert_dialog_storage_permission_text)) show(this, resourceHelper.gs(R.string.permission), resourceHelper.gs(R.string.alert_dialog_storage_permission_text))
AndroidPermission.CASE_LOCATION, AndroidPermission.CASE_SMS, AndroidPermission.CASE_BATTERY -> { AndroidPermission.CASE_LOCATION, AndroidPermission.CASE_SMS, AndroidPermission.CASE_BATTERY -> {

View file

@ -64,10 +64,6 @@
<color name="splashBackground">#2E2E2E</color> <color name="splashBackground">#2E2E2E</color>
<color name="metadataOk">#77dd77</color>
<color name="metadataTextWarning">#FF8C00</color>
<color name="metadataTextError">#FF5555</color>
<color name="importListFileName">#FFFFFF</color> <color name="importListFileName">#FFFFFF</color>
<color name="importListAdditionalInfo">#BBBBBB</color> <color name="importListAdditionalInfo">#BBBBBB</color>

View file

@ -66,16 +66,4 @@
<string name="failedretrievetime">Failed retrieve time</string> <string name="failedretrievetime">Failed retrieve time</string>
<string name="requirementnotmet">Objective requirements not met</string> <string name="requirementnotmet">Objective requirements not met</string>
<plurals name="objective_days">
<item quantity="one">%1$d day</item>
<item quantity="other">%1$d days</item>
</plurals>
<plurals name="objective_hours">
<item quantity="one">%1$d hour</item>
<item quantity="other">%1$d hours</item>
</plurals>
<plurals name="objective_minutes">
<item quantity="one">%1$d minute</item>
<item quantity="other">%1$d minutes</item>
</plurals>
</resources> </resources>

View file

@ -204,8 +204,6 @@
<string name="different_password_used">This file was exported and encrypted with different master password. Provide old master password to decrypt file.</string> <string name="different_password_used">This file was exported and encrypted with different master password. Provide old master password to decrypt file.</string>
<string name="master_password_will_be_replaced">As a result of successful import current master password WILL BE REPLACED with that old master password!</string> <string name="master_password_will_be_replaced">As a result of successful import current master password WILL BE REPLACED with that old master password!</string>
<string name="preferences_import_list_title">Select file to import</string>
<string name="check_preferences_before_import">Please check preferences before importing:</string> <string name="check_preferences_before_import">Please check preferences before importing:</string>
<string name="check_preferences_cannot_import">Preferences cannot be imported!</string> <string name="check_preferences_cannot_import">Preferences cannot be imported!</string>
<string name="check_preferences_dangerous_import">Preferences should not be imported!</string> <string name="check_preferences_dangerous_import">Preferences should not be imported!</string>
@ -214,45 +212,6 @@
<string name="check_preferences_import_btn">Import</string> <string name="check_preferences_import_btn">Import</string>
<string name="check_preferences_import_anyway_btn">Import anyway (DANGEROUS!)</string> <string name="check_preferences_import_anyway_btn">Import anyway (DANGEROUS!)</string>
<string name="metadata_warning_different_flavour">Preferences were created with different variant of AAPS (%1$s) while you have: %2$s.\n\nSome settings may be missing or invalid - after importing please check and update your preferences.</string>
<string name="metadata_warning_different_device">Preferences were created on a different device. It is OK if you are importing from older/different phone, but make sure imported preferences are correct!</string>
<string name="metadata_warning_outdated_format">You are using the outdated legacy format from old versions of AAPS, which is not secure! Only use it as a last resort, if you do not have an export in current, JSON format.</string>
<string name="metadata_warning_old_export">Imported preferences are already %1$s days old! Maybe you have more up-to-date preferences or you choose the wrong file? Remember to export preferences regularly!</string>
<string name="metadata_warning_date_format">Invalid date-time format!</string>
<string name="metadata_warning_different_version">Preferences from different minor version of application. It is OK if you are importing after upgrade, but check after import if preferences are still correct!</string>
<string name="metadata_urgent_different_version">Preferences from different major version of application. Major versions differ significantly and may have incompatible preferences! Make sure after import that preferences are still correct!</string>
<string name="metadata_label_format">File format</string>
<string name="metadata_label_created_at">Created at</string>
<string name="metadata_label_aaps_version">AAPS Version</string>
<string name="metadata_label_aaps_flavour">Build Variant</string>
<string name="metadata_label_device_name">Exporting device patient name</string>
<string name="metadata_label_device_model">Exporting device model</string>
<string name="metadata_label_encryption">File encryption</string>
<string name="metadata_format_old">Old export format</string>
<string name="metadata_format_new">New encrypted format</string>
<string name="metadata_format_debug">New debug format (unencrypted)</string>
<string name="metadata_format_other">Unknown export format</string>
<string name="prefdecrypt_settings_tampered">Settings file tampered</string>
<string name="prefdecrypt_settings_secure">Settings file is secure</string>
<string name="prefdecrypt_settings_unencrypted">Using not secure, unencrypted settings format</string>
<string name="prefdecrypt_wrong_json">JSON format error, missing required field (format, content, metadata or security)</string>
<string name="prefdecrypt_wrong_password">Decryption error, the given password cannot decrypt the file</string>
<string name="prefdecrypt_issue_missing_file_hash">File checksum (hash) missing, cannot verify the authenticity of settings!</string>
<string name="prefdecrypt_issue_modified">File was modified after export!</string>
<string name="prefdecrypt_issue_parsing">Decryption error, parsing preferences failed!</string>
<string name="prefdecrypt_issue_wrong_pass">Decryption error, the provided password is invalid or settings file was modified! It may happen that the imported file was exported with a different Master password.</string>
<string name="prefdecrypt_issue_wrong_format">Missing encryption configuration, settings format is invalid!</string>
<string name="prefdecrypt_issue_wrong_algorithm">Unsupported or not specified encryption algorithm!</string>
<string name="exported_ago" comment="at placeholder we add pluralized number of hours/minutes">exported %1$s ago</string>
<string name="exported_at" comment="at placeholder we add export date">exported at %1$s</string>
<string name="exported_less_than_hour_ago">exported less than hour ago</string>
<string name="in_directory" comment="placeholder is for exported file path">in directory: %1$s</string>
<string name="end_user_license_agreement">End User License Agreement</string> <string name="end_user_license_agreement">End User License Agreement</string>
<string name="end_user_license_agreement_text">MUST NOT BE USED TO MAKE MEDICAL DECISIONS. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.</string> <string name="end_user_license_agreement_text">MUST NOT BE USED TO MAKE MEDICAL DECISIONS. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.</string>
<string name="end_user_license_agreement_i_understand">I UNDERSTAND AND AGREE</string> <string name="end_user_license_agreement_i_understand">I UNDERSTAND AND AGREE</string>
@ -977,7 +936,6 @@
<string name="nav_logsettings">Log settings</string> <string name="nav_logsettings">Log settings</string>
<string name="resettodefaults">Reset to defaults</string> <string name="resettodefaults">Reset to defaults</string>
<string name="nsmalfunction">NSClient malfunction. Consider NS and NSClient restart.</string> <string name="nsmalfunction">NSClient malfunction. Consider NS and NSClient restart.</string>
<string name="versionavailable">Version %1$s available</string>
<string name="time_offset">Time offset</string> <string name="time_offset">Time offset</string>
<string name="key_aps_mode" translatable="false">aps_mode</string> <string name="key_aps_mode" translatable="false">aps_mode</string>
<string name="setupwizard_preferred_aps_mode">Preferred APS mode</string> <string name="setupwizard_preferred_aps_mode">Preferred APS mode</string>
@ -1158,12 +1116,6 @@
<string name="copy_short">COPY</string> <string name="copy_short">COPY</string>
<string name="addnew">Add new</string> <string name="addnew">Add new</string>
<string name="versionChecker">Version Checker</string> <string name="versionChecker">Version Checker</string>
<string name="key_last_time_this_version_detected" translatable="false">last_time_this_version_detected</string>
<string name="key_last_versionchecker_warning" translatable="false">last_versionchecker_waring</string>
<string name="key_last_versionchecker_plugin_warning" translatable="false">last_versionchecker_plugin_waring</string>
<string name="key_last_revoked_certs_check" translatable="false">last_revoked_certs_check</string>
<string name="signature_verifier">Signature verifier</string>
<string name="running_invalid_version">We have detected that you are running an invalid version. Loop disabled!</string>
<string name="old_version">old version</string> <string name="old_version">old version</string>
<string name="very_old_version">very old version</string> <string name="very_old_version">very old version</string>

View file

@ -0,0 +1,17 @@
package info.nightscout.androidaps.activities
import dagger.android.support.DaggerAppCompatActivity
import info.nightscout.androidaps.interfaces.ImportExportPrefsInterface
import info.nightscout.androidaps.plugins.general.maintenance.PrefsFileContract
import javax.inject.Inject
open class DaggerAppCompatActivityWithResult : DaggerAppCompatActivity() {
@Inject lateinit var importExportPrefs: ImportExportPrefsInterface
val callForPrefFile = registerForActivityResult(PrefsFileContract()) {
it?.let {
importExportPrefs.importSharedPreferences(this, it)
}
}
}

View file

@ -2,12 +2,10 @@ package info.nightscout.androidaps.activities
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import dagger.android.support.DaggerAppCompatActivity
import info.nightscout.androidaps.core.R import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.utils.locale.LocaleHelper import info.nightscout.androidaps.utils.locale.LocaleHelper
//@Suppress("registered") open class NoSplashAppCompatActivity : DaggerAppCompatActivityWithResult() {
open class NoSplashAppCompatActivity : DaggerAppCompatActivity() {
public override fun onCreate(savedInstanceState: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View file

@ -9,11 +9,13 @@ import info.nightscout.androidaps.dialogs.BolusProgressDialog
import info.nightscout.androidaps.dialogs.ErrorDialog import info.nightscout.androidaps.dialogs.ErrorDialog
import info.nightscout.androidaps.dialogs.NtpProgressDialog import info.nightscout.androidaps.dialogs.NtpProgressDialog
import info.nightscout.androidaps.dialogs.ProfileViewerDialog import info.nightscout.androidaps.dialogs.ProfileViewerDialog
import info.nightscout.androidaps.plugins.general.maintenance.activities.PrefImportListActivity
@Module @Module
@Suppress("unused") @Suppress("unused")
abstract class CoreFragmentsModule { abstract class CoreFragmentsModule {
@ContributesAndroidInjector abstract fun contributesPrefImportListActivity(): PrefImportListActivity
@ContributesAndroidInjector abstract fun contributesTDDStatsActivity(): TDDStatsActivity @ContributesAndroidInjector abstract fun contributesTDDStatsActivity(): TDDStatsActivity
@ContributesAndroidInjector abstract fun contributeBolusProgressHelperActivity(): BolusProgressHelperActivity @ContributesAndroidInjector abstract fun contributeBolusProgressHelperActivity(): BolusProgressHelperActivity
@ContributesAndroidInjector abstract fun contributeErrorHelperActivity(): ErrorHelperActivity @ContributesAndroidInjector abstract fun contributeErrorHelperActivity(): ErrorHelperActivity

View file

@ -6,4 +6,6 @@ interface ConfigInterface {
val NSCLIENT: Boolean val NSCLIENT: Boolean
val PUMPCONTROL: Boolean val PUMPCONTROL: Boolean
val PUMPDRIVERS: Boolean val PUMPDRIVERS: Boolean
val FLAVOR: String
val VERSION_NAME: String
} }

View file

@ -0,0 +1,15 @@
package info.nightscout.androidaps.interfaces
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import info.nightscout.androidaps.plugins.general.maintenance.PrefsFile
interface ImportExportPrefsInterface {
fun importSharedPreferences(activity: FragmentActivity, importFile: PrefsFile)
fun importSharedPreferences(activity: FragmentActivity)
fun importSharedPreferences(fragment: Fragment)
fun prefsFileExists(): Boolean
fun verifyStoragePermissions(fragment: Fragment, onGranted: Runnable)
fun exportSharedPreferences(f: Fragment)
}

View file

@ -2,9 +2,8 @@ package info.nightscout.androidaps.plugins.constraints.versionChecker
import android.content.Context import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
import info.nightscout.androidaps.BuildConfig import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.interfaces.ConfigInterface
import info.nightscout.androidaps.R
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.bus.RxBusWrapper
@ -24,6 +23,7 @@ class VersionCheckerUtils @Inject constructor(
val sp: SP, val sp: SP,
val resourceHelper: ResourceHelper, val resourceHelper: ResourceHelper,
val rxBus: RxBusWrapper, val rxBus: RxBusWrapper,
private val config: ConfigInterface,
val context: Context val context: Context
) { ) {
@ -50,7 +50,7 @@ class VersionCheckerUtils @Inject constructor(
Thread { Thread {
try { try {
val version: String? = findVersion(URL("https://raw.githubusercontent.com/nightscout/AndroidAPS/master/app/build.gradle").readText()) val version: String? = findVersion(URL("https://raw.githubusercontent.com/nightscout/AndroidAPS/master/app/build.gradle").readText())
compareWithCurrentVersion(version, BuildConfig.VERSION_NAME) compareWithCurrentVersion(version, config.VERSION_NAME)
} catch (e: IOException) { } catch (e: IOException) {
aapsLogger.error(LTag.CORE, "Github master version check error: $e") aapsLogger.error(LTag.CORE, "Github master version check error: $e")
} }
@ -130,6 +130,7 @@ class VersionCheckerUtils @Inject constructor(
} }
companion object { companion object {
private val CHECK_EVERY = TimeUnit.DAYS.toMillis(1) private val CHECK_EVERY = TimeUnit.DAYS.toMillis(1)
private val WARN_EVERY = TimeUnit.DAYS.toMillis(1) private val WARN_EVERY = TimeUnit.DAYS.toMillis(1)
} }
@ -148,7 +149,7 @@ fun findVersion(file: String?): String? {
fun String.versionStrip() = this.mapNotNull { fun String.versionStrip() = this.mapNotNull {
when (it) { when (it) {
in '0'..'9' -> it in '0'..'9' -> it
'.' -> it '.' -> it
else -> null else -> null
} }
}.joinToString(separator = "") }.joinToString(separator = "")

View file

@ -1,220 +1,178 @@
package info.nightscout.androidaps.plugins.general.maintenance package info.nightscout.androidaps.plugins.general.maintenance
import android.content.Context import android.os.Build
import android.content.Intent import android.os.Environment
import android.os.Build import info.nightscout.androidaps.core.R
import android.os.Environment import info.nightscout.androidaps.interfaces.ConfigInterface
import android.os.Parcelable import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils
import androidx.activity.result.contract.ActivityResultContract import info.nightscout.androidaps.plugins.general.maintenance.formats.*
import androidx.fragment.app.FragmentActivity import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.BuildConfig import info.nightscout.androidaps.utils.storage.Storage
import info.nightscout.androidaps.R import org.joda.time.DateTime
import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils import org.joda.time.Days
import info.nightscout.androidaps.plugins.general.maintenance.activities.PrefImportListActivity import org.joda.time.Hours
import info.nightscout.androidaps.plugins.general.maintenance.formats.* import org.joda.time.LocalDateTime
import info.nightscout.androidaps.utils.resources.ResourceHelper import org.joda.time.format.DateTimeFormat
import info.nightscout.androidaps.utils.storage.Storage import java.io.File
import kotlinx.android.parcel.Parcelize import javax.inject.Inject
import kotlinx.android.parcel.RawValue import javax.inject.Singleton
import org.joda.time.DateTime import kotlin.math.abs
import org.joda.time.Days
import org.joda.time.Hours fun getCurrentDeviceModelString() =
import org.joda.time.LocalDateTime Build.MANUFACTURER + " " + Build.MODEL + " (" + Build.DEVICE + ")"
import org.joda.time.format.DateTimeFormat
import java.io.File @Singleton
import javax.inject.Inject class PrefFileListProvider @Inject constructor(
import javax.inject.Singleton private val resourceHelper: ResourceHelper,
private val config: ConfigInterface,
enum class PrefsImportDir { private val classicPrefsFormat: ClassicPrefsFormat,
ROOT_DIR, private val encryptedPrefsFormat: EncryptedPrefsFormat,
AAPS_DIR private val storage: Storage,
} private val versionCheckerUtils: VersionCheckerUtils
) {
@Parcelize
data class PrefsFile( companion object {
val name: String,
val file: File, private val path = File(Environment.getExternalStorageDirectory().toString())
val baseDir: File, private val aapsPath = File(path, "AAPS" + File.separator + "preferences")
val dirKind: PrefsImportDir, private const val IMPORT_AGE_NOT_YET_OLD_DAYS = 60
val handler: PrefsFormatsHandler, }
// metadata here is used only for list display /**
val metadata: @RawValue Map<PrefsMetadataKey, PrefMetadata> * This function tries to list possible preference files from main SDCard root dir and AAPS/preferences dir
) : Parcelable * and tries to do quick assessment for preferences format plausibility.
* It does NOT load full metadata or is 100% accurate - it tries to do QUICK detection, based on:
class PrefsFileContract : ActivityResultContract<Void, PrefsFile>() { * - file name and extension
* - predicted file contents
companion object { */
fun listPreferenceFiles(loadMetadata: Boolean = false): MutableList<PrefsFile> {
const val OUTPUT_PARAM = "prefs_file" val prefFiles = mutableListOf<PrefsFile>()
}
// searching rood dir for legacy files
override fun parseResult(resultCode: Int, intent: Intent?): PrefsFile? { path.walk().maxDepth(1).filter { it.isFile && (it.name.endsWith(".json") || it.name.contains("Preferences")) }.forEach {
return when (resultCode) { val contents = storage.getFileContents(it)
FragmentActivity.RESULT_OK -> intent?.getParcelableExtra(OUTPUT_PARAM) val detectedNew = encryptedPrefsFormat.isPreferencesFile(it, contents)
else -> null val detectedOld = !detectedNew && classicPrefsFormat.isPreferencesFile(it, contents)
} if (detectedNew || detectedOld) {
} val formatHandler = if (detectedNew) PrefsFormatsHandler.ENCRYPTED else PrefsFormatsHandler.CLASSIC
prefFiles.add(PrefsFile(it.name, it, path, PrefsImportDir.ROOT_DIR, formatHandler, metadataFor(loadMetadata, formatHandler, contents)))
override fun createIntent(context: Context, input: Void?): Intent { }
return Intent(context, PrefImportListActivity::class.java) }
}
} // searching dedicated dir, only for new JSON format
aapsPath.walk().filter { it.isFile && it.name.endsWith(".json") }.forEach {
fun getCurrentDeviceModelString() = val contents = storage.getFileContents(it)
Build.MANUFACTURER + " " + Build.MODEL + " (" + Build.DEVICE + ")" if (encryptedPrefsFormat.isPreferencesFile(it, contents)) {
prefFiles.add(PrefsFile(it.name, it, aapsPath, PrefsImportDir.AAPS_DIR, PrefsFormatsHandler.ENCRYPTED, metadataFor(loadMetadata, PrefsFormatsHandler.ENCRYPTED, contents)))
@Singleton }
class PrefFileListProvider @Inject constructor( }
private val resourceHelper: ResourceHelper,
private val classicPrefsFormat: ClassicPrefsFormat, // we sort only if we have metadata to be used for that
private val encryptedPrefsFormat: EncryptedPrefsFormat, if (loadMetadata) {
private val storage: Storage, prefFiles.sortWith(
private val versionCheckerUtils: VersionCheckerUtils compareByDescending<PrefsFile> { it.handler }
) { .thenBy { it.metadata[PrefsMetadataKey.AAPS_FLAVOUR]?.status }
.thenByDescending { it.metadata[PrefsMetadataKey.CREATED_AT]?.value }
companion object { )
}
private val path = File(Environment.getExternalStorageDirectory().toString())
private val aapsPath = File(path, "AAPS" + File.separator + "preferences") return prefFiles
private const val IMPORT_AGE_NOT_YET_OLD_DAYS = 60 }
}
private fun metadataFor(loadMetadata: Boolean, formatHandler: PrefsFormatsHandler, contents: String): PrefMetadataMap {
/** if (!loadMetadata) {
* This function tries to list possible preference files from main SDCard root dir and AAPS/preferences dir return mapOf()
* and tries to do quick assessment for preferences format plausibility. }
* It does NOT load full metadata or is 100% accurate - it tries to do QUICK detection, based on: return checkMetadata(when (formatHandler) {
* - file name and extension PrefsFormatsHandler.CLASSIC -> classicPrefsFormat.loadMetadata(contents)
* - predicted file contents PrefsFormatsHandler.ENCRYPTED -> encryptedPrefsFormat.loadMetadata(contents)
*/ })
fun listPreferenceFiles(loadMetadata: Boolean = false): MutableList<PrefsFile> { }
val prefFiles = mutableListOf<PrefsFile>()
fun legacyFile(): File {
// searching rood dir for legacy files return File(path, resourceHelper.gs(R.string.app_name) + "Preferences")
path.walk().maxDepth(1).filter { it.isFile && (it.name.endsWith(".json") || it.name.contains("Preferences")) }.forEach { }
val contents = storage.getFileContents(it)
val detectedNew = encryptedPrefsFormat.isPreferencesFile(it, contents) fun ensureExportDirExists() {
val detectedOld = !detectedNew && classicPrefsFormat.isPreferencesFile(it, contents) if (!aapsPath.exists()) {
if (detectedNew || detectedOld) { aapsPath.mkdirs()
val formatHandler = if (detectedNew) PrefsFormatsHandler.ENCRYPTED else PrefsFormatsHandler.CLASSIC }
prefFiles.add(PrefsFile(it.name, it, path, PrefsImportDir.ROOT_DIR, formatHandler, metadataFor(loadMetadata, formatHandler, contents))) }
}
} fun newExportFile(): File {
val timeLocal = LocalDateTime.now().toString(DateTimeFormat.forPattern("yyyy-MM-dd'_'HHmmss"))
// searching dedicated dir, only for new JSON format return File(aapsPath, timeLocal + "_" + config.FLAVOR + ".json")
aapsPath.walk().filter { it.isFile && it.name.endsWith(".json") }.forEach { }
val contents = storage.getFileContents(it)
if (encryptedPrefsFormat.isPreferencesFile(it, contents)) { // check metadata for known issues, change their status and add info with explanations
prefFiles.add(PrefsFile(it.name, it, aapsPath, PrefsImportDir.AAPS_DIR, PrefsFormatsHandler.ENCRYPTED, metadataFor(loadMetadata, PrefsFormatsHandler.ENCRYPTED, contents))) fun checkMetadata(metadata: Map<PrefsMetadataKey, PrefMetadata>): Map<PrefsMetadataKey, PrefMetadata> {
} val meta = metadata.toMutableMap()
}
meta[PrefsMetadataKey.AAPS_FLAVOUR]?.let { flavour ->
// we sort only if we have metadata to be used for that val flavourOfPrefs = flavour.value
if (loadMetadata) { if (flavour.value != config.FLAVOR) {
prefFiles.sortWith( flavour.status = PrefsStatus.WARN
compareByDescending<PrefsFile> { it.handler } flavour.info = resourceHelper.gs(R.string.metadata_warning_different_flavour, flavourOfPrefs, config.FLAVOR)
.thenBy { it.metadata[PrefsMetadataKey.AAPS_FLAVOUR]?.status } }
.thenByDescending { it.metadata[PrefsMetadataKey.CREATED_AT]?.value } }
)
} meta[PrefsMetadataKey.DEVICE_MODEL]?.let { model ->
if (model.value != getCurrentDeviceModelString()) {
return prefFiles model.status = PrefsStatus.WARN
} model.info = resourceHelper.gs(R.string.metadata_warning_different_device)
}
private fun metadataFor(loadMetadata: Boolean, formatHandler: PrefsFormatsHandler, contents: String): PrefMetadataMap { }
if (!loadMetadata) {
return mapOf() meta[PrefsMetadataKey.CREATED_AT]?.let { createdAt ->
} try {
return checkMetadata(when (formatHandler) { val date1 = DateTime.parse(createdAt.value)
PrefsFormatsHandler.CLASSIC -> classicPrefsFormat.loadMetadata(contents) val date2 = DateTime.now()
PrefsFormatsHandler.ENCRYPTED -> encryptedPrefsFormat.loadMetadata(contents)
}) val daysOld = Days.daysBetween(date1.toLocalDate(), date2.toLocalDate()).days
}
if (daysOld > IMPORT_AGE_NOT_YET_OLD_DAYS) {
fun legacyFile(): File { createdAt.status = PrefsStatus.WARN
return File(path, resourceHelper.gs(R.string.app_name) + "Preferences") createdAt.info = resourceHelper.gs(R.string.metadata_warning_old_export, daysOld.toString())
} }
} catch (e: Exception) {
fun ensureExportDirExists() { createdAt.status = PrefsStatus.WARN
if (!aapsPath.exists()) { createdAt.info = resourceHelper.gs(R.string.metadata_warning_date_format)
aapsPath.mkdirs() }
} }
}
meta[PrefsMetadataKey.AAPS_VERSION]?.let { version ->
fun newExportFile(): File { val currentAppVer = versionCheckerUtils.versionDigits(config.VERSION_NAME)
val timeLocal = LocalDateTime.now().toString(DateTimeFormat.forPattern("yyyy-MM-dd'_'HHmmss")) val metadataVer = versionCheckerUtils.versionDigits(version.value)
return File(aapsPath, timeLocal + "_" + BuildConfig.FLAVOR + ".json")
} if ((currentAppVer.size >= 2) && (metadataVer.size >= 2) && (abs(currentAppVer[1] - metadataVer[1]) > 1)) {
version.status = PrefsStatus.WARN
// check metadata for known issues, change their status and add info with explanations version.info = resourceHelper.gs(R.string.metadata_warning_different_version)
fun checkMetadata(metadata: Map<PrefsMetadataKey, PrefMetadata>): Map<PrefsMetadataKey, PrefMetadata> { }
val meta = metadata.toMutableMap()
if ((currentAppVer.isNotEmpty()) && (metadataVer.isNotEmpty()) && (currentAppVer[0] != metadataVer[0])) {
meta[PrefsMetadataKey.AAPS_FLAVOUR]?.let { flavour -> version.status = PrefsStatus.WARN
val flavourOfPrefs = flavour.value version.info = resourceHelper.gs(R.string.metadata_urgent_different_version)
if (flavour.value != BuildConfig.FLAVOR) { }
flavour.status = PrefsStatus.WARN }
flavour.info = resourceHelper.gs(R.string.metadata_warning_different_flavour, flavourOfPrefs, BuildConfig.FLAVOR)
} return meta
} }
meta[PrefsMetadataKey.DEVICE_MODEL]?.let { model -> fun formatExportedAgo(utcTime: String): String {
if (model.value != getCurrentDeviceModelString()) { val refTime = DateTime.now()
model.status = PrefsStatus.WARN val itTime = DateTime.parse(utcTime)
model.info = resourceHelper.gs(R.string.metadata_warning_different_device) val days = Days.daysBetween(itTime, refTime).days
} val hours = Hours.hoursBetween(itTime, refTime).hours
}
return if (hours == 0) {
meta[PrefsMetadataKey.CREATED_AT]?.let { createdAt -> resourceHelper.gs(R.string.exported_less_than_hour_ago)
try { } else if ((hours < 24) && (hours > 0)) {
val date1 = DateTime.parse(createdAt.value) resourceHelper.gs(R.string.exported_ago, resourceHelper.gq(R.plurals.hours, hours, hours))
val date2 = DateTime.now() } else if ((days < IMPORT_AGE_NOT_YET_OLD_DAYS) && (days > 0)) {
resourceHelper.gs(R.string.exported_ago, resourceHelper.gq(R.plurals.days, days, days))
val daysOld = Days.daysBetween(date1.toLocalDate(), date2.toLocalDate()).days } else {
resourceHelper.gs(R.string.exported_at, utcTime.substring(0, 10))
if (daysOld > IMPORT_AGE_NOT_YET_OLD_DAYS) { }
createdAt.status = PrefsStatus.WARN }
createdAt.info = resourceHelper.gs(R.string.metadata_warning_old_export, daysOld.toString())
}
} catch (e: Exception) {
createdAt.status = PrefsStatus.WARN
createdAt.info = resourceHelper.gs(R.string.metadata_warning_date_format)
}
}
meta[PrefsMetadataKey.AAPS_VERSION]?.let { version ->
val currentAppVer = versionCheckerUtils.versionDigits(BuildConfig.VERSION_NAME)
val metadataVer = versionCheckerUtils.versionDigits(version.value)
if ((currentAppVer.size >= 2) && (metadataVer.size >= 2) && (Math.abs(currentAppVer[1] - metadataVer[1]) > 1)) {
version.status = PrefsStatus.WARN
version.info = resourceHelper.gs(R.string.metadata_warning_different_version)
}
if ((currentAppVer.isNotEmpty()) && (metadataVer.isNotEmpty()) && (currentAppVer[0] != metadataVer[0])) {
version.status = PrefsStatus.WARN
version.info = resourceHelper.gs(R.string.metadata_urgent_different_version)
}
}
return meta
}
fun formatExportedAgo(utcTime: String): String {
val refTime = DateTime.now()
val itTime = DateTime.parse(utcTime)
val days = Days.daysBetween(itTime, refTime).days
val hours = Hours.hoursBetween(itTime, refTime).hours
return if (hours == 0) {
resourceHelper.gs(R.string.exported_less_than_hour_ago)
} else if ((hours < 24) && (hours > 0)) {
resourceHelper.gs(R.string.exported_ago, resourceHelper.gq(R.plurals.objective_hours, hours, hours))
} else if ((days < IMPORT_AGE_NOT_YET_OLD_DAYS) && (days > 0)) {
resourceHelper.gs(R.string.exported_ago, resourceHelper.gq(R.plurals.objective_days, days, days))
} else {
resourceHelper.gs(R.string.exported_at, utcTime.substring(0, 10))
}
}
} }

View file

@ -0,0 +1,21 @@
package info.nightscout.androidaps.plugins.general.maintenance
import android.os.Parcelable
import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefMetadata
import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsMetadataKey
import kotlinx.android.parcel.Parcelize
import kotlinx.android.parcel.RawValue
import java.io.File
@Parcelize
data class PrefsFile(
val name: String,
val file: File,
val baseDir: File,
val dirKind: PrefsImportDir,
val handler: PrefsFormatsHandler,
// metadata here is used only for list display
val metadata: @RawValue Map<PrefsMetadataKey, PrefMetadata>
) : Parcelable

View file

@ -0,0 +1,27 @@
package info.nightscout.androidaps.plugins.general.maintenance
import android.content.Context
import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract
import androidx.fragment.app.FragmentActivity
import info.nightscout.androidaps.plugins.general.maintenance.activities.PrefImportListActivity
class PrefsFileContract : ActivityResultContract<Void, PrefsFile>() {
companion object {
const val OUTPUT_PARAM = "prefs_file"
}
override fun parseResult(resultCode: Int, intent: Intent?): PrefsFile? {
return when (resultCode) {
FragmentActivity.RESULT_OK -> intent?.getParcelableExtra(OUTPUT_PARAM)
else -> null
}
}
override fun createIntent(context: Context, input: Void?): Intent {
return Intent(context, PrefImportListActivity::class.java)
}
}

View file

@ -0,0 +1,6 @@
package info.nightscout.androidaps.plugins.general.maintenance
enum class PrefsFormatsHandler {
CLASSIC,
ENCRYPTED
}

View file

@ -0,0 +1,6 @@
package info.nightscout.androidaps.plugins.general.maintenance
enum class PrefsImportDir {
ROOT_DIR,
AAPS_DIR
}

View file

@ -12,11 +12,11 @@ import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import dagger.android.support.DaggerAppCompatActivity import dagger.android.support.DaggerAppCompatActivity
import info.nightscout.androidaps.R import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.plugins.general.maintenance.PrefFileListProvider import info.nightscout.androidaps.plugins.general.maintenance.PrefFileListProvider
import info.nightscout.androidaps.plugins.general.maintenance.PrefsFile import info.nightscout.androidaps.plugins.general.maintenance.PrefsFile
import info.nightscout.androidaps.plugins.general.maintenance.PrefsFileContract import info.nightscout.androidaps.plugins.general.maintenance.PrefsFileContract
import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsFormatsHandler import info.nightscout.androidaps.plugins.general.maintenance.PrefsFormatsHandler
import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsMetadataKey import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsMetadataKey
import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsStatus import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsStatus
import info.nightscout.androidaps.utils.locale.LocaleHelper import info.nightscout.androidaps.utils.locale.LocaleHelper
@ -46,6 +46,7 @@ class PrefImportListActivity : DaggerAppCompatActivity() {
inner class RecyclerViewAdapter internal constructor(private var prefFileList: List<PrefsFile>) : RecyclerView.Adapter<RecyclerViewAdapter.PrefFileViewHolder>() { inner class RecyclerViewAdapter internal constructor(private var prefFileList: List<PrefsFile>) : RecyclerView.Adapter<RecyclerViewAdapter.PrefFileViewHolder>() {
inner class PrefFileViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { inner class PrefFileViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var fileName: TextView = itemView.findViewById(R.id.filelist_name) var fileName: TextView = itemView.findViewById(R.id.filelist_name)
var fileDir: TextView = itemView.findViewById(R.id.filelist_dir) var fileDir: TextView = itemView.findViewById(R.id.filelist_dir)
var metaDateTime: TextView = itemView.findViewById(R.id.meta_date_time) var metaDateTime: TextView = itemView.findViewById(R.id.meta_date_time)

View file

@ -1,7 +1,7 @@
package info.nightscout.androidaps.plugins.general.maintenance.formats package info.nightscout.androidaps.plugins.general.maintenance.formats
import info.nightscout.androidaps.Constants import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.utils.resources.ResourceHelper import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.storage.Storage import info.nightscout.androidaps.utils.storage.Storage
import java.io.File import java.io.File
@ -17,6 +17,7 @@ class ClassicPrefsFormat @Inject constructor(
) : PrefsFormat { ) : PrefsFormat {
companion object { companion object {
val FORMAT_KEY = "aaps_old" val FORMAT_KEY = "aaps_old"
} }

View file

@ -1,6 +1,6 @@
package info.nightscout.androidaps.plugins.general.maintenance.formats package info.nightscout.androidaps.plugins.general.maintenance.formats
import info.nightscout.androidaps.R import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.utils.CryptoUtil import info.nightscout.androidaps.utils.CryptoUtil
import info.nightscout.androidaps.utils.extensions.hexStringToByteArray import info.nightscout.androidaps.utils.extensions.hexStringToByteArray
import info.nightscout.androidaps.utils.extensions.toHex import info.nightscout.androidaps.utils.extensions.toHex
@ -23,6 +23,7 @@ class EncryptedPrefsFormat @Inject constructor(
) : PrefsFormat { ) : PrefsFormat {
companion object { companion object {
val FORMAT_KEY_ENC = "aaps_encrypted" val FORMAT_KEY_ENC = "aaps_encrypted"
val FORMAT_KEY_NOENC = "aaps_structured" val FORMAT_KEY_NOENC = "aaps_structured"

View file

@ -4,7 +4,7 @@ import android.content.Context
import android.os.Parcelable import android.os.Parcelable
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import info.nightscout.androidaps.R import info.nightscout.androidaps.core.R
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import java.io.File import java.io.File
@ -74,11 +74,6 @@ enum class PrefsStatus(@DrawableRes val icon: Int) {
DISABLED(R.drawable.ic_meta_error) DISABLED(R.drawable.ic_meta_error)
} }
enum class PrefsFormatsHandler {
CLASSIC,
ENCRYPTED
}
class PrefFileNotFoundError(message: String) : Exception(message) class PrefFileNotFoundError(message: String) : Exception(message)
class PrefIOError(message: String) : Exception(message) class PrefIOError(message: String) : Exception(message)
class PrefFormatError(message: String) : Exception(message) class PrefFormatError(message: String) : Exception(message)

View file

@ -1,17 +1,17 @@
package info.nightscout.androidaps.utils.storage package info.nightscout.androidaps.utils.storage
import java.io.File import java.io.File
import javax.inject.Singleton import javax.inject.Singleton
@Singleton @Singleton
class FileStorage : Storage { class FileStorage : Storage {
override fun getFileContents(file: File): String { override fun getFileContents(file: File): String {
return file.readText() return file.readText()
} }
override fun putFileContents(file: File, contents: String) { override fun putFileContents(file: File, contents: String) {
file.writeText(contents) file.writeText(contents)
} }
} }

View file

@ -1,11 +1,11 @@
package info.nightscout.androidaps.utils.storage package info.nightscout.androidaps.utils.storage
import java.io.File import java.io.File
// This may seems unnecessary abstraction - but it will simplify testing // This may seems unnecessary abstraction - but it will simplify testing
interface Storage { interface Storage {
fun getFileContents(file: File) : String fun getFileContents(file: File): String
fun putFileContents(file: File, contents: String) fun putFileContents(file: File, contents: String)
} }

View file

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#000" android:fillColor="#000"
android:pathData="M15,13H16.5V15.82L18.94,17.23L18.19,18.53L15,16.69V13M19,8H5V19H9.67C9.24,18.09 9,17.07 9,16A7,7 0 0,1 16,9C17.07,9 18.09,9.24 19,9.67V8M5,21C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3H6V1H8V3H16V1H18V3H19A2,2 0 0,1 21,5V11.1C22.24,12.36 23,14.09 23,16A7,7 0 0,1 16,23C14.09,23 12.36,22.24 11.1,21H5M16,11.15A4.85,4.85 0 0,0 11.15,16C11.15,18.68 13.32,20.85 16,20.85A4.85,4.85 0 0,0 20.85,16C20.85,13.32 18.68,11.15 16,11.15Z" /> android:pathData="M15,13H16.5V15.82L18.94,17.23L18.19,18.53L15,16.69V13M19,8H5V19H9.67C9.24,18.09 9,17.07 9,16A7,7 0 0,1 16,9C17.07,9 18.09,9.24 19,9.67V8M5,21C3.89,21 3,20.1 3,19V5C3,3.89 3.89,3 5,3H6V1H8V3H16V1H18V3H19A2,2 0 0,1 21,5V11.1C22.24,12.36 23,14.09 23,16A7,7 0 0,1 16,23C14.09,23 12.36,22.24 11.1,21H5M16,11.15A4.85,4.85 0 0,0 11.15,16C11.15,18.68 13.32,20.85 16,20.85A4.85,4.85 0 0,0 20.85,16C20.85,13.32 18.68,11.15 16,11.15Z" />
</vector> </vector>

View file

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#000" android:fillColor="#000"
android:pathData="M21,11C21,16.55 17.16,21.74 12,23C6.84,21.74 3,16.55 3,11V5L12,1L21,5V11M12,21C15.75,20 19,15.54 19,11.22V6.3L12,3.18L5,6.3V11.22C5,15.54 8.25,20 12,21M14.8,11V9.5C14.8,8.1 13.4,7 12,7C10.6,7 9.2,8.1 9.2,9.5V11C8.6,11 8,11.6 8,12.2V15.7C8,16.4 8.6,17 9.2,17H14.7C15.4,17 16,16.4 16,15.8V12.3C16,11.6 15.4,11 14.8,11M13.5,11H10.5V9.5C10.5,8.7 11.2,8.2 12,8.2C12.8,8.2 13.5,8.7 13.5,9.5V11Z" /> android:pathData="M21,11C21,16.55 17.16,21.74 12,23C6.84,21.74 3,16.55 3,11V5L12,1L21,5V11M12,21C15.75,20 19,15.54 19,11.22V6.3L12,3.18L5,6.3V11.22C5,15.54 8.25,20 12,21M14.8,11V9.5C14.8,8.1 13.4,7 12,7C10.6,7 9.2,8.1 9.2,9.5V11C8.6,11 8,11.6 8,12.2V15.7C8,16.4 8.6,17 9.2,17H14.7C15.4,17 16,16.4 16,15.8V12.3C16,11.6 15.4,11 14.8,11M13.5,11H10.5V9.5C10.5,8.7 11.2,8.2 12,8.2C12.8,8.2 13.5,8.7 13.5,9.5V11Z" />
</vector> </vector>

View file

@ -1,10 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:tint="@color/metadataTextError" android:tint="@color/metadataTextError"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#000" android:fillColor="#000"
android:pathData="M8.27,3L3,8.27V15.73L8.27,21H15.73L21,15.73V8.27L15.73,3M8.41,7L12,10.59L15.59,7L17,8.41L13.41,12L17,15.59L15.59,17L12,13.41L8.41,17L7,15.59L10.59,12L7,8.41" /> android:pathData="M8.27,3L3,8.27V15.73L8.27,21H15.73L21,15.73V8.27L15.73,3M8.41,7L12,10.59L15.59,7L17,8.41L13.41,12L17,15.59L15.59,17L12,13.41L8.41,17L7,15.59L10.59,12L7,8.41" />
</vector> </vector>

View file

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#000" android:fillColor="#000"
android:pathData="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,10.84 21.79,9.69 21.39,8.61L19.79,10.21C19.93,10.8 20,11.4 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4C12.6,4 13.2,4.07 13.79,4.21L15.4,2.6C14.31,2.21 13.16,2 12,2M19,2L15,6V7.5L12.45,10.05C12.3,10 12.15,10 12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12C14,11.85 14,11.7 13.95,11.55L16.5,9H18L22,5H19V2M12,6A6,6 0 0,0 6,12A6,6 0 0,0 12,18A6,6 0 0,0 18,12H16A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8V6Z" /> android:pathData="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,10.84 21.79,9.69 21.39,8.61L19.79,10.21C19.93,10.8 20,11.4 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4C12.6,4 13.2,4.07 13.79,4.21L15.4,2.6C14.31,2.21 13.16,2 12,2M19,2L15,6V7.5L12.45,10.05C12.3,10 12.15,10 12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12C14,11.85 14,11.7 13.95,11.55L16.5,9H18L22,5H19V2M12,6A6,6 0 0,0 6,12A6,6 0 0,0 12,18A6,6 0 0,0 18,12H16A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8V6Z" />
</vector> </vector>

View file

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#000" android:fillColor="#000"
android:pathData="M6 2C4.89 2 4 2.9 4 4V20C4 21.11 4.89 22 6 22H12V20H6V4H13V9H18V12H20V8L14 2M18 14C17.87 14 17.76 14.09 17.74 14.21L17.55 15.53C17.25 15.66 16.96 15.82 16.7 16L15.46 15.5C15.35 15.5 15.22 15.5 15.15 15.63L14.15 17.36C14.09 17.47 14.11 17.6 14.21 17.68L15.27 18.5C15.25 18.67 15.24 18.83 15.24 19C15.24 19.17 15.25 19.33 15.27 19.5L14.21 20.32C14.12 20.4 14.09 20.53 14.15 20.64L15.15 22.37C15.21 22.5 15.34 22.5 15.46 22.5L16.7 22C16.96 22.18 17.24 22.35 17.55 22.47L17.74 23.79C17.76 23.91 17.86 24 18 24H20C20.11 24 20.22 23.91 20.24 23.79L20.43 22.47C20.73 22.34 21 22.18 21.27 22L22.5 22.5C22.63 22.5 22.76 22.5 22.83 22.37L23.83 20.64C23.89 20.53 23.86 20.4 23.77 20.32L22.7 19.5C22.72 19.33 22.74 19.17 22.74 19C22.74 18.83 22.73 18.67 22.7 18.5L23.76 17.68C23.85 17.6 23.88 17.47 23.82 17.36L22.82 15.63C22.76 15.5 22.63 15.5 22.5 15.5L21.27 16C21 15.82 20.73 15.65 20.42 15.53L20.23 14.21C20.22 14.09 20.11 14 20 14M19 17.5C19.83 17.5 20.5 18.17 20.5 19C20.5 19.83 19.83 20.5 19 20.5C18.16 20.5 17.5 19.83 17.5 19C17.5 18.17 18.17 17.5 19 17.5Z" /> android:pathData="M6 2C4.89 2 4 2.9 4 4V20C4 21.11 4.89 22 6 22H12V20H6V4H13V9H18V12H20V8L14 2M18 14C17.87 14 17.76 14.09 17.74 14.21L17.55 15.53C17.25 15.66 16.96 15.82 16.7 16L15.46 15.5C15.35 15.5 15.22 15.5 15.15 15.63L14.15 17.36C14.09 17.47 14.11 17.6 14.21 17.68L15.27 18.5C15.25 18.67 15.24 18.83 15.24 19C15.24 19.17 15.25 19.33 15.27 19.5L14.21 20.32C14.12 20.4 14.09 20.53 14.15 20.64L15.15 22.37C15.21 22.5 15.34 22.5 15.46 22.5L16.7 22C16.96 22.18 17.24 22.35 17.55 22.47L17.74 23.79C17.76 23.91 17.86 24 18 24H20C20.11 24 20.22 23.91 20.24 23.79L20.43 22.47C20.73 22.34 21 22.18 21.27 22L22.5 22.5C22.63 22.5 22.76 22.5 22.83 22.37L23.83 20.64C23.89 20.53 23.86 20.4 23.77 20.32L22.7 19.5C22.72 19.33 22.74 19.17 22.74 19C22.74 18.83 22.73 18.67 22.7 18.5L23.76 17.68C23.85 17.6 23.88 17.47 23.82 17.36L22.82 15.63C22.76 15.5 22.63 15.5 22.5 15.5L21.27 16C21 15.82 20.73 15.65 20.42 15.53L20.23 14.21C20.22 14.09 20.11 14 20 14M19 17.5C19.83 17.5 20.5 18.17 20.5 19C20.5 19.83 19.83 20.5 19 20.5C18.16 20.5 17.5 19.83 17.5 19C17.5 18.17 18.17 17.5 19 17.5Z" />
</vector> </vector>

View file

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#000" android:fillColor="#000"
android:pathData="M13 7H11V9H13V7M13 11H11V17H13V11M17 1H7C5.9 1 5 1.9 5 3V21C5 22.1 5.9 23 7 23H17C18.1 23 19 22.1 19 21V3C19 1.9 18.1 1 17 1M17 19H7V5H17V19Z" /> android:pathData="M13 7H11V9H13V7M13 11H11V17H13V11M17 1H7C5.9 1 5 1.9 5 3V21C5 22.1 5.9 23 7 23H17C18.1 23 19 22.1 19 21V3C19 1.9 18.1 1 17 1M17 19H7V5H17V19Z" />
</vector> </vector>

View file

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#000" android:fillColor="#000"
android:pathData="M22,3H2C0.91,3.04 0.04,3.91 0,5V19C0.04,20.09 0.91,20.96 2,21H22C23.09,20.96 23.96,20.09 24,19V5C23.96,3.91 23.09,3.04 22,3M22,19H2V5H22V19M14,17V15.75C14,14.09 10.66,13.25 9,13.25C7.34,13.25 4,14.09 4,15.75V17H14M9,7A2.5,2.5 0 0,0 6.5,9.5A2.5,2.5 0 0,0 9,12A2.5,2.5 0 0,0 11.5,9.5A2.5,2.5 0 0,0 9,7M14,7V8H20V7H14M14,9V10H20V9H14M14,11V12H18V11H14" /> android:pathData="M22,3H2C0.91,3.04 0.04,3.91 0,5V19C0.04,20.09 0.91,20.96 2,21H22C23.09,20.96 23.96,20.09 24,19V5C23.96,3.91 23.09,3.04 22,3M22,19H2V5H22V19M14,17V15.75C14,14.09 10.66,13.25 9,13.25C7.34,13.25 4,14.09 4,15.75V17H14M9,7A2.5,2.5 0 0,0 6.5,9.5A2.5,2.5 0 0,0 9,12A2.5,2.5 0 0,0 11.5,9.5A2.5,2.5 0 0,0 9,7M14,7V8H20V7H14M14,9V10H20V9H14M14,11V12H18V11H14" />
</vector> </vector>

View file

@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#000" android:fillColor="#000"
android:pathData="M2,10.96C1.5,10.68 1.35,10.07 1.63,9.59L3.13,7C3.24,6.8 3.41,6.66 3.6,6.58L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.66,6.72 20.82,6.88 20.91,7.08L22.36,9.6C22.64,10.08 22.47,10.69 22,10.96L21,11.54V16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V10.96C2.7,11.13 2.32,11.14 2,10.96M12,4.15V4.15L12,10.85V10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V12.69L14,15.59C13.67,15.77 13.3,15.76 13,15.6V19.29L19,15.91M13.85,13.36L20.13,9.73L19.55,8.72L13.27,12.35L13.85,13.36Z" /> android:pathData="M2,10.96C1.5,10.68 1.35,10.07 1.63,9.59L3.13,7C3.24,6.8 3.41,6.66 3.6,6.58L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.66,6.72 20.82,6.88 20.91,7.08L22.36,9.6C22.64,10.08 22.47,10.69 22,10.96L21,11.54V16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V10.96C2.7,11.13 2.32,11.14 2,10.96M12,4.15V4.15L12,10.85V10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V12.69L14,15.59C13.67,15.77 13.3,15.76 13,15.6V19.29L19,15.91M13.85,13.36L20.13,9.73L19.55,8.72L13.27,12.35L13.85,13.36Z" />
</vector> </vector>

View file

@ -1,10 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:tint="@color/metadataTextWarning" android:tint="@color/metadataTextWarning"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#000" android:fillColor="#000"
android:pathData="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z" /> android:pathData="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z" />
</vector> </vector>

View file

@ -77,5 +77,10 @@
<color name="white_alpha_80">#CCFFFFFF</color> <color name="white_alpha_80">#CCFFFFFF</color>
<color name="white_alpha_90">#E6FFFFFF</color> <color name="white_alpha_90">#E6FFFFFF</color>
<!-- Maintenance -->
<color name="metadataOk">#77dd77</color>
<color name="metadataTextWarning">#FF8C00</color>
<color name="metadataTextError">#FF5555</color>
</resources> </resources>

View file

@ -266,29 +266,62 @@
<string name="history_group_glucose">Glucose</string> <string name="history_group_glucose">Glucose</string>
<string name="mute5min">Mute for 5 minutes</string> <string name="mute5min">Mute for 5 minutes</string>
<!-- <string name="medtronic_pump_status_never_contacted">Never contacted</string>--> <!-- Maintenance -->
<!-- <string name="medtronic_pump_status_waking_up">Waking up</string>--> <string name="metadata_label_format">File format</string>
<!-- <string name="medtronic_pump_status_error_comm">Error with communication</string>--> <string name="metadata_label_created_at">Created at</string>
<!-- <string name="medtronic_pump_status_timeout_comm">Timeout on communication</string>--> <string name="metadata_label_aaps_version">AAPS Version</string>
<!-- <string name="medtronic_pump_status_pump_unreachable">Pump unreachable</string>--> <string name="metadata_label_aaps_flavour">Build Variant</string>
<!-- <string name="medtronic_pump_status_invalid_config">Invalid configuration</string>--> <string name="metadata_label_device_name">Exporting device patient name</string>
<!-- <string name="medtronic_pump_status_active">Active</string>--> <string name="metadata_label_device_model">Exporting device model</string>
<!-- <string name="medtronic_pump_status_sleeping">Sleeping</string>--> <string name="metadata_label_encryption">File encryption</string>
<string name="metadata_format_old">Old export format</string>
<string name="metadata_format_new">New encrypted format</string>
<string name="metadata_format_debug">New debug format (unencrypted)</string>
<string name="metadata_format_other">Unknown export format</string>
<string name="exported_ago" comment="at placeholder we add pluralized number of hours/minutes">exported %1$s ago</string>
<string name="exported_at" comment="at placeholder we add export date">exported at %1$s</string>
<string name="exported_less_than_hour_ago">exported less than hour ago</string>
<string name="in_directory" comment="placeholder is for exported file path">in directory: %1$s</string>
<string name="preferences_import_list_title">Select file to import</string>
<string name="metadata_warning_different_flavour">Preferences were created with different variant of AAPS (%1$s) while you have: %2$s.\n\nSome settings may be missing or invalid - after importing please check and update your preferences.</string>
<string name="metadata_warning_different_device">Preferences were created on a different device. It is OK if you are importing from older/different phone, but make sure imported preferences are correct!</string>
<string name="metadata_warning_outdated_format">You are using the outdated legacy format from old versions of AAPS, which is not secure! Only use it as a last resort, if you do not have an export in current, JSON format.</string>
<string name="metadata_warning_old_export">Imported preferences are already %1$s days old! Maybe you have more up-to-date preferences or you choose the wrong file? Remember to export preferences regularly!</string>
<string name="metadata_warning_date_format">Invalid date-time format!</string>
<string name="metadata_warning_different_version">Preferences from different minor version of application. It is OK if you are importing after upgrade, but check after import if preferences are still correct!</string>
<string name="metadata_urgent_different_version">Preferences from different major version of application. Major versions differ significantly and may have incompatible preferences! Make sure after import that preferences are still correct!</string>
<string name="prefdecrypt_settings_tampered">Settings file tampered</string>
<string name="prefdecrypt_settings_secure">Settings file is secure</string>
<string name="prefdecrypt_settings_unencrypted">Using not secure, unencrypted settings format</string>
<string name="prefdecrypt_wrong_json">JSON format error, missing required field (format, content, metadata or security)</string>
<string name="prefdecrypt_wrong_password">Decryption error, the given password cannot decrypt the file</string>
<string name="prefdecrypt_issue_missing_file_hash">File checksum (hash) missing, cannot verify the authenticity of settings!</string>
<string name="prefdecrypt_issue_modified">File was modified after export!</string>
<string name="prefdecrypt_issue_parsing">Decryption error, parsing preferences failed!</string>
<string name="prefdecrypt_issue_wrong_pass">Decryption error, the provided password is invalid or settings file was modified! It may happen that the imported file was exported with a different Master password.</string>
<string name="prefdecrypt_issue_wrong_format">Missing encryption configuration, settings format is invalid!</string>
<string name="prefdecrypt_issue_wrong_algorithm">Unsupported or not specified encryption algorithm!</string>
<!-- <string name="danar_history_alarm">Alarms</string>--> <!-- VersionChecker -->
<!-- <string name="danar_history_glucose">Glucose</string>--> <string name="key_last_time_this_version_detected" translatable="false">last_time_this_version_detected</string>
<string name="key_last_versionchecker_warning" translatable="false">last_versionchecker_waring</string>
<string name="key_last_versionchecker_plugin_warning" translatable="false">last_versionchecker_plugin_waring</string>
<!-- All(R.string.medtronic_history_group_all), //--> <string name="key_last_revoked_certs_check" translatable="false">last_revoked_certs_check</string>
<!-- Bolus(R.string.danar_history_bolus), //--> <string name="signature_verifier">Signature verifier</string>
<!-- Basal(R.string.medtronic_history_group_basal), //--> <string name="running_invalid_version">We have detected that you are running an invalid version. Loop disabled!</string>
<!-- Prime(R.string.danar_history_prime), //--> <string name="versionavailable">Version %1$s available</string>
<!-- Configuration(R.string.medtronic_history_group_configuration), //-->
<!-- Alarm(R.string.danar_history_alarm), //-->
<!-- Glucose(R.string.danar_history_glucose), //-->
<!-- Notification(R.string.medtronic_history_group_notification), //-->
<!-- Statistic(R.string.medtronic_history_group_statistic),-->
<!-- Unknown(R.string.medtronic_history_group_unknown), //-->
<plurals name="days">
<item quantity="one">%1$d day</item>
<item quantity="other">%1$d days</item>
</plurals>
<plurals name="hours">
<item quantity="one">%1$d hour</item>
<item quantity="other">%1$d hours</item>
</plurals>
<plurals name="minutes">
<item quantity="one">%1$d minute</item>
<item quantity="other">%1$d minutes</item>
</plurals>
</resources> </resources>