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:support-annotations:$support_library_version"
|
||||||
compile "com.android.support:customtabs:$support_library_version"
|
compile "com.android.support:customtabs:$support_library_version"
|
||||||
|
|
||||||
|
compile 'com.google.android.gms:play-services-gcm:9.4.0'
|
||||||
|
|
||||||
// ReactiveX
|
// ReactiveX
|
||||||
compile 'io.reactivex:rxandroid:1.2.1'
|
compile 'io.reactivex:rxandroid:1.2.1'
|
||||||
compile 'io.reactivex:rxjava:1.1.8'
|
compile 'io.reactivex:rxjava:1.1.8'
|
||||||
|
|
|
@ -59,6 +59,20 @@
|
||||||
<service android:name=".data.mangasync.UpdateMangaSyncService"
|
<service android:name=".data.mangasync.UpdateMangaSyncService"
|
||||||
android:exported="false"/>
|
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
|
<receiver
|
||||||
android:name=".data.library.LibraryUpdateService$SyncOnConnectionAvailable"
|
android:name=".data.library.LibraryUpdateService$SyncOnConnectionAvailable"
|
||||||
android:enabled="false">
|
android:enabled="false">
|
||||||
|
@ -79,10 +93,6 @@
|
||||||
android:name=".data.library.LibraryUpdateService$CancelUpdateReceiver">
|
android:name=".data.library.LibraryUpdateService$CancelUpdateReceiver">
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver
|
|
||||||
android:name=".data.updater.UpdateDownloader$InstallOnReceived">
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".data.library.LibraryUpdateAlarm">
|
android:name=".data.library.LibraryUpdateAlarm">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -91,15 +101,6 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</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
|
<meta-data
|
||||||
android:name="eu.kanade.tachiyomi.data.glide.AppGlideModule"
|
android:name="eu.kanade.tachiyomi.data.glide.AppGlideModule"
|
||||||
android:value="GlideModule" />
|
android:value="GlideModule" />
|
||||||
|
|
|
@ -78,7 +78,7 @@ class PreferenceKeys(context: Context) {
|
||||||
|
|
||||||
val filterUnread = context.getString(R.string.pref_filter_unread_key)
|
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)
|
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 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 retrofit2.http.GET
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to connect with the Github API.
|
* Used to connect with the Github API.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
package eu.kanade.tachiyomi.data.updater
|
package eu.kanade.tachiyomi.data.updater
|
||||||
|
|
||||||
import android.content.Context
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.toast
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
|
class GithubUpdateChecker() {
|
||||||
|
|
||||||
class GithubUpdateChecker(private val context: Context) {
|
private val service: GithubService = GithubService.create()
|
||||||
|
|
||||||
val service: GithubService = GithubService.create()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns observable containing release information
|
* Returns observable containing release information
|
||||||
*/
|
*/
|
||||||
fun checkForApplicationUpdate(): Observable<GithubRelease> {
|
fun checkForUpdate(): Observable<GithubUpdateResult> {
|
||||||
context.toast(R.string.update_check_look_for_updates)
|
return service.getLatestVersion().map { release ->
|
||||||
return service.getLatestVersion()
|
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
|
package eu.kanade.tachiyomi.ui.setting
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.preference.SwitchPreferenceCompat
|
|
||||||
import android.support.v7.preference.XpPreferenceFragment
|
import android.support.v7.preference.XpPreferenceFragment
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.updater.GithubUpdateChecker
|
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 eu.kanade.tachiyomi.util.toast
|
||||||
|
import net.xpece.android.support.preference.SwitchPreference
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
|
import timber.log.Timber
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
@ -22,15 +25,15 @@ class SettingsAboutFragment : SettingsFragment() {
|
||||||
/**
|
/**
|
||||||
* Checks for new releases
|
* 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
|
* The subscribtion service of the obtained release object
|
||||||
*/
|
*/
|
||||||
private var releaseSubscription: Subscription? = null
|
private var releaseSubscription: Subscription? = null
|
||||||
|
|
||||||
val automaticUpdateToggle by lazy {
|
val automaticUpdates by lazy {
|
||||||
findPreference(getString(R.string.pref_enable_automatic_updates_key)) as SwitchPreferenceCompat
|
findPreference(getString(R.string.pref_enable_automatic_updates_key)) as SwitchPreference
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -59,13 +62,17 @@ class SettingsAboutFragment : SettingsFragment() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO One glorious day enable this and add the magnificent option for auto update checking.
|
automaticUpdates.setOnPreferenceChangeListener { preference, any ->
|
||||||
// automaticUpdateToggle.isEnabled = true
|
val checked = any as Boolean
|
||||||
// automaticUpdateToggle.setOnPreferenceChangeListener { preference, any ->
|
if (checked) {
|
||||||
// val status = any as Boolean
|
UpdateCheckerService.setupTask(context)
|
||||||
// UpdateDownloaderAlarm.startAlarm(activity, 12, status)
|
} else {
|
||||||
// true
|
UpdateCheckerService.cancelTask(context)
|
||||||
// }
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
automaticUpdates.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTime.summary = getFormattedBuildTime()
|
buildTime.summary = getFormattedBuildTime()
|
||||||
|
@ -98,36 +105,35 @@ class SettingsAboutFragment : SettingsFragment() {
|
||||||
private fun checkVersion() {
|
private fun checkVersion() {
|
||||||
releaseSubscription?.unsubscribe()
|
releaseSubscription?.unsubscribe()
|
||||||
|
|
||||||
releaseSubscription = updateChecker.checkForApplicationUpdate()
|
context.toast(R.string.update_check_look_for_updates)
|
||||||
|
|
||||||
|
releaseSubscription = updateChecker.checkForUpdate()
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe({ release ->
|
.subscribe({ result ->
|
||||||
//Get version of latest release
|
when (result) {
|
||||||
var newVersion = release.version
|
is GithubUpdateResult.NewUpdate -> {
|
||||||
newVersion = newVersion.replace("[^\\d.]".toRegex(), "")
|
val body = result.release.changeLog
|
||||||
|
val url = result.release.downloadLink
|
||||||
|
|
||||||
//Check if latest version is different from current version
|
// Create confirmation window
|
||||||
if (newVersion != BuildConfig.VERSION_NAME) {
|
MaterialDialog.Builder(context)
|
||||||
val downloadLink = release.downloadLink
|
.title(R.string.update_check_title)
|
||||||
val body = release.changeLog
|
.content(body)
|
||||||
|
.positiveText(getString(R.string.update_check_confirm))
|
||||||
//Create confirmation window
|
.negativeText(getString(R.string.update_check_ignore))
|
||||||
MaterialDialog.Builder(activity)
|
.onPositive { dialog, which ->
|
||||||
.title(R.string.update_check_title)
|
// Start download
|
||||||
.content(body)
|
UpdateDownloaderService.downloadUpdate(context, url)
|
||||||
.positiveText(getString(R.string.update_check_confirm))
|
}
|
||||||
.negativeText(getString(R.string.update_check_ignore))
|
.show()
|
||||||
.onPositive { dialog, which ->
|
}
|
||||||
// User output that download has started
|
is GithubUpdateResult.NoNewUpdate -> {
|
||||||
activity.toast(R.string.update_check_download_started)
|
context.toast(R.string.update_check_no_new_updates)
|
||||||
// Start download
|
}
|
||||||
UpdateDownloader(activity.applicationContext).execute(downloadLink)
|
|
||||||
}.show()
|
|
||||||
} else {
|
|
||||||
activity.toast(R.string.update_check_no_new_updates)
|
|
||||||
}
|
}
|
||||||
}, {
|
}, { error ->
|
||||||
it.printStackTrace()
|
Timber.e(error, error.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,11 @@
|
||||||
android:summary="@string/pref_acra_summary"
|
android:summary="@string/pref_acra_summary"
|
||||||
android:title="@string/pref_enable_acra"/>
|
android:title="@string/pref_enable_acra"/>
|
||||||
|
|
||||||
<!--<SwitchPreferenceCompat-->
|
<SwitchPreference
|
||||||
<!--android:defaultValue="false"-->
|
android:defaultValue="false"
|
||||||
<!--android:enabled="false"-->
|
android:key="@string/pref_enable_automatic_updates_key"
|
||||||
<!--android:key="@string/pref_enable_automatic_updates_key"-->
|
android:summary="@string/pref_enable_automatic_updates_summary"
|
||||||
<!--android:summary="@string/pref_enable_automatic_updates_summary"-->
|
android:title="@string/pref_enable_automatic_updates"/>
|
||||||
<!--android:title="@string/pref_enable_automatic_updates"/>-->
|
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="@string/pref_version"
|
android:key="@string/pref_version"
|
||||||
|
|
Reference in a new issue