瀏覽代碼

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
inorichi 8 年之前
父節點
當前提交
ccdc336112

+ 2 - 0
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'

+ 14 - 13
app/src/main/AndroidManifest.xml

@@ -59,6 +59,20 @@
         <service android:name=".data.mangasync.UpdateMangaSyncService"
             android:exported="false"/>
 
+        <service
+            android:name=".data.updater.UpdateCheckerService"
+            android:exported="true"
+            android:permission="com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE">
+            <intent-filter>
+                <action android:name="com.google.android.gms.gcm.ACTION_TASK_READY" />
+            </intent-filter>
+        </service>
+
+        <service android:name=".data.updater.UpdateDownloaderService"
+            android:exported="false"/>
+
+        <receiver android:name=".data.updater.UpdateNotificationReceiver"/>
+
         <receiver
             android:name=".data.library.LibraryUpdateService$SyncOnConnectionAvailable"
             android:enabled="false">
@@ -79,10 +93,6 @@
             android:name=".data.library.LibraryUpdateService$CancelUpdateReceiver">
         </receiver>
 
-        <receiver
-            android:name=".data.updater.UpdateDownloader$InstallOnReceived">
-        </receiver>
-
         <receiver
             android:name=".data.library.LibraryUpdateAlarm">
             <intent-filter>
@@ -91,15 +101,6 @@
             </intent-filter>
         </receiver>
 
-        <receiver
-            android:name=".data.updater.UpdateDownloaderAlarm">
-            <intent-filter>
-                <action android:name="android.intent.action.BOOT_COMPLETED"/>
-                <action android:name="eu.kanade.CHECK_UPDATE"/>
-            </intent-filter>
-        </receiver>
-
-
         <meta-data
             android:name="eu.kanade.tachiyomi.data.glide.AppGlideModule"
             android:value="GlideModule" />

+ 1 - 1
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)
 

+ 1 - 1
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)
 
 }

+ 0 - 1
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.
  */

+ 14 - 9
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<GithubRelease> {
-        context.toast(R.string.update_check_look_for_updates)
-        return service.getLatestVersion()
+    fun checkForUpdate(): Observable<GithubUpdateResult> {
+        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()
+            }
+        }
     }
 }

+ 7 - 0
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()
+}

+ 80 - 0
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)
+        }
+
+    }
+
+}

+ 0 - 202
app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloader.kt

@@ -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<String, Int, UpdateDownloader.DownloadResult>() {
-
-    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)
-            }
-        }
-
-    }
-}

+ 0 - 110
app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateDownloaderAlarm.kt

@@ -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<PreferencesHelper>().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()
-                    })
-        }
-    }
-
-}

+ 149 - 0
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())
+    }
+
+}

+ 67 - 0
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)
+        }
+    }
+
+}

+ 45 - 39
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(), "")
-
-                    //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)
+                .subscribe({ result ->
+                    when (result) {
+                        is GithubUpdateResult.NewUpdate -> {
+                            val body = result.release.changeLog
+                            val url = result.release.downloadLink
+
+                            // 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)
                 })
     }
 

+ 5 - 6
app/src/main/res/xml/pref_about.xml

@@ -12,12 +12,11 @@
             android:summary="@string/pref_acra_summary"
             android:title="@string/pref_enable_acra"/>
 
-        <!--<SwitchPreferenceCompat-->
-        <!--android:defaultValue="false"-->
-        <!--android:enabled="false"-->
-        <!--android:key="@string/pref_enable_automatic_updates_key"-->
-        <!--android:summary="@string/pref_enable_automatic_updates_summary"-->
-        <!--android:title="@string/pref_enable_automatic_updates"/>-->
+        <SwitchPreference
+            android:defaultValue="false"
+            android:key="@string/pref_enable_automatic_updates_key"
+            android:summary="@string/pref_enable_automatic_updates_summary"
+            android:title="@string/pref_enable_automatic_updates"/>
 
         <Preference
             android:key="@string/pref_version"