diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d0b32cf..e397b26 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -21,7 +21,7 @@
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
@@ -31,14 +31,14 @@
+ android:value="eu.depau.etchdroid.ui.activities.StartActivity"/>
@@ -126,17 +126,17 @@
+ android:value="eu.depau.etchdroid.ui.activities.UsbDrivePickerActivity"/>
-
- private fun updateLicenses() {
- if (!::licenses.isInitialized) {
- licenses = arrayOf(
- License(getString(R.string.this_app), Uri.parse("https://github.com/Depau/EtchDroid"), getString(R.string.license_gpl3)),
- License("Storage Chooser 2.0", Uri.parse("https://github.com/codekidX/storage-chooser"), getString(R.string.license_mpl_2_0), getString(R.string.storagechooser_license_description)),
- License("libaums (fork)", Uri.parse("https://github.com/Depau/libaums"), getString(R.string.license_apache2_0), getString(R.string.libaums_license_desc)),
- License("dmg2img (fork)", Uri.parse("https://github.com/Depau/dmg2img-cmake"), getString(R.string.license_gpl2), getString(R.string.dmg2img_license_desc)),
- License("bzip2", Uri.parse("https://github.com/LuaDist/bzip2/"), getString(R.string.license_bzip2)),
- License("LibreSSL", Uri.parse("https://github.com/libressl-portable/portable"), getString(R.string.license_custom))
- )
- }
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_licenses)
- updateLicenses()
-
- // Enable back button in action bar
- supportActionBar!!.setDisplayHomeAsUpEnabled(true)
-
- viewManager = LinearLayoutManager(this)
- recyclerView = licenses_recycler_view
- viewAdapter = LicenseRecyclerViewAdapter(licenses)
- recyclerView.adapter = viewAdapter
- recyclerView.layoutManager = viewManager
-
- recyclerView.addOnItemTouchListener(RecyclerViewTouchListener(this, recyclerView, object : ClickListener {
- override fun onClick(view: View, position: Int) {
- val intent = Intent(Intent.ACTION_VIEW, viewAdapter.get(position).url)
- startActivity(intent)
- }
-
- override fun onLongClick(view: View, position: Int) {}
- }))
- }
-
- override fun onOptionsItemSelected(item: MenuItem?): Boolean {
- when (item?.itemId) {
- android.R.id.home -> {
- finish()
- return true
- }
- }
- return super.onOptionsItemSelected(item)
- }
-}
diff --git a/app/src/main/java/eu/depau/etchdroid/adapters/LicenseRecyclerViewAdapter.kt b/app/src/main/java/eu/depau/etchdroid/adapters/LicenseRecyclerViewAdapter.kt
deleted file mode 100644
index 01dbffb..0000000
--- a/app/src/main/java/eu/depau/etchdroid/adapters/LicenseRecyclerViewAdapter.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-package eu.depau.etchdroid.adapters
-
-import android.annotation.SuppressLint
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.RelativeLayout
-import androidx.recyclerview.widget.RecyclerView
-import eu.depau.etchdroid.R
-import eu.depau.etchdroid.utils.License
-import kotlinx.android.synthetic.main.license_row.view.*
-
-
-class LicenseRecyclerViewAdapter(private val dataset: Array) : RecyclerView.Adapter() {
-
- class ViewHolder(val relLayout: RelativeLayout) : RecyclerView.ViewHolder(relLayout)
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
- ViewHolder {
-
- val relLayout = LayoutInflater.from(parent.context)
- .inflate(R.layout.license_row, parent, false) as RelativeLayout
- return ViewHolder(relLayout)
- }
-
- @SuppressLint("SetTextI18n")
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- val license = dataset[position]
-
- holder.relLayout.software_name.text = license.name
- holder.relLayout.software_desc.text = license.description
- holder.relLayout.software_license.text = license.license
- holder.relLayout.software_url.text = license.url.toString()
-
- holder.relLayout.software_desc.visibility = if (license.description == null) View.GONE else View.VISIBLE
- }
-
- override fun getItemCount(): Int = dataset.size
-
- fun get(position: Int): License {
- return dataset[position]
- }
-}
diff --git a/app/src/main/java/eu/depau/etchdroid/enums/PartitionType.kt b/app/src/main/java/eu/depau/etchdroid/enums/PartitionType.kt
deleted file mode 100644
index ebc8d6f..0000000
--- a/app/src/main/java/eu/depau/etchdroid/enums/PartitionType.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package eu.depau.etchdroid.enums
-
-enum class PartitionType
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/services/UsbApiDmgWriteService.kt b/app/src/main/java/eu/depau/etchdroid/services/UsbApiDmgWriteService.kt
index f3f7925..4c73d9b 100644
--- a/app/src/main/java/eu/depau/etchdroid/services/UsbApiDmgWriteService.kt
+++ b/app/src/main/java/eu/depau/etchdroid/services/UsbApiDmgWriteService.kt
@@ -5,10 +5,10 @@ import android.net.Uri
import com.google.common.util.concurrent.SimpleTimeLimiter
import com.google.common.util.concurrent.TimeLimiter
import com.google.common.util.concurrent.UncheckedTimeoutException
-import eu.depau.etchdroid.kotlin_exts.getBinary
-import eu.depau.etchdroid.kotlin_exts.getFileName
-import eu.depau.etchdroid.kotlin_exts.getFileSize
-import eu.depau.etchdroid.kotlin_exts.name
+import eu.depau.etchdroid.utils.ktexts.getBinary
+import eu.depau.etchdroid.utils.ktexts.getFileName
+import eu.depau.etchdroid.utils.ktexts.getFileSize
+import eu.depau.etchdroid.utils.ktexts.name
import java.io.BufferedReader
import java.io.InputStream
import java.util.concurrent.Executors
diff --git a/app/src/main/java/eu/depau/etchdroid/services/UsbApiImgWriteService.kt b/app/src/main/java/eu/depau/etchdroid/services/UsbApiImgWriteService.kt
index 9e86d62..5917283 100644
--- a/app/src/main/java/eu/depau/etchdroid/services/UsbApiImgWriteService.kt
+++ b/app/src/main/java/eu/depau/etchdroid/services/UsbApiImgWriteService.kt
@@ -2,9 +2,9 @@ package eu.depau.etchdroid.services
import android.hardware.usb.UsbDevice
import android.net.Uri
-import eu.depau.etchdroid.kotlin_exts.getFileName
-import eu.depau.etchdroid.kotlin_exts.getFileSize
-import eu.depau.etchdroid.kotlin_exts.name
+import eu.depau.etchdroid.utils.ktexts.getFileName
+import eu.depau.etchdroid.utils.ktexts.getFileSize
+import eu.depau.etchdroid.utils.ktexts.name
import java.io.InputStream
class UsbApiImgWriteService : UsbApiWriteService("UsbApiImgWriteService") {
diff --git a/app/src/main/java/eu/depau/etchdroid/services/UsbApiWriteService.kt b/app/src/main/java/eu/depau/etchdroid/services/UsbApiWriteService.kt
index ca2438f..34aeed7 100644
--- a/app/src/main/java/eu/depau/etchdroid/services/UsbApiWriteService.kt
+++ b/app/src/main/java/eu/depau/etchdroid/services/UsbApiWriteService.kt
@@ -5,9 +5,9 @@ import android.hardware.usb.UsbDevice
import android.net.Uri
import android.util.Log
import com.github.mjdev.libaums.UsbMassStorageDevice
-import eu.depau.etchdroid.exceptions.UsbWriteException
-import eu.depau.etchdroid.kotlin_exts.getFileName
-import eu.depau.etchdroid.kotlin_exts.name
+import eu.depau.etchdroid.utils.exception.UsbWriteException
+import eu.depau.etchdroid.utils.ktexts.getFileName
+import eu.depau.etchdroid.utils.ktexts.name
import java.io.BufferedInputStream
import java.io.InputStream
import java.nio.ByteBuffer
diff --git a/app/src/main/java/eu/depau/etchdroid/services/UsbWriteService.kt b/app/src/main/java/eu/depau/etchdroid/services/UsbWriteService.kt
index 9f2e0e8..39689e4 100644
--- a/app/src/main/java/eu/depau/etchdroid/services/UsbWriteService.kt
+++ b/app/src/main/java/eu/depau/etchdroid/services/UsbWriteService.kt
@@ -7,9 +7,9 @@ import android.os.Build
import android.os.PowerManager
import androidx.core.app.NotificationCompat
import eu.depau.etchdroid.R
-import eu.depau.etchdroid.activities.ErrorActivity
-import eu.depau.etchdroid.kotlin_exts.toHRSize
-import eu.depau.etchdroid.kotlin_exts.toHRTime
+import eu.depau.etchdroid.ui.activities.ErrorActivity
+import eu.depau.etchdroid.utils.ktexts.toHRSize
+import eu.depau.etchdroid.utils.ktexts.toHRTime
import java.util.*
import kotlin.math.max
diff --git a/app/src/main/java/eu/depau/etchdroid/activities/ActivityBase.kt b/app/src/main/java/eu/depau/etchdroid/ui/activities/ActivityBase.kt
similarity index 95%
rename from app/src/main/java/eu/depau/etchdroid/activities/ActivityBase.kt
rename to app/src/main/java/eu/depau/etchdroid/ui/activities/ActivityBase.kt
index d3e9019..a02fcb1 100644
--- a/app/src/main/java/eu/depau/etchdroid/activities/ActivityBase.kt
+++ b/app/src/main/java/eu/depau/etchdroid/ui/activities/ActivityBase.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.activities
+package eu.depau.etchdroid.ui.activities
import android.Manifest
import android.content.pm.PackageManager
@@ -10,9 +10,9 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import eu.depau.etchdroid.R
-import eu.depau.etchdroid.kotlin_exts.toast
-import eu.depau.etchdroid.utils.DoNotShowAgainDialogFragment
-import eu.depau.etchdroid.utils.NightModeHelper
+import eu.depau.etchdroid.utils.ktexts.toast
+import eu.depau.etchdroid.ui.misc.DoNotShowAgainDialogFragment
+import eu.depau.etchdroid.ui.misc.NightModeHelper
import me.jfenn.attribouter.Attribouter
import android.content.Intent
import android.net.Uri
diff --git a/app/src/main/java/eu/depau/etchdroid/activities/ConfirmationActivity.kt b/app/src/main/java/eu/depau/etchdroid/ui/activities/ConfirmationActivity.kt
similarity index 95%
rename from app/src/main/java/eu/depau/etchdroid/activities/ConfirmationActivity.kt
rename to app/src/main/java/eu/depau/etchdroid/ui/activities/ConfirmationActivity.kt
index 0bbcf06..8af296e 100644
--- a/app/src/main/java/eu/depau/etchdroid/activities/ConfirmationActivity.kt
+++ b/app/src/main/java/eu/depau/etchdroid/ui/activities/ConfirmationActivity.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.activities
+package eu.depau.etchdroid.ui.activities
import android.content.Intent
import android.os.Build
@@ -8,13 +8,13 @@ import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import eu.depau.etchdroid.R
import eu.depau.etchdroid.StateKeeper
-import eu.depau.etchdroid.adapters.PartitionTableRecyclerViewAdapter
-import eu.depau.etchdroid.enums.FlashMethod
-import eu.depau.etchdroid.img_types.DMGImage
-import eu.depau.etchdroid.kotlin_exts.*
+import eu.depau.etchdroid.ui.adapters.PartitionTableRecyclerViewAdapter
+import eu.depau.etchdroid.utils.enums.FlashMethod
+import eu.depau.etchdroid.utils.imagetypes.DMGImage
+import eu.depau.etchdroid.utils.ktexts.*
import eu.depau.etchdroid.services.UsbApiDmgWriteService
import eu.depau.etchdroid.services.UsbApiImgWriteService
-import eu.depau.etchdroid.utils.DoNotShowAgainDialogFragment
+import eu.depau.etchdroid.ui.misc.DoNotShowAgainDialogFragment
import kotlinx.android.synthetic.main.activity_confirmation.*
import java.io.IOException
diff --git a/app/src/main/java/eu/depau/etchdroid/activities/ErrorActivity.kt b/app/src/main/java/eu/depau/etchdroid/ui/activities/ErrorActivity.kt
similarity index 93%
rename from app/src/main/java/eu/depau/etchdroid/activities/ErrorActivity.kt
rename to app/src/main/java/eu/depau/etchdroid/ui/activities/ErrorActivity.kt
index 50a35ed..15a14f8 100644
--- a/app/src/main/java/eu/depau/etchdroid/activities/ErrorActivity.kt
+++ b/app/src/main/java/eu/depau/etchdroid/ui/activities/ErrorActivity.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.activities
+package eu.depau.etchdroid.ui.activities
import android.os.Bundle
import eu.depau.etchdroid.R
diff --git a/app/src/main/java/eu/depau/etchdroid/activities/StartActivity.kt b/app/src/main/java/eu/depau/etchdroid/ui/activities/StartActivity.kt
similarity index 97%
rename from app/src/main/java/eu/depau/etchdroid/activities/StartActivity.kt
rename to app/src/main/java/eu/depau/etchdroid/ui/activities/StartActivity.kt
index 1f60bbd..2e8207a 100644
--- a/app/src/main/java/eu/depau/etchdroid/activities/StartActivity.kt
+++ b/app/src/main/java/eu/depau/etchdroid/ui/activities/StartActivity.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.activities
+package eu.depau.etchdroid.ui.activities
import android.content.Intent
import android.content.pm.PackageManager
@@ -10,8 +10,8 @@ import androidx.appcompat.app.AppCompatActivity
import com.codekidlabs.storagechooser.StorageChooser
import eu.depau.etchdroid.R
import eu.depau.etchdroid.StateKeeper
-import eu.depau.etchdroid.enums.FlashMethod
-import eu.depau.etchdroid.utils.DoNotShowAgainDialogFragment
+import eu.depau.etchdroid.utils.enums.FlashMethod
+import eu.depau.etchdroid.ui.misc.DoNotShowAgainDialogFragment
import kotlinx.android.synthetic.main.activity_start.*
import java.io.File
diff --git a/app/src/main/java/eu/depau/etchdroid/activities/UsbDrivePickerActivity.kt b/app/src/main/java/eu/depau/etchdroid/ui/activities/UsbDrivePickerActivity.kt
similarity index 95%
rename from app/src/main/java/eu/depau/etchdroid/activities/UsbDrivePickerActivity.kt
rename to app/src/main/java/eu/depau/etchdroid/ui/activities/UsbDrivePickerActivity.kt
index 6463dd5..621d384 100644
--- a/app/src/main/java/eu/depau/etchdroid/activities/UsbDrivePickerActivity.kt
+++ b/app/src/main/java/eu/depau/etchdroid/ui/activities/UsbDrivePickerActivity.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.activities
+package eu.depau.etchdroid.ui.activities
import android.app.PendingIntent
import android.content.BroadcastReceiver
@@ -19,12 +19,12 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.github.mjdev.libaums.UsbMassStorageDevice
import eu.depau.etchdroid.R
import eu.depau.etchdroid.StateKeeper
-import eu.depau.etchdroid.adapters.UsbDrivesRecyclerViewAdapter
-import eu.depau.etchdroid.enums.FlashMethod
-import eu.depau.etchdroid.kotlin_exts.*
-import eu.depau.etchdroid.utils.ClickListener
-import eu.depau.etchdroid.utils.EmptyRecyclerView
-import eu.depau.etchdroid.utils.RecyclerViewTouchListener
+import eu.depau.etchdroid.ui.adapters.UsbDrivesRecyclerViewAdapter
+import eu.depau.etchdroid.utils.enums.FlashMethod
+import eu.depau.etchdroid.utils.ktexts.*
+import eu.depau.etchdroid.ui.misc.ClickListener
+import eu.depau.etchdroid.ui.misc.EmptyRecyclerView
+import eu.depau.etchdroid.ui.misc.RecyclerViewTouchListener
import kotlinx.android.synthetic.main.activity_usb_drive_picker.*
import java.io.File
diff --git a/app/src/main/java/eu/depau/etchdroid/adapters/PartitionTableRecyclerViewAdapter.kt b/app/src/main/java/eu/depau/etchdroid/ui/adapters/PartitionTableRecyclerViewAdapter.kt
similarity index 96%
rename from app/src/main/java/eu/depau/etchdroid/adapters/PartitionTableRecyclerViewAdapter.kt
rename to app/src/main/java/eu/depau/etchdroid/ui/adapters/PartitionTableRecyclerViewAdapter.kt
index d2602b8..a145c11 100644
--- a/app/src/main/java/eu/depau/etchdroid/adapters/PartitionTableRecyclerViewAdapter.kt
+++ b/app/src/main/java/eu/depau/etchdroid/ui/adapters/PartitionTableRecyclerViewAdapter.kt
@@ -1,11 +1,11 @@
-package eu.depau.etchdroid.adapters
+package eu.depau.etchdroid.ui.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.recyclerview.widget.RecyclerView
import eu.depau.etchdroid.R
-import eu.depau.etchdroid.kotlin_exts.toHRSize
+import eu.depau.etchdroid.utils.ktexts.toHRSize
import eu.depau.etchdroid.utils.Partition
import kotlinx.android.synthetic.main.part_data_keyvalue.view.*
import kotlinx.android.synthetic.main.partition_row.view.*
diff --git a/app/src/main/java/eu/depau/etchdroid/adapters/UsbDrivesRecyclerViewAdapter.kt b/app/src/main/java/eu/depau/etchdroid/ui/adapters/UsbDrivesRecyclerViewAdapter.kt
similarity index 95%
rename from app/src/main/java/eu/depau/etchdroid/adapters/UsbDrivesRecyclerViewAdapter.kt
rename to app/src/main/java/eu/depau/etchdroid/ui/adapters/UsbDrivesRecyclerViewAdapter.kt
index 3ee467d..e015a94 100644
--- a/app/src/main/java/eu/depau/etchdroid/adapters/UsbDrivesRecyclerViewAdapter.kt
+++ b/app/src/main/java/eu/depau/etchdroid/ui/adapters/UsbDrivesRecyclerViewAdapter.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.adapters
+package eu.depau.etchdroid.ui.adapters
import android.annotation.SuppressLint
import android.os.Build
@@ -8,7 +8,7 @@ import android.widget.RelativeLayout
import androidx.recyclerview.widget.RecyclerView
import com.github.mjdev.libaums.UsbMassStorageDevice
import eu.depau.etchdroid.R
-import eu.depau.etchdroid.kotlin_exts.vidpid
+import eu.depau.etchdroid.utils.ktexts.vidpid
import kotlinx.android.synthetic.main.usb_device_row.view.*
class UsbDrivesRecyclerViewAdapter(private val dataset: Array) : RecyclerView.Adapter() {
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/ClickListener.kt b/app/src/main/java/eu/depau/etchdroid/ui/misc/ClickListener.kt
similarity index 80%
rename from app/src/main/java/eu/depau/etchdroid/utils/ClickListener.kt
rename to app/src/main/java/eu/depau/etchdroid/ui/misc/ClickListener.kt
index 835fd9e..6f2a0ed 100644
--- a/app/src/main/java/eu/depau/etchdroid/utils/ClickListener.kt
+++ b/app/src/main/java/eu/depau/etchdroid/ui/misc/ClickListener.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.utils
+package eu.depau.etchdroid.ui.misc
import android.view.View
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/DoNotShowAgainDialogFragment.kt b/app/src/main/java/eu/depau/etchdroid/ui/misc/DoNotShowAgainDialogFragment.kt
similarity index 98%
rename from app/src/main/java/eu/depau/etchdroid/utils/DoNotShowAgainDialogFragment.kt
rename to app/src/main/java/eu/depau/etchdroid/ui/misc/DoNotShowAgainDialogFragment.kt
index 8b05be1..dc51347 100644
--- a/app/src/main/java/eu/depau/etchdroid/utils/DoNotShowAgainDialogFragment.kt
+++ b/app/src/main/java/eu/depau/etchdroid/ui/misc/DoNotShowAgainDialogFragment.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.utils
+package eu.depau.etchdroid.ui.misc
import android.annotation.SuppressLint
import android.app.Dialog
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/EmptyRecyclerView.kt b/app/src/main/java/eu/depau/etchdroid/ui/misc/EmptyRecyclerView.kt
similarity index 97%
rename from app/src/main/java/eu/depau/etchdroid/utils/EmptyRecyclerView.kt
rename to app/src/main/java/eu/depau/etchdroid/ui/misc/EmptyRecyclerView.kt
index b0479ce..025036a 100644
--- a/app/src/main/java/eu/depau/etchdroid/utils/EmptyRecyclerView.kt
+++ b/app/src/main/java/eu/depau/etchdroid/ui/misc/EmptyRecyclerView.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.utils
+package eu.depau.etchdroid.ui.misc
import android.content.Context
import android.util.AttributeSet
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/NightModeHelper.kt b/app/src/main/java/eu/depau/etchdroid/ui/misc/NightModeHelper.kt
similarity index 99%
rename from app/src/main/java/eu/depau/etchdroid/utils/NightModeHelper.kt
rename to app/src/main/java/eu/depau/etchdroid/ui/misc/NightModeHelper.kt
index 510313b..cff1fe5 100644
--- a/app/src/main/java/eu/depau/etchdroid/utils/NightModeHelper.kt
+++ b/app/src/main/java/eu/depau/etchdroid/ui/misc/NightModeHelper.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.utils
+package eu.depau.etchdroid.ui.misc
import android.content.SharedPreferences
import android.content.res.Configuration
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/RecyclerViewTouchListener.kt b/app/src/main/java/eu/depau/etchdroid/ui/misc/RecyclerViewTouchListener.kt
similarity index 97%
rename from app/src/main/java/eu/depau/etchdroid/utils/RecyclerViewTouchListener.kt
rename to app/src/main/java/eu/depau/etchdroid/ui/misc/RecyclerViewTouchListener.kt
index aca5810..7f17f9d 100644
--- a/app/src/main/java/eu/depau/etchdroid/utils/RecyclerViewTouchListener.kt
+++ b/app/src/main/java/eu/depau/etchdroid/ui/misc/RecyclerViewTouchListener.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.utils
+package eu.depau.etchdroid.ui.misc
import android.content.Context
import android.view.GestureDetector
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/License.kt b/app/src/main/java/eu/depau/etchdroid/utils/License.kt
deleted file mode 100644
index bfe3048..0000000
--- a/app/src/main/java/eu/depau/etchdroid/utils/License.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package eu.depau.etchdroid.utils
-
-import android.net.Uri
-
-data class License(
- val name: String,
- val url: Uri,
- val license: String,
- val description: String? = null
-)
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/Partition.kt b/app/src/main/java/eu/depau/etchdroid/utils/Partition.kt
index 31d2305..7ab46ea 100644
--- a/app/src/main/java/eu/depau/etchdroid/utils/Partition.kt
+++ b/app/src/main/java/eu/depau/etchdroid/utils/Partition.kt
@@ -1,7 +1,7 @@
package eu.depau.etchdroid.utils
-import eu.depau.etchdroid.enums.FilesystemType
-import eu.depau.etchdroid.enums.PartitionType
+import eu.depau.etchdroid.utils.enums.FilesystemType
+import eu.depau.etchdroid.utils.enums.PartitionType
data class Partition(
val number: Int?,
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/PartitionBuilder.kt b/app/src/main/java/eu/depau/etchdroid/utils/PartitionBuilder.kt
index 363dbcc..901e6d2 100644
--- a/app/src/main/java/eu/depau/etchdroid/utils/PartitionBuilder.kt
+++ b/app/src/main/java/eu/depau/etchdroid/utils/PartitionBuilder.kt
@@ -1,7 +1,7 @@
package eu.depau.etchdroid.utils
-import eu.depau.etchdroid.enums.FilesystemType
-import eu.depau.etchdroid.enums.PartitionType
+import eu.depau.etchdroid.utils.enums.FilesystemType
+import eu.depau.etchdroid.utils.enums.PartitionType
class PartitionBuilder {
var number: Int? = null
diff --git a/app/src/main/java/eu/depau/etchdroid/enums/FilesystemType.kt b/app/src/main/java/eu/depau/etchdroid/utils/enums/FilesystemType.kt
similarity index 98%
rename from app/src/main/java/eu/depau/etchdroid/enums/FilesystemType.kt
rename to app/src/main/java/eu/depau/etchdroid/utils/enums/FilesystemType.kt
index 43349dd..c0c26c7 100644
--- a/app/src/main/java/eu/depau/etchdroid/enums/FilesystemType.kt
+++ b/app/src/main/java/eu/depau/etchdroid/utils/enums/FilesystemType.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.enums
+package eu.depau.etchdroid.utils.enums
import android.content.Context
import eu.depau.etchdroid.R
diff --git a/app/src/main/java/eu/depau/etchdroid/enums/FlashMethod.kt b/app/src/main/java/eu/depau/etchdroid/utils/enums/FlashMethod.kt
similarity index 71%
rename from app/src/main/java/eu/depau/etchdroid/enums/FlashMethod.kt
rename to app/src/main/java/eu/depau/etchdroid/utils/enums/FlashMethod.kt
index 42ca9ef..464f0a1 100644
--- a/app/src/main/java/eu/depau/etchdroid/enums/FlashMethod.kt
+++ b/app/src/main/java/eu/depau/etchdroid/utils/enums/FlashMethod.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.enums
+package eu.depau.etchdroid.utils.enums
enum class FlashMethod {
FLASH_API,
diff --git a/app/src/main/java/eu/depau/etchdroid/enums/ImageLocation.kt b/app/src/main/java/eu/depau/etchdroid/utils/enums/ImageLocation.kt
similarity index 55%
rename from app/src/main/java/eu/depau/etchdroid/enums/ImageLocation.kt
rename to app/src/main/java/eu/depau/etchdroid/utils/enums/ImageLocation.kt
index aa2c410..af8f6af 100644
--- a/app/src/main/java/eu/depau/etchdroid/enums/ImageLocation.kt
+++ b/app/src/main/java/eu/depau/etchdroid/utils/enums/ImageLocation.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.enums
+package eu.depau.etchdroid.utils.enums
enum class ImageLocation {
REMOTE,
diff --git a/app/src/main/java/eu/depau/etchdroid/enums/PartitionTableType.kt b/app/src/main/java/eu/depau/etchdroid/utils/enums/PartitionTableType.kt
similarity index 96%
rename from app/src/main/java/eu/depau/etchdroid/enums/PartitionTableType.kt
rename to app/src/main/java/eu/depau/etchdroid/utils/enums/PartitionTableType.kt
index d687bfe..45fa1c6 100644
--- a/app/src/main/java/eu/depau/etchdroid/enums/PartitionTableType.kt
+++ b/app/src/main/java/eu/depau/etchdroid/utils/enums/PartitionTableType.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.enums
+package eu.depau.etchdroid.utils.enums
import android.content.Context
import eu.depau.etchdroid.R
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/enums/PartitionType.kt b/app/src/main/java/eu/depau/etchdroid/utils/enums/PartitionType.kt
new file mode 100644
index 0000000..8c75168
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/enums/PartitionType.kt
@@ -0,0 +1,3 @@
+package eu.depau.etchdroid.utils.enums
+
+enum class PartitionType
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/enums/WizardStep.kt b/app/src/main/java/eu/depau/etchdroid/utils/enums/WizardStep.kt
similarity index 72%
rename from app/src/main/java/eu/depau/etchdroid/enums/WizardStep.kt
rename to app/src/main/java/eu/depau/etchdroid/utils/enums/WizardStep.kt
index 645ae36..5569f1d 100644
--- a/app/src/main/java/eu/depau/etchdroid/enums/WizardStep.kt
+++ b/app/src/main/java/eu/depau/etchdroid/utils/enums/WizardStep.kt
@@ -1,4 +1,4 @@
-package eu.depau.etchdroid.enums
+package eu.depau.etchdroid.utils.enums
enum class WizardStep {
SELECT_FLASH_METHOD,
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/exception/CannotGetFilePathException.kt b/app/src/main/java/eu/depau/etchdroid/utils/exception/CannotGetFilePathException.kt
new file mode 100644
index 0000000..180b596
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/exception/CannotGetFilePathException.kt
@@ -0,0 +1,3 @@
+package eu.depau.etchdroid.utils.exception
+
+class CannotGetFilePathException(cause: Exception) : Exception(cause)
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/exception/UsbWriteException.kt b/app/src/main/java/eu/depau/etchdroid/utils/exception/UsbWriteException.kt
new file mode 100644
index 0000000..3a54fad
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/exception/UsbWriteException.kt
@@ -0,0 +1,8 @@
+package eu.depau.etchdroid.utils.exception
+
+import eu.depau.etchdroid.utils.ktexts.toHRSize
+import java.io.IOException
+
+class UsbWriteException(offset: Long, writtenBytes: Long, exc: Exception) : IOException(
+ "Write failed at block $offset, ${writtenBytes.toHRSize()} written. Error: $exc", exc
+)
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/imagetypes/DMGImage.kt b/app/src/main/java/eu/depau/etchdroid/utils/imagetypes/DMGImage.kt
new file mode 100644
index 0000000..8c73f0a
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/imagetypes/DMGImage.kt
@@ -0,0 +1,112 @@
+package eu.depau.etchdroid.utils.imagetypes
+
+import android.content.Context
+import android.net.Uri
+import eu.depau.etchdroid.utils.enums.FilesystemType
+import eu.depau.etchdroid.utils.enums.PartitionTableType
+import eu.depau.etchdroid.utils.ktexts.getBinary
+import eu.depau.etchdroid.utils.ktexts.readString
+import eu.depau.etchdroid.utils.Partition
+import eu.depau.etchdroid.utils.PartitionBuilder
+import java.io.File
+
+val SECTOR_SIZE = 512
+private val partRegex = Regex("partition (\\d+): begin=(\\d+), size=(\\d+), decoded=(\\d+), firstsector=(\\d+), sectorcount=(\\d+), blocksruncount=(\\d+)\\s+(.*) \\((.+) : \\d+\\)", RegexOption.MULTILINE)
+
+private fun readPartitionTable(dmg2img: File, libDir: String, file: File): Triple?, Long?> {
+ val pt = ArrayList()
+ var ptType: PartitionTableType? = null
+ var imgSize = 0L
+
+ val pb = ProcessBuilder(dmg2img.path, "-v", "-l", file.path)
+ pb.environment()["LD_LIBRARY_PATH"] = libDir
+ pb.redirectErrorStream(true)
+
+ val p = pb.start()
+ val out = p.inputStream.readString()
+ System.err.println(out)
+ p.waitFor()
+ val matches = partRegex.findAll(out)
+
+ matchloop@ for (m in matches) {
+ val (
+ number, begin, size,
+ decoded, firstsector,
+ sectorcount, blocksruncount,
+ label, type
+ ) = m.destructured
+
+ val pb = PartitionBuilder()
+
+ pb.number = number.toInt()
+ pb.size = SECTOR_SIZE * sectorcount.toLong()
+ imgSize += pb.size!!
+
+ if (label.isNotEmpty())
+ pb.fsLabel = label
+
+ pb.partLabel = type
+
+ when (type) {
+ "Apple_partition_map" -> {
+ ptType = PartitionTableType.MAC
+ }
+ "MBR" -> {
+ ptType = PartitionTableType.MSDOS
+ continue@matchloop
+ }
+ }
+
+ pb.fsType = when {
+ type == "Apple_Empty" || type == "Apple_Scratch" || type == "Apple_Free" -> FilesystemType.FREE
+ type == "Apple_HFS" -> FilesystemType.HFSPLUS
+ type == "Apple_HFSX" -> FilesystemType.HFSPLUS
+ type == "Windows_FAT_32" -> FilesystemType.FAT32
+ type.startsWith("Apple_") || type == "DDM" -> FilesystemType.APT_DATA
+ else -> FilesystemType.UNKNOWN
+ }
+
+ pt.add(pb.build())
+ }
+
+ return Triple(ptType, pt, imgSize)
+}
+
+class DMGImage(private val uri: Uri, private val context: Context) : Image {
+ private val dmg2img: File = context.getBinary("dmg2img")
+ private val libDir: String = context.applicationInfo.nativeLibraryDir
+ private var loaded: Boolean = false
+ private var partTable: List? = null
+ private var partTableType: PartitionTableType? = null
+ private var imgSize: Long? = null
+
+ private fun readInfo() {
+ if (loaded)
+ return
+ val triple = readPartitionTable(dmg2img, libDir, File(uri.path))
+ loaded = true
+ partTableType = triple.first
+ partTable = triple.second
+ imgSize = triple.third
+ }
+
+ override val partitionTable: List?
+ get() {
+ readInfo()
+ return partTable
+ }
+
+
+ override val tableType: PartitionTableType?
+ get() {
+ readInfo()
+ return partTableType
+ }
+
+ override val size: Long?
+ get() {
+ readInfo()
+ return imgSize
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/imagetypes/Image.kt b/app/src/main/java/eu/depau/etchdroid/utils/imagetypes/Image.kt
new file mode 100644
index 0000000..93722f9
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/imagetypes/Image.kt
@@ -0,0 +1,10 @@
+package eu.depau.etchdroid.utils.imagetypes
+
+import eu.depau.etchdroid.utils.enums.PartitionTableType
+import eu.depau.etchdroid.utils.Partition
+
+interface Image {
+ val partitionTable: List?
+ val tableType: PartitionTableType?
+ val size: Long?
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/ktexts/ContextGetBinary.kt b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/ContextGetBinary.kt
new file mode 100644
index 0000000..8644ef0
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/ContextGetBinary.kt
@@ -0,0 +1,56 @@
+package eu.depau.etchdroid.utils.ktexts
+
+import android.content.Context
+import android.os.Build
+import java.io.File
+import java.io.FileOutputStream
+
+
+private val copiedBinaries: MutableMap = HashMap()
+
+
+fun Context.getBinary(name: String): File {
+ if (name in copiedBinaries.keys)
+ return copiedBinaries[name]!!
+
+ val abi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ Build.SUPPORTED_ABIS[0]
+ else
+ Build.CPU_ABI
+
+ val arch = when {
+ abi.contains("armeabi-v7a") -> "armeabi-v7a"
+ abi.contains("x86_64") -> "x86_64"
+ abi.contains("x86") -> "x86"
+ abi.contains("arm64-v8a") -> "arm64-v8a"
+ else -> null!!
+ }
+
+ val assetManager = assets
+ val assetIn = assetManager.open("bin/$arch/$name")
+
+ val bin = File("$filesDir/bin/$arch/$name")
+ bin.parentFile?.mkdirs()
+ if (!bin.exists())
+ bin.createNewFile()
+ val binOut = FileOutputStream(bin)
+
+ // Copy executable
+ var size: Long = 0
+ val buff = ByteArray(1024)
+ var nRead = assetIn.read(buff)
+
+ while (nRead != -1) {
+ binOut.write(buff, 0, nRead)
+ size += nRead.toLong()
+ nRead = assetIn.read(buff)
+ }
+ assetIn.close()
+ binOut.close()
+
+ bin.setExecutable(true)
+
+ copiedBinaries[name] = bin
+
+ return bin
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/ktexts/ContextToast.kt b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/ContextToast.kt
new file mode 100644
index 0000000..03e9dfe
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/ContextToast.kt
@@ -0,0 +1,8 @@
+package eu.depau.etchdroid.utils.ktexts
+
+import android.content.Context
+import android.widget.Toast
+
+fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_LONG) {
+ Toast.makeText(this, message, duration).show()
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/ktexts/InputStreamToString.kt b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/InputStreamToString.kt
new file mode 100644
index 0000000..6b9a752
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/InputStreamToString.kt
@@ -0,0 +1,18 @@
+package eu.depau.etchdroid.utils.ktexts
+
+import java.io.ByteArrayOutputStream
+import java.io.IOException
+import java.io.InputStream
+
+@Throws(IOException::class)
+fun InputStream.readString(): String {
+ val baos = ByteArrayOutputStream()
+ val buffer = ByteArray(1024)
+ var length = this.read(buffer)
+
+ while (length != -1) {
+ baos.write(buffer, 0, length)
+ length = this.read(buffer)
+ }
+ return baos.toString("UTF-8")
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/ktexts/NumberToSizeString.kt b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/NumberToSizeString.kt
new file mode 100644
index 0000000..1e65fab
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/NumberToSizeString.kt
@@ -0,0 +1,18 @@
+package eu.depau.etchdroid.utils.ktexts
+
+// https://stackoverflow.com/a/3758880/1124621
+
+private fun humanReadableByteCount(bytes: T, si: Boolean = true): String where T : Comparable, T : Number {
+ val unit: Long = if (si) 1000 else 1024
+ if (bytes.toLong() < unit) return String.format("%.1f B", bytes.toDouble())
+ val exp = (Math.log(bytes.toDouble()) / Math.log(unit.toDouble())).toInt()
+ val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i"
+ return String.format("%.1f %sB", bytes.toDouble() / Math.pow(unit.toDouble(), exp.toDouble()), pre)
+}
+
+fun Long.toHRSize(si: Boolean = true) = humanReadableByteCount(this, si)
+fun Float.toHRSize(si: Boolean = true) = humanReadableByteCount(this, si)
+fun Double.toHRSize(si: Boolean = true) = humanReadableByteCount(this, si)
+fun Int.toHRSize(si: Boolean = true) = humanReadableByteCount(this, si)
+fun Byte.toHRSize(si: Boolean = true) = humanReadableByteCount(this, si)
+fun Short.toHRSize(si: Boolean = true) = humanReadableByteCount(this, si)
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/ktexts/NumberToTimeString.kt b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/NumberToTimeString.kt
new file mode 100644
index 0000000..983c43b
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/NumberToTimeString.kt
@@ -0,0 +1,31 @@
+package eu.depau.etchdroid.utils.ktexts
+
+private val timeStrings = arrayOf("s", "m", "h", "d")
+private val timeDivs = arrayOf(60, 60, 24)
+
+fun humanReadableTimeDelta(time: T): String where T : Number {
+ var dbTime = time.toDouble() / 1000.0
+ var outString = ""
+
+ for (i in 0..(timeDivs.size - 1)) {
+ val div = timeDivs[i]
+ val str = timeStrings[i]
+
+ outString = "${(dbTime % div).toInt()}$str$outString"
+
+ if (dbTime < div)
+ return outString
+
+ outString = " $outString"
+ dbTime /= div
+ }
+
+ return "${dbTime.toInt()}${timeStrings[-1]} $outString"
+}
+
+fun Long.toHRTime() = humanReadableTimeDelta(this)
+fun Float.toHRTime() = humanReadableTimeDelta(this)
+fun Double.toHRTime() = humanReadableTimeDelta(this)
+fun Int.toHRTime() = humanReadableTimeDelta(this)
+fun Byte.toHRTime() = humanReadableTimeDelta(this)
+fun Short.toHRTime() = humanReadableTimeDelta(this)
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/ktexts/UriGetDisplayName.kt b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/UriGetDisplayName.kt
new file mode 100644
index 0000000..34d8fbf
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/UriGetDisplayName.kt
@@ -0,0 +1,29 @@
+package eu.depau.etchdroid.utils.ktexts
+
+import android.content.Context
+import android.net.Uri
+import android.provider.OpenableColumns
+
+
+fun Uri.getFileName(context: Context): String? {
+ val TAG = "UriGetFileNameExt"
+ var result: String? = null
+
+ if (this.scheme == "content") {
+ val cursor = context.getContentResolver().query(this, null, null, null, null)
+ cursor.use {
+ if (it != null && it.moveToFirst()) {
+ result = it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))
+ }
+ }
+ }
+ if (result == null) {
+ result = this.getPath()
+ val cut = result!!.lastIndexOf('/')
+ if (cut != -1) {
+ result = result!!.substring(cut + 1)
+ }
+ }
+
+ return result
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/ktexts/UriGetFileExt.kt b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/UriGetFileExt.kt
new file mode 100644
index 0000000..2e058a7
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/UriGetFileExt.kt
@@ -0,0 +1,16 @@
+package eu.depau.etchdroid.utils.ktexts
+
+import android.content.ContentResolver
+import android.net.Uri
+import android.webkit.MimeTypeMap
+import java.io.File
+
+fun Uri.getExtension(contentResolver: ContentResolver): String {
+ return when (scheme) {
+ ContentResolver.SCHEME_CONTENT -> {
+ val mime = MimeTypeMap.getSingleton()
+ mime.getExtensionFromMimeType(contentResolver.getType(this))!!
+ }
+ else -> MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(File(path)).toString())
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/ktexts/UriGetFilePath.kt b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/UriGetFilePath.kt
new file mode 100644
index 0000000..6981ca3
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/UriGetFilePath.kt
@@ -0,0 +1,135 @@
+package eu.depau.etchdroid.utils.ktexts
+
+import android.content.ContentUris
+import android.content.Context
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.provider.DocumentsContract
+import android.provider.MediaStore
+import eu.depau.etchdroid.utils.exception.CannotGetFilePathException
+
+
+/**
+ * Get a file path from a Uri. This will get the the path for Storage Access
+ * Framework Documents, as well as the _data field for the MediaStore and
+ * other file-based ContentProviders.
+ *
+ * @param context The context.
+ * @author paulburke
+ *
+ * https://stackoverflow.com/a/27271131/1124621
+ */
+fun Uri.getFilePath(context: Context): String? {
+ val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+
+ try {
+ if (isKitKat && DocumentsContract.isDocumentUri(context, this)) {
+ // DocumentProvider
+
+ if (isExternalStorageDocument) {
+ // ExternalStorageProvider
+
+ val docId = DocumentsContract.getDocumentId(this)
+ val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+ val type = split[0]
+
+ if (type.equals("primary", ignoreCase = true)) {
+ return Environment.getExternalStorageDirectory().path + "/" + split[1]
+ }
+
+ // TODO handle non-primary volumes
+
+ } else if (isDownloadsDocument) {
+ // DownloadsProvider
+
+ val id = DocumentsContract.getDocumentId(this)
+
+ if (id.startsWith("raw:/"))
+ return Uri.parse(id).path
+
+ val contentUri = ContentUris.withAppendedId(
+ Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
+
+
+ return contentUri.getDataColumn(context, null, null)
+
+ } else if (isMediaDocument) {
+ // MediaProvider
+
+ val docId = DocumentsContract.getDocumentId(this)
+ val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+ val type = split[0]
+
+ val contentUri = when (type) {
+ "image" -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ "video" -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+ "audio" -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
+ else -> null
+ }
+
+ val selection = "_id=?"
+ val selectionArgs = arrayOf(split[1])
+
+ return contentUri?.getDataColumn(context, selection, selectionArgs)
+ }
+
+ } else if ("content".equals(scheme, ignoreCase = true)) {
+ // MediaStore (and general)
+
+ return getDataColumn(context, null, null)
+
+ } else if ("file".equals(scheme, ignoreCase = true)) {
+ // File
+
+ return path
+ }
+ } catch (e: Exception) {
+ // Wrap into own exception to make debugging easier
+ throw CannotGetFilePathException(e)
+ }
+
+ return null
+}
+
+/**
+ * Get the value of the data column for this Uri. This is useful for
+ * MediaStore Uris, and other file-based ContentProviders.
+ *
+ * @param context The context.
+ * @param selection (Optional) Filter used in the query.
+ * @param selectionArgs (Optional) Selection arguments used in the query.
+ * @return The value of the _data column, which is typically a file path.
+ */
+fun Uri.getDataColumn(context: Context, selection: String?, selectionArgs: Array?): String? {
+ val column = "_data"
+ val projection = arrayOf(column)
+
+ context.contentResolver.query(this, projection, selection, selectionArgs, null)?.use {
+ if (it.moveToFirst()) {
+ val columnIndex = it.getColumnIndexOrThrow(column)
+ return it.getString(columnIndex)
+ }
+ }
+
+ return null
+}
+
+
+/**
+ * Whether the Uri authority is ExternalStorageProvider.
+ */
+val Uri.isExternalStorageDocument: Boolean
+ get() = "com.android.externalstorage.documents" == authority
+
+/**
+ * Whether the Uri authority is DownloadsProvider.
+ */
+val Uri.isDownloadsDocument: Boolean
+ get() = "com.android.providers.downloads.documents" == authority
+
+/**
+ * Whether the Uri authority is MediaProvider.
+ */
+val Uri.isMediaDocument: Boolean
+ get() = "com.android.providers.media.documents" == authority
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/ktexts/UriGetFileSize.kt b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/UriGetFileSize.kt
new file mode 100644
index 0000000..91fa893
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/UriGetFileSize.kt
@@ -0,0 +1,39 @@
+package eu.depau.etchdroid.utils.ktexts
+
+import android.content.ContentResolver
+import android.content.Context
+import android.database.Cursor
+import android.database.SQLException
+import android.net.Uri
+import android.provider.OpenableColumns
+import java.io.File
+
+// https://github.com/android-rcs/rcsjta/blob/master/RI/src/com/gsma/rcs/ri/utils/FileUtils.java#L214
+
+fun Uri.getFileSize(context: Context): Long {
+ when (this.scheme) {
+ ContentResolver.SCHEME_FILE -> {
+ val f = File(this.path)
+ return f.length()
+ }
+
+ ContentResolver.SCHEME_CONTENT -> {
+ val cursor: Cursor? = context.contentResolver.query(this, null, null, null, null)
+
+ cursor.use {
+ if (it == null) {
+ throw SQLException("Failed to query file $this")
+ }
+ return if (it.moveToFirst()) {
+ java.lang.Long.valueOf(it.getString(it
+ .getColumnIndexOrThrow(OpenableColumns.SIZE)))
+ } else {
+ throw IllegalArgumentException(
+ "Error in retrieving this size form the URI")
+ }
+ }
+ }
+
+ else -> throw IllegalArgumentException("Unsupported URI scheme")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/ktexts/UsbDeviceVidPidName.kt b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/UsbDeviceVidPidName.kt
new file mode 100644
index 0000000..b0cd036
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/UsbDeviceVidPidName.kt
@@ -0,0 +1,17 @@
+package eu.depau.etchdroid.utils.ktexts
+
+import android.hardware.usb.UsbDevice
+import android.os.Build
+
+fun formatID(id: Int): String = Integer.toHexString(id).padStart(4, '0')
+
+val UsbDevice.vidpid: String
+ get() = "${formatID(this.vendorId)}:${formatID(this.productId)}"
+
+
+val UsbDevice.name: String
+ get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ "${this.manufacturerName} ${this.productName}"
+ } else {
+ this.deviceName
+ }
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/etchdroid/utils/ktexts/ViewSnackbar.kt b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/ViewSnackbar.kt
new file mode 100644
index 0000000..9e67127
--- /dev/null
+++ b/app/src/main/java/eu/depau/etchdroid/utils/ktexts/ViewSnackbar.kt
@@ -0,0 +1,8 @@
+package eu.depau.etchdroid.utils.ktexts
+
+import android.view.View
+import com.google.android.material.snackbar.Snackbar
+
+fun View.snackbar(message: CharSequence, duration: Int = Snackbar.LENGTH_LONG) {
+ Snackbar.make(this, message, duration).show()
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_confirmation.xml b/app/src/main/res/layout/activity_confirmation.xml
index 283aabd..13f5458 100644
--- a/app/src/main/res/layout/activity_confirmation.xml
+++ b/app/src/main/res/layout/activity_confirmation.xml
@@ -7,7 +7,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/DarkThemeOverlay"
- tools:context=".activities.ConfirmationActivity">
+ tools:context=".ui.activities.ConfirmationActivity">
+ tools:context=".ui.activities.ErrorActivity">
+ tools:context=".ui.activities.LicensesActivity">
+ tools:context=".ui.activities.StartActivity">
+ tools:context=".ui.activities.UsbDrivePickerActivity">
+ tools:context=".ui.activities.StartActivity">