mirror of
https://git.mihon.tech/mihonapp/mihon
synced 2024-11-26 15:16:04 +03: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:
parent
a4b71f4d11
commit
ccdc336112
14 changed files with 384 additions and 381 deletions
|
@ -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'
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue