From ccdc3361125aba856977e8db05993299e2ac59ed Mon Sep 17 00:00:00 2001 From: inorichi Date: Wed, 7 Sep 2016 19:44:55 +0200 Subject: [PATCH] Complete auto updates checker (#449) * Complete auto updates checker * Use GcmTaskService for the periodical updates checker * Persist task across reinstalls * Hide setting instead of disabling * Minor refactor --- app/build.gradle | 2 + app/src/main/AndroidManifest.xml | 27 +-- .../data/preference/PreferenceKeys.kt | 2 +- .../data/preference/PreferencesHelper.kt | 2 +- .../tachiyomi/data/updater/GithubService.kt | 1 - .../data/updater/GithubUpdateChecker.kt | 23 +- .../data/updater/GithubUpdateResult.kt | 7 + .../data/updater/UpdateCheckerService.kt | 80 +++++++ .../data/updater/UpdateDownloader.kt | 202 ------------------ .../data/updater/UpdateDownloaderAlarm.kt | 110 ---------- .../data/updater/UpdateDownloaderService.kt | 149 +++++++++++++ .../updater/UpdateNotificationReceiver.kt | 67 ++++++ .../ui/setting/SettingsAboutFragment.kt | 82 +++---- app/src/main/res/xml/pref_about.xml | 11 +- 14 files changed, 384 insertions(+), 381 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateResult.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateCheckerService.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderAlarm.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateNotificationReceiver.kt diff --git a/app/build.gradle b/app/build.gradle index b178c3a6f..a60ef1f04 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -96,6 +96,8 @@ dependencies { compile "com.android.support:support-annotations:$support_library_version" compile "com.android.support:customtabs:$support_library_version" + compile 'com.google.android.gms:play-services-gcm:9.4.0' + // ReactiveX compile 'io.reactivex:rxandroid:1.2.1' compile 'io.reactivex:rxjava:1.1.8' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 503af4860..3c8f5400d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -59,6 +59,20 @@ + + + + + + + + + + @@ -79,10 +93,6 @@ android:name=".data.library.LibraryUpdateService$CancelUpdateReceiver"> - - - @@ -91,15 +101,6 @@ - - - - - - - - diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index 605e927e5..c575c96e9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -78,7 +78,7 @@ class PreferenceKeys(context: Context) { val filterUnread = context.getString(R.string.pref_filter_unread_key) - val automaticUpdateStatus = context.getString(R.string.pref_enable_automatic_updates_key) + val automaticUpdates = context.getString(R.string.pref_enable_automatic_updates_key) val startScreen = context.getString(R.string.pref_start_screen_key) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 0707afdbb..d88f17e0c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -130,6 +130,6 @@ class PreferencesHelper(context: Context) { fun filterUnread() = rxPrefs.getBoolean(keys.filterUnread, false) - fun automaticUpdateStatus() = prefs.getBoolean(keys.automaticUpdateStatus, false) + fun automaticUpdates() = prefs.getBoolean(keys.automaticUpdates, false) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt index 7bce4082b..42ff97324 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubService.kt @@ -6,7 +6,6 @@ import retrofit2.converter.gson.GsonConverterFactory import retrofit2.http.GET import rx.Observable - /** * Used to connect with the Github API. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateChecker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateChecker.kt index 306fab71b..8d6210845 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateChecker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateChecker.kt @@ -1,20 +1,25 @@ package eu.kanade.tachiyomi.data.updater -import android.content.Context -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.util.toast +import eu.kanade.tachiyomi.BuildConfig import rx.Observable +class GithubUpdateChecker() { -class GithubUpdateChecker(private val context: Context) { - - val service: GithubService = GithubService.create() + private val service: GithubService = GithubService.create() /** * Returns observable containing release information */ - fun checkForApplicationUpdate(): Observable { - context.toast(R.string.update_check_look_for_updates) - return service.getLatestVersion() + fun checkForUpdate(): Observable { + return service.getLatestVersion().map { release -> + val newVersion = release.version.replace("[^\\d.]".toRegex(), "") + + // Check if latest version is different from current version + if (newVersion != BuildConfig.VERSION_NAME) { + GithubUpdateResult.NewUpdate(release) + } else { + GithubUpdateResult.NoNewUpdate() + } + } } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateResult.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateResult.kt new file mode 100644 index 000000000..a4a89a1c0 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/GithubUpdateResult.kt @@ -0,0 +1,7 @@ +package eu.kanade.tachiyomi.data.updater + +sealed class GithubUpdateResult { + + class NewUpdate(val release: GithubRelease): GithubUpdateResult() + class NoNewUpdate(): GithubUpdateResult() +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateCheckerService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateCheckerService.kt new file mode 100644 index 000000000..7386fc580 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateCheckerService.kt @@ -0,0 +1,80 @@ +package eu.kanade.tachiyomi.data.updater + +import android.content.Context +import android.support.v4.app.NotificationCompat +import com.google.android.gms.gcm.* +import eu.kanade.tachiyomi.Constants.NOTIFICATION_UPDATER_ID +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.util.notificationManager +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class UpdateCheckerService : GcmTaskService() { + + override fun onInitializeTasks() { + val preferences: PreferencesHelper = Injekt.get() + if (preferences.automaticUpdates()) { + setupTask(this) + } + } + + override fun onRunTask(params: TaskParams): Int { + return checkVersion() + } + + fun checkVersion(): Int { + return GithubUpdateChecker() + .checkForUpdate() + .map { result -> + if (result is GithubUpdateResult.NewUpdate) { + val url = result.release.downloadLink + + NotificationCompat.Builder(this).update { + setContentTitle(getString(R.string.app_name)) + setContentText(getString(R.string.update_check_notification_update_available)) + setSmallIcon(android.R.drawable.stat_sys_download_done) + // Download action + addAction(android.R.drawable.stat_sys_download_done, + getString(R.string.action_download), + UpdateNotificationReceiver.downloadApkIntent( + this@UpdateCheckerService, url)) + } + } + GcmNetworkManager.RESULT_SUCCESS + } + .onErrorReturn { GcmNetworkManager.RESULT_FAILURE } + // Sadly, the task needs to be synchronous. + .toBlocking() + .single() + } + + fun NotificationCompat.Builder.update(block: NotificationCompat.Builder.() -> Unit) { + block() + notificationManager.notify(NOTIFICATION_UPDATER_ID, build()) + } + + companion object { + fun setupTask(context: Context) { + val task = PeriodicTask.Builder() + .setService(UpdateCheckerService::class.java) + .setTag("Updater") + // 24 hours + .setPeriod(24 * 60 * 60) + // Run between the last two hours + .setFlex(2 * 60 * 60) + .setRequiredNetwork(Task.NETWORK_STATE_CONNECTED) + .setPersisted(true) + .setUpdateCurrent(true) + .build() + + GcmNetworkManager.getInstance(context).schedule(task) + } + + fun cancelTask(context: Context) { + GcmNetworkManager.getInstance(context).cancelAllTasks(UpdateCheckerService::class.java) + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.kt deleted file mode 100644 index 3dad020a7..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.kt +++ /dev/null @@ -1,202 +0,0 @@ -package eu.kanade.tachiyomi.data.updater - -import android.app.Notification -import android.app.PendingIntent -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.os.AsyncTask -import android.support.v4.app.NotificationCompat -import eu.kanade.tachiyomi.Constants -import eu.kanade.tachiyomi.R -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.saveTo -import timber.log.Timber -import uy.kohesive.injekt.injectLazy -import java.io.File - -class UpdateDownloader(private val context: Context) : - AsyncTask() { - - companion object { - /** - * Prompt user with apk install intent - * @param context context - * @param file file of apk that is installed - */ - fun installAPK(context: Context, file: File) { - // Prompt install interface - val intent = Intent(Intent.ACTION_VIEW) - intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive") - // Without this flag android returned a intent error! - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - context.startActivity(intent) - } - } - - val network: NetworkHelper by injectLazy() - - /** - * Default download dir - */ - private val apkFile = File(context.externalCacheDir, "update.apk") - - - /** - * Notification builder - */ - private val notificationBuilder = NotificationCompat.Builder(context) - - /** - * Id of the notification - */ - private val notificationId: Int - get() = Constants.NOTIFICATION_UPDATER_ID - - - /** - * Class containing download result - * @param url url of file - * @param successful status of download - */ - class DownloadResult(var url: String, var successful: Boolean) - - /** - * Called before downloading - */ - override fun onPreExecute() { - // Create download notification - with(notificationBuilder) { - setContentTitle(context.getString(R.string.update_check_notification_file_download)) - setContentText(context.getString(R.string.update_check_notification_download_in_progress)) - setSmallIcon(android.R.drawable.stat_sys_download) - } - } - - override fun doInBackground(vararg params: String?): DownloadResult { - // Initialize information array containing path and url to file. - val result = DownloadResult(params[0]!!, false) - - // 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 - publishProgress(progress) - } - } - } - - try { - // Make the request and download the file - val response = network.client.newCallWithProgress(GET(result.url), progressListener).execute() - - if (response.isSuccessful) { - response.body().source().saveTo(apkFile) - // Set download successful - result.successful = true - } else { - response.close() - } - } catch (e: Exception) { - Timber.e(e, e.message) - } - - return result - } - - /** - * Called when progress is updated - * @param values values containing progress - */ - override fun onProgressUpdate(vararg values: Int?) { - // Notify notification manager to update notification - values.getOrNull(0)?.let { - notificationBuilder.setProgress(100, it, false) - // Displays the progress bar on notification - context.notificationManager.notify(notificationId, notificationBuilder.build()) - } - } - - /** - * Called when download done - * @param result string containing download information - */ - override fun onPostExecute(result: DownloadResult) { - with(notificationBuilder) { - if (result.successful) { - setContentTitle(context.getString(R.string.app_name)) - setContentText(context.getString(R.string.update_check_notification_download_complete)) - addAction(R.drawable.ic_system_update_grey_24dp_img, context.getString(R.string.action_install), - getInstallOnReceivedIntent(InstallOnReceived.INSTALL_APK, apkFile.absolutePath)) - addAction(R.drawable.ic_clear_grey_24dp_img, context.getString(R.string.action_cancel), - getInstallOnReceivedIntent(InstallOnReceived.CANCEL_NOTIFICATION)) - } else { - setContentText(context.getString(R.string.update_check_notification_download_error)) - addAction(R.drawable.ic_refresh_grey_24dp_img, context.getString(R.string.action_retry), - getInstallOnReceivedIntent(InstallOnReceived.RETRY_DOWNLOAD, result.url)) - addAction(R.drawable.ic_clear_grey_24dp_img, context.getString(R.string.action_cancel), - getInstallOnReceivedIntent(InstallOnReceived.CANCEL_NOTIFICATION)) - } - setSmallIcon(android.R.drawable.stat_sys_download_done) - setProgress(0, 0, false) - } - val notification = notificationBuilder.build() - notification.flags = Notification.FLAG_NO_CLEAR - context.notificationManager.notify(notificationId, notification) - } - - /** - * Returns broadcast intent - * @param action action name of broadcast intent - * @param path path of file | url of file - * @return broadcast intent - */ - fun getInstallOnReceivedIntent(action: String, path: String = ""): PendingIntent { - val intent = Intent(context, InstallOnReceived::class.java).apply { - this.action = action - putExtra(InstallOnReceived.FILE_LOCATION, path) - } - return PendingIntent.getBroadcast(context, 0, intent, 0) - } - - - /** - * BroadcastEvent used to install apk or retry download - */ - class InstallOnReceived : BroadcastReceiver() { - companion object { - // Install apk action - const val INSTALL_APK = "eu.kanade.INSTALL_APK" - - // Retry download action - const val RETRY_DOWNLOAD = "eu.kanade.RETRY_DOWNLOAD" - - // Retry download action - const val CANCEL_NOTIFICATION = "eu.kanade.CANCEL_NOTIFICATION" - - // Absolute path of file || URL of file - const val FILE_LOCATION = "file_location" - } - - override fun onReceive(context: Context, intent: Intent) { - when (intent.action) { - // Install apk. - INSTALL_APK -> UpdateDownloader.installAPK(context, File(intent.getStringExtra(FILE_LOCATION))) - // Retry download. - RETRY_DOWNLOAD -> UpdateDownloader(context).execute(intent.getStringExtra(FILE_LOCATION)) - - CANCEL_NOTIFICATION -> context.notificationManager.cancel(Constants.NOTIFICATION_UPDATER_ID) - } - } - - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderAlarm.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderAlarm.kt deleted file mode 100644 index 66e01deb9..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderAlarm.kt +++ /dev/null @@ -1,110 +0,0 @@ -package eu.kanade.tachiyomi.data.updater - -import android.app.AlarmManager -import android.app.PendingIntent -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.os.SystemClock -import eu.kanade.tachiyomi.BuildConfig -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.util.DeviceUtil -import eu.kanade.tachiyomi.util.alarmManager -import eu.kanade.tachiyomi.util.notification -import eu.kanade.tachiyomi.util.notificationManager -import rx.android.schedulers.AndroidSchedulers -import rx.schedulers.Schedulers -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class UpdateDownloaderAlarm : BroadcastReceiver() { - - companion object { - const val CHECK_UPDATE_ACTION = "eu.kanade.CHECK_UPDATE" - - /** - * Sets the alarm to run the intent that checks for update - * @param context the application context. - * @param intervalInHours the time in hours when it will be executed. - */ - fun startAlarm(context: Context, intervalInHours: Int = 12, - isEnabled: Boolean = Injekt.get().automaticUpdateStatus()) { - // Stop previous running alarms if needed, and do not restart it if the interval is 0. - UpdateDownloaderAlarm.stopAlarm(context) - if (intervalInHours == 0 || !isEnabled) - return - - // Get the time the alarm should fire the event to update. - val intervalInMillis = intervalInHours * 60 * 60 * 1000 - val nextRun = SystemClock.elapsedRealtime() + intervalInMillis - - // Start the alarm. - val pendingIntent = getPendingIntent(context) - context.alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, - nextRun, intervalInMillis.toLong(), pendingIntent) - } - - /** - * Stops the alarm if it's running. - * @param context the application context. - */ - fun stopAlarm(context: Context) { - val pendingIntent = getPendingIntent(context) - context.alarmManager.cancel(pendingIntent) - } - - /** - * Returns broadcast intent - * @param context the application context. - * @return broadcast intent - */ - fun getPendingIntent(context: Context): PendingIntent { - return PendingIntent.getBroadcast(context, 0, - Intent(context, UpdateDownloaderAlarm::class.java).apply { - this.action = CHECK_UPDATE_ACTION - }, PendingIntent.FLAG_UPDATE_CURRENT) - } - } - - - override fun onReceive(context: Context, intent: Intent) { - when (intent.action) { - // Start the alarm when the system is booted. - Intent.ACTION_BOOT_COMPLETED -> startAlarm(context) - // Update the library when the alarm fires an event. - CHECK_UPDATE_ACTION -> checkVersion(context) - } - } - - fun checkVersion(context: Context) { - if (DeviceUtil.isNetworkConnected(context)) { - val updateChecker = GithubUpdateChecker(context) - updateChecker.checkForApplicationUpdate() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ release -> - //Get version of latest release - var newVersion = release.version - newVersion = newVersion.replace("[^\\d.]".toRegex(), "") - - //Check if latest version is different from current version - if (newVersion != BuildConfig.VERSION_NAME) { - val downloadLink = release.downloadLink - - val n = context.notification() { - setContentTitle(context.getString(R.string.update_check_notification_update_available)) - addAction(android.R.drawable.stat_sys_download_done, context.getString(eu.kanade.tachiyomi.R.string.action_download), - UpdateDownloader(context).getInstallOnReceivedIntent(UpdateDownloader.InstallOnReceived.RETRY_DOWNLOAD, downloadLink)) - setSmallIcon(android.R.drawable.stat_sys_download_done) - } - // Displays the progress bar on notification - context.notificationManager.notify(0, n); - } - }, { - it.printStackTrace() - }) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt new file mode 100644 index 000000000..3cd50c152 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderService.kt @@ -0,0 +1,149 @@ +package eu.kanade.tachiyomi.data.updater + +import android.app.IntentService +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.support.v4.app.NotificationCompat +import eu.kanade.tachiyomi.Constants.NOTIFICATION_UPDATER_ID +import eu.kanade.tachiyomi.R +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.saveTo +import timber.log.Timber +import uy.kohesive.injekt.injectLazy +import java.io.File + +class UpdateDownloaderService : IntentService(UpdateDownloaderService::class.java.name) { + + companion object { + /** + * Download url. + */ + const val EXTRA_DOWNLOAD_URL = "eu.kanade.APP_DOWNLOAD_URL" + + /** + * 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 downloadUpdate(context: Context, url: String) { + val intent = Intent(context, UpdateDownloaderService::class.java).apply { + putExtra(EXTRA_DOWNLOAD_URL, url) + } + context.startService(intent) + } + + /** + * Prompt user with apk install intent + * @param context context + * @param file file of apk that is installed + */ + fun installAPK(context: Context, file: File) { + // Prompt install interface + val intent = Intent(Intent.ACTION_VIEW).apply { + setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive") + // Without this flag android returned a intent error! + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + context.startActivity(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") + } + + // 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 + addAction(R.drawable.ic_system_update_grey_24dp_img, + getString(R.string.action_install), + UpdateNotificationReceiver.installApkIntent(ctx, apkFile.absolutePath)) + // Cancel action + addAction(R.drawable.ic_clear_grey_24dp_img, + getString(R.string.action_cancel), + UpdateNotificationReceiver.cancelNotificationIntent(ctx)) + } + + } catch (e: Exception) { + Timber.e(e, e.message) + + // 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)) + } + } + } + + fun NotificationCompat.Builder.update(block: NotificationCompat.Builder.() -> Unit) { + block() + notificationManager.notify(NOTIFICATION_UPDATER_ID, build()) + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateNotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateNotificationReceiver.kt new file mode 100644 index 000000000..cb8716115 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateNotificationReceiver.kt @@ -0,0 +1,67 @@ +package eu.kanade.tachiyomi.data.updater + +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +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_INSTALL_APK -> { + UpdateDownloaderService.installAPK(context, + File(intent.getStringExtra(EXTRA_FILE_LOCATION))) + cancelNotification(context) + } + ACTION_DOWNLOAD_UPDATE -> UpdateDownloaderService.downloadUpdate(context, + intent.getStringExtra(UpdateDownloaderService.EXTRA_DOWNLOAD_URL)) + ACTION_CANCEL_NOTIFICATION -> cancelNotification(context) + } + } + + fun cancelNotification(context: Context) { + context.notificationManager.cancel(NOTIFICATION_UPDATER_ID) + } + + companion object { + // Install apk action + const val ACTION_INSTALL_APK = "eu.kanade.INSTALL_APK" + + // Download apk action + const val ACTION_DOWNLOAD_UPDATE = "eu.kanade.RETRY_DOWNLOAD" + + // Cancel notification action + const val ACTION_CANCEL_NOTIFICATION = "eu.kanade.CANCEL_NOTIFICATION" + + // Absolute path of apk file + const val EXTRA_FILE_LOCATION = "file_location" + + fun cancelNotificationIntent(context: Context): PendingIntent { + val intent = Intent(context, UpdateNotificationReceiver::class.java).apply { + action = ACTION_CANCEL_NOTIFICATION + } + return PendingIntent.getBroadcast(context, 0, intent, 0) + } + + fun installApkIntent(context: Context, path: String): PendingIntent { + val intent = Intent(context, UpdateNotificationReceiver::class.java).apply { + action = ACTION_INSTALL_APK + putExtra(EXTRA_FILE_LOCATION, path) + } + return PendingIntent.getBroadcast(context, 0, intent, 0) + } + + fun downloadApkIntent(context: Context, url: String): PendingIntent { + val intent = Intent(context, UpdateNotificationReceiver::class.java).apply { + action = ACTION_DOWNLOAD_UPDATE + putExtra(UpdateDownloaderService.EXTRA_DOWNLOAD_URL, url) + } + return PendingIntent.getBroadcast(context, 0, intent, 0) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt index 284995a36..7a7463478 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt @@ -1,18 +1,21 @@ package eu.kanade.tachiyomi.ui.setting import android.os.Bundle -import android.support.v7.preference.SwitchPreferenceCompat import android.support.v7.preference.XpPreferenceFragment import android.view.View import com.afollestad.materialdialogs.MaterialDialog import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.updater.GithubUpdateChecker -import eu.kanade.tachiyomi.data.updater.UpdateDownloader +import eu.kanade.tachiyomi.data.updater.GithubUpdateResult +import eu.kanade.tachiyomi.data.updater.UpdateCheckerService +import eu.kanade.tachiyomi.data.updater.UpdateDownloaderService import eu.kanade.tachiyomi.util.toast +import net.xpece.android.support.preference.SwitchPreference import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers +import timber.log.Timber import java.text.DateFormat import java.text.ParseException import java.text.SimpleDateFormat @@ -22,15 +25,15 @@ class SettingsAboutFragment : SettingsFragment() { /** * Checks for new releases */ - private val updateChecker by lazy { GithubUpdateChecker(activity) } + private val updateChecker by lazy { GithubUpdateChecker() } /** * The subscribtion service of the obtained release object */ private var releaseSubscription: Subscription? = null - val automaticUpdateToggle by lazy { - findPreference(getString(R.string.pref_enable_automatic_updates_key)) as SwitchPreferenceCompat + val automaticUpdates by lazy { + findPreference(getString(R.string.pref_enable_automatic_updates_key)) as SwitchPreference } companion object { @@ -59,13 +62,17 @@ class SettingsAboutFragment : SettingsFragment() { true } - //TODO One glorious day enable this and add the magnificent option for auto update checking. - // automaticUpdateToggle.isEnabled = true - // automaticUpdateToggle.setOnPreferenceChangeListener { preference, any -> - // val status = any as Boolean - // UpdateDownloaderAlarm.startAlarm(activity, 12, status) - // true - // } + automaticUpdates.setOnPreferenceChangeListener { preference, any -> + val checked = any as Boolean + if (checked) { + UpdateCheckerService.setupTask(context) + } else { + UpdateCheckerService.cancelTask(context) + } + true + } + } else { + automaticUpdates.isVisible = false } buildTime.summary = getFormattedBuildTime() @@ -98,36 +105,35 @@ class SettingsAboutFragment : SettingsFragment() { private fun checkVersion() { releaseSubscription?.unsubscribe() - releaseSubscription = updateChecker.checkForApplicationUpdate() + context.toast(R.string.update_check_look_for_updates) + + releaseSubscription = updateChecker.checkForUpdate() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ release -> - //Get version of latest release - var newVersion = release.version - newVersion = newVersion.replace("[^\\d.]".toRegex(), "") + .subscribe({ result -> + when (result) { + is GithubUpdateResult.NewUpdate -> { + val body = result.release.changeLog + val url = result.release.downloadLink - //Check if latest version is different from current version - if (newVersion != BuildConfig.VERSION_NAME) { - val downloadLink = release.downloadLink - val body = release.changeLog - - //Create confirmation window - MaterialDialog.Builder(activity) - .title(R.string.update_check_title) - .content(body) - .positiveText(getString(R.string.update_check_confirm)) - .negativeText(getString(R.string.update_check_ignore)) - .onPositive { dialog, which -> - // User output that download has started - activity.toast(R.string.update_check_download_started) - // Start download - UpdateDownloader(activity.applicationContext).execute(downloadLink) - }.show() - } else { - activity.toast(R.string.update_check_no_new_updates) + // Create confirmation window + MaterialDialog.Builder(context) + .title(R.string.update_check_title) + .content(body) + .positiveText(getString(R.string.update_check_confirm)) + .negativeText(getString(R.string.update_check_ignore)) + .onPositive { dialog, which -> + // Start download + UpdateDownloaderService.downloadUpdate(context, url) + } + .show() + } + is GithubUpdateResult.NoNewUpdate -> { + context.toast(R.string.update_check_no_new_updates) + } } - }, { - it.printStackTrace() + }, { error -> + Timber.e(error, error.message) }) } diff --git a/app/src/main/res/xml/pref_about.xml b/app/src/main/res/xml/pref_about.xml index b706570d5..e422ce039 100644 --- a/app/src/main/res/xml/pref_about.xml +++ b/app/src/main/res/xml/pref_about.xml @@ -12,12 +12,11 @@ android:summary="@string/pref_acra_summary" android:title="@string/pref_enable_acra"/> - - - - - - +