From 79f5828ef64fe4cedd6a1aee6d0b58c3f21ff2b2 Mon Sep 17 00:00:00 2001 From: Davide Depau Date: Tue, 14 Aug 2018 18:49:22 +0200 Subject: [PATCH] Send notification on job completed --- .../eu/depau/ddroid/abc/UsbWriteService.kt | 112 +++++++++++++++--- .../ddroid/services/UsbAPIWriteService.kt | 57 ++++----- app/src/main/res/values/strings.xml | 4 + 3 files changed, 127 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/eu/depau/ddroid/abc/UsbWriteService.kt b/app/src/main/java/eu/depau/ddroid/abc/UsbWriteService.kt index 4f7624a..916f601 100644 --- a/app/src/main/java/eu/depau/ddroid/abc/UsbWriteService.kt +++ b/app/src/main/java/eu/depau/ddroid/abc/UsbWriteService.kt @@ -4,46 +4,78 @@ import android.app.IntentService import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager +import android.content.Context +import android.content.Intent import android.net.Uri import android.os.Build +import android.os.PowerManager import android.support.v4.app.NotificationCompat import eu.depau.ddroid.R import eu.depau.ddroid.utils.getFileName import eu.depau.ddroid.utils.toHRSize +import eu.depau.ddroid.utils.toHRTime abstract class UsbWriteService(name: String) : IntentService(name) { val TAG = name val FOREGROUND_ID = 1931 + val RESULT_NOTIFICATION_ID = 3829 val WRITE_PROGRESS_CHANNEL_ID = "eu.depau.ddroid.notifications.USB_WRITE_PROGRESS" + val WRITE_RESULT_CHANNEL_ID = "eu.depau.ddroid.notifications.USB_WRITE_RESULT" + val WAKELOCK_TAG = "eu.depau.ddroid.wakelocks.USB_WRITING" + private var prevTime = System.currentTimeMillis() private var prevBytes = 0L private var notifyChanRegistered = false + private var mWakeLock: PowerManager.WakeLock? = null + private var wlAcquireTime = -1L + private val WL_TIMEOUT = 10 * 60 * 1000L - fun getNotificationBuilder(): NotificationCompat.Builder { + override fun onHandleIntent(intent: Intent?) { + startForeground(FOREGROUND_ID, buildForegroundNotification(null, null, -1, -1)) + + try { + writeImage(intent!!) + } finally { + stopForeground(true) + } + } + + abstract fun writeImage(intent: Intent): Long + + fun getNotificationBuilder(channel: String = WRITE_PROGRESS_CHANNEL_ID): NotificationCompat.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 + val statusChannel = NotificationChannel( + WRITE_PROGRESS_CHANNEL_ID, + getString(R.string.notchan_writestatus_title), + NotificationManager.IMPORTANCE_LOW + ) + statusChannel.description = getString(R.string.notchan_writestatus_desc) + + val resultChannel = NotificationChannel( + WRITE_RESULT_CHANNEL_ID, + "USB write result notifications", + NotificationManager.IMPORTANCE_DEFAULT + ) + resultChannel.description = "Used to display the result of a finished write operation" // 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) + notificationManager!!.createNotificationChannel(statusChannel) + notificationManager!!.createNotificationChannel(resultChannel) } notifyChanRegistered = true } return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - NotificationCompat.Builder(this, WRITE_PROGRESS_CHANNEL_ID) + NotificationCompat.Builder(this, channel) else NotificationCompat.Builder(this) } - fun updateNotification(usbDevice: String, uri: Uri, bytes: Long, total: Long) { + fun updateNotification(usbDevice: String, filename: String?, bytes: Long, total: Long) { // Notification rate limiting val time = System.currentTimeMillis() if (time <= prevTime + 1000) @@ -56,24 +88,35 @@ abstract class UsbWriteService(name: String) : IntentService(name) { 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")) + notificationManager.notify(FOREGROUND_ID, buildForegroundNotification(usbDevice, filename, bytes, total, "$perc% • $speed/s")) } - fun errorNotification() { + fun resultNotification(usbDevice: String, filename: String, success: Boolean, bytes: Long = 0, startTime: Long = 0) { val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager - val b = getNotificationBuilder() - .setContentTitle("Write failed") - .setContentText("The USB drive may have been unplugged while writing.") + val b = getNotificationBuilder(WRITE_RESULT_CHANNEL_ID) .setOngoing(false) + val dt = System.currentTimeMillis() - startTime + + if (!success) + b.setContentTitle("Write failed") + .setContentText("$usbDevice may have been unplugged while writing.") + .setSubText(dt.toHRTime()) + else { + val speed = (dt.toDouble() / bytes.toDouble()).toHRSize() + "/s" + b.setContentTitle("Write finished") + .setContentText("$filename successfully written to $usbDevice") + .setSubText("${dt.toHRTime()} • ${bytes.toHRSize()} • $speed") + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) b.setSmallIcon(R.drawable.ic_usb_white_24dp) - notificationManager.notify(FOREGROUND_ID, b.build()) + notificationManager.notify(RESULT_NOTIFICATION_ID, b.build()) } - fun buildForegroundNotification(usbDevice: String, uri: Uri, bytes: Long, total: Long, subText: String? = null): Notification { + fun buildForegroundNotification(usbDevice: String?, filename: String?, bytes: Long, total: Long, subText: String? = null): Notification { val progr: Int val indet: Boolean @@ -81,17 +124,21 @@ abstract class UsbWriteService(name: String) : IntentService(name) { progr = 0 indet = true } else { - progr = (bytes.toFloat()/total * 100).toInt() + progr = (bytes.toFloat() / total * 100).toInt() indet = false } val b = getNotificationBuilder() - b.setContentTitle("Writing image") - .setContentText("${uri.getFileName(this)} to $usbDevice") + b.setContentTitle(getString(R.string.notif_writing_img)) .setOngoing(true) .setProgress(100, progr, indet) + if (usbDevice != null && filename != null) + b.setContentText("${filename} to $usbDevice") + else + b.setContentText(getString(R.string.notif_initializing)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) b.setSmallIcon(R.drawable.ic_usb_white_24dp) @@ -101,4 +148,31 @@ abstract class UsbWriteService(name: String) : IntentService(name) { return b.build() } + + fun wakeLock(acquire: Boolean) { + // Do not reacquire wakelock if timeout not expired + if (acquire && mWakeLock != null && wlAcquireTime > 0 && System.currentTimeMillis() < wlAcquireTime + WL_TIMEOUT - 5000) + return + + wlAcquireTime = if (acquire) + System.currentTimeMillis() + else + -1 + + val powerMgr = getSystemService(Context.POWER_SERVICE) as PowerManager + + powerMgr.run { + if (mWakeLock == null) + mWakeLock = newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG) + + mWakeLock.apply { + if (acquire) + this!!.acquire(WL_TIMEOUT /*10 minutes*/) + else + this!!.release() + } + } + + } + } \ No newline at end of file diff --git a/app/src/main/java/eu/depau/ddroid/services/UsbAPIWriteService.kt b/app/src/main/java/eu/depau/ddroid/services/UsbAPIWriteService.kt index 2e2a76f..2d5df85 100644 --- a/app/src/main/java/eu/depau/ddroid/services/UsbAPIWriteService.kt +++ b/app/src/main/java/eu/depau/ddroid/services/UsbAPIWriteService.kt @@ -6,6 +6,7 @@ import android.net.Uri import android.util.Log import com.github.mjdev.libaums.UsbMassStorageDevice import eu.depau.ddroid.abc.UsbWriteService +import eu.depau.ddroid.utils.getFileName import eu.depau.ddroid.utils.getFileSize import eu.depau.ddroid.utils.name import java.nio.ByteBuffer @@ -18,20 +19,6 @@ class UsbAPIWriteService : UsbWriteService("UsbAPIWriteService") { 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) @@ -43,34 +30,50 @@ class UsbAPIWriteService : UsbWriteService("UsbAPIWriteService") { return null } - private fun writeImage(usbDevice: UsbDevice, uri: Uri, notify: (Long, Long) -> Unit): Long { + override fun writeImage(intent: Intent): Long { + val uri: Uri = intent.data!! + val usbDevice: UsbDevice = intent.getParcelableExtra("usbDevice") + val msDev = getUsbMSDevice(usbDevice)!! msDev.init() val blockDev = msDev.blockDevice - var bsFactor = DD_BLOCKSIZE / blockDev.blockSize + val bsFactor = DD_BLOCKSIZE / blockDev.blockSize val byteBuffer = ByteBuffer.allocate(blockDev.blockSize * bsFactor) val imageSize = uri.getFileSize(this) val inputStream = contentResolver.openInputStream(uri)!! + val startTime = System.currentTimeMillis() + var readBytes: Int var offset = 0L + var writtenBytes: Long = 0 - while (true) { - readBytes = inputStream.read(byteBuffer.array()!!) - if (readBytes < 0) - break - byteBuffer.position(0) + try { + while (true) { + wakeLock(true) + readBytes = inputStream.read(byteBuffer.array()!!) + if (readBytes < 0) + break + byteBuffer.position(0) - blockDev.write(offset, byteBuffer) - offset += bsFactor + blockDev.write(offset, byteBuffer) + offset += bsFactor + writtenBytes += readBytes - notify(offset * blockDev.blockSize * bsFactor, imageSize) + updateNotification(usbDevice.name, uri.getFileName(this), offset * blockDev.blockSize, imageSize) + } + + resultNotification(usbDevice.name, uri.getFileName(this)!!, true, writtenBytes, startTime) + } catch (e: Exception) { + resultNotification(usbDevice.name, uri.getFileName(this)!!, false, writtenBytes, startTime) + Log.e(TAG, "Could't write image to ${usbDevice.name}") + throw e + } finally { + wakeLock(false) + msDev.close() } - msDev.close() - - val writtenBytes = offset * blockDev.blockSize Log.d(TAG, "Written $writtenBytes bytes to ${usbDevice.name} using API") return writtenBytes } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 165a215..1ba3bf5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,4 +25,8 @@ Image is bigger than the USB drive, so it can\'t be written Cannot read USB device Tap Next to write the image to the USB drive + USB write status + Used to display the status of images being written to USB drives + Initializing... + Writing image