diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 86406c7..4906bff 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,7 +2,7 @@
-
+
+
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/ddroid/StateKeeper.kt b/app/src/main/java/eu/depau/ddroid/StateKeeper.kt
index bbf3656..18e0e25 100644
--- a/app/src/main/java/eu/depau/ddroid/StateKeeper.kt
+++ b/app/src/main/java/eu/depau/ddroid/StateKeeper.kt
@@ -1,6 +1,8 @@
package eu.depau.ddroid
+import android.hardware.usb.UsbDevice
import android.net.Uri
+import com.github.mjdev.libaums.UsbMassStorageDevice
import eu.depau.ddroid.abc.WizardFragment
import eu.depau.ddroid.values.FlashMethod
import eu.depau.ddroid.values.ImageLocation
@@ -13,4 +15,7 @@ object StateKeeper {
var imageLocation: ImageLocation? = null
var streamingWrite: Boolean = false
var imageFile: Uri? = null
+
+ var usbDevice: UsbDevice? = null
+ var usbMassStorageDevice: UsbMassStorageDevice? = null
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/ddroid/abc/ClickListener.kt b/app/src/main/java/eu/depau/ddroid/abc/ClickListener.kt
new file mode 100644
index 0000000..50d4d00
--- /dev/null
+++ b/app/src/main/java/eu/depau/ddroid/abc/ClickListener.kt
@@ -0,0 +1,9 @@
+package eu.depau.ddroid.abc
+
+import android.view.View
+
+interface ClickListener {
+ fun onClick(view: View, position: Int)
+
+ fun onLongClick(view: View, position: Int)
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/ddroid/abc/UsbWriteService.kt b/app/src/main/java/eu/depau/ddroid/abc/UsbWriteService.kt
new file mode 100644
index 0000000..e6e3f88
--- /dev/null
+++ b/app/src/main/java/eu/depau/ddroid/abc/UsbWriteService.kt
@@ -0,0 +1,99 @@
+package eu.depau.ddroid.abc
+
+import android.app.IntentService
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.net.Uri
+import android.os.Build
+import eu.depau.ddroid.R
+import eu.depau.ddroid.utils.getFileName
+import eu.depau.ddroid.utils.toHRSize
+
+
+abstract class UsbWriteService(name: String) : IntentService(name) {
+ val TAG = name
+ val FOREGROUND_ID = 1931
+ val WRITE_PROGRESS_CHANNEL_ID = "eu.depau.ddroid.notifications.USB_WRITE_PROGRESS"
+ private var prevTime = System.currentTimeMillis()
+ private var prevBytes = 0L
+ private var notifyChanRegistered = false
+
+ fun getNotificationBuilder(): Notification.Builder {
+ if (!notifyChanRegistered) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channame = "USB write progress"
+ val description = "Displays the status of ongoing USB writes"
+ val importance = NotificationManager.IMPORTANCE_LOW
+ val channel = NotificationChannel(WRITE_PROGRESS_CHANNEL_ID, channame, importance)
+ channel.description = description
+
+ // Register the channel with the system; you can't change the importance
+ // or other notification behaviors after this
+ val notificationManager = getSystemService(NotificationManager::class.java)
+ notificationManager!!.createNotificationChannel(channel)
+ }
+ notifyChanRegistered = true
+ }
+
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ Notification.Builder(this, WRITE_PROGRESS_CHANNEL_ID)
+ else
+ Notification.Builder(this)
+ }
+
+ fun updateNotification(usbDevice: String, uri: Uri, bytes: Long, total: Long) {
+ // Notification rate limiting
+ val time = System.currentTimeMillis()
+ if (time <= prevTime + 1000)
+ return
+
+ val speed = ((bytes - prevBytes).toDouble() / (time - prevTime).toDouble()).toHRSize()
+ prevTime = time
+ prevBytes = bytes
+
+ val perc: Int = (bytes.toDouble() / total * 100.0).toInt()
+
+ val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.notify(FOREGROUND_ID, buildForegroundNotification(usbDevice, uri, bytes, total, "$perc% • $speed/s"))
+ }
+
+ fun errorNotification() {
+ val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+
+ val b = getNotificationBuilder()
+ .setContentTitle("Write failed")
+ .setContentText("The USB drive may have been unplugged while writing.")
+ .setOngoing(false)
+ .setSmallIcon(R.drawable.ic_usb_white_24dp)
+
+ notificationManager.notify(FOREGROUND_ID, b.build())
+ }
+
+ fun buildForegroundNotification(usbDevice: String, uri: Uri, bytes: Long, total: Long, subText: String? = null): Notification {
+ val progr: Int
+ val indet: Boolean
+
+ if (total < 0) {
+ progr = 0
+ indet = true
+ } else {
+ progr = (bytes.toFloat()/total * 100).toInt()
+ indet = false
+ }
+
+ val b = getNotificationBuilder()
+
+ b.setContentTitle("Writing image")
+ .setContentText("${uri.getFileName(this)} to $usbDevice")
+ .setOngoing(true)
+ .setSmallIcon(R.drawable.ic_usb_white_24dp)
+ .setProgress(100, progr, indet)
+
+ if (subText != null)
+ b.setSubText(subText)
+
+ return b.build()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/depau/ddroid/abc/WizardFragment.kt b/app/src/main/java/eu/depau/ddroid/abc/WizardFragment.kt
index 2e12a0a..ebe7c78 100644
--- a/app/src/main/java/eu/depau/ddroid/abc/WizardFragment.kt
+++ b/app/src/main/java/eu/depau/ddroid/abc/WizardFragment.kt
@@ -4,10 +4,13 @@ import android.content.Intent
import android.support.v4.app.Fragment
import android.view.View
-abstract class WizardFragment : Fragment() {
- abstract fun nextStep(view: View)
+abstract class WizardFragment() : Fragment() {
+ private lateinit var wizardActivity: WizardActivity
+
+ abstract fun nextStep(view: View?)
open fun onFragmentAdded(activity: WizardActivity) {}
+ open fun onFragmentRemoving(activity: WizardActivity) {}
open fun onRadioButtonClicked(view: View) {}
open fun onCheckBoxClicked(view: View) {}
open override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
diff --git a/app/src/main/java/eu/depau/ddroid/activities/MainActivity.kt b/app/src/main/java/eu/depau/ddroid/activities/MainActivity.kt
index 44e7cc3..eea10fc 100644
--- a/app/src/main/java/eu/depau/ddroid/activities/MainActivity.kt
+++ b/app/src/main/java/eu/depau/ddroid/activities/MainActivity.kt
@@ -1,15 +1,49 @@
package eu.depau.ddroid.activities
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.hardware.usb.UsbDevice
+import android.hardware.usb.UsbManager
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import eu.depau.ddroid.R
+import eu.depau.ddroid.StateKeeper
import eu.depau.ddroid.abc.WizardActivity
import eu.depau.ddroid.abc.WizardFragment
import eu.depau.ddroid.fragments.FlashMethodFragment
+import eu.depau.ddroid.fragments.UsbDriveFragment
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : WizardActivity() {
+ val TAG = "MainActivity"
+ val ACTION_USB_PERMISSION = "eu.depau.ddroid.USB_PERMISSION"
+ lateinit var mUsbPermissionIntent: PendingIntent
+
+ private val mUsbReceiver = object : BroadcastReceiver() {
+
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == ACTION_USB_PERMISSION) {
+ synchronized(this) {
+ val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
+
+ val result = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)
+ if (result)
+ device?.apply {
+ StateKeeper.usbDevice = this
+ }
+
+ if (StateKeeper.currentFragment is UsbDriveFragment)
+ (StateKeeper.currentFragment as UsbDriveFragment).onUsbPermissionResult(device, result)
+ }
+ }
+ }
+ }
+
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@@ -22,14 +56,22 @@ class MainActivity : WizardActivity() {
transaction.replace(R.id.fragment_layout, fragment)
transaction.commit()
fragment.onFragmentAdded(this)
+
+ val usbManager = getSystemService(Context.USB_SERVICE) as UsbManager
+ mUsbPermissionIntent = PendingIntent.getBroadcast(this, 0, Intent(ACTION_USB_PERMISSION), 0)
+ val filter = IntentFilter(ACTION_USB_PERMISSION)
+ registerReceiver(mUsbReceiver, filter)
}
override fun goToNewFragment(fragment: WizardFragment) {
+ StateKeeper.currentFragment?.onFragmentRemoving(this)
+
val transaction = supportFragmentManager.beginTransaction()
transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left)
transaction.replace(R.id.fragment_layout, fragment)
transaction.addToBackStack(null)
transaction.commit()
+
fragment.onFragmentAdded(this)
}
diff --git a/app/src/main/java/eu/depau/ddroid/fragments/ConfirmInfoFragment.kt b/app/src/main/java/eu/depau/ddroid/fragments/ConfirmInfoFragment.kt
new file mode 100644
index 0000000..6ed0bd0
--- /dev/null
+++ b/app/src/main/java/eu/depau/ddroid/fragments/ConfirmInfoFragment.kt
@@ -0,0 +1,87 @@
+package eu.depau.ddroid.fragments
+
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import eu.depau.ddroid.R
+import eu.depau.ddroid.StateKeeper
+import eu.depau.ddroid.abc.WizardActivity
+import eu.depau.ddroid.abc.WizardFragment
+import eu.depau.ddroid.services.UsbAPIWriteService
+import eu.depau.ddroid.utils.*
+import eu.depau.ddroid.values.FlashMethod
+import eu.depau.ddroid.values.WizardStep
+import kotlinx.android.synthetic.main.fragment_confirminfo.view.*
+
+/**
+ * A placeholder fragment containing a simple view.
+ */
+class ConfirmInfoFragment : WizardFragment() {
+ val TAG = "ConfirmInfoFragment"
+ var canContinue = false
+
+ override fun nextStep(view: View?) {
+ if (!canContinue) {
+ view?.snackbar("Cannot write image to USB drive")
+ return
+ }
+
+ context?.toast("Check notification for progress")
+
+ val intent = Intent(activity, UsbAPIWriteService::class.java)
+ intent.setDataAndType(StateKeeper.imageFile, "application/octet-stream")
+ intent.putExtra("usbDevice", StateKeeper.usbDevice)
+ activity?.startService(intent)
+ activity?.finish()
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?): View? {
+ StateKeeper.currentFragment = this
+ StateKeeper.wizardStep = WizardStep.CONFIRM
+
+ val view = inflater.inflate(R.layout.fragment_confirminfo, container, false)
+
+ view.confirm_sel_method.text = when (StateKeeper.flashMethod) {
+ FlashMethod.FLASH_API -> getString(R.string.flash_dd_usb_api)
+ FlashMethod.FLASH_DD -> getString(R.string.flash_dd_root)
+ FlashMethod.FLASH_UNETBOOTIN -> getString(R.string.flash_unetbootin)
+ FlashMethod.FLASH_WOEUSB -> getString(R.string.flash_woeusb)
+ else -> null
+ }
+
+ view.confirm_sel_image.text = StateKeeper.imageFile?.getFileName(context!!)
+
+ if (view.confirm_sel_image.text == null)
+ view.confirm_sel_image.text = getString(R.string.unknown_filename)
+
+ val imgSize = StateKeeper.imageFile?.getFileSize(context!!)
+ view.confirm_sel_image_size.text = imgSize?.toHRSize()
+
+ view.confirm_sel_usbdev.text = StateKeeper.usbDevice?.name
+
+ StateKeeper.usbMassStorageDevice!!.init()
+ val blockDev = StateKeeper.usbMassStorageDevice?.blockDevice
+
+ if (blockDev != null) {
+ val devSize = (blockDev.size.toLong() * blockDev.blockSize.toLong())
+ view.confirm_sel_usbdev_size.text = devSize.toHRSize()
+
+ if (imgSize!! > devSize)
+ view.confirm_extra_info.text = getString(R.string.image_bigger_than_usb)
+ else {
+ view.confirm_extra_info.text = getString(R.string.tap_next_to_write)
+ canContinue = true
+ }
+ } else {
+ view.confirm_extra_info.text = getString(R.string.cant_read_usbdev)
+ }
+
+ return view
+ }
+
+
+}
diff --git a/app/src/main/java/eu/depau/ddroid/fragments/FlashMethodFragment.kt b/app/src/main/java/eu/depau/ddroid/fragments/FlashMethodFragment.kt
index df21359..e6f2ea1 100644
--- a/app/src/main/java/eu/depau/ddroid/fragments/FlashMethodFragment.kt
+++ b/app/src/main/java/eu/depau/ddroid/fragments/FlashMethodFragment.kt
@@ -1,7 +1,6 @@
package eu.depau.ddroid.fragments
import android.os.Bundle
-import android.support.design.widget.Snackbar
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -9,6 +8,7 @@ import eu.depau.ddroid.R
import eu.depau.ddroid.StateKeeper
import eu.depau.ddroid.abc.WizardActivity
import eu.depau.ddroid.abc.WizardFragment
+import eu.depau.ddroid.utils.snackbar
import eu.depau.ddroid.values.FlashMethod
import eu.depau.ddroid.values.WizardStep
@@ -16,9 +16,9 @@ import eu.depau.ddroid.values.WizardStep
* A placeholder fragment containing a simple view.
*/
class FlashMethodFragment : WizardFragment() {
- override fun nextStep(view: View) {
+ override fun nextStep(view: View?) {
if (StateKeeper.flashMethod == null)
- Snackbar.make(view, "Please select writing method", Snackbar.LENGTH_LONG).show()
+ view?.snackbar(getString(R.string.please_select_writing_method))
else
(activity as WizardActivity).goToNewFragment(ImageLocationFragment())
}
diff --git a/app/src/main/java/eu/depau/ddroid/fragments/ImageLocationFragment.kt b/app/src/main/java/eu/depau/ddroid/fragments/ImageLocationFragment.kt
index e5b169c..786b5ab 100644
--- a/app/src/main/java/eu/depau/ddroid/fragments/ImageLocationFragment.kt
+++ b/app/src/main/java/eu/depau/ddroid/fragments/ImageLocationFragment.kt
@@ -6,7 +6,6 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
-import android.provider.OpenableColumns
import android.support.design.widget.Snackbar
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
@@ -14,16 +13,18 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.widget.Button
-import android.widget.CheckBox
-import android.widget.EditText
import eu.depau.ddroid.R
import eu.depau.ddroid.StateKeeper
import eu.depau.ddroid.abc.WizardActivity
import eu.depau.ddroid.abc.WizardFragment
+import eu.depau.ddroid.utils.getFileName
+import eu.depau.ddroid.utils.snackbar
import eu.depau.ddroid.values.FlashMethod
import eu.depau.ddroid.values.ImageLocation
import eu.depau.ddroid.values.WizardStep
+import kotlinx.android.synthetic.main.activity_main.*
+import kotlinx.android.synthetic.main.fragment_select_location.*
+import kotlinx.android.synthetic.main.wizard_fragment_layout.*
/**
@@ -43,7 +44,7 @@ class ImageLocationFragment : WizardFragment() {
}
fun setStreamingCheckBoxAvailability(context: WizardActivity) {
- val checkBox = context.findViewById(R.id.streaming_write_checkbox)
+ val checkBox = streaming_write_checkbox
if (checkBox == null)
return
@@ -71,8 +72,10 @@ class ImageLocationFragment : WizardFragment() {
else -> null
}
- activity?.findViewById