mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	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
This commit is contained in:
		| @@ -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) | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -6,7 +6,6 @@ import retrofit2.converter.gson.GsonConverterFactory | ||||
| import retrofit2.http.GET | ||||
| import rx.Observable | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Used to connect with the Github API. | ||||
|  */ | ||||
|   | ||||
| @@ -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() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| package eu.kanade.tachiyomi.data.updater | ||||
|  | ||||
| sealed class GithubUpdateResult { | ||||
|  | ||||
|     class NewUpdate(val release: GithubRelease): GithubUpdateResult() | ||||
|     class NoNewUpdate(): GithubUpdateResult() | ||||
| } | ||||
| @@ -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) | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -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() | ||||
|                     }) | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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()) | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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) | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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) | ||||
|                 }) | ||||
|     } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user