diff --git a/android/app/src/main/java/com/metallic/chiaki/common/MacAddress.kt b/android/app/src/main/java/com/metallic/chiaki/common/MacAddress.kt index 2c8f8ed..39cc934 100644 --- a/android/app/src/main/java/com/metallic/chiaki/common/MacAddress.kt +++ b/android/app/src/main/java/com/metallic/chiaki/common/MacAddress.kt @@ -42,11 +42,11 @@ class MacAddress(v: Long) }) constructor(string: String) : this( - (Regex("([0-9A-Fa-f]{2})[:-]{5}([0-9A-Fa-f]{2})").matchEntire(string) + (Regex("([0-9A-Fa-f]{2})[:-]([0-9A-Fa-f]{2})[:-]([0-9A-Fa-f]{2})[:-]([0-9A-Fa-f]{2})[:-]([0-9A-Fa-f]{2})[:-]([0-9A-Fa-f]{2})").matchEntire(string) ?: throw IllegalArgumentException("Invalid MAC Address String")) .groupValues .subList(1, 7) - .map { it.toByte() } + .map { it.toUByte(16).toByte() } .toByteArray()) val value: Long = v and 0xffffffffffff diff --git a/android/app/src/main/java/com/metallic/chiaki/common/SerializedSettings.kt b/android/app/src/main/java/com/metallic/chiaki/common/SerializedSettings.kt index 50d9e0e..e7d9098 100644 --- a/android/app/src/main/java/com/metallic/chiaki/common/SerializedSettings.kt +++ b/android/app/src/main/java/com/metallic/chiaki/common/SerializedSettings.kt @@ -20,7 +20,9 @@ package com.metallic.chiaki.common import android.app.Activity import android.content.ClipData import android.content.Intent +import android.net.Uri import android.util.Base64 +import android.util.Log import androidx.core.content.FileProvider import com.metallic.chiaki.R import com.squareup.moshi.* @@ -29,7 +31,9 @@ import io.reactivex.disposables.Disposable import io.reactivex.rxkotlin.Singles import io.reactivex.schedulers.Schedulers import okio.Buffer +import okio.Okio import java.io.File +import java.io.IOException @JsonClass(generateAdapter = true) class SerializedRegisteredHost( @@ -100,10 +104,14 @@ private fun moshi() = .add(ByteArrayJsonAdapter()) .build() +private fun Moshi.serializedSettingsAdapter() = + adapter(SerializedSettings::class.java) + .serializeNulls() + private const val KEY_FORMAT = "format" private const val FORMAT = "chiaki-settings" private const val KEY_VERSION = "version" -private const val VERSION = "1" +private const val VERSION = 1 private const val KEY_SETTINGS = "settings" fun exportAllSettings(db: AppDatabase) = SerializedSettings.fromDatabase(db) @@ -111,9 +119,7 @@ fun exportAllSettings(db: AppDatabase) = SerializedSettings.fromDatabase(db) .map { val buffer = Buffer() val writer = JsonWriter.of(buffer) - val adapter = moshi() - .adapter(SerializedSettings::class.java) - .serializeNulls() + val adapter = moshi().serializedSettingsAdapter() writer.indent = " " writer. beginObject() @@ -125,8 +131,9 @@ fun exportAllSettings(db: AppDatabase) = SerializedSettings.fromDatabase(db) buffer.readUtf8() } -fun exportAndShareAllSettings(db: AppDatabase, activity: Activity): Disposable +fun exportAndShareAllSettings(activity: Activity): Disposable { + val db = getDatabase(activity) val dir = File(activity.cacheDir, "export_settings") dir.mkdirs() val file = File(dir, "chiaki-settings.json") @@ -146,4 +153,53 @@ fun exportAndShareAllSettings(db: AppDatabase, activity: Activity): Disposable activity.startActivity(Intent.createChooser(it, activity.getString(R.string.action_share_log))) } } +} + +fun importSettingsFromUri(activity: Activity, uri: Uri) +{ + try + { + val inputStream = activity.contentResolver.openInputStream(uri) ?: throw IOException() + val buffer = Okio.buffer(Okio.source(inputStream)) + val reader = JsonReader.of(buffer) + val adapter = moshi().serializedSettingsAdapter() + + var format: String? = null + var version: Int? = null + var settingsValue: Any? = null + + reader.beginObject() + while(reader.hasNext()) + { + when(reader.nextName()) + { + KEY_FORMAT -> format = reader.nextString() + KEY_VERSION -> version = reader.nextInt() + KEY_SETTINGS -> settingsValue = reader.readJsonValue() + } + } + reader.endObject() + + if(format == null || version == null || settingsValue == null) + throw IOException("Missing format, version or settings from JSON") + if(format != FORMAT) + throw IOException("Value of format is invalid") + if(version != VERSION) // Add migrations here when necessary + throw IOException("Value of version is invalid") + + val settings = adapter.fromJsonValue(settingsValue) + Log.i("SerializedSettings", "would import: $settings") + // TODO: show dialog and import + } + catch(e: IOException) + { + // TODO + e.printStackTrace() + } + catch(e: JsonDataException) + { + // TODO + e.printStackTrace() + } + } \ No newline at end of file diff --git a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt index 44096f8..336c296 100644 --- a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt +++ b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt @@ -17,6 +17,7 @@ package com.metallic.chiaki.settings +import android.app.Activity import android.content.ClipData import android.content.Intent import android.content.res.Resources @@ -83,7 +84,10 @@ class DataStore(val preferences: Preferences): PreferenceDataStore() class SettingsFragment: PreferenceFragmentCompat(), TitleFragment { - private lateinit var viewModel: SettingsViewModel + companion object + { + private const val PICK_SETTINGS_JSON_REQUEST = 1 + } private var disposable = CompositeDisposable() @@ -91,7 +95,7 @@ class SettingsFragment: PreferenceFragmentCompat(), TitleFragment { val context = context ?: return - viewModel = ViewModelProviders + val viewModel = ViewModelProviders .of(this, viewModelFactory { SettingsViewModel(getDatabase(context), Preferences(context)) }) .get(SettingsViewModel::class.java) @@ -146,11 +150,26 @@ class SettingsFragment: PreferenceFragmentCompat(), TitleFragment { val activity = activity ?: return disposable.clear() - exportAndShareAllSettings(viewModel.database, activity).addTo(disposable) + exportAndShareAllSettings(activity).addTo(disposable) } private fun importSettings() { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/json" + } + startActivityForResult(intent, PICK_SETTINGS_JSON_REQUEST) + } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) + { + if(requestCode == PICK_SETTINGS_JSON_REQUEST && resultCode == Activity.RESULT_OK) + { + val activity = activity ?: return + data?.data?.also { + importSettingsFromUri(activity, it) + } + } } } \ No newline at end of file