diff --git a/app/src/main/java/de/ccc/events/badge/card10/filetransfer/BatchTransferFragment.kt b/app/src/main/java/de/ccc/events/badge/card10/filetransfer/BatchTransferFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..c1503eaa04ca5d090707a9a63a479c11372dabfb --- /dev/null +++ b/app/src/main/java/de/ccc/events/badge/card10/filetransfer/BatchTransferFragment.kt @@ -0,0 +1,147 @@ +/* + * Copyright by the original author or authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package de.ccc.events.badge.card10.filetransfer + +import android.bluetooth.BluetoothGatt +import android.net.Uri +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import de.ccc.events.badge.card10.R +import de.ccc.events.badge.card10.common.ConnectionService +import de.ccc.events.badge.card10.common.GattListener +import de.ccc.events.badge.card10.main.MainFragment +import kotlinx.android.synthetic.main.batch_transfer_fragment.* +import java.lang.Exception +import java.lang.IllegalStateException + +private const val TAG = "BatchTransferFragment" + +class BatchTransferFragment : Fragment(), FileTransferListener, GattListener { + private lateinit var queue: TransferQueue + private var transfer: FileTransfer? = null + private var isCancelled = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val args = arguments ?: throw IllegalStateException() + val jobs = args.get("jobs") as? Array<TransferJob> ?: throw IllegalStateException() + queue = TransferQueue(jobs) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = + inflater.inflate(R.layout.batch_transfer_fragment, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + label_status.text = getString(R.string.batch_transfer_label_initializing) + progress.max = 5 + + button_cancel.setOnClickListener { +// isCancelled = true + startTransfer() + } + + button_done.setOnClickListener { + val fragment = MainFragment() + fragmentManager!!.beginTransaction() + .replace(R.id.fragment_container, fragment) + .addToBackStack(null) + .commit() + } + + initConnection() + } + + private fun initConnection() { + val ctx = context ?: throw IllegalStateException() + ConnectionService.connect(ctx) + } + + private fun startTransfer() { + activity?.runOnUiThread { + label_status.text = getString(R.string.batch_transfer_label_transferring) + progress.max = queue.size + } + + transferNext() + } + + private fun transferNext() { + val item = queue.dequeue() + + if (item == null || isCancelled) { + activity?.runOnUiThread { + progress.progress = 0 + label_status.text = if (isCancelled) { + getString(R.string.batch_transfer_label_cancelled) + } else { + getString(R.string.batch_transfer_label_complete) + } + button_cancel.visibility = View.GONE + button_done.visibility = View.VISIBLE + } + } else { + transferItem(item) + } + } + + private fun transferItem(transferJob: TransferJob) { + try { + val ctx = activity ?: throw IllegalStateException() + val reader = ChunkedReader(ctx, transferJob.sourceUri, ConnectionService.mtu) + val service = ConnectionService.leService ?: throw IllegalStateException() + transfer = FileTransfer(service, reader,this, transferJob.destPath) + } catch (e: Exception) { + Log.e(TAG, "Failed to initialize transfer") + return + } + } + + override fun onConnectionStateChange(state: Int) { + if (state == BluetoothGatt.STATE_CONNECTED) { + startTransfer() + } + } + + override fun onError() { + activity?.runOnUiThread { + label_status.text = getString(R.string.batch_transfer_label_error) + button_cancel.visibility = View.GONE + button_done.visibility = View.VISIBLE + } + } + + override fun onFinish() { + activity?.runOnUiThread { + // TODO: Add workaround for broken progress bars + // https://stackoverflow.com/questions/4348032/android-progressbar-does-not-update-progress-view-drawable + progress.incrementProgressBy(1) + } + + transferNext() + } +} \ No newline at end of file diff --git a/app/src/main/java/de/ccc/events/badge/card10/filetransfer/TransferJob.kt b/app/src/main/java/de/ccc/events/badge/card10/filetransfer/TransferJob.kt new file mode 100644 index 0000000000000000000000000000000000000000..efcc40f24a82476a06f920ea3d9cfd9475b7dd4e --- /dev/null +++ b/app/src/main/java/de/ccc/events/badge/card10/filetransfer/TransferJob.kt @@ -0,0 +1,35 @@ +/* + * Copyright by the original author or authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package de.ccc.events.badge.card10.filetransfer + +import android.net.Uri +import android.os.Parcel +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +// Ignore IDE warnings. @Parcelize will take care of everything +@Parcelize +data class TransferJob( + val sourceUri: Uri, + val destPath: String +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/de/ccc/events/badge/card10/filetransfer/TransferQueue.kt b/app/src/main/java/de/ccc/events/badge/card10/filetransfer/TransferQueue.kt new file mode 100644 index 0000000000000000000000000000000000000000..d4813e16f2926f6801b55afd31518579a9d39df4 --- /dev/null +++ b/app/src/main/java/de/ccc/events/badge/card10/filetransfer/TransferQueue.kt @@ -0,0 +1,54 @@ +/* + * Copyright by the original author or authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package de.ccc.events.badge.card10.filetransfer + +import android.net.Uri +import java.util.* +import kotlin.NoSuchElementException + +class TransferQueue() { + private val queue = LinkedList<TransferJob>() + + val size: Int + get() = queue.size + + constructor(jobs: Array<TransferJob>) : this() { + for (job in jobs) { + queue.add(job) + } + } + + fun enqueue(sourceUri: Uri, destPath: String) { + queue.add(TransferJob(sourceUri, destPath)) + } + + fun dequeue(): TransferJob? { + return try { + queue.removeFirst() + } catch (e: NoSuchElementException) { + null + } + } + + fun clear() = queue.clear() +} \ No newline at end of file diff --git a/app/src/main/res/layout/batch_transfer_fragment.xml b/app/src/main/res/layout/batch_transfer_fragment.xml new file mode 100644 index 0000000000000000000000000000000000000000..ba1820674e95ab7ddf0209f06a24c3e18a50f437 --- /dev/null +++ b/app/src/main/res/layout/batch_transfer_fragment.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/activity_padding"> + + <LinearLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + android:orientation="vertical" + android:gravity="center_horizontal"> + + <ProgressBar android:layout_width="@dimen/batch_transfer_progress" + android:layout_height="wrap_content" + android:id="@+id/progress" + android:indeterminate="false" + style="@style/Widget.AppCompat.ProgressBar.Horizontal"/> + + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/label_status"/> + </LinearLayout> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + android:id="@+id/button_cancel" + android:text="@string/batch_transfer_button_cancel"/> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + android:id="@+id/button_done" + android:visibility="gone" + android:text="@string/batch_transfer_button_done"/> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/values/dimensions.xml b/app/src/main/res/values/dimensions.xml index 9d3852ddac2326519ee115bc885caa70752968bb..76751de92b4a80563a16e7c7ffef71d936e6ed6b 100644 --- a/app/src/main/res/values/dimensions.xml +++ b/app/src/main/res/values/dimensions.xml @@ -7,6 +7,8 @@ <dimen name="main_button_margin">16dp</dimen> <dimen name="send_label_margin">24dp</dimen> + <dimen name="batch_transfer_progress">200dp</dimen> + <dimen name="app_list_item_padding">16dp</dimen> <dimen name="app_detail_description_margin">16dp</dimen> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fbc5ec97ccb0bf51c6e38b9498d5adc7857631c0..87c8728edfd0dd8b7127fa71b36a8599ea4b04e8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,6 +19,15 @@ <string name="file_transfer_hint_destination">Destination path</string> <string name="file_transfer_label_destination_help">(e.g. /test.py)</string> + <string name="batch_transfer_label_initializing">Initializing…</string> + <string name="batch_transfer_label_transferring">Transferring files…</string> + <string name="batch_transfer_label_complete">Files transferred</string> + <string name="batch_transfer_label_error">Transfer failed</string> + <string name="batch_transfer_label_cancelled">Transfer cancelled</string> + <string name="batch_transfer_button_cancel">Cancel</string> + <string name="batch_transfer_button_done">Done</string> + + <string name="loading_dialog_loading">Loading</string> <string name="dialog_action_ok">OK</string> <string name="dialog_action_cancel">Cancel</string>