EtchDroid/app/src/main/java/eu/depau/etchdroid/abc/UsbWriteService.kt

176 lines
6.4 KiB
Kotlin

package eu.depau.etchdroid.abc
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.os.Build
import android.os.PowerManager
import android.support.v4.app.NotificationCompat
import eu.depau.etchdroid.R
import eu.depau.etchdroid.utils.toHRSize
import eu.depau.etchdroid.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.etchdroid.notifications.USB_WRITE_PROGRESS"
val WRITE_RESULT_CHANNEL_ID = "eu.depau.etchdroid.notifications.USB_WRITE_RESULT"
val WAKELOCK_TAG = "eu.depau.etchdroid.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
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 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(statusChannel)
notificationManager!!.createNotificationChannel(resultChannel)
}
notifyChanRegistered = true
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
NotificationCompat.Builder(this, channel)
else
NotificationCompat.Builder(this)
}
fun updateNotification(usbDevice: String, filename: String?, bytes: Long, total: Long) {
// Notification rate limiting
val time = System.currentTimeMillis()
if (time <= prevTime + 1000)
return
val speed = ((bytes - prevBytes).toDouble() / (time - prevTime).toDouble() * 1000).toHRSize()
prevTime = time
prevBytes = bytes
val perc: Int = (bytes.toDouble() / total * 100.0).toInt()
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(FOREGROUND_ID, buildForegroundNotification(usbDevice, filename, bytes, total, "$perc% • $speed/s"))
}
fun resultNotification(usbDevice: String, filename: String, success: Boolean, bytes: Long = 0, startTime: Long = 0) {
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
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 = (bytes.toDouble() / dt.toDouble() * 1000).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(RESULT_NOTIFICATION_ID, b.build())
}
fun buildForegroundNotification(usbDevice: String?, filename: String?, bytes: Long, total: Long, subText: String? = null): Notification {
val progr: Int
val indet: Boolean
if (total < 0) {
progr = 0
indet = true
} else {
progr = (bytes.toFloat() / total * 100).toInt()
indet = false
}
val b = getNotificationBuilder()
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)
if (subText != null)
b.setSubText(subText)
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()
}
}
}
}