Implement API writing
This commit is contained in:
parent
4e861c7c09
commit
5e92baa4a0
32 changed files with 921 additions and 125 deletions
|
@ -2,7 +2,7 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="eu.depau.ddroid">
|
package="eu.depau.ddroid">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
@ -21,6 +21,9 @@
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<service
|
||||||
|
android:name=".services.UsbAPIWriteService"
|
||||||
|
android:exported="false"/>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -1,6 +1,8 @@
|
||||||
package eu.depau.ddroid
|
package eu.depau.ddroid
|
||||||
|
|
||||||
|
import android.hardware.usb.UsbDevice
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import com.github.mjdev.libaums.UsbMassStorageDevice
|
||||||
import eu.depau.ddroid.abc.WizardFragment
|
import eu.depau.ddroid.abc.WizardFragment
|
||||||
import eu.depau.ddroid.values.FlashMethod
|
import eu.depau.ddroid.values.FlashMethod
|
||||||
import eu.depau.ddroid.values.ImageLocation
|
import eu.depau.ddroid.values.ImageLocation
|
||||||
|
@ -13,4 +15,7 @@ object StateKeeper {
|
||||||
var imageLocation: ImageLocation? = null
|
var imageLocation: ImageLocation? = null
|
||||||
var streamingWrite: Boolean = false
|
var streamingWrite: Boolean = false
|
||||||
var imageFile: Uri? = null
|
var imageFile: Uri? = null
|
||||||
|
|
||||||
|
var usbDevice: UsbDevice? = null
|
||||||
|
var usbMassStorageDevice: UsbMassStorageDevice? = null
|
||||||
}
|
}
|
9
app/src/main/java/eu/depau/ddroid/abc/ClickListener.kt
Normal file
9
app/src/main/java/eu/depau/ddroid/abc/ClickListener.kt
Normal file
|
@ -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)
|
||||||
|
}
|
99
app/src/main/java/eu/depau/ddroid/abc/UsbWriteService.kt
Normal file
99
app/src/main/java/eu/depau/ddroid/abc/UsbWriteService.kt
Normal file
|
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,10 +4,13 @@ import android.content.Intent
|
||||||
import android.support.v4.app.Fragment
|
import android.support.v4.app.Fragment
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
|
||||||
abstract class WizardFragment : Fragment() {
|
abstract class WizardFragment() : Fragment() {
|
||||||
abstract fun nextStep(view: View)
|
private lateinit var wizardActivity: WizardActivity
|
||||||
|
|
||||||
|
abstract fun nextStep(view: View?)
|
||||||
|
|
||||||
open fun onFragmentAdded(activity: WizardActivity) {}
|
open fun onFragmentAdded(activity: WizardActivity) {}
|
||||||
|
open fun onFragmentRemoving(activity: WizardActivity) {}
|
||||||
open fun onRadioButtonClicked(view: View) {}
|
open fun onRadioButtonClicked(view: View) {}
|
||||||
open fun onCheckBoxClicked(view: View) {}
|
open fun onCheckBoxClicked(view: View) {}
|
||||||
open override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
|
open override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
|
||||||
|
|
|
@ -1,15 +1,49 @@
|
||||||
package eu.depau.ddroid.activities
|
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.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import eu.depau.ddroid.R
|
import eu.depau.ddroid.R
|
||||||
|
import eu.depau.ddroid.StateKeeper
|
||||||
import eu.depau.ddroid.abc.WizardActivity
|
import eu.depau.ddroid.abc.WizardActivity
|
||||||
import eu.depau.ddroid.abc.WizardFragment
|
import eu.depau.ddroid.abc.WizardFragment
|
||||||
import eu.depau.ddroid.fragments.FlashMethodFragment
|
import eu.depau.ddroid.fragments.FlashMethodFragment
|
||||||
|
import eu.depau.ddroid.fragments.UsbDriveFragment
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
|
|
||||||
class MainActivity : WizardActivity() {
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
@ -22,14 +56,22 @@ class MainActivity : WizardActivity() {
|
||||||
transaction.replace(R.id.fragment_layout, fragment)
|
transaction.replace(R.id.fragment_layout, fragment)
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
fragment.onFragmentAdded(this)
|
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) {
|
override fun goToNewFragment(fragment: WizardFragment) {
|
||||||
|
StateKeeper.currentFragment?.onFragmentRemoving(this)
|
||||||
|
|
||||||
val transaction = supportFragmentManager.beginTransaction()
|
val transaction = supportFragmentManager.beginTransaction()
|
||||||
transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left)
|
transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left)
|
||||||
transaction.replace(R.id.fragment_layout, fragment)
|
transaction.replace(R.id.fragment_layout, fragment)
|
||||||
transaction.addToBackStack(null)
|
transaction.addToBackStack(null)
|
||||||
transaction.commit()
|
transaction.commit()
|
||||||
|
|
||||||
fragment.onFragmentAdded(this)
|
fragment.onFragmentAdded(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package eu.depau.ddroid.fragments
|
package eu.depau.ddroid.fragments
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.design.widget.Snackbar
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -9,6 +8,7 @@ import eu.depau.ddroid.R
|
||||||
import eu.depau.ddroid.StateKeeper
|
import eu.depau.ddroid.StateKeeper
|
||||||
import eu.depau.ddroid.abc.WizardActivity
|
import eu.depau.ddroid.abc.WizardActivity
|
||||||
import eu.depau.ddroid.abc.WizardFragment
|
import eu.depau.ddroid.abc.WizardFragment
|
||||||
|
import eu.depau.ddroid.utils.snackbar
|
||||||
import eu.depau.ddroid.values.FlashMethod
|
import eu.depau.ddroid.values.FlashMethod
|
||||||
import eu.depau.ddroid.values.WizardStep
|
import eu.depau.ddroid.values.WizardStep
|
||||||
|
|
||||||
|
@ -16,9 +16,9 @@ import eu.depau.ddroid.values.WizardStep
|
||||||
* A placeholder fragment containing a simple view.
|
* A placeholder fragment containing a simple view.
|
||||||
*/
|
*/
|
||||||
class FlashMethodFragment : WizardFragment() {
|
class FlashMethodFragment : WizardFragment() {
|
||||||
override fun nextStep(view: View) {
|
override fun nextStep(view: View?) {
|
||||||
if (StateKeeper.flashMethod == null)
|
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
|
else
|
||||||
(activity as WizardActivity).goToNewFragment(ImageLocationFragment())
|
(activity as WizardActivity).goToNewFragment(ImageLocationFragment())
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.OpenableColumns
|
|
||||||
import android.support.design.widget.Snackbar
|
import android.support.design.widget.Snackbar
|
||||||
import android.support.v4.app.ActivityCompat
|
import android.support.v4.app.ActivityCompat
|
||||||
import android.support.v4.content.ContextCompat
|
import android.support.v4.content.ContextCompat
|
||||||
|
@ -14,16 +13,18 @@ import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
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.R
|
||||||
import eu.depau.ddroid.StateKeeper
|
import eu.depau.ddroid.StateKeeper
|
||||||
import eu.depau.ddroid.abc.WizardActivity
|
import eu.depau.ddroid.abc.WizardActivity
|
||||||
import eu.depau.ddroid.abc.WizardFragment
|
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.FlashMethod
|
||||||
import eu.depau.ddroid.values.ImageLocation
|
import eu.depau.ddroid.values.ImageLocation
|
||||||
import eu.depau.ddroid.values.WizardStep
|
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) {
|
fun setStreamingCheckBoxAvailability(context: WizardActivity) {
|
||||||
val checkBox = context.findViewById<CheckBox>(R.id.streaming_write_checkbox)
|
val checkBox = streaming_write_checkbox
|
||||||
|
|
||||||
if (checkBox == null)
|
if (checkBox == null)
|
||||||
return
|
return
|
||||||
|
@ -71,8 +72,10 @@ class ImageLocationFragment : WizardFragment() {
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
activity?.findViewById<Button>(R.id.pick_file_btn)?.isEnabled = StateKeeper.imageLocation == ImageLocation.LOCAL
|
fab?.show()
|
||||||
activity?.findViewById<EditText>(R.id.img_url_textview)?.isEnabled = StateKeeper.imageLocation == ImageLocation.REMOTE
|
|
||||||
|
pick_file_btn?.isEnabled = StateKeeper.imageLocation == ImageLocation.LOCAL
|
||||||
|
img_url_textview?.isEnabled = StateKeeper.imageLocation == ImageLocation.REMOTE
|
||||||
|
|
||||||
setStreamingCheckBoxAvailability(activity as WizardActivity)
|
setStreamingCheckBoxAvailability(activity as WizardActivity)
|
||||||
updateFileButtonLabel(activity as WizardActivity)
|
updateFileButtonLabel(activity as WizardActivity)
|
||||||
|
@ -87,9 +90,9 @@ class ImageLocationFragment : WizardFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun nextStep(view: View) {
|
override fun nextStep(view: View?) {
|
||||||
if (StateKeeper.imageLocation == null) {
|
if (StateKeeper.imageLocation == null) {
|
||||||
Snackbar.make(view, getString(R.string.select_image_location), Snackbar.LENGTH_LONG).show()
|
view?.snackbar(getString(R.string.select_image_location))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,13 +101,13 @@ class ImageLocationFragment : WizardFragment() {
|
||||||
StateKeeper.imageFile = getRemoteImageUri(activity as WizardActivity)
|
StateKeeper.imageFile = getRemoteImageUri(activity as WizardActivity)
|
||||||
} catch (e: RuntimeException) {
|
} catch (e: RuntimeException) {
|
||||||
Log.e(TAG, "Invalid URI specified", e)
|
Log.e(TAG, "Invalid URI specified", e)
|
||||||
Snackbar.make(view, getString(R.string.provided_url_invalid), Snackbar.LENGTH_LONG).show()
|
view?.snackbar(getString(R.string.provided_url_invalid))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StateKeeper.imageFile == null) {
|
if (StateKeeper.imageFile == null) {
|
||||||
Snackbar.make(view, getString(R.string.provide_image_file), Snackbar.LENGTH_LONG).show()
|
view?.snackbar(getString(R.string.provide_image_file))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +125,7 @@ class ImageLocationFragment : WizardFragment() {
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
(activity as WizardActivity).goToNewFragment(USBDriveFragment())
|
(activity as WizardActivity).goToNewFragment(UsbDriveFragment())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
|
@ -130,9 +133,9 @@ class ImageLocationFragment : WizardFragment() {
|
||||||
MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE -> {
|
MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE -> {
|
||||||
// If request is cancelled, the result arrays are empty.
|
// If request is cancelled, the result arrays are empty.
|
||||||
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
|
||||||
nextStep(activity!!.findViewById(R.id.fragment_layout))
|
nextStep(fragment_layout)
|
||||||
} else {
|
} else {
|
||||||
Snackbar.make(activity!!.findViewById(R.id.fragment_layout), getString(R.string.storage_perm_required_explaination), Snackbar.LENGTH_LONG).show()
|
Snackbar.make(fragment_layout, getString(R.string.storage_perm_required_explaination), Snackbar.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -157,31 +160,20 @@ class ImageLocationFragment : WizardFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRemoteImageUri(context: WizardActivity): Uri {
|
fun getRemoteImageUri(context: WizardActivity): Uri {
|
||||||
val text = context.findViewById<EditText>(R.id.img_url_textview).text.toString()
|
val text = img_url_textview.text.toString()
|
||||||
return Uri.parse(text)
|
return Uri.parse(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateFileButtonLabel(context: WizardActivity) {
|
fun updateFileButtonLabel(context: WizardActivity) {
|
||||||
val button = context.findViewById<Button>(R.id.pick_file_btn)
|
val button = pick_file_btn
|
||||||
val uri = StateKeeper.imageFile
|
val uri = StateKeeper.imageFile
|
||||||
|
|
||||||
if (uri != null && uri.scheme != null && !uri.scheme!!.startsWith("http")) {
|
val text = uri?.getFileName(context)
|
||||||
val cursor = context.contentResolver.query(uri, null, null, null, null, null)
|
|
||||||
|
|
||||||
try {
|
if (text != null)
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
button.text = text
|
||||||
button.text = cursor.getString(
|
else
|
||||||
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
|
button.text = getString(R.string.pick_a_file)
|
||||||
}
|
|
||||||
return
|
|
||||||
} catch (e: RuntimeException) {
|
|
||||||
Log.e(TAG, "Error retrieving file name", e)
|
|
||||||
} finally {
|
|
||||||
cursor?.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button.text = getString(R.string.pick_a_file)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
package eu.depau.ddroid.fragments
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.support.annotation.RequiresApi
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import com.github.mjdev.libaums.UsbMassStorageDevice
|
|
||||||
import eu.depau.ddroid.R
|
|
||||||
import eu.depau.ddroid.StateKeeper
|
|
||||||
import eu.depau.ddroid.abc.WizardFragment
|
|
||||||
import eu.depau.ddroid.values.WizardStep
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A placeholder fragment containing a simple view.
|
|
||||||
*/
|
|
||||||
class USBDriveFragment : WizardFragment() {
|
|
||||||
val TAG = "USBDriveFragment"
|
|
||||||
val ACTION_USB_PERMISSION = "eu.depau.ddroid.USB_PERMISSION"
|
|
||||||
|
|
||||||
|
|
||||||
override fun nextStep(view: View) {
|
|
||||||
// if (StateKeeper.flashMethod == null)
|
|
||||||
// Snackbar.make(view, "Please select writing method", Snackbar.LENGTH_LONG).show()
|
|
||||||
// else
|
|
||||||
// (activity as WizardActivity).goToNewFragment(ImageLocationFragment())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?): View? {
|
|
||||||
StateKeeper.currentFragment = this
|
|
||||||
StateKeeper.wizardStep = WizardStep.SELECT_USB_DRIVE
|
|
||||||
|
|
||||||
return inflater.inflate(R.layout.fragment_select_usb_drive, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
override fun onButtonClicked(view: View) {
|
|
||||||
// val usbManager = activity!!.getSystemService(Context.USB_SERVICE) as UsbManager
|
|
||||||
|
|
||||||
val devices = UsbMassStorageDevice.getMassStorageDevices(activity)
|
|
||||||
|
|
||||||
for (device in devices) {
|
|
||||||
Log.d(TAG, """USB device ${device.usbDevice.deviceName}
|
|
||||||
| proto ${device.usbDevice.deviceProtocol}
|
|
||||||
| id ${device.usbDevice.deviceId}
|
|
||||||
| class ${device.usbDevice.deviceClass}
|
|
||||||
| subclass ${device.usbDevice.deviceSubclass}
|
|
||||||
| iface count ${device.usbDevice.interfaceCount}
|
|
||||||
| manuf name ${device.usbDevice.manufacturerName}
|
|
||||||
| product name ${device.usbDevice.productName}
|
|
||||||
| id ${device.usbDevice.vendorId}:${device.usbDevice.productId}
|
|
||||||
""".trimMargin())
|
|
||||||
// val permissionIntent = PendingIntent.getBroadcast(activity, 0, Intent(MY_PERMISSIONS_REQUEST_USB_DRIVE), 0)
|
|
||||||
// usbManager.requestPermission(device.usbDevice, permissionIntent)
|
|
||||||
// // before interacting with a device you need to call init()!
|
|
||||||
// device.init()
|
|
||||||
//
|
|
||||||
// // Only uses the first partition on the device
|
|
||||||
// val currentFs = device.partitions[0].fileSystem
|
|
||||||
// Log.d(TAG, "Capacity: " + currentFs.capacity)
|
|
||||||
// Log.d(TAG, "Occupied Space: " + currentFs.occupiedSpace)
|
|
||||||
// Log.d(TAG, "Free Space: " + currentFs.freeSpace)
|
|
||||||
// Log.d(TAG, "Chunk size: " + currentFs.chunkSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
171
app/src/main/java/eu/depau/ddroid/fragments/UsbDriveFragment.kt
Normal file
171
app/src/main/java/eu/depau/ddroid/fragments/UsbDriveFragment.kt
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
package eu.depau.ddroid.fragments
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.hardware.usb.UsbDevice
|
||||||
|
import android.hardware.usb.UsbManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.design.widget.Snackbar
|
||||||
|
import android.support.v4.widget.SwipeRefreshLayout
|
||||||
|
import android.support.v7.widget.LinearLayoutManager
|
||||||
|
import android.support.v7.widget.RecyclerView
|
||||||
|
import android.view.*
|
||||||
|
import com.github.mjdev.libaums.UsbMassStorageDevice
|
||||||
|
import eu.depau.ddroid.R
|
||||||
|
import eu.depau.ddroid.StateKeeper
|
||||||
|
import eu.depau.ddroid.abc.ClickListener
|
||||||
|
import eu.depau.ddroid.abc.WizardActivity
|
||||||
|
import eu.depau.ddroid.abc.WizardFragment
|
||||||
|
import eu.depau.ddroid.activities.MainActivity
|
||||||
|
import eu.depau.ddroid.utils.UsbDrivesRecyclerViewAdapter
|
||||||
|
import eu.depau.ddroid.utils.name
|
||||||
|
import eu.depau.ddroid.utils.snackbar
|
||||||
|
import eu.depau.ddroid.values.WizardStep
|
||||||
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
|
import kotlinx.android.synthetic.main.fragment_select_usb_drive.view.*
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A placeholder fragment containing a simple view.
|
||||||
|
*/
|
||||||
|
class UsbDriveFragment : WizardFragment(), SwipeRefreshLayout.OnRefreshListener {
|
||||||
|
val TAG = "UsbDriveFragment"
|
||||||
|
|
||||||
|
private lateinit var recyclerView: RecyclerView
|
||||||
|
private lateinit var viewAdapter: UsbDrivesRecyclerViewAdapter
|
||||||
|
private lateinit var viewManager: RecyclerView.LayoutManager
|
||||||
|
private lateinit var refreshLayout: SwipeRefreshLayout
|
||||||
|
|
||||||
|
|
||||||
|
class RecyclerViewTouchListener(context: Context, val recyclerView: RecyclerView, val clickListener: ClickListener) : RecyclerView.OnItemTouchListener {
|
||||||
|
private var gestureDetector: GestureDetector
|
||||||
|
|
||||||
|
init {
|
||||||
|
gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
|
||||||
|
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLongPress(e: MotionEvent) {
|
||||||
|
val child = recyclerView.findChildViewUnder(e.x, e.y)
|
||||||
|
if (child != null)
|
||||||
|
clickListener.onLongClick(child, recyclerView.getChildAdapterPosition(child))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {}
|
||||||
|
|
||||||
|
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
|
||||||
|
val child = rv.findChildViewUnder(e.x, e.y)
|
||||||
|
if (child != null && gestureDetector.onTouchEvent(e)) {
|
||||||
|
clickListener.onClick(child, rv.getChildAdapterPosition(child))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onRefresh() {
|
||||||
|
loadUsbDevices()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun nextStep(view: View?) {
|
||||||
|
(activity as WizardActivity).goToNewFragment(ConfirmInfoFragment())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?): View? {
|
||||||
|
StateKeeper.currentFragment = this
|
||||||
|
StateKeeper.wizardStep = WizardStep.SELECT_USB_DRIVE
|
||||||
|
|
||||||
|
val view = inflater.inflate(R.layout.fragment_select_usb_drive, container, false)
|
||||||
|
|
||||||
|
refreshLayout = view.usbdevs_refresh_layout
|
||||||
|
refreshLayout.setOnRefreshListener(this)
|
||||||
|
refreshLayout.post {
|
||||||
|
refreshLayout.isRefreshing = true
|
||||||
|
loadUsbDevices()
|
||||||
|
}
|
||||||
|
|
||||||
|
viewManager = LinearLayoutManager(activity)
|
||||||
|
recyclerView = view.usbdevs_recycler_view
|
||||||
|
|
||||||
|
recyclerView.addOnItemTouchListener(RecyclerViewTouchListener(activity!!, recyclerView, object : ClickListener {
|
||||||
|
override fun onClick(view: View, position: Int) {
|
||||||
|
val device = viewAdapter.get(position)
|
||||||
|
val manager = activity!!.getSystemService(Context.USB_SERVICE) as UsbManager
|
||||||
|
manager.requestPermission(device.usbDevice, (activity as MainActivity).mUsbPermissionIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLongClick(view: View, position: Int) {}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadUsbDevices() {
|
||||||
|
try {
|
||||||
|
viewAdapter = UsbDrivesRecyclerViewAdapter(UsbMassStorageDevice.getMassStorageDevices(activity))
|
||||||
|
|
||||||
|
recyclerView.apply {
|
||||||
|
setHasFixedSize(true)
|
||||||
|
layoutManager = viewManager
|
||||||
|
adapter = viewAdapter
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
refreshLayout.isRefreshing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFragmentAdded(activity: WizardActivity) {
|
||||||
|
activity.fab.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFragmentRemoving(activity: WizardActivity) {
|
||||||
|
activity.fab.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||||
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
|
menuInflater.inflate(R.menu.usb_devices_menu, menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
// Handle action bar item clicks here. The action bar will
|
||||||
|
// automatically handle clicks on the Home/Up button, so long
|
||||||
|
// as you specify a parent activity in AndroidManifest.xml.
|
||||||
|
return when (item.itemId) {
|
||||||
|
R.id.action_refresh -> {
|
||||||
|
loadUsbDevices()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onUsbPermissionResult(usbDevice: UsbDevice?, granted: Boolean) {
|
||||||
|
if (!granted) {
|
||||||
|
if (usbDevice != null) {
|
||||||
|
recyclerView.snackbar(getString(R.string.usb_perm_denied) + usbDevice.name)
|
||||||
|
} else {
|
||||||
|
recyclerView.snackbar(getString(R.string.usb_perm_denied_noname))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
StateKeeper.usbDevice = usbDevice
|
||||||
|
StateKeeper.usbMassStorageDevice = UsbMassStorageDevice.getMassStorageDevices(activity).find { it.usbDevice == usbDevice }
|
||||||
|
|
||||||
|
nextStep(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
package eu.depau.ddroid.services
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.hardware.usb.UsbDevice
|
||||||
|
import android.net.Uri
|
||||||
|
import com.github.mjdev.libaums.UsbMassStorageDevice
|
||||||
|
import eu.depau.ddroid.abc.UsbWriteService
|
||||||
|
import eu.depau.ddroid.utils.getFileSize
|
||||||
|
import eu.depau.ddroid.utils.name
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
class UsbAPIWriteService : UsbWriteService("UsbAPIWriteService") {
|
||||||
|
class Action {
|
||||||
|
val WRITE_IMAGE = "eu.depau.ddroid.action.API_WRITE_IMAGE"
|
||||||
|
val WRITE_CANCEL = "eu.depau.ddroid.action.API_WRITE_CANCEL"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onHandleIntent(intent: Intent?) {
|
||||||
|
val uri: Uri = intent!!.data!!
|
||||||
|
val usbDevice: UsbDevice = intent.getParcelableExtra("usbDevice")
|
||||||
|
|
||||||
|
startForeground(FOREGROUND_ID, buildForegroundNotification(usbDevice.name, uri, -1, -1))
|
||||||
|
|
||||||
|
try {
|
||||||
|
val notify = { bytes: Long, total: Long -> updateNotification(usbDevice.name, uri, bytes, total) }
|
||||||
|
writeImage(usbDevice, uri, notify)
|
||||||
|
} finally {
|
||||||
|
stopForeground(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getUsbMSDevice(usbDevice: UsbDevice): UsbMassStorageDevice? {
|
||||||
|
val msDevs = UsbMassStorageDevice.getMassStorageDevices(this)
|
||||||
|
|
||||||
|
for (dev in msDevs) {
|
||||||
|
if (dev.usbDevice == usbDevice)
|
||||||
|
return dev
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeImage(usbDevice: UsbDevice, uri: Uri, notify: (Long, Long) -> Unit): Long {
|
||||||
|
val msDev = getUsbMSDevice(usbDevice)!!
|
||||||
|
msDev.init()
|
||||||
|
|
||||||
|
val blockDev = msDev.blockDevice
|
||||||
|
val byteBuffer = ByteBuffer.allocate(blockDev.blockSize)
|
||||||
|
val imageSize = uri.getFileSize(this)
|
||||||
|
val inputStream = contentResolver.openInputStream(uri)!!
|
||||||
|
|
||||||
|
var readBytes: Int
|
||||||
|
var offset = 0L
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
readBytes = inputStream.read(byteBuffer.array()!!)
|
||||||
|
if (readBytes < 0)
|
||||||
|
break
|
||||||
|
|
||||||
|
byteBuffer.position(readBytes)
|
||||||
|
blockDev.write(offset, byteBuffer)
|
||||||
|
offset++
|
||||||
|
|
||||||
|
notify(offset * blockDev.blockSize, imageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
msDev.close()
|
||||||
|
|
||||||
|
return offset * blockDev.blockSize
|
||||||
|
}
|
||||||
|
}
|
8
app/src/main/java/eu/depau/ddroid/utils/ContextToast.kt
Normal file
8
app/src/main/java/eu/depau/ddroid/utils/ContextToast.kt
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package eu.depau.ddroid.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.widget.Toast
|
||||||
|
|
||||||
|
fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_LONG) {
|
||||||
|
Toast.makeText(this, message, duration).show()
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package eu.depau.ddroid.utils
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/3758880/1124621
|
||||||
|
|
||||||
|
private fun <T> humanReadableByteCount(bytes: T, si: Boolean = false): String where T : Comparable<T>, T : Number {
|
||||||
|
val unit: Long = if (si) 1000 else 1024
|
||||||
|
if (bytes.toLong() < unit) return bytes.toString() + " B"
|
||||||
|
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 = false) = humanReadableByteCount(this, si)
|
||||||
|
fun Float.toHRSize(si: Boolean = false) = humanReadableByteCount(this, si)
|
||||||
|
fun Double.toHRSize(si: Boolean = false) = humanReadableByteCount(this, si)
|
||||||
|
fun Int.toHRSize(si: Boolean = false) = humanReadableByteCount(this, si)
|
||||||
|
fun Byte.toHRSize(si: Boolean = false) = humanReadableByteCount(this, si)
|
||||||
|
fun Short.toHRSize(si: Boolean = false) = humanReadableByteCount(this, si)
|
29
app/src/main/java/eu/depau/ddroid/utils/UriGetDisplayName.kt
Normal file
29
app/src/main/java/eu/depau/ddroid/utils/UriGetDisplayName.kt
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package eu.depau.ddroid.utils
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
39
app/src/main/java/eu/depau/ddroid/utils/UriGetFileSize.kt
Normal file
39
app/src/main/java/eu/depau/ddroid/utils/UriGetFileSize.kt
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package eu.depau.ddroid.utils
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package eu.depau.ddroid.utils
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package eu.depau.ddroid.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Build
|
||||||
|
import android.support.v7.widget.RecyclerView
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.RelativeLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.github.mjdev.libaums.UsbMassStorageDevice
|
||||||
|
import eu.depau.ddroid.R
|
||||||
|
import kotlinx.android.synthetic.main.usb_device_row.view.*
|
||||||
|
import java.lang.Integer
|
||||||
|
|
||||||
|
class UsbDrivesRecyclerViewAdapter(private val dataset: Array<UsbMassStorageDevice>) : RecyclerView.Adapter<UsbDrivesRecyclerViewAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
class ViewHolder(val relLayout: RelativeLayout) : RecyclerView.ViewHolder(relLayout)
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
|
||||||
|
UsbDrivesRecyclerViewAdapter.ViewHolder {
|
||||||
|
|
||||||
|
val relLayout = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.usb_device_row, parent, false) as RelativeLayout
|
||||||
|
return ViewHolder(relLayout)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val usbDevice = dataset[position].usbDevice
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
holder.relLayout.name.text = "${usbDevice.manufacturerName} ${usbDevice.productName}"
|
||||||
|
holder.relLayout.devpath.text = usbDevice.deviceName
|
||||||
|
holder.relLayout.vidpid.text = usbDevice.vidpid
|
||||||
|
} else {
|
||||||
|
holder.relLayout.name.text = usbDevice.deviceName
|
||||||
|
holder.relLayout.devpath.text = usbDevice.vidpid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = dataset.size
|
||||||
|
|
||||||
|
|
||||||
|
fun get(position: Int): UsbMassStorageDevice {
|
||||||
|
return dataset[position]
|
||||||
|
}
|
||||||
|
}
|
8
app/src/main/java/eu/depau/ddroid/utils/ViewSnackbar.kt
Normal file
8
app/src/main/java/eu/depau/ddroid/utils/ViewSnackbar.kt
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package eu.depau.ddroid.utils
|
||||||
|
|
||||||
|
import android.support.design.widget.Snackbar
|
||||||
|
import android.view.View
|
||||||
|
|
||||||
|
fun View.snackbar(message: CharSequence, duration: Int = Snackbar.LENGTH_LONG) {
|
||||||
|
Snackbar.make(this, message, duration).show()
|
||||||
|
}
|
|
@ -3,5 +3,6 @@ package eu.depau.ddroid.values
|
||||||
enum class WizardStep {
|
enum class WizardStep {
|
||||||
SELECT_FLASH_METHOD,
|
SELECT_FLASH_METHOD,
|
||||||
SELECT_LOCATION,
|
SELECT_LOCATION,
|
||||||
SELECT_USB_DRIVE
|
SELECT_USB_DRIVE,
|
||||||
|
CONFIRM
|
||||||
}
|
}
|
5
app/src/main/res/drawable/ic_usb_white_24dp.xml
Normal file
5
app/src/main/res/drawable/ic_usb_white_24dp.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M15,7v4h1v2h-3V5h2l-3,-4 -3,4h2v8H8v-2.07c0.7,-0.37 1.2,-1.08 1.2,-1.93 0,-1.21 -0.99,-2.2 -2.2,-2.2 -1.21,0 -2.2,0.99 -2.2,2.2 0,0.85 0.5,1.56 1.2,1.93V13c0,1.11 0.89,2 2,2h3v3.05c-0.71,0.37 -1.2,1.1 -1.2,1.95 0,1.22 0.99,2.2 2.2,2.2 1.21,0 2.2,-0.98 2.2,-2.2 0,-0.85 -0.49,-1.58 -1.2,-1.95V15h3c1.11,0 2,-0.89 2,-2v-2h1V7h-4z"/>
|
||||||
|
</vector>
|
145
app/src/main/res/layout/fragment_confirminfo.xml
Normal file
145
app/src/main/res/layout/fragment_confirminfo.xml
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:context=".fragments.ConfirmInfoFragment">
|
||||||
|
<!--tools:showIn="@layout/activity_main"-->
|
||||||
|
|
||||||
|
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="@dimen/row_padding_vertical"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/row_padding_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/confirm_sel_method_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:textColor="@color/name"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:text="@string/selected_method"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/confirm_sel_method"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/confirm_sel_method_title"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="@dimen/row_padding_vertical"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/row_padding_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/confirm_sel_image_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:textColor="@color/name"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:text="@string/selected_image"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/confirm_sel_image"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/confirm_sel_image_title"/>
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/confirm_sel_image_size"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:textColor="@color/info"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="@dimen/row_padding_vertical"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/row_padding_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/confirm_sel_usbdev_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:textColor="@color/name"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:text="@string/selected_usbdev"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/confirm_sel_usbdev"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/confirm_sel_usbdev_title"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/confirm_sel_usbdev_size"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:textColor="@color/info"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="@dimen/row_padding_vertical"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/row_padding_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/confirm_extra_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:textColor="@color/name"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:ellipsize="end"/>
|
||||||
|
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -24,6 +24,7 @@
|
||||||
android:id="@+id/flash_dd_root_radio"
|
android:id="@+id/flash_dd_root_radio"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:enabled="false"
|
||||||
android:text="@string/flash_dd_root"
|
android:text="@string/flash_dd_root"
|
||||||
android:onClick="onRadioButtonClicked"/>
|
android:onClick="onRadioButtonClicked"/>
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@
|
||||||
android:id="@+id/flash_unetbootin_radio"
|
android:id="@+id/flash_unetbootin_radio"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:enabled="false"
|
||||||
android:text="@string/flash_unetbootin"
|
android:text="@string/flash_unetbootin"
|
||||||
android:onClick="onRadioButtonClicked"/>
|
android:onClick="onRadioButtonClicked"/>
|
||||||
|
|
||||||
|
@ -38,6 +40,7 @@
|
||||||
android:id="@+id/flash_woeusb_radio"
|
android:id="@+id/flash_woeusb_radio"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:enabled="false"
|
||||||
android:text="@string/flash_woeusb"
|
android:text="@string/flash_woeusb"
|
||||||
android:onClick="onRadioButtonClicked"/>
|
android:onClick="onRadioButtonClicked"/>
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
android:id="@+id/download_img_radio"
|
android:id="@+id/download_img_radio"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:enabled="false"
|
||||||
android:onClick="onRadioButtonClicked"
|
android:onClick="onRadioButtonClicked"
|
||||||
android:text="@string/download_image_from_url"/>
|
android:text="@string/download_image_from_url"/>
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,16 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
|
||||||
|
<android.support.v4.widget.SwipeRefreshLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
android:id="@+id/usbdevs_refresh_layout"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical"
|
|
||||||
tools:context=".fragments.USBDriveFragment"
|
|
||||||
tools:showIn="@layout/activity_main">
|
|
||||||
|
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/usbdevs_recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scrollbars="vertical"/>
|
||||||
|
|
||||||
|
</android.support.v4.widget.SwipeRefreshLayout>
|
||||||
<Button
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:onClick="onButtonClicked"
|
|
||||||
android:text="Do USB stuff"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
37
app/src/main/res/layout/usb_device_row.xml
Normal file
37
app/src/main/res/layout/usb_device_row.xml
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="@dimen/row_padding_vertical"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/row_padding_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:textColor="@color/name"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:ellipsize="end"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/devpath"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/name"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/vidpid"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:textColor="@color/info"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
10
app/src/main/res/menu/usb_devices_menu.xml
Normal file
10
app/src/main/res/menu/usb_devices_menu.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:context="eu.depau.ddroid.fragments.UsbDriveFragment">
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_refresh"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
android:title="@string/action_refresh"
|
||||||
|
app:showAsAction="never"/>
|
||||||
|
</menu>
|
|
@ -3,4 +3,6 @@
|
||||||
<color name="colorPrimary">#3F51B5</color>
|
<color name="colorPrimary">#3F51B5</color>
|
||||||
<color name="colorPrimaryDark">#303F9F</color>
|
<color name="colorPrimaryDark">#303F9F</color>
|
||||||
<color name="colorAccent">#FF4081</color>
|
<color name="colorAccent">#FF4081</color>
|
||||||
|
<color name="info">#999999</color>
|
||||||
|
<color name="name">#222222</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
<resources>
|
<resources>
|
||||||
<dimen name="fab_margin">16dp</dimen>
|
<dimen name="fab_margin">16dp</dimen>
|
||||||
|
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||||
|
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||||
|
<dimen name="row_padding_vertical">10dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -14,4 +14,15 @@
|
||||||
<string name="select_image_location">Please select image location</string>
|
<string name="select_image_location">Please select image location</string>
|
||||||
<string name="provide_image_file">Please provide an image file</string>
|
<string name="provide_image_file">Please provide an image file</string>
|
||||||
<string name="storage_perm_required_explaination">Storage permission required to download images</string>
|
<string name="storage_perm_required_explaination">Storage permission required to download images</string>
|
||||||
|
<string name="action_refresh">Refresh</string>
|
||||||
|
<string name="usb_perm_denied">Permission denied for</string>
|
||||||
|
<string name="usb_perm_denied_noname">Permission denied for USB device</string>
|
||||||
|
<string name="selected_image">Selected image:</string>
|
||||||
|
<string name="selected_method">Selected writing method:</string>
|
||||||
|
<string name="selected_usbdev">Selected USB device:</string>
|
||||||
|
<string name="unknown_filename">Unknown filename</string>
|
||||||
|
<string name="please_select_writing_method">Please select writing method</string>
|
||||||
|
<string name="image_bigger_than_usb">Image is bigger than the USB drive, so it can\'t be written</string>
|
||||||
|
<string name="cant_read_usbdev">Cannot read USB device</string>
|
||||||
|
<string name="tap_next_to_write">Tap Next to write the image to the USB drive</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -32,6 +32,7 @@ task clean(type: Delete) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// For libaums
|
||||||
ext {
|
ext {
|
||||||
bintrayRepo = 'maven'
|
bintrayRepo = 'maven'
|
||||||
|
|
||||||
|
|
2
libaums
2
libaums
|
@ -1 +1 @@
|
||||||
Subproject commit a57ae278535946db69e2e1ea7f02171d002198a5
|
Subproject commit 310bc4680581c845ab2f30474eee02b7b238669f
|
Loading…
Reference in a new issue