diff --git a/app/build.gradle b/app/build.gradle
index f767cfc823..593efb2304 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -31,6 +31,7 @@ ext {
retrofit2Version = '2.8.1'
okhttp3Version = '4.6.0'
coroutinesVersion = '1.3.5'
+ activityVersion = '1.2.0-alpha03'
}
@@ -264,6 +265,8 @@ dependencies {
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.percentlayout:percentlayout:1.0.0'
implementation "androidx.preference:preference-ktx:1.1.1"
+ implementation "androidx.activity:activity:${activityVersion}"
+ implementation "androidx.activity:activity-ktx:${activityVersion}"
implementation 'com.google.android.material:material:1.1.0'
implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index caf37d462d..2bd4527cf6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -82,6 +82,7 @@
+
diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt
index e7411ad56b..2f6d515b0e 100644
--- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt
+++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/ActivitiesModule.kt
@@ -6,6 +6,7 @@ import info.nightscout.androidaps.MainActivity
import info.nightscout.androidaps.activities.*
import info.nightscout.androidaps.historyBrowser.HistoryBrowseActivity
import info.nightscout.androidaps.plugins.general.maintenance.activities.LogSettingActivity
+import info.nightscout.androidaps.plugins.general.maintenance.activities.PrefImportListActivity
import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizardListActivity
import info.nightscout.androidaps.plugins.general.smsCommunicator.activities.SmsCommunicatorOtpActivity
import info.nightscout.androidaps.plugins.pump.common.dialog.RileyLinkBLEScanActivity
@@ -48,4 +49,5 @@ abstract class ActivitiesModule {
@ContributesAndroidInjector abstract fun contributesStatsActivity(): StatsActivity
@ContributesAndroidInjector abstract fun contributesSurveyActivity(): SurveyActivity
@ContributesAndroidInjector abstract fun contributesTDDStatsActivity(): TDDStatsActivity
+ @ContributesAndroidInjector abstract fun contributesPrefImportListActivity(): PrefImportListActivity
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PreferencesModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PreferencesModule.kt
index fa2df68ef0..39fb959c15 100644
--- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PreferencesModule.kt
+++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/PreferencesModule.kt
@@ -3,6 +3,7 @@ package info.nightscout.androidaps.dependencyInjection
import dagger.Module
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.formats.ClassicPrefsFormat
import info.nightscout.androidaps.plugins.general.maintenance.formats.EncryptedPrefsFormat
import info.nightscout.androidaps.utils.CryptoUtil
@@ -15,4 +16,5 @@ abstract class PreferencesModule {
@ContributesAndroidInjector abstract fun importExportPrefsInjector(): ImportExportPrefs
@ContributesAndroidInjector abstract fun encryptedPrefsFormatInjector(): EncryptedPrefsFormat
@ContributesAndroidInjector abstract fun classicPrefsFormatInjector(): ClassicPrefsFormat
+ @ContributesAndroidInjector abstract fun prefImportListProviderInjector(): PrefFileListProvider
}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/versionChecker/VersionCheckerUtils.kt b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/versionChecker/VersionCheckerUtils.kt
index d1ce90fc72..636d623bd0 100644
--- a/app/src/main/java/info/nightscout/androidaps/plugins/constraints/versionChecker/VersionCheckerUtils.kt
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/constraints/versionChecker/VersionCheckerUtils.kt
@@ -116,6 +116,14 @@ class VersionCheckerUtils @Inject constructor(
private fun String?.toNumberList() =
this?.numericVersionPart().takeIf { !it.isNullOrBlank() }?.split(".")?.map { it.toInt() }
+ fun versionDigits(versionString: String?): IntArray {
+ val digits = mutableListOf()
+ versionString?.numericVersionPart().toNumberList()?.let {
+ digits.addAll(it.take(4))
+ }
+ return digits.toIntArray()
+ }
+
fun findVersion(file: String?): String? {
val regex = "(.*)version(.*)\"(((\\d+)\\.)+(\\d+))\"(.*)".toRegex()
return file?.lines()?.filter { regex.matches(it) }?.mapNotNull { regex.matchEntire(it)?.groupValues?.getOrNull(3) }?.firstOrNull()
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 1d72212fb3..b859a1dee5 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
@@ -6,12 +6,12 @@ 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.activity.invoke
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
import info.nightscout.androidaps.BuildConfig
import info.nightscout.androidaps.R
import info.nightscout.androidaps.activities.PreferencesActivity
@@ -22,9 +22,9 @@ 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.ToastUtils
import info.nightscout.androidaps.utils.alertDialogs.OKDialog
import info.nightscout.androidaps.utils.alertDialogs.OKDialog.show
-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
@@ -32,8 +32,6 @@ 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
@@ -51,37 +49,25 @@ 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(
private var log: AAPSLogger,
private val resourceHelper: ResourceHelper,
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
+ private val encryptedPrefsFormat: EncryptedPrefsFormat,
+ private val prefFileList: PrefFileListProvider
) {
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()
+ return prefFileList.listPreferenceFiles().size > 0
}
-
fun exportSharedPreferences(f: Fragment) {
f.activity?.let { exportSharedPreferences(it) }
}
@@ -135,9 +121,6 @@ class ImportExportPrefs @Inject constructor(
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)
@@ -173,36 +156,41 @@ class ImportExportPrefs @Inject constructor(
return true
}
- private fun askToConfirmExport(activity: Activity, then: ((password: String) -> Unit)) {
+ private fun askToConfirmExport(activity: Activity, fileToExport: File, 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.export_to) + " " + fileToExport + " ?",
resourceHelper.gs(R.string.password_preferences_encrypt_prompt), {
askForMasterPassIfNeeded(activity, R.string.preferences_export_canceled, then)
- }, null, R.drawable.ic_header_export)
+ }, null, R.drawable.ic_header_export)
}
- private fun askToConfirmImport(activity: Activity, fileToImport: File, then: ((password: String) -> Unit)) {
+ private fun askToConfirmImport(activity: Activity, fileToImport: PrefsFile, then: ((password: String) -> Unit)) {
- if (encFile.exists()) {
+ if (fileToImport.handler == PrefsFormatsHandler.ENCRYPTED) {
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.import_from) + " " + fileToImport.file + " ?",
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 + " ?",
+ resourceHelper.gs(R.string.import_from) + " " + fileToImport.file + " ?",
Runnable { then("") })
}
}
private fun exportSharedPreferences(activity: Activity) {
- askToConfirmExport(activity) { password ->
+
+ prefFileList.ensureExportDirExists()
+ val legacyFile = prefFileList.legacyFile()
+ val newFile = prefFileList.newExportFile()
+
+ askToConfirmExport(activity, newFile) { password ->
try {
val entries: MutableMap = mutableMapOf()
for ((key, value) in sp.getAll()) {
@@ -211,12 +199,14 @@ class ImportExportPrefs @Inject constructor(
val prefs = Prefs(entries, prepareMetadata(activity))
- classicPrefsFormat.savePreferences(file, prefs)
- encryptedPrefsFormat.savePreferences(encFile, prefs, password)
+ if (BuildConfig.DEBUG && buildHelper.isEngineeringMode()) {
+ classicPrefsFormat.savePreferences(legacyFile, prefs)
+ }
+ encryptedPrefsFormat.savePreferences(newFile, prefs, password)
ToastUtils.okToast(activity, resourceHelper.gs(R.string.exported))
} catch (e: FileNotFoundException) {
- ToastUtils.errorToast(activity, resourceHelper.gs(R.string.filenotfound) + " " + encFile)
+ ToastUtils.errorToast(activity, resourceHelper.gs(R.string.filenotfound) + " " + newFile)
log.error(TAG, "Unhandled exception", e)
} catch (e: IOException) {
ToastUtils.errorToast(activity, e.message)
@@ -226,21 +216,38 @@ class ImportExportPrefs @Inject constructor(
}
fun importSharedPreferences(fragment: Fragment) {
- fragment.activity?.let { importSharedPreferences(it) }
+ fragment.activity?.let { fragmentAct ->
+ val callForPrefFile = fragmentAct.prepareCall(PrefsFileContract()) {
+ it?.let {
+ importSharedPreferences(fragmentAct, it)
+ }
+ }
+ callForPrefFile.invoke()
+ }
}
- fun importSharedPreferences(activity: Activity) {
+ fun importSharedPreferences(activity: FragmentActivity) {
+ val callForPrefFile = activity.prepareCall(PrefsFileContract()) {
+ it?.let {
+ importSharedPreferences(activity, it)
+ }
+ }
+ callForPrefFile.invoke()
+ }
- val importFile = prefsImportFile()
+ private fun importSharedPreferences(activity: Activity, importFile: PrefsFile) {
askToConfirmImport(activity, importFile) { password ->
- val format: PrefsFormat = if (encFile.exists()) encryptedPrefsFormat else classicPrefsFormat
+ val format: PrefsFormat = when (importFile.handler) {
+ PrefsFormatsHandler.CLASSIC -> classicPrefsFormat
+ PrefsFormatsHandler.ENCRYPTED -> encryptedPrefsFormat
+ }
try {
- val prefs = format.loadPreferences(importFile, password)
- prefs.metadata = checkMetadata(prefs.metadata)
+ val prefs = format.loadPreferences(importFile.file, password)
+ prefs.metadata = prefFileList.checkMetadata(prefs.metadata)
// import is OK when we do not have errors (warnings are allowed)
val importOk = checkIfImportIsOk(prefs)
@@ -276,45 +283,6 @@ class ImportExportPrefs @Inject constructor(
}
}
- // check metadata for known issues, change their status and add info with explanations
- private fun checkMetadata(metadata: Map): Map {
- val meta = metadata.toMutableMap()
-
- meta[PrefsMetadataKey.AAPS_FLAVOUR]?.let { flavour ->
- val flavourOfPrefs = flavour.value
- if (flavour.value != BuildConfig.FLAVOR) {
- flavour.status = PrefsStatus.WARN
- flavour.info = resourceHelper.gs(R.string.metadata_warning_different_flavour, flavourOfPrefs, BuildConfig.FLAVOR)
- }
- }
-
- meta[PrefsMetadataKey.DEVICE_MODEL]?.let { model ->
- if (model.value != getCurrentDeviceModelString()) {
- model.status = PrefsStatus.WARN
- model.info = resourceHelper.gs(R.string.metadata_warning_different_device)
- }
- }
-
- meta[PrefsMetadataKey.CREATED_AT]?.let { createdAt ->
- try {
- val date1 = DateTime.parse(createdAt.value);
- val date2 = DateTime.now()
-
- val daysOld = Days.daysBetween(date1.toLocalDate(), date2.toLocalDate()).getDays()
-
- 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)
- }
- }
-
- return meta
- }
-
private fun checkIfImportIsOk(prefs: Prefs): Boolean {
var importOk = true
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/PrefFileListProvider.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/PrefFileListProvider.kt
new file mode 100644
index 0000000000..52910dfff1
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/PrefFileListProvider.kt
@@ -0,0 +1,219 @@
+package info.nightscout.androidaps.plugins.general.maintenance
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.Environment
+import android.os.Parcelable
+import androidx.activity.result.contract.ActivityResultContract
+import info.nightscout.androidaps.BuildConfig
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.logging.AAPSLogger
+import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionCheckerUtils
+import info.nightscout.androidaps.plugins.general.maintenance.activities.PrefImportListActivity
+import info.nightscout.androidaps.plugins.general.maintenance.formats.*
+import info.nightscout.androidaps.utils.resources.ResourceHelper
+import info.nightscout.androidaps.utils.sharedPreferences.SP
+import info.nightscout.androidaps.utils.storage.Storage
+import kotlinx.android.parcel.Parcelize
+import kotlinx.android.parcel.RawValue
+import org.joda.time.DateTime
+import org.joda.time.Days
+import org.joda.time.Hours
+import org.joda.time.LocalDateTime
+import org.joda.time.format.DateTimeFormat
+import java.io.File
+import javax.inject.Inject
+import javax.inject.Singleton
+
+enum class PrefsImportDir {
+ ROOT_DIR,
+ AAPS_DIR
+}
+
+@Parcelize
+data class PrefsFile(
+ val file: File,
+ val baseDir: File,
+ val dirKind: PrefsImportDir,
+ val handler: PrefsFormatsHandler,
+
+ // metadata here is used only for list display
+ val metadata: @RawValue Map
+) : Parcelable
+
+class PrefsFileContract : ActivityResultContract() {
+
+ companion object {
+ const val OUTPUT_PARAM = "prefs_file"
+ }
+
+ override fun parseResult(resultCode: Int, intent: Intent?): PrefsFile? {
+ return when (resultCode) {
+ Activity.RESULT_OK -> intent?.getParcelableExtra(OUTPUT_PARAM)
+ else -> null
+ }
+ }
+
+ override fun createIntent(context: Context, input: Void?): Intent {
+ return Intent(context, PrefImportListActivity::class.java)
+ }
+}
+
+fun getCurrentDeviceModelString() =
+ Build.MANUFACTURER + " " + Build.MODEL + " (" + Build.DEVICE + ")"
+
+@Singleton
+class PrefFileListProvider @Inject constructor(
+ private val resourceHelper: ResourceHelper,
+ private val classicPrefsFormat: ClassicPrefsFormat,
+ private val encryptedPrefsFormat: EncryptedPrefsFormat,
+ private val storage: Storage,
+ private val versionCheckerUtils: VersionCheckerUtils
+) {
+
+ companion object {
+ private val path = File(Environment.getExternalStorageDirectory().toString())
+ private val aapsPath = File(path, "AAPS" + File.separator + "preferences")
+ private const val IMPORT_AGE_NOT_YET_OLD_DAYS = 60
+ }
+
+ /**
+ * This function tries to list possible preference files from main SDCard root dir and AAPS/preferences dir
+ * 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:
+ * - file name and extension
+ * - predicted file contents
+ */
+ fun listPreferenceFiles(loadMetadata: Boolean = false): MutableList {
+ val prefFiles = mutableListOf()
+
+ // searching rood dir for legacy files
+ 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)
+ val detectedOld = !detectedNew && classicPrefsFormat.isPreferencesFile(it, contents)
+ if (detectedNew || detectedOld) {
+ val formatHandler = if (detectedNew) PrefsFormatsHandler.ENCRYPTED else PrefsFormatsHandler.CLASSIC
+ prefFiles.add(PrefsFile(it, path, PrefsImportDir.ROOT_DIR, formatHandler, metadataFor(loadMetadata, formatHandler, contents)))
+ }
+ }
+
+ // searching dedicated dir, only for new JSON format
+ aapsPath.walk().filter { it.isFile && it.name.endsWith(".json") }.forEach {
+ val contents = storage.getFileContents(it)
+ if (encryptedPrefsFormat.isPreferencesFile(it, contents)) {
+ prefFiles.add(PrefsFile(it, aapsPath, PrefsImportDir.AAPS_DIR, PrefsFormatsHandler.ENCRYPTED, metadataFor(loadMetadata, PrefsFormatsHandler.ENCRYPTED, contents)))
+ }
+ }
+
+ // we sort only if we have metadata to be used for that
+ if (loadMetadata) {
+ prefFiles.sortWith(
+ compareByDescending { it.handler }
+ .thenBy { it.metadata[PrefsMetadataKey.AAPS_FLAVOUR]?.status }
+ .thenByDescending { it.metadata[PrefsMetadataKey.CREATED_AT]?.value }
+ )
+ }
+
+ return prefFiles
+ }
+
+ private fun metadataFor(loadMetadata: Boolean, formatHandler: PrefsFormatsHandler, contents: String): PrefMetadataMap {
+ if (!loadMetadata) {
+ return mapOf()
+ }
+ return checkMetadata(when (formatHandler) {
+ PrefsFormatsHandler.CLASSIC -> classicPrefsFormat.loadMetadata(contents)
+ PrefsFormatsHandler.ENCRYPTED -> encryptedPrefsFormat.loadMetadata(contents)
+ })
+ }
+
+ fun legacyFile(): File {
+ return File(path, resourceHelper.gs(R.string.app_name) + "Preferences")
+ }
+
+ fun ensureExportDirExists() {
+ if (!aapsPath.exists()) {
+ aapsPath.mkdirs()
+ }
+ }
+
+ fun newExportFile(): File {
+ val timeLocal = LocalDateTime.now().toString(DateTimeFormat.forPattern("yyyy-MM-dd'_'HHmmss"))
+ return File(aapsPath, timeLocal + "_" + BuildConfig.FLAVOR + ".json")
+ }
+
+ // check metadata for known issues, change their status and add info with explanations
+ fun checkMetadata(metadata: Map): Map {
+ val meta = metadata.toMutableMap()
+
+ meta[PrefsMetadataKey.AAPS_FLAVOUR]?.let { flavour ->
+ val flavourOfPrefs = flavour.value
+ if (flavour.value != BuildConfig.FLAVOR) {
+ flavour.status = PrefsStatus.WARN
+ flavour.info = resourceHelper.gs(R.string.metadata_warning_different_flavour, flavourOfPrefs, BuildConfig.FLAVOR)
+ }
+ }
+
+ meta[PrefsMetadataKey.DEVICE_MODEL]?.let { model ->
+ if (model.value != getCurrentDeviceModelString()) {
+ model.status = PrefsStatus.WARN
+ model.info = resourceHelper.gs(R.string.metadata_warning_different_device)
+ }
+ }
+
+ meta[PrefsMetadataKey.CREATED_AT]?.let { createdAt ->
+ try {
+ val date1 = DateTime.parse(createdAt.value);
+ val date2 = DateTime.now()
+
+ val daysOld = Days.daysBetween(date1.toLocalDate(), date2.toLocalDate()).getDays()
+
+ 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))
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/activities/PrefImportListActivity.kt b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/activities/PrefImportListActivity.kt
new file mode 100644
index 0000000000..af19414794
--- /dev/null
+++ b/app/src/main/java/info/nightscout/androidaps/plugins/general/maintenance/activities/PrefImportListActivity.kt
@@ -0,0 +1,135 @@
+package info.nightscout.androidaps.plugins.general.maintenance.activities
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import dagger.android.support.DaggerAppCompatActivity
+import info.nightscout.androidaps.R
+import info.nightscout.androidaps.plugins.general.maintenance.PrefFileListProvider
+import info.nightscout.androidaps.plugins.general.maintenance.PrefsFile
+import info.nightscout.androidaps.plugins.general.maintenance.PrefsFileContract
+import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsFormatsHandler
+import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsMetadataKey
+import info.nightscout.androidaps.plugins.general.maintenance.formats.PrefsStatus
+import info.nightscout.androidaps.utils.LocaleHelper
+import info.nightscout.androidaps.utils.resources.ResourceHelper
+import kotlinx.android.synthetic.main.maintenance_importlist_activity.*
+import javax.inject.Inject
+
+class PrefImportListActivity : DaggerAppCompatActivity() {
+
+ @Inject lateinit var resourceHelper: ResourceHelper
+ @Inject lateinit var prefFileListProvider: PrefFileListProvider
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setTheme(R.style.AppTheme)
+ setContentView(R.layout.maintenance_importlist_activity)
+
+ title = resourceHelper.gs(R.string.preferences_import_list_title)
+ supportActionBar?.setDisplayHomeAsUpEnabled(true)
+ supportActionBar?.setDisplayShowHomeEnabled(true)
+ supportActionBar?.setDisplayShowTitleEnabled(true)
+
+ importlist_recyclerview.layoutManager = LinearLayoutManager(this)
+ importlist_recyclerview.adapter = RecyclerViewAdapter(prefFileListProvider.listPreferenceFiles(loadMetadata = true))
+ }
+
+ inner class RecyclerViewAdapter internal constructor(private var prefFileList: List) : RecyclerView.Adapter() {
+
+ inner class PrefFileViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ var fileName: TextView = itemView.findViewById(R.id.filelist_name)
+ var fileDir: TextView = itemView.findViewById(R.id.filelist_dir)
+ var metaDateTime: TextView = itemView.findViewById(R.id.meta_date_time)
+ var metaDeviceName: TextView = itemView.findViewById(R.id.meta_device_name)
+ var metaAppVersion: TextView = itemView.findViewById(R.id.meta_app_version)
+ var metaVariantFormat: TextView = itemView.findViewById(R.id.meta_variant_format)
+
+ var metalineName: View = itemView.findViewById(R.id.metaline_name)
+ var metaDateTimeIcon: View = itemView.findViewById(R.id.meta_date_time_icon)
+
+ init {
+ itemView.isClickable = true
+ itemView.setOnClickListener { v: View ->
+ val prefFile = fileName.tag as PrefsFile
+ val i = Intent()
+
+ i.putExtra(PrefsFileContract.OUTPUT_PARAM, prefFile)
+ setResult(Activity.RESULT_OK, i)
+ finish()
+ }
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PrefFileViewHolder {
+ val v = LayoutInflater.from(parent.context).inflate(R.layout.maintenance_importlist_item, parent, false)
+ return PrefFileViewHolder(v)
+ }
+
+ override fun getItemCount(): Int {
+ return prefFileList.size
+ }
+
+ override fun onBindViewHolder(holder: PrefFileViewHolder, position: Int) {
+ val prefFile = prefFileList[position]
+ holder.fileName.text = prefFile.file.name
+ holder.fileName.tag = prefFile
+
+ holder.fileDir.text = resourceHelper.gs(R.string.in_directory, prefFile.file.parentFile.absolutePath)
+
+ val visible = if (prefFile.handler == PrefsFormatsHandler.CLASSIC) View.GONE else View.VISIBLE
+ holder.metalineName.visibility = visible
+ holder.metaDateTimeIcon.visibility = visible
+ holder.metaAppVersion.visibility = visible
+
+ if (prefFile.handler == PrefsFormatsHandler.CLASSIC) {
+ holder.metaVariantFormat.text = resourceHelper.gs(R.string.metadata_format_old)
+ holder.metaVariantFormat.setTextColor(resourceHelper.gc(R.color.metadataTextWarning))
+ holder.metaDateTime.text = " "
+ } else {
+
+ prefFile.metadata[PrefsMetadataKey.AAPS_FLAVOUR]?.let {
+ holder.metaVariantFormat.text = it.value
+ val color = if (it.status == PrefsStatus.OK) R.color.metadataOk else R.color.metadataTextWarning
+ holder.metaVariantFormat.setTextColor(resourceHelper.gc(color))
+ }
+
+ prefFile.metadata[PrefsMetadataKey.CREATED_AT]?.let {
+ holder.metaDateTime.text = prefFileListProvider.formatExportedAgo(it.value)
+ }
+
+ prefFile.metadata[PrefsMetadataKey.AAPS_VERSION]?.let {
+ holder.metaAppVersion.text = it.value
+ val color = if (it.status == PrefsStatus.OK) R.color.metadataOk else R.color.metadataTextWarning
+ holder.metaAppVersion.setTextColor(resourceHelper.gc(color))
+ }
+
+ prefFile.metadata[PrefsMetadataKey.DEVICE_NAME]?.let {
+ holder.metaDeviceName.text = it.value
+ }
+
+ }
+
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (item.itemId == android.R.id.home) {
+ finish()
+ return true
+ }
+ return false
+ }
+
+ public override fun attachBaseContext(newBase: Context) {
+ super.attachBaseContext(LocaleHelper.wrap(newBase))
+ }
+}
\ 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 4689f549a6..40b1ceada4 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,5 +1,6 @@
package info.nightscout.androidaps.plugins.general.maintenance.formats
+import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.storage.Storage
@@ -19,6 +20,11 @@ class ClassicPrefsFormat @Inject constructor(
val FORMAT_KEY = "aaps_old"
}
+ override fun isPreferencesFile(file: File, preloadedContents: String?): Boolean {
+ val contents = preloadedContents ?: storage.getFileContents(file)
+ return contents.contains("units::" + Constants.MGDL) || contents.contains("units::" + Constants.MMOL)
+ }
+
override fun savePreferences(file: File, prefs: Prefs, masterPassword: String?) {
try {
val contents = prefs.values.entries.joinToString("\n") { entry ->
@@ -35,7 +41,6 @@ class ClassicPrefsFormat @Inject constructor(
override fun loadPreferences(file: File, masterPassword: String?): Prefs {
var lineParts: Array
val entries: MutableMap = mutableMapOf()
- val metadata: MutableMap = mutableMapOf()
try {
val rawLines = storage.getFileContents(file).split("\n")
@@ -46,9 +51,7 @@ class ClassicPrefsFormat @Inject constructor(
}
}
- metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(FORMAT_KEY, PrefsStatus.WARN, resourceHelper.gs(R.string.metadata_warning_outdated_format))
-
- return Prefs(entries, metadata)
+ return Prefs(entries, loadMetadata())
} catch (e: FileNotFoundException) {
throw PrefFileNotFoundError(file.absolutePath)
@@ -57,4 +60,10 @@ class ClassicPrefsFormat @Inject constructor(
}
}
+ override fun loadMetadata(contents: String?): PrefMetadataMap {
+ val metadata: MutableMap = mutableMapOf()
+ metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(FORMAT_KEY, PrefsStatus.WARN, resourceHelper.gs(R.string.metadata_warning_outdated_format))
+ return metadata
+ }
+
}
\ 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 463b5a31cb..ec88c07bec 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
@@ -27,6 +27,16 @@ class EncryptedPrefsFormat @Inject constructor(
val FORMAT_KEY_NOENC = "aaps_structured"
private val KEY_CONSCIENCE = "if you remove/change this, please make sure you know the consequences!"
+ private val FORMAT_TEST_REGEX = Regex("(\\\"format\\\"\\s*\\:\\s*\\\"aaps_[^\"]*\\\")")
+ }
+
+ override fun isPreferencesFile(file: File, preloadedContents: String?): Boolean {
+ return if (file.absolutePath.endsWith(".json")) {
+ val contents = preloadedContents ?: storage.getFileContents(file)
+ FORMAT_TEST_REGEX.containsMatchIn(contents)
+ } else {
+ false
+ }
}
override fun savePreferences(file: File, prefs: Prefs, masterPassword: String?) {
@@ -97,7 +107,6 @@ class EncryptedPrefsFormat @Inject constructor(
override fun loadPreferences(file: File, masterPassword: String?): Prefs {
val entries: MutableMap = mutableMapOf()
- val metadata: MutableMap = mutableMapOf()
val issues = LinkedList()
try {
@@ -105,25 +114,11 @@ class EncryptedPrefsFormat @Inject constructor(
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)
+ val metadata: MutableMap = loadMetadata(container)
- if (container.has(PrefsMetadataKey.FILE_FORMAT.key) && container.has("security") && container.has("content") && container.has("metadata")) {
+ if (container.has(PrefsMetadataKey.FILE_FORMAT.key) && container.has("security") && container.has("content")) {
val fileFormat = container.getString(PrefsMetadataKey.FILE_FORMAT.key)
-
- 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
@@ -208,8 +203,6 @@ class EncryptedPrefsFormat @Inject constructor(
}
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)
@@ -223,4 +216,35 @@ class EncryptedPrefsFormat @Inject constructor(
}
}
+ override fun loadMetadata(contents: String?): PrefMetadataMap {
+ contents?.let {
+ val container = JSONObject(contents)
+ return loadMetadata(container)
+ }
+ return mutableMapOf()
+ }
+
+ private fun loadMetadata(container: JSONObject): MutableMap {
+ val metadata: MutableMap = mutableMapOf()
+ if (container.has(PrefsMetadataKey.FILE_FORMAT.key) && container.has("security") && container.has("content") && container.has("metadata")) {
+ val fileFormat = container.getString(PrefsMetadataKey.FILE_FORMAT.key)
+ if ((fileFormat != FORMAT_KEY_ENC) && (fileFormat != FORMAT_KEY_NOENC)) {
+ metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(resourceHelper.gs(R.string.metadata_format_other), PrefsStatus.ERROR)
+ } else {
+ val meta = container.getJSONObject("metadata")
+ 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)
+ }
+ }
+ }
+ } else {
+ metadata[PrefsMetadataKey.FILE_FORMAT] = PrefMetadata(resourceHelper.gs(R.string.prefdecrypt_wrong_json), PrefsStatus.ERROR)
+ }
+
+ return metadata;
+ }
+
}
\ 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 a47add0dd7..d0d3935023 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,12 +1,14 @@
package info.nightscout.androidaps.plugins.general.maintenance.formats
import android.content.Context
+import android.os.Parcelable
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import info.nightscout.androidaps.R
+import kotlinx.android.parcel.Parcelize
import java.io.File
-enum class PrefsMetadataKey(val key: String, @DrawableRes val icon:Int, @StringRes val label:Int) {
+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),
@@ -33,16 +35,15 @@ enum class PrefsMetadataKey(val key: String, @DrawableRes val icon:Int, @StringR
}
}
-
}
- fun formatForDisplay(context: Context, value:String): String {
+ 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)
+ 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)
+ else -> context.getString(R.string.metadata_format_other)
}
CREATED_AT -> value.replace("T", " ").replace("Z", " (UTC)")
else -> value
@@ -51,16 +52,21 @@ enum class PrefsMetadataKey(val key: String, @DrawableRes val icon:Int, @StringR
}
-data class PrefMetadata(var value : String, var status : PrefsStatus, var info : String? = null)
+@Parcelize
+data class PrefMetadata(var value: String, var status: PrefsStatus, var info: String? = null) : Parcelable
-data class Prefs(val values : Map, var metadata : Map)
+typealias PrefMetadataMap = Map
+
+data class Prefs(val values: Map, var metadata: PrefMetadataMap)
interface PrefsFormat {
fun savePreferences(file: File, prefs: Prefs, masterPassword: String? = null)
- fun loadPreferences(file: File, masterPassword: String? = null) : Prefs
+ fun loadPreferences(file: File, masterPassword: String? = null): Prefs
+ fun loadMetadata(contents: String? = null): PrefMetadataMap
+ fun isPreferencesFile(file: File, preloadedContents: String? = null): Boolean
}
-enum class PrefsStatus(@DrawableRes val icon:Int) {
+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),
@@ -68,6 +74,11 @@ enum class PrefsStatus(@DrawableRes val icon:Int) {
DISABLED(R.drawable.ic_meta_error)
}
+enum class PrefsFormatsHandler {
+ CLASSIC,
+ ENCRYPTED
+}
+
class PrefFileNotFoundError(message: String) : Exception(message)
class PrefIOError(message: String) : Exception(message)
class PrefFormatError(message: String) : Exception(message)
diff --git a/app/src/main/res/layout/maintenance_importlist_activity.xml b/app/src/main/res/layout/maintenance_importlist_activity.xml
new file mode 100644
index 0000000000..0bdd43b927
--- /dev/null
+++ b/app/src/main/res/layout/maintenance_importlist_activity.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/maintenance_importlist_item.xml b/app/src/main/res/layout/maintenance_importlist_item.xml
new file mode 100644
index 0000000000..8c2cfc0557
--- /dev/null
+++ b/app/src/main/res/layout/maintenance_importlist_item.xml
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index c1873d786f..de49a0aee5 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -90,7 +90,11 @@
#FF8C00
#03A9F4
+ #77dd77
#FF8C00
#FF5555
+ #FFFFFF
+ #BBBBBB
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e09f297a7b..c87bff5c35 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -257,6 +257,8 @@
Export canceled! Preferences were NOT exported!
Import canceled! Preferences were NOT imported!
+ Select file to import
+
Please check preferences before importing:
Preferences cannot be imported!
Preferences should not be imported!
@@ -270,6 +272,8 @@
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.
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!
Invalid date-time format!
+ 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!
+ 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!
File format
Created at
@@ -297,6 +301,12 @@
Missing encryption configuration, settings format is invalid!
Unsupported or not specified encryption algorithm!
+ exported today
+ exported %1$s ago
+ exported at %1$s
+ exported less than hour ago
+ in directory: %1$s
+
DanaR
Connecting
Connected
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 17a9c171d0..db693198bb 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
@@ -6,6 +6,7 @@ import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.sharedPreferences.SP
+import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@@ -25,6 +26,41 @@ class VersionCheckerUtilsKtTest : TestBase() {
versionCheckerUtils = VersionCheckerUtils(aapsLogger, sp, resourceHelper, rxBus, context)
}
+ @Test
+ fun `should handle invalid version`() {
+ assertArrayEquals(intArrayOf(), versionCheckerUtils.versionDigits("definitely not version string"))
+ }
+
+ @Test
+ fun `should handle empty version`() {
+ assertArrayEquals(intArrayOf(), versionCheckerUtils.versionDigits(""))
+ }
+
+ @Test
+ fun `should parse 2 digit version`() {
+ assertArrayEquals(intArrayOf(0, 999), versionCheckerUtils.versionDigits("0.999-beta"))
+ }
+
+ @Test
+ fun `should parse 3 digit version`() {
+ assertArrayEquals(intArrayOf(6, 83, 93), versionCheckerUtils.versionDigits("6.83.93"))
+ }
+
+ @Test
+ fun `should parse 4 digit version`() {
+ assertArrayEquals(intArrayOf(42, 7, 13, 101), versionCheckerUtils.versionDigits("42.7.13.101"))
+ }
+
+ @Test
+ fun `should parse 4 digit version with extra`() {
+ assertArrayEquals(intArrayOf(1, 2, 3, 4), versionCheckerUtils.versionDigits("1.2.3.4-RC5"))
+ }
+
+ @Test
+ fun `should parse version but only 4 digits are taken`() {
+ assertArrayEquals(intArrayOf(67, 8, 31, 5), versionCheckerUtils.versionDigits("67.8.31.5.153.4.2"))
+ }
+
/*
@Test
fun `should keep 2 digit version`() {