Send notification on job completed

This commit is contained in:
Davide Depau 2018-08-14 18:49:22 +02:00
parent 49a344a61d
commit 4ec84a8ff0
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.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()
}
}
}
}

View file

@ -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
}

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="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="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>