Skip to content

Commit

Permalink
Add Cancel button to update notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
ztimms73 committed May 30, 2023
1 parent d33a071 commit 2f41549
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import org.xtimms.ridebus.data.updater.app.AppUpdateService
import org.xtimms.ridebus.data.updater.database.DatabaseUpdateService
import org.xtimms.ridebus.util.system.notificationManager
import org.xtimms.ridebus.BuildConfig.APPLICATION_ID as ID

Expand All @@ -14,16 +16,30 @@ class NotificationReceiver : BroadcastReceiver() {
when (intent.action) {
// Dismiss notification
ACTION_DISMISS_NOTIFICATION -> dismissNotification(context, intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1))
// Cancel downloading app update
ACTION_CANCEL_APP_UPDATE_DOWNLOAD -> cancelDownloadAppUpdate(context)
ACTION_CANCEL_DATABASE_UPDATE_DOWNLOAD -> cancelDownloadDatabaseUpdate(context)
}
}

private fun cancelDownloadAppUpdate(context: Context) {
AppUpdateService.stop(context)
}

private fun cancelDownloadDatabaseUpdate(context: Context) {
DatabaseUpdateService.stop(context)
}

companion object {
private const val NAME = "NotificationReceiver"

private const val ACTION_DISMISS_NOTIFICATION = "$ID.$NAME.ACTION_DISMISS_NOTIFICATION"

private const val EXTRA_NOTIFICATION_ID = "$ID.$NAME.NOTIFICATION_ID"

private const val ACTION_CANCEL_APP_UPDATE_DOWNLOAD = "$ID.$NAME.CANCEL_APP_UPDATE_DOWNLOAD"
private const val ACTION_CANCEL_DATABASE_UPDATE_DOWNLOAD = "$ID.$NAME.CANCEL_DATABASE_UPDATE_DOWNLOAD"

/**
* Returns [PendingIntent] that starts a service which dismissed the notification
*
Expand Down Expand Up @@ -66,5 +82,12 @@ class NotificationReceiver : BroadcastReceiver() {

context.notificationManager.cancel(notificationId)
}

internal fun cancelUpdateDownloadPendingBroadcast(context: Context): PendingIntent {
val intent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_CANCEL_APP_UPDATE_DOWNLOAD
}
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ internal class AppUpdateNotifier(private val context: Context) {
context.notificationManager.notify(id, build())
}

fun cancel() {
NotificationReceiver.dismissNotification(context, Notifications.ID_APP_UPDATER)
}

fun promptUpdate(release: GithubRelease) {
val intent = Intent(context, AppUpdateService::class.java).apply {
putExtra(AppUpdateService.EXTRA_DOWNLOAD_URL, release.getDownloadLink())
Expand Down Expand Up @@ -59,6 +63,13 @@ internal class AppUpdateNotifier(private val context: Context) {
setContentText(context.getString(R.string.update_check_notification_download_in_progress))
setSmallIcon(android.R.drawable.stat_sys_download)
setOngoing(true)

clearActions()
addAction(
R.drawable.ic_close,
context.getString(R.string.action_cancel),
NotificationReceiver.cancelUpdateDownloadPendingBroadcast(context),
)
}
notificationBuilder.show()
return notificationBuilder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ import android.content.Intent
import android.os.IBinder
import android.os.PowerManager
import androidx.core.content.ContextCompat
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import logcat.LogPriority
import okhttp3.internal.http2.ErrorCode
import okhttp3.internal.http2.StreamResetException
import org.xtimms.ridebus.BuildConfig
import org.xtimms.ridebus.R
import org.xtimms.ridebus.data.notification.Notifications
Expand All @@ -29,13 +37,16 @@ class AppUpdateService : Service() {

private val network: NetworkHelper by injectLazy()

/**
* Wake lock that will be held until the service is destroyed.
*/
private lateinit var wakeLock: PowerManager.WakeLock

private lateinit var notifier: AppUpdateNotifier

override fun onCreate() {
super.onCreate()
private val job = SupervisorJob()
private val serviceScope = CoroutineScope(Dispatchers.IO + job)

override fun onCreate() {
notifier = AppUpdateNotifier(this)
wakeLock = acquireWakeLock(javaClass.name)

Expand All @@ -50,11 +61,11 @@ class AppUpdateService : Service() {
val url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return START_NOT_STICKY
val title = intent.getStringExtra(EXTRA_DOWNLOAD_TITLE) ?: getString(R.string.app_name)

launchIO {
serviceScope.launch {
downloadApk(title, url)
}

stopSelf(startId)
job.invokeOnCompletion { stopSelf(startId) }
return START_NOT_STICKY
}

Expand All @@ -65,10 +76,11 @@ class AppUpdateService : Service() {

override fun onDestroy() {
destroyJob()
super.onDestroy()
}

private fun destroyJob() {
serviceScope.cancel()
job.cancel()
if (wakeLock.isHeld) {
wakeLock.release()
}
Expand All @@ -92,9 +104,8 @@ class AppUpdateService : Service() {
}
}

val response = network.client.newCallWithProgress(GET(url), progressListener).await()

try {
val response = network.client.newCallWithProgress(GET(url), progressListener).await()
val apkFile = File(externalCacheDir, "update.apk")

if (response.isSuccessful) {
Expand All @@ -105,36 +116,54 @@ class AppUpdateService : Service() {
}
notifier.onDownloadFinished(apkFile.getUriCompat(this))
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
notifier.onDownloadError(url)
} finally {
response.close()
val shouldCancel = e is CancellationException ||
(e is StreamResetException && e.errorCode == ErrorCode.CANCEL)
if (shouldCancel) {
notifier.cancel()
} else {
notifier.onDownloadError(url)
}
}
}

companion object {

internal const val EXTRA_DOWNLOAD_URL = "${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_URL"
internal const val EXTRA_DOWNLOAD_TITLE = "${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_TITLE"
internal const val EXTRA_DOWNLOAD_URL =
"${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_URL"
internal const val EXTRA_DOWNLOAD_TITLE =
"${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_TITLE"

private fun isRunning(context: Context): Boolean =
context.isServiceRunning(AppUpdateService::class.java)

fun start(context: Context, url: String, title: String = context.getString(R.string.app_name)) {
if (!isRunning(context)) {
val intent = Intent(context, AppUpdateService::class.java).apply {
putExtra(EXTRA_DOWNLOAD_TITLE, title)
putExtra(EXTRA_DOWNLOAD_URL, url)
}
ContextCompat.startForegroundService(context, intent)
fun start(
context: Context,
url: String,
title: String = context.getString(R.string.app_name)
) {
if (isRunning(context)) return

Intent(context, AppUpdateService::class.java).apply {
putExtra(EXTRA_DOWNLOAD_TITLE, title)
putExtra(EXTRA_DOWNLOAD_URL, url)
ContextCompat.startForegroundService(context, this)
}
}

fun stop(context: Context) {
context.stopService(Intent(context, AppUpdateService::class.java))
}

internal fun downloadApkPendingService(context: Context, url: String): PendingIntent {
val intent = Intent(context, AppUpdateService::class.java).apply {
putExtra(EXTRA_DOWNLOAD_URL, url)
}
return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
return PendingIntent.getService(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ class DatabaseUpdateNotifier(private val context: Context) {
context.notificationManager.notify(id, build())
}

fun cancel() {
NotificationReceiver.dismissNotification(context, Notifications.ID_APP_UPDATER)
}

@SuppressLint("LaunchActivityFromNotification")
fun promptUpdate(release: GithubDatabase) {
val intent = Intent(context, DatabaseUpdateService::class.java).apply {
Expand Down Expand Up @@ -81,6 +85,13 @@ class DatabaseUpdateNotifier(private val context: Context) {
setContentText(context.getString(R.string.update_check_notification_download_in_progress))
setSmallIcon(android.R.drawable.stat_sys_download)
setOngoing(true)

clearActions()
addAction(
R.drawable.ic_close,
context.getString(R.string.action_cancel),
NotificationReceiver.cancelUpdateDownloadPendingBroadcast(context),
)
}
notificationBuilder.show()
return notificationBuilder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ import android.content.Intent
import android.os.IBinder
import android.os.PowerManager
import androidx.core.content.ContextCompat
import logcat.LogPriority
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import okhttp3.internal.http2.ErrorCode
import okhttp3.internal.http2.StreamResetException
import org.xtimms.ridebus.BuildConfig
import org.xtimms.ridebus.R
import org.xtimms.ridebus.data.database.RideBusDatabase
Expand All @@ -18,30 +25,26 @@ import org.xtimms.ridebus.network.NetworkHelper
import org.xtimms.ridebus.network.ProgressListener
import org.xtimms.ridebus.network.await
import org.xtimms.ridebus.network.newCallWithProgress
import org.xtimms.ridebus.util.lang.launchIO
import org.xtimms.ridebus.util.lang.withIOContext
import org.xtimms.ridebus.util.storage.saveTo
import org.xtimms.ridebus.util.system.acquireWakeLock
import org.xtimms.ridebus.util.system.isServiceRunning
import org.xtimms.ridebus.util.system.logcat
import uy.kohesive.injekt.injectLazy
import java.io.File

class DatabaseUpdateService : Service() {

private val network: NetworkHelper by injectLazy()

private val preferences: PreferencesHelper by injectLazy()

private val database: RideBusDatabase by injectLazy()

private lateinit var wakeLock: PowerManager.WakeLock

private lateinit var notifier: DatabaseUpdateNotifier

override fun onCreate() {
super.onCreate()
private val job = SupervisorJob()
private val serviceScope = CoroutineScope(Dispatchers.IO + job)

override fun onCreate() {
notifier = DatabaseUpdateNotifier(this)
wakeLock = acquireWakeLock(javaClass.name)

Expand All @@ -57,11 +60,11 @@ class DatabaseUpdateService : Service() {
val title = intent.getStringExtra(EXTRA_DOWNLOAD_TITLE) ?: getString(R.string.app_name)
val version = intent.getStringExtra(EXTRA_DOWNLOAD_VERSION) ?: BuildConfig.DATABASE_VERSION

launchIO {
serviceScope.launch {
downloadDatabase(title, url, version)
}

stopSelf(startId)
job.invokeOnCompletion { stopSelf(startId) }
return START_NOT_STICKY
}

Expand All @@ -72,10 +75,11 @@ class DatabaseUpdateService : Service() {

override fun onDestroy() {
destroyJob()
super.onDestroy()
}

private fun destroyJob() {
serviceScope.cancel()
job.cancel()
if (wakeLock.isHeld) {
wakeLock.release()
}
Expand All @@ -99,9 +103,9 @@ class DatabaseUpdateService : Service() {
}
}

val response = network.client.newCallWithProgress(GET(url), progressListener).await()

try {
val response = network.client.newCallWithProgress(GET(url), progressListener).await()

val databasePath = database.openHelper.writableDatabase.path.replace("ridebus.db", "")
val oldDatabaseFile = withIOContext { File(databasePath, "ridebus.db") }
val databaseFile = withIOContext { File(databasePath, "update.db") }
Expand All @@ -122,11 +126,13 @@ class DatabaseUpdateService : Service() {
}
notifier.onDownloadFinished(title)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
notifier.onDownloadError(url, version)
} finally {
response.close()
database.openHelper.writableDatabase.endTransaction()
val shouldCancel = e is CancellationException ||
(e is StreamResetException && e.errorCode == ErrorCode.CANCEL)
if (shouldCancel) {
notifier.cancel()
} else {
notifier.onDownloadError(url, version)
}
}
}

Expand Down Expand Up @@ -158,6 +164,10 @@ class DatabaseUpdateService : Service() {
}
}

fun stop(context: Context) {
context.stopService(Intent(context, DatabaseUpdateService::class.java))
}

internal fun downloadDatabasePendingService(
context: Context,
url: String,
Expand All @@ -171,7 +181,7 @@ class DatabaseUpdateService : Service() {
context,
0,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}
}
Expand Down

0 comments on commit 2f41549

Please sign in to comment.