Notification Improvements (#594)
* Download notifier improvements * Notification improvements Added a Notification Service. Added a Notification Activity Handler. * Removed service. Everything is now managed by single broadcast * Fixed some flags * Fixed ReaderActivity call * Code review * Added Handler. Removed dismiss onDestroy
This commit is contained in:
committed by
inorichi
parent
52c50398b8
commit
c445ea90ba
@@ -1,5 +1,7 @@
|
||||
package eu.kanade.tachiyomi.data.updater
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import com.evernote.android.job.Job
|
||||
import com.evernote.android.job.JobManager
|
||||
@@ -17,6 +19,10 @@ class UpdateCheckerJob : Job() {
|
||||
if (result is GithubUpdateResult.NewUpdate) {
|
||||
val url = result.release.downloadLink
|
||||
|
||||
val intent = Intent(context, UpdateDownloaderService::class.java).apply {
|
||||
putExtra(UpdateDownloaderService.EXTRA_DOWNLOAD_URL, url)
|
||||
}
|
||||
|
||||
NotificationCompat.Builder(context).update {
|
||||
setContentTitle(context.getString(R.string.app_name))
|
||||
setContentText(context.getString(R.string.update_check_notification_update_available))
|
||||
@@ -24,7 +30,7 @@ class UpdateCheckerJob : Job() {
|
||||
// Download action
|
||||
addAction(android.R.drawable.stat_sys_download_done,
|
||||
context.getString(R.string.action_download),
|
||||
UpdateNotificationReceiver.downloadApkIntent(context, url))
|
||||
PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
}
|
||||
}
|
||||
Job.Result.SUCCESS
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
package eu.kanade.tachiyomi.data.updater
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import eu.kanade.tachiyomi.Constants
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationHandler
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
import eu.kanade.tachiyomi.util.notificationManager
|
||||
import java.io.File
|
||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
||||
|
||||
/**
|
||||
* Local [BroadcastReceiver] that runs on UI thread
|
||||
* Notification calls from [UpdateDownloaderService] should be made from here.
|
||||
*/
|
||||
internal class UpdateDownloaderReceiver(val context: Context) : BroadcastReceiver() {
|
||||
|
||||
companion object {
|
||||
private const val NAME = "UpdateDownloaderReceiver"
|
||||
|
||||
// Called to show initial notification.
|
||||
internal const val NOTIFICATION_UPDATER_INITIAL = "$ID.$NAME.UPDATER_INITIAL"
|
||||
|
||||
// Called to show progress notification.
|
||||
internal const val NOTIFICATION_UPDATER_PROGRESS = "$ID.$NAME.UPDATER_PROGRESS"
|
||||
|
||||
// Called to show install notification.
|
||||
internal const val NOTIFICATION_UPDATER_INSTALL = "$ID.$NAME.UPDATER_INSTALL"
|
||||
|
||||
// Called to show error notification
|
||||
internal const val NOTIFICATION_UPDATER_ERROR = "$ID.$NAME.UPDATER_ERROR"
|
||||
|
||||
// Value containing action of BroadcastReceiver
|
||||
internal const val EXTRA_ACTION = "$ID.$NAME.ACTION"
|
||||
|
||||
// Value containing progress
|
||||
internal const val EXTRA_PROGRESS = "$ID.$NAME.PROGRESS"
|
||||
|
||||
// Value containing apk path
|
||||
internal const val EXTRA_APK_PATH = "$ID.$NAME.APK_PATH"
|
||||
|
||||
// Value containing apk url
|
||||
internal const val EXTRA_APK_URL = "$ID.$NAME.APK_URL"
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification shown to user
|
||||
*/
|
||||
private val notification = NotificationCompat.Builder(context)
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.getStringExtra(EXTRA_ACTION)) {
|
||||
NOTIFICATION_UPDATER_INITIAL -> basicNotification()
|
||||
NOTIFICATION_UPDATER_PROGRESS -> updateProgress(intent.getIntExtra(EXTRA_PROGRESS, 0))
|
||||
NOTIFICATION_UPDATER_INSTALL -> installNotification(intent.getStringExtra(EXTRA_APK_PATH))
|
||||
NOTIFICATION_UPDATER_ERROR -> errorNotification(intent.getStringExtra(EXTRA_APK_URL))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to show basic notification
|
||||
*/
|
||||
private fun basicNotification() {
|
||||
// Create notification
|
||||
with(notification) {
|
||||
setContentTitle(context.getString(R.string.app_name))
|
||||
setContentText(context.getString(R.string.update_check_notification_download_in_progress))
|
||||
setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
setOngoing(true)
|
||||
}
|
||||
notification.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to show progress notification
|
||||
*
|
||||
* @param progress progress of download
|
||||
*/
|
||||
private fun updateProgress(progress: Int) {
|
||||
with(notification) {
|
||||
setProgress(100, progress, false)
|
||||
}
|
||||
notification.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to show install notification
|
||||
*
|
||||
* @param path path of file
|
||||
*/
|
||||
private fun installNotification(path: String) {
|
||||
// Prompt the user to install the new update.
|
||||
with(notification) {
|
||||
setContentText(context.getString(R.string.update_check_notification_download_complete))
|
||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
setProgress(0, 0, false)
|
||||
// Install action
|
||||
setContentIntent(NotificationHandler.installApkPendingActivity(context, File(path)))
|
||||
addAction(R.drawable.ic_system_update_grey_24dp_img,
|
||||
context.getString(R.string.action_install),
|
||||
NotificationHandler.installApkPendingActivity(context, File(path)))
|
||||
// Cancel action
|
||||
addAction(R.drawable.ic_clear_grey_24dp_img,
|
||||
context.getString(R.string.action_cancel),
|
||||
NotificationReceiver.dismissNotificationPendingBroadcast(context, Constants.NOTIFICATION_UPDATER_ID))
|
||||
}
|
||||
notification.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to show error notification
|
||||
*
|
||||
* @param url url of apk
|
||||
*/
|
||||
private fun errorNotification(url: String) {
|
||||
// Prompt the user to retry the download.
|
||||
with(notification) {
|
||||
setContentText(context.getString(R.string.update_check_notification_download_error))
|
||||
setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||
setProgress(0, 0, false)
|
||||
// Retry action
|
||||
addAction(R.drawable.ic_refresh_grey_24dp_img,
|
||||
context.getString(R.string.action_retry),
|
||||
UpdateDownloaderService.downloadApkPendingService(context, url))
|
||||
// Cancel action
|
||||
addAction(R.drawable.ic_clear_grey_24dp_img,
|
||||
context.getString(R.string.action_cancel),
|
||||
NotificationReceiver.dismissNotificationPendingBroadcast(context, Constants.NOTIFICATION_UPDATER_ID))
|
||||
}
|
||||
notification.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a notification from this builder.
|
||||
*
|
||||
* @param id the id of the notification.
|
||||
*/
|
||||
private fun NotificationCompat.Builder.show(id: Int = Constants.NOTIFICATION_UPDATER_ID) {
|
||||
context.notificationManager.notify(id, build())
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,160 @@
|
||||
package eu.kanade.tachiyomi.data.updater
|
||||
|
||||
import android.app.IntentService
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import eu.kanade.tachiyomi.Constants.NOTIFICATION_UPDATER_ID
|
||||
import eu.kanade.tachiyomi.R
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.network.GET
|
||||
import eu.kanade.tachiyomi.data.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.data.network.ProgressListener
|
||||
import eu.kanade.tachiyomi.data.network.newCallWithProgress
|
||||
import eu.kanade.tachiyomi.util.notificationManager
|
||||
import eu.kanade.tachiyomi.util.registerLocalReceiver
|
||||
import eu.kanade.tachiyomi.util.saveTo
|
||||
import eu.kanade.tachiyomi.util.sendLocalBroadcastSync
|
||||
import eu.kanade.tachiyomi.util.unregisterLocalReceiver
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
|
||||
class UpdateDownloaderService : IntentService(UpdateDownloaderService::class.java.name) {
|
||||
/**
|
||||
* Network helper
|
||||
*/
|
||||
private val network: NetworkHelper by injectLazy()
|
||||
|
||||
/**
|
||||
* Local [BroadcastReceiver] that runs on UI thread
|
||||
*/
|
||||
private val updaterNotificationReceiver = UpdateDownloaderReceiver(this)
|
||||
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
// Register receiver
|
||||
registerLocalReceiver(updaterNotificationReceiver, IntentFilter(INTENT_FILTER_NAME))
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
// Unregister receiver
|
||||
unregisterLocalReceiver(updaterNotificationReceiver)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onHandleIntent(intent: Intent?) {
|
||||
if (intent == null) return
|
||||
|
||||
val url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return
|
||||
downloadApk(url)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to start downloading apk of new update
|
||||
*
|
||||
* @param url url location of file
|
||||
*/
|
||||
fun downloadApk(url: String) {
|
||||
// Show notification download starting.
|
||||
sendInitialBroadcast()
|
||||
// Progress of the download
|
||||
var savedProgress = 0
|
||||
|
||||
val progressListener = object : ProgressListener {
|
||||
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
|
||||
val progress = (100 * bytesRead / contentLength).toInt()
|
||||
if (progress > savedProgress) {
|
||||
savedProgress = progress
|
||||
sendProgressBroadcast(progress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Download the new update.
|
||||
val response = network.client.newCallWithProgress(GET(url), progressListener).execute()
|
||||
|
||||
// File where the apk will be saved.
|
||||
val apkFile = File(externalCacheDir, "update.apk")
|
||||
|
||||
if (response.isSuccessful) {
|
||||
response.body().source().saveTo(apkFile)
|
||||
} else {
|
||||
response.close()
|
||||
throw Exception("Unsuccessful response")
|
||||
}
|
||||
sendInstallBroadcast(apkFile.absolutePath)
|
||||
} catch (error: Exception) {
|
||||
Timber.e(error)
|
||||
sendErrorBroadcast(url)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show notification download starting.
|
||||
*/
|
||||
private fun sendInitialBroadcast() {
|
||||
val intent = Intent(INTENT_FILTER_NAME).apply {
|
||||
putExtra(UpdateDownloaderReceiver.EXTRA_ACTION, UpdateDownloaderReceiver.NOTIFICATION_UPDATER_INITIAL)
|
||||
}
|
||||
sendLocalBroadcastSync(intent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Show notification progress changed
|
||||
*
|
||||
* @param progress progress of download
|
||||
*/
|
||||
private fun sendProgressBroadcast(progress: Int) {
|
||||
val intent = Intent(INTENT_FILTER_NAME).apply {
|
||||
putExtra(UpdateDownloaderReceiver.EXTRA_ACTION, UpdateDownloaderReceiver.NOTIFICATION_UPDATER_PROGRESS)
|
||||
putExtra(UpdateDownloaderReceiver.EXTRA_PROGRESS, progress)
|
||||
}
|
||||
// Prevents not showing of install notification TODO weird Android N bug. Find out what goes wrong
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || progress <= 95) {
|
||||
// Show download progress notification.
|
||||
sendLocalBroadcastSync(intent)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show install notification.
|
||||
*
|
||||
* @param path location of file
|
||||
*/
|
||||
private fun sendInstallBroadcast(path: String){
|
||||
val intent = Intent(INTENT_FILTER_NAME).apply {
|
||||
putExtra(UpdateDownloaderReceiver.EXTRA_ACTION, UpdateDownloaderReceiver.NOTIFICATION_UPDATER_INSTALL)
|
||||
putExtra(UpdateDownloaderReceiver.EXTRA_APK_PATH, path)
|
||||
}
|
||||
sendLocalBroadcastSync(intent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Show error notification.
|
||||
*
|
||||
* @param url url of file
|
||||
*/
|
||||
private fun sendErrorBroadcast(url: String){
|
||||
val intent = Intent(INTENT_FILTER_NAME).apply {
|
||||
putExtra(UpdateDownloaderReceiver.EXTRA_ACTION, UpdateDownloaderReceiver.NOTIFICATION_UPDATER_ERROR)
|
||||
putExtra(UpdateDownloaderReceiver.EXTRA_APK_URL, url)
|
||||
}
|
||||
sendLocalBroadcastSync(intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Name of Local BroadCastReceiver.
|
||||
*/
|
||||
private val INTENT_FILTER_NAME = UpdateDownloaderService::class.java.name
|
||||
|
||||
/**
|
||||
* Download url.
|
||||
*/
|
||||
const val EXTRA_DOWNLOAD_URL = "eu.kanade.APP_DOWNLOAD_URL"
|
||||
internal const val EXTRA_DOWNLOAD_URL = "${BuildConfig.APPLICATION_ID}.UpdateDownloaderService.DOWNLOAD_URL"
|
||||
|
||||
/**
|
||||
* Downloads a new update and let the user install the new version from a notification.
|
||||
@@ -35,102 +167,20 @@ class UpdateDownloaderService : IntentService(UpdateDownloaderService::class.jav
|
||||
}
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Network helper
|
||||
*/
|
||||
private val network: NetworkHelper by injectLazy()
|
||||
|
||||
override fun onHandleIntent(intent: Intent?) {
|
||||
if (intent == null) return
|
||||
|
||||
val url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return
|
||||
downloadApk(url)
|
||||
}
|
||||
|
||||
fun downloadApk(url: String) {
|
||||
val progressNotification = NotificationCompat.Builder(this)
|
||||
|
||||
progressNotification.update {
|
||||
setContentTitle(getString(R.string.app_name))
|
||||
setContentText(getString(R.string.update_check_notification_download_in_progress))
|
||||
setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
setOngoing(true)
|
||||
}
|
||||
|
||||
// Progress of the download
|
||||
var savedProgress = 0
|
||||
|
||||
val progressListener = object : ProgressListener {
|
||||
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
|
||||
val progress = (100 * bytesRead / contentLength).toInt()
|
||||
if (progress > savedProgress) {
|
||||
savedProgress = progress
|
||||
|
||||
progressNotification.update { setProgress(100, progress, false) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reference the context for later usage inside apply blocks.
|
||||
val ctx = this
|
||||
|
||||
try {
|
||||
// Download the new update.
|
||||
val response = network.client.newCallWithProgress(GET(url), progressListener).execute()
|
||||
|
||||
// File where the apk will be saved
|
||||
val apkFile = File(externalCacheDir, "update.apk")
|
||||
|
||||
if (response.isSuccessful) {
|
||||
response.body().source().saveTo(apkFile)
|
||||
} else {
|
||||
response.close()
|
||||
throw Exception("Unsuccessful response")
|
||||
}
|
||||
|
||||
val installIntent = UpdateNotificationReceiver.installApkIntent(ctx, apkFile)
|
||||
|
||||
// Prompt the user to install the new update.
|
||||
NotificationCompat.Builder(this).update {
|
||||
setContentTitle(getString(R.string.app_name))
|
||||
setContentText(getString(R.string.update_check_notification_download_complete))
|
||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
// Install action
|
||||
setContentIntent(installIntent)
|
||||
addAction(R.drawable.ic_system_update_grey_24dp_img,
|
||||
getString(R.string.action_install),
|
||||
installIntent)
|
||||
// Cancel action
|
||||
addAction(R.drawable.ic_clear_grey_24dp_img,
|
||||
getString(R.string.action_cancel),
|
||||
UpdateNotificationReceiver.cancelNotificationIntent(ctx))
|
||||
}
|
||||
|
||||
} catch (error: Exception) {
|
||||
Timber.e(error)
|
||||
|
||||
// Prompt the user to retry the download.
|
||||
NotificationCompat.Builder(this).update {
|
||||
setContentTitle(getString(R.string.app_name))
|
||||
setContentText(getString(R.string.update_check_notification_download_error))
|
||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
// Retry action
|
||||
addAction(R.drawable.ic_refresh_grey_24dp_img,
|
||||
getString(R.string.action_retry),
|
||||
UpdateNotificationReceiver.downloadApkIntent(ctx, url))
|
||||
// Cancel action
|
||||
addAction(R.drawable.ic_clear_grey_24dp_img,
|
||||
getString(R.string.action_cancel),
|
||||
UpdateNotificationReceiver.cancelNotificationIntent(ctx))
|
||||
/**
|
||||
* Returns [PendingIntent] that starts a service which downloads the apk specified in url.
|
||||
*
|
||||
* @param url the url to the new update.
|
||||
* @return [PendingIntent]
|
||||
*/
|
||||
internal fun downloadApkPendingService(context: Context, url: String): PendingIntent {
|
||||
val intent = Intent(context, UpdateDownloaderService::class.java).apply {
|
||||
putExtra(EXTRA_DOWNLOAD_URL, url)
|
||||
}
|
||||
return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun NotificationCompat.Builder.update(block: NotificationCompat.Builder.() -> Unit) {
|
||||
block()
|
||||
notificationManager.notify(NOTIFICATION_UPDATER_ID, build())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package eu.kanade.tachiyomi.data.updater
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.support.v4.content.FileProvider
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.Constants.NOTIFICATION_UPDATER_ID
|
||||
import eu.kanade.tachiyomi.util.notificationManager
|
||||
import java.io.File
|
||||
|
||||
class UpdateNotificationReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action) {
|
||||
ACTION_CANCEL_NOTIFICATION -> cancelNotification(context)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Cancel notification action
|
||||
const val ACTION_CANCEL_NOTIFICATION = "eu.kanade.CANCEL_NOTIFICATION"
|
||||
|
||||
fun cancelNotificationIntent(context: Context): PendingIntent {
|
||||
val intent = Intent(context, UpdateNotificationReceiver::class.java).apply {
|
||||
action = ACTION_CANCEL_NOTIFICATION
|
||||
}
|
||||
return PendingIntent.getBroadcast(context, 0, intent, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt user with apk install intent
|
||||
*
|
||||
* @param context context
|
||||
* @param file file of apk that is installed
|
||||
*/
|
||||
fun installApkIntent(context: Context, file: File): PendingIntent {
|
||||
val intent = Intent(Intent.ACTION_VIEW).apply {
|
||||
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||
FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file)
|
||||
else Uri.fromFile(file)
|
||||
setDataAndType(uri, "application/vnd.android.package-archive")
|
||||
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
}
|
||||
cancelNotification(context)
|
||||
return PendingIntent.getActivity(context, 0, intent, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a new update and let the user install the new version from a notification.
|
||||
*
|
||||
* @param context the application context.
|
||||
* @param url the url to the new update.
|
||||
*/
|
||||
fun downloadApkIntent(context: Context, url: String): PendingIntent {
|
||||
val intent = Intent(context, UpdateDownloaderService::class.java).apply {
|
||||
putExtra(UpdateDownloaderService.EXTRA_DOWNLOAD_URL, url)
|
||||
}
|
||||
return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
}
|
||||
|
||||
fun cancelNotification(context: Context) {
|
||||
context.notificationManager.cancel(NOTIFICATION_UPDATER_ID)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user