Send notification on job completed

This commit is contained in:
Davide Depau 2018-08-14 18:49:22 +02:00
parent ebada3884a
commit 79f5828ef6
Signed by: depau
GPG key ID: C7D999B6A55EFE86
3 changed files with 127 additions and 46 deletions

View file

@ -4,46 +4,78 @@ import android.app.IntentService
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.PowerManager
import android.support.v4.app.NotificationCompat import android.support.v4.app.NotificationCompat
import eu.depau.ddroid.R import eu.depau.ddroid.R
import eu.depau.ddroid.utils.getFileName import eu.depau.ddroid.utils.getFileName
import eu.depau.ddroid.utils.toHRSize import eu.depau.ddroid.utils.toHRSize
import eu.depau.ddroid.utils.toHRTime
abstract class UsbWriteService(name: String) : IntentService(name) { abstract class UsbWriteService(name: String) : IntentService(name) {
val TAG = name val TAG = name
val FOREGROUND_ID = 1931 val FOREGROUND_ID = 1931
val RESULT_NOTIFICATION_ID = 3829
val WRITE_PROGRESS_CHANNEL_ID = "eu.depau.ddroid.notifications.USB_WRITE_PROGRESS" 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 prevTime = System.currentTimeMillis()
private var prevBytes = 0L private var prevBytes = 0L
private var notifyChanRegistered = false 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 (!notifyChanRegistered) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channame = "USB write progress" val statusChannel = NotificationChannel(
val description = "Displays the status of ongoing USB writes" WRITE_PROGRESS_CHANNEL_ID,
val importance = NotificationManager.IMPORTANCE_LOW getString(R.string.notchan_writestatus_title),
val channel = NotificationChannel(WRITE_PROGRESS_CHANNEL_ID, channame, importance) NotificationManager.IMPORTANCE_LOW
channel.description = description )
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 // Register the channel with the system; you can't change the importance
// or other notification behaviors after this // or other notification behaviors after this
val notificationManager = getSystemService(NotificationManager::class.java) val notificationManager = getSystemService(NotificationManager::class.java)
notificationManager!!.createNotificationChannel(channel) notificationManager!!.createNotificationChannel(statusChannel)
notificationManager!!.createNotificationChannel(resultChannel)
} }
notifyChanRegistered = true notifyChanRegistered = true
} }
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
NotificationCompat.Builder(this, WRITE_PROGRESS_CHANNEL_ID) NotificationCompat.Builder(this, channel)
else else
NotificationCompat.Builder(this) 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 // Notification rate limiting
val time = System.currentTimeMillis() val time = System.currentTimeMillis()
if (time <= prevTime + 1000) 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 perc: Int = (bytes.toDouble() / total * 100.0).toInt()
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager 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 notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val b = getNotificationBuilder() val b = getNotificationBuilder(WRITE_RESULT_CHANNEL_ID)
.setContentTitle("Write failed")
.setContentText("The USB drive may have been unplugged while writing.")
.setOngoing(false) .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) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
b.setSmallIcon(R.drawable.ic_usb_white_24dp) 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 progr: Int
val indet: Boolean val indet: Boolean
@ -87,11 +130,15 @@ abstract class UsbWriteService(name: String) : IntentService(name) {
val b = getNotificationBuilder() val b = getNotificationBuilder()
b.setContentTitle("Writing image") b.setContentTitle(getString(R.string.notif_writing_img))
.setContentText("${uri.getFileName(this)} to $usbDevice")
.setOngoing(true) .setOngoing(true)
.setProgress(100, progr, indet) .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) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
b.setSmallIcon(R.drawable.ic_usb_white_24dp) b.setSmallIcon(R.drawable.ic_usb_white_24dp)
@ -101,4 +148,31 @@ abstract class UsbWriteService(name: String) : IntentService(name) {
return b.build() 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()
}
}
}
} }

View file

@ -6,6 +6,7 @@ import android.net.Uri
import android.util.Log import android.util.Log
import com.github.mjdev.libaums.UsbMassStorageDevice import com.github.mjdev.libaums.UsbMassStorageDevice
import eu.depau.ddroid.abc.UsbWriteService import eu.depau.ddroid.abc.UsbWriteService
import eu.depau.ddroid.utils.getFileName
import eu.depau.ddroid.utils.getFileSize import eu.depau.ddroid.utils.getFileSize
import eu.depau.ddroid.utils.name import eu.depau.ddroid.utils.name
import java.nio.ByteBuffer import java.nio.ByteBuffer
@ -18,20 +19,6 @@ class UsbAPIWriteService : UsbWriteService("UsbAPIWriteService") {
val WRITE_CANCEL = "eu.depau.ddroid.action.API_WRITE_CANCEL" 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? { private fun getUsbMSDevice(usbDevice: UsbDevice): UsbMassStorageDevice? {
val msDevs = UsbMassStorageDevice.getMassStorageDevices(this) val msDevs = UsbMassStorageDevice.getMassStorageDevices(this)
@ -43,20 +30,28 @@ class UsbAPIWriteService : UsbWriteService("UsbAPIWriteService") {
return null 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)!! val msDev = getUsbMSDevice(usbDevice)!!
msDev.init() msDev.init()
val blockDev = msDev.blockDevice val blockDev = msDev.blockDevice
var bsFactor = DD_BLOCKSIZE / blockDev.blockSize val bsFactor = DD_BLOCKSIZE / blockDev.blockSize
val byteBuffer = ByteBuffer.allocate(blockDev.blockSize * bsFactor) val byteBuffer = ByteBuffer.allocate(blockDev.blockSize * bsFactor)
val imageSize = uri.getFileSize(this) val imageSize = uri.getFileSize(this)
val inputStream = contentResolver.openInputStream(uri)!! val inputStream = contentResolver.openInputStream(uri)!!
val startTime = System.currentTimeMillis()
var readBytes: Int var readBytes: Int
var offset = 0L var offset = 0L
var writtenBytes: Long = 0
try {
while (true) { while (true) {
wakeLock(true)
readBytes = inputStream.read(byteBuffer.array()!!) readBytes = inputStream.read(byteBuffer.array()!!)
if (readBytes < 0) if (readBytes < 0)
break break
@ -64,13 +59,21 @@ class UsbAPIWriteService : UsbWriteService("UsbAPIWriteService") {
blockDev.write(offset, byteBuffer) blockDev.write(offset, byteBuffer)
offset += bsFactor 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") Log.d(TAG, "Written $writtenBytes bytes to ${usbDevice.name} using API")
return writtenBytes return writtenBytes
} }

View file

@ -25,4 +25,8 @@
<string name="image_bigger_than_usb">Image is bigger than the USB drive, so it can\'t be written</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="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> <string name="tap_next_to_write">Tap Next to write the image to the USB drive</string>
<string name="notchan_writestatus_title">USB write status</string>
<string name="notchan_writestatus_desc">Used to display the status of images being written to USB drives</string>
<string name="notif_initializing">Initializing...</string>
<string name="notif_writing_img">Writing image</string>
</resources> </resources>