From 53dc07490c87214a29cca044f47f81a25eeb9f1a Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Sat, 1 Sep 2018 03:06:06 +0200 Subject: [PATCH] Complete app redesign - Switched to AndroidX namespace - Switched from Fragments to Activities - Automatically detects USB drives on (dis)connection - UX is simpler, less taps are needed --- app/build.gradle | 18 +- .../etchdroid/ExampleInstrumentedTest.kt | 4 +- app/src/main/AndroidManifest.xml | 37 ++- .../java/eu/depau/etchdroid/StateKeeper.kt | 7 - .../etchdroid/activities/ActivityBase.kt | 30 +++ .../activities/ConfirmationActivity.kt | 150 ++++++++++++ .../etchdroid/activities/LicensesActivity.kt | 9 +- .../etchdroid/activities/MainActivity.kt | 102 -------- .../etchdroid/activities/StartActivity.kt | 128 ++++++++++ .../activities/UsbDrivePickerActivity.kt | 150 ++++++++++++ .../etchdroid/activities/WizardActivity.kt | 35 --- .../adapters/LicenseRecyclerViewAdapter.kt | 2 +- .../PartitionTableRecyclerViewAdapter.kt | 2 +- .../adapters/UsbDrivesRecyclerViewAdapter.kt | 2 +- .../fragments/ConfirmInfoFragment.kt | 115 --------- .../fragments/FlashMethodFragment.kt | 42 ---- .../fragments/ImageLocationFragment.kt | 211 ----------------- .../etchdroid/fragments/UsbDriveFragment.kt | 139 ----------- .../etchdroid/fragments/WizardFragment.kt | 20 -- .../etchdroid/kotlin_exts/ViewSnackbar.kt | 2 +- .../etchdroid/services/UsbWriteService.kt | 2 +- .../etchdroid/utils/EmptyRecyclerView.kt | 42 ++++ .../utils/RecyclerViewTouchListener.kt | 2 +- app/src/main/res/drawable/ic_beta.xml | 34 +++ app/src/main/res/drawable/ic_dmg.xml | 16 ++ app/src/main/res/drawable/ic_raw.xml | 13 ++ .../res/drawable/ic_twotone_save_alt_24px.xml | 9 + .../main/res/drawable/ic_usb_black_200dp.xml | 5 + .../main/res/layout/activity_confirmation.xml | 219 ++++++++++++++++++ app/src/main/res/layout/activity_licenses.xml | 6 +- app/src/main/res/layout/activity_main.xml | 34 --- app/src/main/res/layout/activity_start.xml | 151 ++++++++++++ .../res/layout/activity_usb_drive_picker.xml | 42 ++++ .../main/res/layout/fragment_confirminfo.xml | 144 ------------ .../layout/fragment_select_flash_method.xml | 46 ---- .../res/layout/fragment_select_location.xml | 59 ----- .../res/layout/fragment_select_usb_drive.xml | 16 -- app/src/main/res/menu/menu_main.xml | 2 +- app/src/main/res/values/colors.xml | 6 +- app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/strings.xml | 13 +- app/src/main/res/values/styles.xml | 12 +- build.gradle | 4 +- gradle.properties | 2 + gradle/wrapper/gradle-wrapper.properties | 4 +- 45 files changed, 1072 insertions(+), 1017 deletions(-) create mode 100644 app/src/main/java/eu/depau/etchdroid/activities/ActivityBase.kt create mode 100644 app/src/main/java/eu/depau/etchdroid/activities/ConfirmationActivity.kt delete mode 100644 app/src/main/java/eu/depau/etchdroid/activities/MainActivity.kt create mode 100644 app/src/main/java/eu/depau/etchdroid/activities/StartActivity.kt create mode 100644 app/src/main/java/eu/depau/etchdroid/activities/UsbDrivePickerActivity.kt delete mode 100644 app/src/main/java/eu/depau/etchdroid/activities/WizardActivity.kt delete mode 100644 app/src/main/java/eu/depau/etchdroid/fragments/ConfirmInfoFragment.kt delete mode 100644 app/src/main/java/eu/depau/etchdroid/fragments/FlashMethodFragment.kt delete mode 100644 app/src/main/java/eu/depau/etchdroid/fragments/ImageLocationFragment.kt delete mode 100644 app/src/main/java/eu/depau/etchdroid/fragments/UsbDriveFragment.kt delete mode 100644 app/src/main/java/eu/depau/etchdroid/fragments/WizardFragment.kt create mode 100644 app/src/main/java/eu/depau/etchdroid/utils/EmptyRecyclerView.kt create mode 100644 app/src/main/res/drawable/ic_beta.xml create mode 100644 app/src/main/res/drawable/ic_dmg.xml create mode 100644 app/src/main/res/drawable/ic_raw.xml create mode 100644 app/src/main/res/drawable/ic_twotone_save_alt_24px.xml create mode 100644 app/src/main/res/drawable/ic_usb_black_200dp.xml create mode 100644 app/src/main/res/layout/activity_confirmation.xml delete mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/activity_start.xml create mode 100644 app/src/main/res/layout/activity_usb_drive_picker.xml delete mode 100644 app/src/main/res/layout/fragment_confirminfo.xml delete mode 100644 app/src/main/res/layout/fragment_select_flash_method.xml delete mode 100644 app/src/main/res/layout/fragment_select_location.xml delete mode 100644 app/src/main/res/layout/fragment_select_usb_drive.xml diff --git a/app/build.gradle b/app/build.gradle index ad7466d..8e08895 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,7 +12,7 @@ android { targetSdkVersion 28 versionCode 1 versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true } buildTypes { @@ -26,11 +26,11 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'com.android.support:appcompat-v7:28.0.0-rc01' - implementation 'com.android.support.constraint:constraint-layout:1.1.2' - implementation 'com.android.support:design:28.0.0-rc01' - implementation 'com.android.support:recyclerview-v7:28.0.0-rc01' - implementation 'com.android.support:gridlayout-v7:28.0.0-rc01' + implementation 'androidx.appcompat:appcompat:1.0.0-rc02' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2' + implementation 'com.google.android.material:material:1.0.0-rc01' + implementation 'androidx.recyclerview:recyclerview:1.0.0-rc02' + implementation 'androidx.gridlayout:gridlayout:1.0.0-rc02' api 'com.google.guava:guava:26.0-android' implementation 'com.github.codekidX:storage-chooser:2.0.4.2' @@ -39,7 +39,9 @@ dependencies { implementation project(':dmg2img') + implementation 'com.android.support.constraint:constraint-layout:1.1.2' + implementation 'com.android.support:design:28.0.0-rc01' testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + androidTestImplementation 'androidx.test:runner:1.1.0-alpha4' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4' } diff --git a/app/src/androidTest/java/eu/depau/etchdroid/ExampleInstrumentedTest.kt b/app/src/androidTest/java/eu/depau/etchdroid/ExampleInstrumentedTest.kt index 61c26bb..802f23a 100644 --- a/app/src/androidTest/java/eu/depau/etchdroid/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/eu/depau/etchdroid/ExampleInstrumentedTest.kt @@ -1,7 +1,7 @@ package eu.depau.etchdroid -import android.support.test.InstrumentationRegistry -import android.support.test.runner.AndroidJUnit4 +import androidx.test.InstrumentationRegistry +import androidx.test.runner.AndroidJUnit4 import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5cce9b4..70a77f6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,9 @@ + @@ -12,17 +14,41 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + tools:ignore="GoogleAppIndexingWarning"> + + android:theme="@style/MaterialAppTheme"> + - + + + + + + + + - - - \ No newline at end of file diff --git a/app/src/main/java/eu/depau/etchdroid/StateKeeper.kt b/app/src/main/java/eu/depau/etchdroid/StateKeeper.kt index 69d85fd..fcb54f8 100644 --- a/app/src/main/java/eu/depau/etchdroid/StateKeeper.kt +++ b/app/src/main/java/eu/depau/etchdroid/StateKeeper.kt @@ -3,18 +3,11 @@ package eu.depau.etchdroid import android.hardware.usb.UsbDevice import android.net.Uri import com.github.mjdev.libaums.UsbMassStorageDevice -import eu.depau.etchdroid.fragments.WizardFragment import eu.depau.etchdroid.enums.FlashMethod -import eu.depau.etchdroid.enums.ImageLocation -import eu.depau.etchdroid.enums.WizardStep import eu.depau.etchdroid.img_types.Image object StateKeeper { - var wizardStep: WizardStep = WizardStep.SELECT_FLASH_METHOD - var currentFragment: WizardFragment? = null var flashMethod: FlashMethod? = null - var imageLocation: ImageLocation? = ImageLocation.LOCAL - var streamingWrite: Boolean = false var imageFile: Uri? = null var imageRepr: Image? = null diff --git a/app/src/main/java/eu/depau/etchdroid/activities/ActivityBase.kt b/app/src/main/java/eu/depau/etchdroid/activities/ActivityBase.kt new file mode 100644 index 0000000..899d3d6 --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/activities/ActivityBase.kt @@ -0,0 +1,30 @@ +package eu.depau.etchdroid.activities + +import android.content.Intent +import android.view.Menu +import android.view.MenuItem +import androidx.appcompat.app.AppCompatActivity +import eu.depau.etchdroid.R + +abstract class ActivityBase: AppCompatActivity() { + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + // Inflate the menu; this adds items to the action bar if it is present. + menuInflater.inflate(R.menu.menu_main, menu) + return true + } + + 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_licenses -> { + val intent = Intent(this, LicensesActivity::class.java) + startActivity(intent) + return true + } + else -> super.onOptionsItemSelected(item) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/depau/etchdroid/activities/ConfirmationActivity.kt b/app/src/main/java/eu/depau/etchdroid/activities/ConfirmationActivity.kt new file mode 100644 index 0000000..9cbfb34 --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/activities/ConfirmationActivity.kt @@ -0,0 +1,150 @@ +package eu.depau.etchdroid.activities + +import android.os.Bundle +import android.app.Activity +import android.content.Intent +import android.os.Build +import android.view.View +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.services.UsbApiDmgWriteService +import eu.depau.etchdroid.services.UsbApiImgWriteService + +import kotlinx.android.synthetic.main.activity_confirmation.* +import java.io.IOException + +class ConfirmationActivity : ActivityBase() { + var canContinue: Boolean = false + var issuesFound: Boolean = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_confirmation) + actionBar?.setDisplayHomeAsUpEnabled(true) + + displayDetails() + displayImageLayout() + } + + fun displayDetails() { + confirm_sel_method.text = when (StateKeeper.flashMethod) { + FlashMethod.FLASH_API -> getString(R.string.flash_dd_usb_api) + FlashMethod.FLASH_DMG_API -> getString(R.string.flash_dmg_api) + FlashMethod.FLASH_UNETBOOTIN -> getString(R.string.flash_unetbootin) + FlashMethod.FLASH_WOEUSB -> getString(R.string.flash_woeusb) + else -> null + } + + confirm_sel_image.text = StateKeeper.imageFile?.getFileName(this) + + if (confirm_sel_image.text == null) + confirm_sel_image.text = getString(R.string.unknown_filename) + + val imgSize = StateKeeper.imageFile?.getFileSize(this) + confirm_sel_image_size.text = imgSize?.toHRSize() + + confirm_sel_usbdev.text = StateKeeper.usbDevice?.name + + for (trial in 0..1) { + try { + StateKeeper.usbMassStorageDevice!!.init() + val blockDev = StateKeeper.usbMassStorageDevice?.blockDevice + + if (blockDev != null) { + val devSize = (blockDev.size.toLong() * blockDev.blockSize.toLong()) + confirm_sel_usbdev_size.text = devSize.toHRSize() + + if (imgSize!! > devSize) + confirm_extra_info.text = getString(R.string.image_bigger_than_usb) + else { + var text = + if (StateKeeper.flashMethod == FlashMethod.FLASH_DMG_API) + getString(R.string.no_image_size_check_dmg) + "\n" + else + "" + text += getString(R.string.tap_next_to_write) + confirm_extra_info.text = text + canContinue = true + } + } else { + confirm_extra_info.text = getString(R.string.cant_read_usbdev) + } + } catch (e: IOException) { + if (trial == 0) { + StateKeeper.usbMassStorageDevice!!.close() + continue + } else { + confirm_extra_info.text = getString(R.string.could_not_access_usb_error) + break + } + } + } + } + + fun displayImageLayout() { + val uri = StateKeeper.imageFile ?: return + val text = uri.getFileName(this) + + if (StateKeeper.flashMethod == FlashMethod.FLASH_DMG_API) { + StateKeeper.imageRepr = DMGImage(uri, this) + val imgRepr = StateKeeper.imageRepr as DMGImage + + if (imgRepr.tableType == null && (imgRepr.partitionTable == null || imgRepr.partitionTable?.size == 0)) { + part_table_header.text = getString(R.string.image_is_not_dmg) + issuesFound = true + return + } else { + part_table_header.text = if (imgRepr.tableType != null) getString(R.string.partition_table_title) else "" + part_table_header_side.text = imgRepr.tableType?.getString(this) ?: "" + issuesFound = false + + val viewAdapter = PartitionTableRecyclerViewAdapter(imgRepr.partitionTable!!) + part_table_recycler.apply { + setHasFixedSize(true) + layoutManager = LinearLayoutManager(context) + adapter = viewAdapter + } + } + } + } + + fun nextStep() { + if (!canContinue) { + confirm_fab.snackbar(getString(R.string.cannot_write)) + return + } + + toast(getString(R.string.check_notification_progress), Toast.LENGTH_LONG) + + val intent: Intent = when (StateKeeper.flashMethod) { + FlashMethod.FLASH_API -> Intent(this, UsbApiImgWriteService::class.java) + FlashMethod.FLASH_DMG_API -> Intent(this, UsbApiDmgWriteService::class.java) + else -> null!! + } + + intent.setDataAndType(StateKeeper.imageFile, "application/octet-stream") + intent.putExtra("usbDevice", StateKeeper.usbDevice) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + startForegroundService(intent) + else + startService(intent) + + moveTaskToBack(true); + finish() + } + + + fun onButtonClicked(view: View) { + when (view.id) { + R.id.confirm_fab -> nextStep() + } + } + +} diff --git a/app/src/main/java/eu/depau/etchdroid/activities/LicensesActivity.kt b/app/src/main/java/eu/depau/etchdroid/activities/LicensesActivity.kt index 0f81157..64fa9ff 100644 --- a/app/src/main/java/eu/depau/etchdroid/activities/LicensesActivity.kt +++ b/app/src/main/java/eu/depau/etchdroid/activities/LicensesActivity.kt @@ -3,10 +3,9 @@ package eu.depau.etchdroid.activities import android.content.Intent import android.net.Uri import android.os.Bundle -import android.support.v7.app.AppCompatActivity -import android.support.v7.widget.LinearLayoutManager -import android.support.v7.widget.RecyclerView -import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import android.view.MenuItem import android.view.View import eu.depau.etchdroid.R @@ -41,8 +40,6 @@ class LicensesActivity : AppCompatActivity() { setContentView(R.layout.activity_licenses) updateLicenses() - title = getString(R.string.licenses) - // Enable back button in action bar supportActionBar!!.setDisplayHomeAsUpEnabled(true) diff --git a/app/src/main/java/eu/depau/etchdroid/activities/MainActivity.kt b/app/src/main/java/eu/depau/etchdroid/activities/MainActivity.kt deleted file mode 100644 index 6aed5ca..0000000 --- a/app/src/main/java/eu/depau/etchdroid/activities/MainActivity.kt +++ /dev/null @@ -1,102 +0,0 @@ -package eu.depau.etchdroid.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.etchdroid.R -import eu.depau.etchdroid.StateKeeper -import eu.depau.etchdroid.fragments.WizardFragment -import eu.depau.etchdroid.fragments.FlashMethodFragment -import eu.depau.etchdroid.fragments.UsbDriveFragment -import kotlinx.android.synthetic.main.activity_main.* - -class MainActivity : WizardActivity() { - val TAG = "MainActivity" - val ACTION_USB_PERMISSION = "eu.depau.etchdroid.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) - setSupportActionBar(toolbar) - fab.setOnClickListener(::nextStep) - - // Create new fragment and transaction - val fragment = FlashMethodFragment() - val transaction = supportFragmentManager.beginTransaction() - 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 onDestroy() { - super.onDestroy() - - unregisterReceiver(mUsbReceiver) - } - - 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) - } - - override fun onCreateOptionsMenu(menu: Menu): Boolean { - // Inflate the menu; this adds items to the action bar if it is present. - menuInflater.inflate(R.menu.menu_main, menu) - return true - } - - 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_licenses -> { - val intent = Intent(this, LicensesActivity::class.java) - startActivity(intent) - return true - } - else -> super.onOptionsItemSelected(item) - } - } -} diff --git a/app/src/main/java/eu/depau/etchdroid/activities/StartActivity.kt b/app/src/main/java/eu/depau/etchdroid/activities/StartActivity.kt new file mode 100644 index 0000000..e9dd21b --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/activities/StartActivity.kt @@ -0,0 +1,128 @@ +package eu.depau.etchdroid.activities + +import android.Manifest +import android.app.Activity +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Bundle +import android.os.Environment +import android.view.View +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +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.kotlin_exts.snackbar +import kotlinx.android.synthetic.main.activity_start.* +import java.io.File + +class StartActivity : ActivityBase() { + val TAG = "StartActivity" + val READ_REQUEST_CODE = 42 + val READ_EXTERNAL_STORAGE_PERMISSION = 29 + var delayedButtonClicked: View? = null + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_start) + } + + fun onButtonClicked(view: View) { + StateKeeper.flashMethod = when (view.id) { + R.id.btn_image_raw -> FlashMethod.FLASH_API + R.id.btn_image_dmg -> FlashMethod.FLASH_DMG_API + else -> null + } + + when (StateKeeper.flashMethod) { + FlashMethod.FLASH_API -> { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.setType("*/*"); + startActivityForResult(intent, READ_REQUEST_CODE) + } + FlashMethod.FLASH_DMG_API -> { + if (checkAndRequestStorageReadPerm()) { + val sdcard = Environment.getExternalStorageDirectory().absolutePath + + val chooser = StorageChooser.Builder() + .withActivity(this) + .withFragmentManager(fragmentManager) + .withMemoryBar(true) + .allowCustomPath(true) + .setType(StorageChooser.FILE_PICKER) + .customFilter(arrayListOf("dmg")) + .build() + chooser.show() + chooser.setOnSelectListener { + StateKeeper.imageFile = Uri.fromFile(File(it)) + nextStep() + } + } else { + delayedButtonClicked = view + } + } + FlashMethod.FLASH_UNETBOOTIN -> { + } + FlashMethod.FLASH_WOEUSB -> { + } + null -> { + } + + } + } + + private fun checkAndRequestStorageReadPerm(): Boolean { + if ((ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)) { + if (ActivityCompat.shouldShowRequestPermissionRationale(this, + Manifest.permission.READ_EXTERNAL_STORAGE)) { + btn_image_dmg.snackbar("Storage permission is required to read DMG images") + } else { + ActivityCompat.requestPermissions(this, + arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), + READ_EXTERNAL_STORAGE_PERMISSION) + } + } else { + // Permission granted + return true + } + return false + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + when (requestCode) { + READ_EXTERNAL_STORAGE_PERMISSION -> { + if (delayedButtonClicked != null) + onButtonClicked(delayedButtonClicked!!) + return + } + else -> { + } + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + // The document selected by the user won't be returned in the intent. + // Instead, a URI to that document will be contained in the return intent + // provided to this method as a parameter. + // Pull that URI using resultData.getData(). + var uri: Uri? = null + if (data != null) { + uri = data.getData() + StateKeeper.imageFile = uri + + nextStep() + } + } + } + + + fun nextStep() { + val intent = Intent(this, UsbDrivePickerActivity::class.java) + startActivity(intent) + } +} diff --git a/app/src/main/java/eu/depau/etchdroid/activities/UsbDrivePickerActivity.kt b/app/src/main/java/eu/depau/etchdroid/activities/UsbDrivePickerActivity.kt new file mode 100644 index 0000000..18c4639 --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/activities/UsbDrivePickerActivity.kt @@ -0,0 +1,150 @@ +package eu.depau.etchdroid.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 android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +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.kotlin_exts.name +import eu.depau.etchdroid.kotlin_exts.snackbar +import eu.depau.etchdroid.utils.ClickListener +import eu.depau.etchdroid.utils.EmptyRecyclerView +import eu.depau.etchdroid.utils.RecyclerViewTouchListener +import kotlinx.android.synthetic.main.activity_usb_drive_picker.* + +class UsbDrivePickerActivity : ActivityBase(), SwipeRefreshLayout.OnRefreshListener { + val USB_PERMISSION = "eu.depau.etchdroid.USB_PERMISSION" + lateinit var mUsbPermissionIntent: PendingIntent + + private lateinit var recyclerView: EmptyRecyclerView + private lateinit var viewAdapter: UsbDrivesRecyclerViewAdapter + private lateinit var viewManager: RecyclerView.LayoutManager + private lateinit var refreshLayout: SwipeRefreshLayout + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_usb_drive_picker) + actionBar?.setDisplayHomeAsUpEnabled(true) + + mUsbPermissionIntent = PendingIntent.getBroadcast(this, 0, Intent(USB_PERMISSION), 0) + val usbPermissionFilter = IntentFilter(USB_PERMISSION) + val usbAttachedFilter = IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED) + val usbDetachedFilter = IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED) + + registerReceiver(mUsbReceiver, usbPermissionFilter) + registerReceiver(mUsbReceiver, usbAttachedFilter) + registerReceiver(mUsbReceiver, usbDetachedFilter) + + refreshLayout = usbdevs_swiperefreshlayout + refreshLayout.setOnRefreshListener(this) + refreshLayout.post { + refreshLayout.isRefreshing = true + loadUsbDevices() + } + + viewManager = LinearLayoutManager(this) + recyclerView = usbdevs_recycler_view + recyclerView.emptyView = usbdevs_recycler_empty_view + + recyclerView.addOnItemTouchListener(RecyclerViewTouchListener(this, recyclerView, object : ClickListener { + override fun onClick(view: View, position: Int) { + val device = viewAdapter.get(position) + val manager = getSystemService(Context.USB_SERVICE) as UsbManager + manager.requestPermission(device.usbDevice, mUsbPermissionIntent) + } + + override fun onLongClick(view: View, position: Int) {} + })) + } + + override fun onDestroy() { + super.onDestroy() + unregisterReceiver(mUsbReceiver) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.usb_devices_menu, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onContextItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_refresh -> { + loadUsbDevices() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun onRefresh() { + loadUsbDevices() + } + + + private val mUsbReceiver = object : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + when (intent.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 (!result) { + if (device != null) { + recyclerView.snackbar(getString(R.string.usb_perm_denied) + device.name) + } else { + recyclerView.snackbar(getString(R.string.usb_perm_denied_noname)) + } + return + } + + StateKeeper.usbDevice = device + StateKeeper.usbMassStorageDevice = UsbMassStorageDevice.getMassStorageDevices(context).find { it.usbDevice == device } + + nextStep() + } + UsbManager.ACTION_USB_DEVICE_ATTACHED, UsbManager.ACTION_USB_DEVICE_DETACHED -> loadUsbDevices() + } + } + } + + fun loadUsbDevices() { + try { + viewAdapter = UsbDrivesRecyclerViewAdapter(UsbMassStorageDevice.getMassStorageDevices(this)) + + recyclerView.apply { + setHasFixedSize(true) + layoutManager = viewManager + adapter = viewAdapter + } + } finally { + refreshLayout.isRefreshing = false + } + } + + + fun nextStep() { + val intent = Intent(this, ConfirmationActivity::class.java) + startActivity(intent) + } +} diff --git a/app/src/main/java/eu/depau/etchdroid/activities/WizardActivity.kt b/app/src/main/java/eu/depau/etchdroid/activities/WizardActivity.kt deleted file mode 100644 index b888d03..0000000 --- a/app/src/main/java/eu/depau/etchdroid/activities/WizardActivity.kt +++ /dev/null @@ -1,35 +0,0 @@ -package eu.depau.etchdroid.activities - -import android.content.Intent -import android.support.v7.app.AppCompatActivity -import android.view.View -import eu.depau.etchdroid.StateKeeper -import eu.depau.etchdroid.fragments.WizardFragment - -abstract class WizardActivity : AppCompatActivity() { - abstract fun goToNewFragment(fragment: WizardFragment) - - open fun onCheckBoxClicked(view: View) { - StateKeeper.currentFragment?.onCheckBoxClicked(view) - } - - open fun onButtonClicked(view: View) { - StateKeeper.currentFragment?.onButtonClicked(view) - } - - open fun onRadioButtonClicked(view: View) { - StateKeeper.currentFragment?.onRadioButtonClicked(view) - } - - open fun nextStep(view: View) { - StateKeeper.currentFragment?.nextStep(view) - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - StateKeeper.currentFragment?.onRequestPermissionsResult(requestCode, permissions, grantResults) - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - StateKeeper.currentFragment?.onActivityResult(requestCode, resultCode, data) - } -} \ No newline at end of file diff --git a/app/src/main/java/eu/depau/etchdroid/adapters/LicenseRecyclerViewAdapter.kt b/app/src/main/java/eu/depau/etchdroid/adapters/LicenseRecyclerViewAdapter.kt index 31fa6be..cebc51d 100644 --- a/app/src/main/java/eu/depau/etchdroid/adapters/LicenseRecyclerViewAdapter.kt +++ b/app/src/main/java/eu/depau/etchdroid/adapters/LicenseRecyclerViewAdapter.kt @@ -1,7 +1,7 @@ package eu.depau.etchdroid.adapters import android.annotation.SuppressLint -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup diff --git a/app/src/main/java/eu/depau/etchdroid/adapters/PartitionTableRecyclerViewAdapter.kt b/app/src/main/java/eu/depau/etchdroid/adapters/PartitionTableRecyclerViewAdapter.kt index d0b2780..3389185 100644 --- a/app/src/main/java/eu/depau/etchdroid/adapters/PartitionTableRecyclerViewAdapter.kt +++ b/app/src/main/java/eu/depau/etchdroid/adapters/PartitionTableRecyclerViewAdapter.kt @@ -1,6 +1,6 @@ package eu.depau.etchdroid.adapters -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import android.view.LayoutInflater import android.view.ViewGroup import android.widget.LinearLayout diff --git a/app/src/main/java/eu/depau/etchdroid/adapters/UsbDrivesRecyclerViewAdapter.kt b/app/src/main/java/eu/depau/etchdroid/adapters/UsbDrivesRecyclerViewAdapter.kt index 4dbbd92..cf0c311 100644 --- a/app/src/main/java/eu/depau/etchdroid/adapters/UsbDrivesRecyclerViewAdapter.kt +++ b/app/src/main/java/eu/depau/etchdroid/adapters/UsbDrivesRecyclerViewAdapter.kt @@ -2,7 +2,7 @@ package eu.depau.etchdroid.adapters import android.annotation.SuppressLint import android.os.Build -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import android.view.LayoutInflater import android.view.ViewGroup import android.widget.RelativeLayout diff --git a/app/src/main/java/eu/depau/etchdroid/fragments/ConfirmInfoFragment.kt b/app/src/main/java/eu/depau/etchdroid/fragments/ConfirmInfoFragment.kt deleted file mode 100644 index b202673..0000000 --- a/app/src/main/java/eu/depau/etchdroid/fragments/ConfirmInfoFragment.kt +++ /dev/null @@ -1,115 +0,0 @@ -package eu.depau.etchdroid.fragments - -import android.content.Intent -import android.os.Build -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import eu.depau.etchdroid.R -import eu.depau.etchdroid.StateKeeper -import eu.depau.etchdroid.enums.FlashMethod -import eu.depau.etchdroid.enums.WizardStep -import eu.depau.etchdroid.kotlin_exts.* -import eu.depau.etchdroid.services.UsbApiDmgWriteService -import eu.depau.etchdroid.services.UsbApiImgWriteService -import kotlinx.android.synthetic.main.fragment_confirminfo.view.* -import java.io.IOException - -/** - * 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 = when (StateKeeper.flashMethod) { - FlashMethod.FLASH_API -> Intent(activity, UsbApiImgWriteService::class.java) - FlashMethod.FLASH_DMG_API -> Intent(activity, UsbApiDmgWriteService::class.java) - else -> null!! - } - - intent.setDataAndType(StateKeeper.imageFile, "application/octet-stream") - intent.putExtra("usbDevice", StateKeeper.usbDevice) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - activity!!.startForegroundService(intent) - else - 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_DMG_API -> getString(R.string.flash_dmg_api) - 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 - - for (trial in 0..1) { - try { - 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 { - var text = - if (StateKeeper.flashMethod == FlashMethod.FLASH_DMG_API) - getString(R.string.no_image_size_check_dmg) + "\n" - else - "" - text += getString(R.string.tap_next_to_write) - view.confirm_extra_info.text = text - canContinue = true - } - } else { - view.confirm_extra_info.text = getString(R.string.cant_read_usbdev) - } - } catch (e: IOException) { - if (trial == 0) { - StateKeeper.usbMassStorageDevice!!.close() - continue - } else { - view.confirm_extra_info.text = "Could not access USB device. Maybe you ran the app previously and it crashed? Remove and reinsert the USB drive, then restart the app." - break - } - } - } - - return view - } - - -} diff --git a/app/src/main/java/eu/depau/etchdroid/fragments/FlashMethodFragment.kt b/app/src/main/java/eu/depau/etchdroid/fragments/FlashMethodFragment.kt deleted file mode 100644 index 3cb7422..0000000 --- a/app/src/main/java/eu/depau/etchdroid/fragments/FlashMethodFragment.kt +++ /dev/null @@ -1,42 +0,0 @@ -package eu.depau.etchdroid.fragments - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import eu.depau.etchdroid.R -import eu.depau.etchdroid.StateKeeper -import eu.depau.etchdroid.activities.WizardActivity -import eu.depau.etchdroid.kotlin_exts.snackbar -import eu.depau.etchdroid.enums.FlashMethod -import eu.depau.etchdroid.enums.WizardStep - -/** - * A placeholder fragment containing a simple view. - */ -class FlashMethodFragment : WizardFragment() { - override fun nextStep(view: View?) { - if (StateKeeper.flashMethod == null) - view?.snackbar(getString(R.string.please_select_writing_method)) - else - (activity as WizardActivity).goToNewFragment(ImageLocationFragment()) - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { - StateKeeper.currentFragment = this - StateKeeper.wizardStep = WizardStep.SELECT_FLASH_METHOD - - return inflater.inflate(R.layout.fragment_select_flash_method, container, false) - } - - override fun onRadioButtonClicked(view: View) { - StateKeeper.flashMethod = when (view.id) { - R.id.flash_dmg_api_radio -> FlashMethod.FLASH_DMG_API - R.id.flash_usb_api_radio -> FlashMethod.FLASH_API - R.id.flash_unetbootin_radio -> FlashMethod.FLASH_UNETBOOTIN - R.id.flash_woeusb_radio -> FlashMethod.FLASH_WOEUSB - else -> null - } - } -} diff --git a/app/src/main/java/eu/depau/etchdroid/fragments/ImageLocationFragment.kt b/app/src/main/java/eu/depau/etchdroid/fragments/ImageLocationFragment.kt deleted file mode 100644 index e527fcc..0000000 --- a/app/src/main/java/eu/depau/etchdroid/fragments/ImageLocationFragment.kt +++ /dev/null @@ -1,211 +0,0 @@ -package eu.depau.etchdroid.fragments - -import android.Manifest -import android.app.Activity -import android.content.Intent -import android.content.pm.PackageManager -import android.net.Uri -import android.os.Bundle -import android.os.Environment -import android.support.v4.app.ActivityCompat -import android.support.v4.content.ContextCompat -import android.support.v7.widget.LinearLayoutManager -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.codekidlabs.storagechooser.StorageChooser -import eu.depau.etchdroid.R -import eu.depau.etchdroid.StateKeeper -import eu.depau.etchdroid.activities.WizardActivity -import eu.depau.etchdroid.adapters.PartitionTableRecyclerViewAdapter -import eu.depau.etchdroid.enums.FlashMethod -import eu.depau.etchdroid.enums.ImageLocation -import eu.depau.etchdroid.enums.WizardStep -import eu.depau.etchdroid.img_types.DMGImage -import eu.depau.etchdroid.kotlin_exts.getFileName -import eu.depau.etchdroid.kotlin_exts.snackbar -import kotlinx.android.synthetic.main.activity_main.* -import kotlinx.android.synthetic.main.fragment_select_location.* -import java.io.File - - -/** - * A placeholder fragment containing a simple view. - */ -class ImageLocationFragment : WizardFragment() { - val READ_REQUEST_CODE = 42 - val MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 29 - val TAG = "ImageLocationFragment" - val PICKER_DIALOG_TAG = "eu.depau.etchdroid.filepicker.DIALOG_TAG" - var issuesFound = false - - fun isStreamingAvailable(): Boolean { - if (StateKeeper.imageLocation != ImageLocation.REMOTE) - return false - if (StateKeeper.flashMethod != FlashMethod.FLASH_DMG_API && StateKeeper.flashMethod != FlashMethod.FLASH_API) - return false - return true - } - - override fun onRadioButtonClicked(view: View) { - StateKeeper.imageLocation = ImageLocation.LOCAL - activity?.fab?.show() - pick_file_btn?.isEnabled = StateKeeper.imageLocation == ImageLocation.LOCAL - loadImageChanges(activity as WizardActivity) - } - - override fun onButtonClicked(view: View) { - if (view.id == R.id.pick_file_btn) { - when (StateKeeper.flashMethod) { - FlashMethod.FLASH_API -> { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.setType("*/*"); - activity?.startActivityForResult(intent, READ_REQUEST_CODE) - } - FlashMethod.FLASH_DMG_API -> { - if (checkAndRequestStorageReadPerm()) { - val sdcard = Environment.getExternalStorageDirectory().absolutePath - - - val chooser = StorageChooser.Builder() - .withActivity(activity) - .withFragmentManager(activity!!.fragmentManager) - .withMemoryBar(true) - .allowCustomPath(true) - .setType(StorageChooser.FILE_PICKER) - .customFilter(arrayListOf("dmg")) - .build() - chooser.show() - chooser.setOnSelectListener { - StateKeeper.imageFile = Uri.fromFile(File(it)) - loadImageChanges(activity as WizardActivity) - - activity?.fab?.show() - } - } - } - FlashMethod.FLASH_UNETBOOTIN -> { - } - FlashMethod.FLASH_WOEUSB -> { - } - null -> { - } - } - } - } - - fun checkAndRequestStorageReadPerm(): Boolean { - if ((ContextCompat.checkSelfPermission(activity!!, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)) { - if (ActivityCompat.shouldShowRequestPermissionRationale(activity!!, - Manifest.permission.READ_EXTERNAL_STORAGE)) { - view!!.snackbar("Storage permission is required to read DMG images") - } else { - ActivityCompat.requestPermissions(activity!!, - arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), - MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) - } - } else { - // Permission granted - return true - } - return false - } - - override fun nextStep(view: View?) { - if (issuesFound) { - view?.snackbar(getString(R.string.issues_found_expl)) - return - } - - if (StateKeeper.imageLocation == null) { - view?.snackbar(getString(R.string.select_image_location)) - return - } - - if (StateKeeper.imageFile == null) { - view?.snackbar(getString(R.string.provide_image_file)) - return - } - - (activity as WizardActivity).goToNewFragment(UsbDriveFragment()) - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - when (requestCode) { - MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE -> { - onButtonClicked(pick_file_btn) - return - } - - else -> {} - } - - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - activity?.fab?.show() - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle?): View? { - StateKeeper.currentFragment = this - StateKeeper.wizardStep = WizardStep.SELECT_LOCATION - - return inflater.inflate(R.layout.fragment_select_location, container, false) - } - - fun loadImageChanges(context: WizardActivity) { - val button = pick_file_btn - val uri = StateKeeper.imageFile ?: return - - val text = uri.getFileName(context) - - if (text != null) - button.text = text - else - button.text = getString(R.string.pick_a_file) - - if (StateKeeper.flashMethod == FlashMethod.FLASH_DMG_API) { - StateKeeper.imageRepr = DMGImage(uri, context) - val imgRepr = StateKeeper.imageRepr as DMGImage - - if (imgRepr.tableType == null && (imgRepr.partitionTable == null || imgRepr.partitionTable?.size == 0)) { - part_table_header.text = getString(R.string.image_is_not_dmg) - issuesFound = true - return - } else { - part_table_header.text = if (imgRepr.tableType != null) "Partition table:" else "" - part_table_header_side.text = imgRepr.tableType?.getString(context) ?: "" - issuesFound = false - - val viewAdapter = PartitionTableRecyclerViewAdapter(imgRepr.partitionTable!!) - part_table_recycler.apply { - setHasFixedSize(true) - layoutManager = LinearLayoutManager(activity) - adapter = viewAdapter - } - } - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) { - // The document selected by the user won't be returned in the intent. - // Instead, a URI to that document will be contained in the return intent - // provided to this method as a parameter. - // Pull that URI using resultData.getData(). - var uri: Uri? = null - if (data != null) { - uri = data.getData() - Log.d(TAG, "Uri: " + uri!!.toString()) - StateKeeper.imageFile = uri - loadImageChanges(activity as WizardActivity) - - activity?.fab?.show() - } - } - } -} diff --git a/app/src/main/java/eu/depau/etchdroid/fragments/UsbDriveFragment.kt b/app/src/main/java/eu/depau/etchdroid/fragments/UsbDriveFragment.kt deleted file mode 100644 index a76c900..0000000 --- a/app/src/main/java/eu/depau/etchdroid/fragments/UsbDriveFragment.kt +++ /dev/null @@ -1,139 +0,0 @@ -package eu.depau.etchdroid.fragments - -import android.content.Context -import android.hardware.usb.UsbDevice -import android.hardware.usb.UsbManager -import android.os.Bundle -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.etchdroid.R -import eu.depau.etchdroid.StateKeeper -import eu.depau.etchdroid.utils.ClickListener -import eu.depau.etchdroid.activities.WizardActivity -import eu.depau.etchdroid.activities.MainActivity -import eu.depau.etchdroid.adapters.UsbDrivesRecyclerViewAdapter -import eu.depau.etchdroid.kotlin_exts.name -import eu.depau.etchdroid.kotlin_exts.snackbar -import eu.depau.etchdroid.enums.WizardStep -import eu.depau.etchdroid.utils.RecyclerViewTouchListener -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 - - - 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) - } -} - diff --git a/app/src/main/java/eu/depau/etchdroid/fragments/WizardFragment.kt b/app/src/main/java/eu/depau/etchdroid/fragments/WizardFragment.kt deleted file mode 100644 index 85fac52..0000000 --- a/app/src/main/java/eu/depau/etchdroid/fragments/WizardFragment.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.depau.etchdroid.fragments - -import android.content.Intent -import android.support.v4.app.Fragment -import android.view.View -import eu.depau.etchdroid.activities.WizardActivity - -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?) {} - open fun onButtonClicked(view: View) {} - open override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {} -} \ No newline at end of file diff --git a/app/src/main/java/eu/depau/etchdroid/kotlin_exts/ViewSnackbar.kt b/app/src/main/java/eu/depau/etchdroid/kotlin_exts/ViewSnackbar.kt index e34c342..c90ecb6 100644 --- a/app/src/main/java/eu/depau/etchdroid/kotlin_exts/ViewSnackbar.kt +++ b/app/src/main/java/eu/depau/etchdroid/kotlin_exts/ViewSnackbar.kt @@ -1,6 +1,6 @@ package eu.depau.etchdroid.kotlin_exts -import android.support.design.widget.Snackbar +import com.google.android.material.snackbar.Snackbar import android.view.View fun View.snackbar(message: CharSequence, duration: Int = Snackbar.LENGTH_LONG) { 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 094c362..e2976b4 100644 --- a/app/src/main/java/eu/depau/etchdroid/services/UsbWriteService.kt +++ b/app/src/main/java/eu/depau/etchdroid/services/UsbWriteService.kt @@ -8,7 +8,7 @@ import android.content.Context import android.content.Intent import android.os.Build import android.os.PowerManager -import android.support.v4.app.NotificationCompat +import androidx.core.app.NotificationCompat import eu.depau.etchdroid.R import eu.depau.etchdroid.kotlin_exts.toHRSize import eu.depau.etchdroid.kotlin_exts.toHRTime diff --git a/app/src/main/java/eu/depau/etchdroid/utils/EmptyRecyclerView.kt b/app/src/main/java/eu/depau/etchdroid/utils/EmptyRecyclerView.kt new file mode 100644 index 0000000..b0479ce --- /dev/null +++ b/app/src/main/java/eu/depau/etchdroid/utils/EmptyRecyclerView.kt @@ -0,0 +1,42 @@ +package eu.depau.etchdroid.utils + +import android.content.Context +import android.util.AttributeSet +import android.view.View + +import androidx.recyclerview.widget.RecyclerView + +class EmptyRecyclerView : RecyclerView { + var emptyView: View? = null + set(value) { + field = value + checkIfEmpty() + } + + private val observer: RecyclerView.AdapterDataObserver = object : RecyclerView.AdapterDataObserver() { + override fun onChanged() { + super.onChanged() + checkIfEmpty() + } + } + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) + + internal fun checkIfEmpty() { + if (emptyView != null) + emptyView!!.visibility = if (adapter?.itemCount ?: 0 > 0) View.GONE else View.VISIBLE + } + + override fun setAdapter(adapter: RecyclerView.Adapter<*>?) { + val oldAdapter = this.adapter + oldAdapter?.unregisterAdapterDataObserver(observer) + super.setAdapter(adapter) + adapter?.registerAdapterDataObserver(observer) + checkIfEmpty() + } + +} diff --git a/app/src/main/java/eu/depau/etchdroid/utils/RecyclerViewTouchListener.kt b/app/src/main/java/eu/depau/etchdroid/utils/RecyclerViewTouchListener.kt index 368b8cd..474391b 100644 --- a/app/src/main/java/eu/depau/etchdroid/utils/RecyclerViewTouchListener.kt +++ b/app/src/main/java/eu/depau/etchdroid/utils/RecyclerViewTouchListener.kt @@ -1,7 +1,7 @@ package eu.depau.etchdroid.utils import android.content.Context -import android.support.v7.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView import android.view.GestureDetector import android.view.MotionEvent diff --git a/app/src/main/res/drawable/ic_beta.xml b/app/src/main/res/drawable/ic_beta.xml new file mode 100644 index 0000000..9dc7684 --- /dev/null +++ b/app/src/main/res/drawable/ic_beta.xml @@ -0,0 +1,34 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_dmg.xml b/app/src/main/res/drawable/ic_dmg.xml new file mode 100644 index 0000000..48e4688 --- /dev/null +++ b/app/src/main/res/drawable/ic_dmg.xml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_raw.xml b/app/src/main/res/drawable/ic_raw.xml new file mode 100644 index 0000000..41fcb79 --- /dev/null +++ b/app/src/main/res/drawable/ic_raw.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_twotone_save_alt_24px.xml b/app/src/main/res/drawable/ic_twotone_save_alt_24px.xml new file mode 100644 index 0000000..28a36b9 --- /dev/null +++ b/app/src/main/res/drawable/ic_twotone_save_alt_24px.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_usb_black_200dp.xml b/app/src/main/res/drawable/ic_usb_black_200dp.xml new file mode 100644 index 0000000..248fd72 --- /dev/null +++ b/app/src/main/res/drawable/ic_usb_black_200dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_confirmation.xml b/app/src/main/res/layout/activity_confirmation.xml new file mode 100644 index 0000000..7124c65 --- /dev/null +++ b/app/src/main/res/layout/activity_confirmation.xml @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_licenses.xml b/app/src/main/res/layout/activity_licenses.xml index be3e695..61e2f8c 100644 --- a/app/src/main/res/layout/activity_licenses.xml +++ b/app/src/main/res/layout/activity_licenses.xml @@ -1,5 +1,5 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 3bf3c36..0000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_start.xml b/app/src/main/res/layout/activity_start.xml new file mode 100644 index 0000000..c087bc1 --- /dev/null +++ b/app/src/main/res/layout/activity_start.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_usb_drive_picker.xml b/app/src/main/res/layout/activity_usb_drive_picker.xml new file mode 100644 index 0000000..071ce4e --- /dev/null +++ b/app/src/main/res/layout/activity_usb_drive_picker.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_confirminfo.xml b/app/src/main/res/layout/fragment_confirminfo.xml deleted file mode 100644 index 286b885..0000000 --- a/app/src/main/res/layout/fragment_confirminfo.xml +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_select_flash_method.xml b/app/src/main/res/layout/fragment_select_flash_method.xml deleted file mode 100644 index 0364cc1..0000000 --- a/app/src/main/res/layout/fragment_select_flash_method.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_select_location.xml b/app/src/main/res/layout/fragment_select_location.xml deleted file mode 100644 index f22c403..0000000 --- a/app/src/main/res/layout/fragment_select_location.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - -