Merge pull request #257 from NoodleMage/update_improv

Rewrote UpdateDownloader to Kotlin + AC fixes
This commit is contained in:
inorichi 2016-04-27 00:20:50 +02:00
commit 4b7159648a
23 changed files with 525 additions and 249 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"> package="eu.kanade.tachiyomi">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -68,7 +68,19 @@
</receiver> </receiver>
<receiver <receiver
android:name=".data.library.LibraryUpdateService$LibraryUpdateReceiver"> android:name=".data.library.LibraryUpdateService$SyncOnPowerConnected"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
</intent-filter>
</receiver>
<receiver
android:name=".data.library.LibraryUpdateService$CancelUpdateReceiver">
</receiver>
<receiver
android:name=".data.updater.UpdateDownloader$InstallOnReceived">
</receiver> </receiver>
<receiver <receiver
@ -79,6 +91,15 @@
</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.cache.CoverGlideModule" android:name="eu.kanade.tachiyomi.data.cache.CoverGlideModule"
android:value="GlideModule" /> android:value="GlideModule" />

View File

@ -9,6 +9,8 @@ import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import android.support.v4.app.NotificationCompat import android.support.v4.app.NotificationCompat
import android.util.Pair import android.util.Pair
import com.github.pwittchen.reactivenetwork.library.ConnectivityStatus
import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork
import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
@ -16,42 +18,14 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.SourceManager
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.AndroidComponentUtil import eu.kanade.tachiyomi.util.*
import eu.kanade.tachiyomi.util.DeviceUtil
import eu.kanade.tachiyomi.util.notification
import eu.kanade.tachiyomi.util.notificationManager
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import timber.log.Timber
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import javax.inject.Inject import javax.inject.Inject
// Intent key for forced library update
val UPDATE_IS_FORCED = "is_forced"
/**
* Get the start intent for [LibraryUpdateService].
* @param context the application context.
* @param isForced true when forcing library update
* @return the intent of the service.
*/
fun getIntent(context: Context, isForced: Boolean = false): Intent {
return Intent(context, LibraryUpdateService::class.java).apply {
putExtra(UPDATE_IS_FORCED, isForced)
}
}
/**
* Returns the status of the service.
* @param context the application context.
* @return true if the service is running, false otherwise.
*/
fun isRunning(context: Context): Boolean {
return AndroidComponentUtil.isServiceRunning(context, LibraryUpdateService::class.java)
}
/** /**
* This class will take care of updating the chapters of the manga from the library. It can be * This class will take care of updating the chapters of the manga from the library. It can be
* started calling the [start] method. If it's already running, it won't do anything. * started calling the [start] method. If it's already running, it won't do anything.
@ -77,6 +51,30 @@ class LibraryUpdateService : Service() {
companion object { companion object {
val UPDATE_NOTIFICATION_ID = 1 val UPDATE_NOTIFICATION_ID = 1
// Intent key for manual library update
val UPDATE_IS_MANUAL = "is_manual"
/**
* Get the start intent for [LibraryUpdateService].
* @param context the application context.
* @param isManual true when user triggers library update.
* @return the intent of the service.
*/
fun getIntent(context: Context, isManual: Boolean = false): Intent {
return Intent(context, LibraryUpdateService::class.java).apply {
putExtra(UPDATE_IS_MANUAL, isManual)
}
}
/**
* Returns the status of the service.
* @param context the application context.
* @return true if the service is running, false otherwise.
*/
fun isRunning(context: Context): Boolean {
return AndroidComponentUtil.isServiceRunning(context, LibraryUpdateService::class.java)
}
/** /**
* Static method to start the service. It will be started only if there isn't another * Static method to start the service. It will be started only if there isn't another
* instance already running. * instance already running.
@ -125,36 +123,52 @@ class LibraryUpdateService : Service() {
/** /**
* Method called when the service receives an intent. In this case, the content of the intent * Method called when the service receives an intent.
* is irrelevant, because everything required is fetched in [updateLibrary]. * @param intent the start intent from.
* @param intent the intent from [start].
* @param flags the flags of the command. * @param flags the flags of the command.
* @param startId the start id of this command. * @param startId the start id of this command.
* @return the start value of the command. * @return the start value of the command.
*/ */
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// If there's no network available, set a component to start this service again when
// a connection is available. // Get connectivity status
if (!DeviceUtil.isNetworkConnected(this)) { val connection = ReactiveNetwork().getConnectivityStatus(this, true)
Timber.i("Sync canceled, connection not available")
showWarningNotification(getString(R.string.notification_no_connection_title), // Get library update restrictions
getString(R.string.notification_no_connection_body)) val restrictions = preferences.libraryUpdateRestriction()
// Check if users updates library manual
val isManualUpdate = intent?.getBooleanExtra(UPDATE_IS_MANUAL, false) ?: false
// Whether to cancel the update.
var cancelUpdate = false
// Check if device has internet connection
// Check if device has wifi connection if only wifi is enabled
if (connection == ConnectivityStatus.OFFLINE || ("wifi" in restrictions
&& connection != ConnectivityStatus.WIFI_CONNECTED_HAS_INTERNET)) {
if (isManualUpdate) {
toast(R.string.notification_no_connection_title)
}
// Enable library update when connection available
AndroidComponentUtil.toggleComponent(this, SyncOnConnectionAvailable::class.java, true) AndroidComponentUtil.toggleComponent(this, SyncOnConnectionAvailable::class.java, true)
cancelUpdate = true
}
if (!isManualUpdate && "ac" in restrictions && !DeviceUtil.isPowerConnected(this)) {
AndroidComponentUtil.toggleComponent(this, SyncOnPowerConnected::class.java, true)
cancelUpdate = true
}
if (cancelUpdate) {
stopSelf(startId) stopSelf(startId)
return Service.START_NOT_STICKY return Service.START_NOT_STICKY
} }
// If user doesn't want to update while phone is not charging, cancel sync // Stop enabled components.
else if (preferences.updateOnlyWhenCharging() && !(intent?.getBooleanExtra(UPDATE_IS_FORCED, false) ?: false) && !DeviceUtil.isPowerConnected(this)) { AndroidComponentUtil.toggleComponent(this, SyncOnConnectionAvailable::class.java, false)
Timber.i("Sync canceled, not connected to ac power") AndroidComponentUtil.toggleComponent(this, SyncOnPowerConnected::class.java, false)
// Create force library update intent
val forceIntent = getLibraryUpdateReceiverIntent(LibraryUpdateReceiver.FORCE_LIBRARY_UPDATE)
// Show warning
showWarningNotification(getString(R.string.notification_not_connected_to_ac_title),
getString(R.string.notification_not_connected_to_ac_body), forceIntent)
stopSelf(startId)
return Service.START_NOT_STICKY
}
// Unsubscribe from any previous subscription if needed. // Unsubscribe from any previous subscription if needed.
subscription?.unsubscribe() subscription?.unsubscribe()
@ -168,20 +182,11 @@ class LibraryUpdateService : Service() {
stopSelf(startId) stopSelf(startId)
}, { }, {
stopSelf(startId) stopSelf(startId)
}) })
return Service.START_STICKY return Service.START_STICKY
} }
/**
* Creates a PendingIntent for LibraryUpdate broadcast class
* @param action id of action
*/
fun getLibraryUpdateReceiverIntent(action: String): PendingIntent {
return PendingIntent.getBroadcast(this, 0,
Intent(this, LibraryUpdateReceiver::class.java).apply { this.action = action }, 0)
}
/** /**
* Method that updates the library. It's called in a background thread, so it's safe to do * Method that updates the library. It's called in a background thread, so it's safe to do
* heavy operations or network calls here. * heavy operations or network calls here.
@ -195,7 +200,8 @@ class LibraryUpdateService : Service() {
val newUpdates = ArrayList<Manga>() val newUpdates = ArrayList<Manga>()
val failedUpdates = ArrayList<Manga>() val failedUpdates = ArrayList<Manga>()
val cancelIntent = getLibraryUpdateReceiverIntent(LibraryUpdateReceiver.CANCEL_LIBRARY_UPDATE) val cancelIntent = PendingIntent.getBroadcast(this, 0,
Intent(this, CancelUpdateReceiver::class.java), 0)
// Get the manga list that is going to be updated. // Get the manga list that is going to be updated.
val allLibraryMangas = db.getFavoriteMangas().executeAsBlocking() val allLibraryMangas = db.getFavoriteMangas().executeAsBlocking()
@ -299,12 +305,11 @@ class LibraryUpdateService : Service() {
* @param body the body of the notification. * @param body the body of the notification.
*/ */
private fun showNotification(title: String, body: String) { private fun showNotification(title: String, body: String) {
val n = notification() { notificationManager.notify(UPDATE_NOTIFICATION_ID, notification() {
setSmallIcon(R.drawable.ic_refresh_white_24dp_img) setSmallIcon(R.drawable.ic_refresh_white_24dp_img)
setContentTitle(title) setContentTitle(title)
setContentText(body) setContentText(body)
} })
notificationManager.notify(UPDATE_NOTIFICATION_ID, n)
} }
/** /**
@ -314,35 +319,15 @@ class LibraryUpdateService : Service() {
* @param total the total progress. * @param total the total progress.
*/ */
private fun showProgressNotification(manga: Manga, current: Int, total: Int, cancelIntent: PendingIntent) { private fun showProgressNotification(manga: Manga, current: Int, total: Int, cancelIntent: PendingIntent) {
val n = notification() { notificationManager.notify(UPDATE_NOTIFICATION_ID, notification() {
setSmallIcon(R.drawable.ic_refresh_white_24dp_img) setSmallIcon(R.drawable.ic_refresh_white_24dp_img)
setContentTitle(manga.title) setContentTitle(manga.title)
setProgress(total, current, false) setProgress(total, current, false)
setOngoing(true) setOngoing(true)
addAction(R.drawable.ic_clear_grey_24dp_img, getString(android.R.string.cancel), cancelIntent) addAction(R.drawable.ic_clear_grey_24dp_img, getString(android.R.string.cancel), cancelIntent)
} })
notificationManager.notify(UPDATE_NOTIFICATION_ID, n)
} }
/**
* Show warning message when library can't be updated
* @param warningTitle title of warning
* @param warningBody warning information
* @param pendingIntent Intent called when action clicked
*/
private fun showWarningNotification(warningTitle: String, warningBody: String, pendingIntent: PendingIntent? = null) {
val n = notification() {
setSmallIcon(R.drawable.ic_warning_white_24dp_img)
setContentTitle(warningTitle)
setStyle(NotificationCompat.BigTextStyle().bigText(warningBody))
setContentIntent(notificationIntent)
if (pendingIntent != null) {
addAction(R.drawable.ic_refresh_grey_24dp_img, getString(R.string.action_force), pendingIntent)
}
setAutoCancel(true)
}
notificationManager.notify(UPDATE_NOTIFICATION_ID, n)
}
/** /**
* Shows the notification containing the result of the update done by the service. * Shows the notification containing the result of the update done by the service.
@ -353,14 +338,13 @@ class LibraryUpdateService : Service() {
val title = getString(R.string.notification_update_completed) val title = getString(R.string.notification_update_completed)
val body = getUpdatedMangasBody(updates, failed) val body = getUpdatedMangasBody(updates, failed)
val n = notification() { notificationManager.notify(UPDATE_NOTIFICATION_ID, notification() {
setSmallIcon(R.drawable.ic_refresh_white_24dp_img) setSmallIcon(R.drawable.ic_refresh_white_24dp_img)
setContentTitle(title) setContentTitle(title)
setStyle(NotificationCompat.BigTextStyle().bigText(body)) setStyle(NotificationCompat.BigTextStyle().bigText(body))
setContentIntent(notificationIntent) setContentIntent(notificationIntent)
setAutoCancel(true) setAutoCancel(true)
} })
notificationManager.notify(UPDATE_NOTIFICATION_ID, n)
} }
/** /**
@ -385,7 +369,6 @@ class LibraryUpdateService : Service() {
* network changes. * network changes.
*/ */
class SyncOnConnectionAvailable : BroadcastReceiver() { class SyncOnConnectionAvailable : BroadcastReceiver() {
/** /**
* Method called when a network change occurs. * Method called when a network change occurs.
* @param context the application context. * @param context the application context.
@ -394,35 +377,38 @@ class LibraryUpdateService : Service() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
if (DeviceUtil.isNetworkConnected(context)) { if (DeviceUtil.isNetworkConnected(context)) {
AndroidComponentUtil.toggleComponent(context, this.javaClass, false) AndroidComponentUtil.toggleComponent(context, this.javaClass, false)
context.startService(getIntent(context)) start(context)
} }
} }
} }
/** /**
* Class that triggers the library to update. * Class that triggers the library to update when connected to power.
*/ */
class LibraryUpdateReceiver : BroadcastReceiver() { class SyncOnPowerConnected: BroadcastReceiver() {
companion object { /**
// Cancel library update action * Method called when AC is connected.
val CANCEL_LIBRARY_UPDATE = "eu.kanade.CANCEL_LIBRARY_UPDATE" * @param context the application context.
// Force library update * @param intent the intent received.
val FORCE_LIBRARY_UPDATE = "eu.kanade.FORCE_LIBRARY_UPDATE" */
override fun onReceive(context: Context, intent: Intent) {
AndroidComponentUtil.toggleComponent(context, this.javaClass, false)
start(context)
} }
}
/**
* Class that stops updating the library.
*/
class CancelUpdateReceiver : BroadcastReceiver() {
/** /**
* Method called when user wants a library update. * Method called when user wants a library update.
* @param context the application context. * @param context the application context.
* @param intent the intent received. * @param intent the intent received.
*/ */
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
when (intent.action) { LibraryUpdateService.stop(context)
CANCEL_LIBRARY_UPDATE -> { context.notificationManager.cancel(UPDATE_NOTIFICATION_ID)
LibraryUpdateService.stop(context)
context.notificationManager.cancel(UPDATE_NOTIFICATION_ID)
}
FORCE_LIBRARY_UPDATE -> LibraryUpdateService.start(context, true)
}
} }
} }

View File

@ -70,16 +70,17 @@ class PreferenceKeys(context: Context) {
val removeAfterMarkedAsRead = context.getString(R.string.pref_remove_after_marked_as_read_key) val removeAfterMarkedAsRead = context.getString(R.string.pref_remove_after_marked_as_read_key)
val updateOnlyWhenCharging = context.getString(R.string.pref_update_only_when_charging_key)
val libraryUpdateInterval = context.getString(R.string.pref_library_update_interval_key) val libraryUpdateInterval = context.getString(R.string.pref_library_update_interval_key)
val libraryUpdateRestriction = context.getString(R.string.pref_library_update_restriction_key)
val filterDownloaded = context.getString(R.string.pref_filter_downloaded_key) val filterDownloaded = context.getString(R.string.pref_filter_downloaded_key)
val filterUnread = context.getString(R.string.pref_filter_unread_key) val filterUnread = context.getString(R.string.pref_filter_unread_key)
fun sourceUsername(sourceId: Int) = "pref_source_username_$sourceId" fun sourceUsername(sourceId: Int) = "pref_source_username_$sourceId"
fun sourcePassword(sourceId: Int) = "pref_source_password_$sourceId" fun sourcePassword(sourceId: Int) = "pref_source_password_$sourceId"
fun syncUsername(syncId: Int) = "pref_mangasync_username_$syncId" fun syncUsername(syncId: Int) = "pref_mangasync_username_$syncId"

View File

@ -39,6 +39,11 @@ class PreferencesHelper(private val context: Context) {
context.getString(R.string.pref_library_update_interval_key), 0) context.getString(R.string.pref_library_update_interval_key), 0)
} }
fun getAutomaticUpdateStatus(context: Context): Boolean {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
context.getString(R.string.pref_enable_automatic_updates), false)
}
@JvmStatic @JvmStatic
fun getTheme(context: Context): Int { fun getTheme(context: Context): Int {
return PreferenceManager.getDefaultSharedPreferences(context).getInt( return PreferenceManager.getDefaultSharedPreferences(context).getInt(
@ -132,10 +137,10 @@ class PreferencesHelper(private val context: Context) {
fun removeAfterMarkedAsRead() = prefs.getBoolean(keys.removeAfterMarkedAsRead, false) fun removeAfterMarkedAsRead() = prefs.getBoolean(keys.removeAfterMarkedAsRead, false)
fun updateOnlyWhenCharging() = prefs.getBoolean(keys.updateOnlyWhenCharging, false)
fun libraryUpdateInterval() = rxPrefs.getInteger(keys.libraryUpdateInterval, 0) fun libraryUpdateInterval() = rxPrefs.getInteger(keys.libraryUpdateInterval, 0)
fun libraryUpdateRestriction() = prefs.getStringSet(keys.libraryUpdateRestriction, emptySet())
fun filterDownloaded() = rxPrefs.getBoolean(keys.filterDownloaded, false) fun filterDownloaded() = rxPrefs.getBoolean(keys.filterDownloaded, false)
fun filterUnread() = rxPrefs.getBoolean(keys.filterUnread, false) fun filterUnread() = rxPrefs.getBoolean(keys.filterUnread, false)

View File

@ -1,99 +0,0 @@
package eu.kanade.tachiyomi.data.updater;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.inject.Inject;
import eu.kanade.tachiyomi.App;
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
public class UpdateDownloader extends AsyncTask<String, Void, Void> {
/**
* Name of cache directory.
*/
private static final String PARAMETER_CACHE_DIRECTORY = "apk_downloads";
/**
* Interface to global information about an application environment.
*/
private final Context context;
/**
* Cache directory used for cache management.
*/
private final File cacheDir;
@Inject PreferencesHelper preferencesHelper;
/**
* Constructor of UpdaterCache.
*
* @param context application environment interface.
*/
public UpdateDownloader(Context context) {
App.get(context).getComponent().inject(this);
this.context = context;
// Get cache directory from parameter.
cacheDir = new File(preferencesHelper.downloadsDirectory().get(), PARAMETER_CACHE_DIRECTORY);
// Create cache directory.
createCacheDir();
}
/**
* Create cache directory if it doesn't exist
*
* @return true if cache dir is created otherwise false.
*/
@SuppressWarnings("UnusedReturnValue")
private boolean createCacheDir() {
return !cacheDir.exists() && cacheDir.mkdirs();
}
@Override
protected Void doInBackground(String... args) {
try {
createCacheDir();
URL url = new URL(args[0]);
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.connect();
File outputFile = new File(cacheDir, "update.apk");
if (outputFile.exists()) {
//noinspection ResultOfMethodCallIgnored
outputFile.delete();
}
FileOutputStream fos = new FileOutputStream(outputFile);
InputStream is = c.getInputStream();
byte[] buffer = new byte[1024];
int len1;
while ((len1 = is.read(buffer)) != -1) {
fos.write(buffer, 0, len1);
}
fos.close();
is.close();
// Prompt install interface
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(outputFile), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error!
context.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View File

@ -0,0 +1,199 @@
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.App
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.network.NetworkHelper
import eu.kanade.tachiyomi.data.network.ProgressListener
import eu.kanade.tachiyomi.data.network.get
import eu.kanade.tachiyomi.util.notificationManager
import eu.kanade.tachiyomi.util.saveTo
import timber.log.Timber
import java.io.File
import javax.inject.Inject
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)
}
}
@Inject lateinit var network: NetworkHelper
/**
* Default download dir
*/
val apkFile = File(context.externalCacheDir, "update.apk")
/**
* Notification builder
*/
val notificationBuilder = NotificationCompat.Builder(context)
init {
App.get(context).component.inject(this)
}
/**
* 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.requestBodyProgressBlocking(get(result.url), progressListener)
if (response.isSuccessful) {
response.body().source().saveTo(apkFile)
// Set download successful
result.successful = true
}
} 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(InstallOnReceived.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(InstallOnReceived.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
val INSTALL_APK = "eu.kanade.INSTALL_APK"
// Retry download action
val RETRY_DOWNLOAD = "eu.kanade.RETRY_DOWNLOAD"
// Retry download action
val CANCEL_NOTIFICATION = "eu.kanade.CANCEL_NOTIFICATION"
// Absolute path of file || URL of file
val FILE_LOCATION = "file_location"
// Id of the notification
val notificationId = 2
}
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(notificationId)
}
}
}
}

View File

@ -0,0 +1,109 @@
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
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.
*/
@JvmStatic
@JvmOverloads
fun startAlarm(context: Context, intervalInHours: Int = 12, isEnabled: Boolean = PreferencesHelper.getAutomaticUpdateStatus(context)) {
// 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()
})
}
}
}

View File

@ -73,5 +73,4 @@ open class BaseActivity : AppCompatActivity() {
snack.f() snack.f()
snack.show() snack.show()
} }
}
}

View File

@ -1,6 +1,7 @@
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.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
@ -27,6 +28,10 @@ class SettingsAboutFragment : SettingsNestedFragment() {
*/ */
private var releaseSubscription: Subscription? = null private var releaseSubscription: Subscription? = null
val automaticUpdateToggle by lazy {
findPreference(getString(R.string.pref_enable_automatic_updates_key)) as SwitchPreferenceCompat
}
companion object { companion object {
fun newInstance(resourcePreference: Int, resourceTitle: Int): SettingsNestedFragment { fun newInstance(resourcePreference: Int, resourceTitle: Int): SettingsNestedFragment {
@ -45,11 +50,19 @@ class SettingsAboutFragment : SettingsNestedFragment() {
else else
BuildConfig.VERSION_NAME BuildConfig.VERSION_NAME
//Set onClickListener to check for new version if (!BuildConfig.DEBUG && BuildConfig.INCLUDE_UPDATER) {
version.setOnPreferenceClickListener { //Set onClickListener to check for new version
if (!BuildConfig.DEBUG && BuildConfig.INCLUDE_UPDATER) version.setOnPreferenceClickListener {
checkVersion() true
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
// }
} }
buildTime.summary = getFormattedBuildTime() buildTime.summary = getFormattedBuildTime()

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.setting
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.support.v14.preference.MultiSelectListPreference
import android.support.v4.app.TaskStackBuilder import android.support.v4.app.TaskStackBuilder
import android.support.v7.preference.Preference import android.support.v7.preference.Preference
import android.view.View import android.view.View
@ -33,13 +34,28 @@ class SettingsGeneralFragment : SettingsNestedFragment() {
findPreference(getString(R.string.pref_library_update_interval_key)) as IntListPreference findPreference(getString(R.string.pref_library_update_interval_key)) as IntListPreference
} }
val updateRestriction by lazy {
findPreference(getString(R.string.pref_library_update_restriction_key)) as MultiSelectListPreference
}
val themePreference by lazy { val themePreference by lazy {
findPreference(getString(R.string.pref_theme_key)) as IntListPreference findPreference(getString(R.string.pref_theme_key)) as IntListPreference
} }
var updateIntervalSubscription: Subscription? = null
var columnsSubscription: Subscription? = null var columnsSubscription: Subscription? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
updateIntervalSubscription = preferences.libraryUpdateInterval().asObservable()
.subscribe { updateRestriction.isVisible = it > 0 }
columnsSubscription = Observable.combineLatest(
preferences.portraitColumns().asObservable(),
preferences.landscapeColumns().asObservable())
{ portraitColumns, landscapeColumns -> Pair(portraitColumns, landscapeColumns) }
.subscribe { updateColumnsSummary(it.first, it.second) }
updateInterval.setOnPreferenceChangeListener { preference, newValue -> updateInterval.setOnPreferenceChangeListener { preference, newValue ->
LibraryUpdateAlarm.startAlarm(activity, (newValue as String).toInt()) LibraryUpdateAlarm.startAlarm(activity, (newValue as String).toInt())
true true
@ -57,17 +73,11 @@ class SettingsGeneralFragment : SettingsNestedFragment() {
} }
} }
override fun onResume() { override fun onDestroyView() {
super.onResume() updateIntervalSubscription?.unsubscribe()
columnsSubscription = Observable.combineLatest(preferences.portraitColumns().asObservable(),
preferences.landscapeColumns().asObservable(),
{ portraitColumns, landscapeColumns -> Pair(portraitColumns, landscapeColumns) })
.subscribe { updateColumnsSummary(it.first, it.second) }
}
override fun onPause() {
columnsSubscription?.unsubscribe() columnsSubscription?.unsubscribe()
super.onPause() super.onDestroyView()
} }
override fun onDisplayPreferenceDialog(preference: Preference) { override fun onDisplayPreferenceDialog(preference: Preference) {

View File

@ -11,7 +11,7 @@ object DeviceUtil {
val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
intent?.let { intent?.let {
val plugged = it.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) val plugged = it.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
return plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB return plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS
} }
return false return false
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -126,4 +126,14 @@
<item>48</item> <item>48</item>
</string-array> </string-array>
<string-array name="library_update_restrictions">
<item>@string/wifi</item>
<item>@string/charging</item>
</string-array>
<string-array name="library_update_restrictions_values">
<item>wifi</item>
<item>ac</item>
</string-array>
</resources> </resources>

View File

@ -13,10 +13,10 @@
<string name="pref_library_columns_landscape_key">pref_library_columns_landscape_key</string> <string name="pref_library_columns_landscape_key">pref_library_columns_landscape_key</string>
<string name="pref_library_update_interval_key">pref_library_update_interval_key</string> <string name="pref_library_update_interval_key">pref_library_update_interval_key</string>
<string name="pref_update_only_non_completed_key">pref_update_only_non_completed_key</string> <string name="pref_update_only_non_completed_key">pref_update_only_non_completed_key</string>
<string name="pref_update_only_when_charging_key">pref_update_only_when_charging_key</string>
<string name="pref_auto_update_manga_sync_key">pref_auto_update_manga_sync_key</string> <string name="pref_auto_update_manga_sync_key">pref_auto_update_manga_sync_key</string>
<string name="pref_ask_update_manga_sync_key">pref_ask_update_manga_sync_key</string> <string name="pref_ask_update_manga_sync_key">pref_ask_update_manga_sync_key</string>
<string name="pref_theme_key">pref_theme_key</string> <string name="pref_theme_key">pref_theme_key</string>
<string name="pref_library_update_restriction_key">library_update_restriction</string>
<string name="pref_default_viewer_key">pref_default_viewer_key</string> <string name="pref_default_viewer_key">pref_default_viewer_key</string>
<string name="pref_image_scale_type_key">pref_image_scale_type_key</string> <string name="pref_image_scale_type_key">pref_image_scale_type_key</string>
@ -52,8 +52,10 @@
<string name="pref_clear_chapter_cache_key">pref_clear_chapter_cache_key</string> <string name="pref_clear_chapter_cache_key">pref_clear_chapter_cache_key</string>
<string name="pref_clear_database_key">pref_clear_database_key</string> <string name="pref_clear_database_key">pref_clear_database_key</string>
<string name="pref_version">pref_version</string> <string name="pref_version">pref_version</string>
<string name="pref_build_time">pref_build_time</string> <string name="pref_build_time">pref_build_time</string>
<string name="pref_enable_automatic_updates_key">pref_enable_automatic_updates_key</string>
<string name="pref_display_catalogue_as_list">pref_display_catalogue_as_list</string> <string name="pref_display_catalogue_as_list">pref_display_catalogue_as_list</string>
<string name="pref_last_catalogue_source_key">pref_last_catalogue_source_key</string> <string name="pref_last_catalogue_source_key">pref_last_catalogue_source_key</string>

View File

@ -51,6 +51,7 @@
<string name="action_cancel">Cancel</string> <string name="action_cancel">Cancel</string>
<string name="action_sort">Sort</string> <string name="action_sort">Sort</string>
<string name="action_force">Force refresh</string> <string name="action_force">Force refresh</string>
<string name="action_install">Install</string>
<!-- Operations --> <!-- Operations -->
<string name="deleting">Deleting…</string> <string name="deleting">Deleting…</string>
@ -72,8 +73,6 @@
<string name="landscape">Landscape</string> <string name="landscape">Landscape</string>
<string name="default_columns">Default</string> <string name="default_columns">Default</string>
<string name="pref_library_update_interval">Library update frequency</string> <string name="pref_library_update_interval">Library update frequency</string>
<string name="pref_update_only_non_completed">Only update incomplete manga</string>
<string name="pref_update_only_when_charging">Only update when charging</string>
<string name="update_never">Manual</string> <string name="update_never">Manual</string>
<string name="update_1hour">Hourly</string> <string name="update_1hour">Hourly</string>
<string name="update_2hour">Every 2 hours</string> <string name="update_2hour">Every 2 hours</string>
@ -82,13 +81,17 @@
<string name="update_12hour">Every 12 hours</string> <string name="update_12hour">Every 12 hours</string>
<string name="update_24hour">Daily</string> <string name="update_24hour">Daily</string>
<string name="update_48hour">Every 2 days</string> <string name="update_48hour">Every 2 days</string>
<string name="pref_library_update_restriction">Library update restrictions</string>
<string name="pref_library_update_restriction_summary">Update only when the conditions are met</string>
<string name="wifi">Wi-Fi</string>
<string name="charging">Charging</string>
<string name="pref_update_only_non_completed">Only update incomplete manga</string>
<string name="pref_auto_update_manga_sync">Sync chapters after reading</string> <string name="pref_auto_update_manga_sync">Sync chapters after reading</string>
<string name="pref_ask_update_manga_sync">Confirm before updating</string> <string name="pref_ask_update_manga_sync">Confirm before updating</string>
<string name="pref_theme">Application theme</string> <string name="pref_theme">Application theme</string>
<string name="light_theme">Main theme</string> <string name="light_theme">Main theme</string>
<string name="dark_theme">Dark theme</string> <string name="dark_theme">Dark theme</string>
<!-- Reader section --> <!-- Reader section -->
<string name="pref_hide_status_bar">Hide status bar</string> <string name="pref_hide_status_bar">Hide status bar</string>
<string name="pref_lock_orientation">Lock orientation</string> <string name="pref_lock_orientation">Lock orientation</string>
@ -166,7 +169,8 @@
<!-- About section --> <!-- About section -->
<string name="version">Version</string> <string name="version">Version</string>
<string name="build_time">Build time</string> <string name="build_time">Build time</string>
<string name="pref_enable_automatic_updates">Check for updates</string>
<string name="pref_enable_automatic_updates_summary">Automatically check for application updates</string>
<!-- ACRA --> <!-- ACRA -->
<string name="pref_enable_acra">Send crash reports</string> <string name="pref_enable_acra">Send crash reports</string>
<string name="pref_acra_summary">Helps fix any bugs. No sensitive data will be sent</string> <string name="pref_acra_summary">Helps fix any bugs. No sensitive data will be sent</string>
@ -282,6 +286,13 @@
<string name="update_check_download_started">Download started</string> <string name="update_check_download_started">Download started</string>
<string name="update_check_look_for_updates">Looking for updates</string> <string name="update_check_look_for_updates">Looking for updates</string>
<!--UpdateCheck Notifications-->
<string name="update_check_notification_file_download">Download update</string>
<string name="update_check_notification_download_in_progress">Download in progress</string>
<string name="update_check_notification_download_complete">Download complete</string>
<string name="update_check_notification_download_error">Download error</string>
<string name="update_check_notification_update_available">Update available</string>
<!--Content Description--> <!--Content Description-->
<string name="description_backdrop">Backdrop image of selected manga</string> <string name="description_backdrop">Backdrop image of selected manga</string>
<string name="description_cover">Cover of selected manga</string> <string name="description_cover">Cover of selected manga</string>

View File

@ -3,19 +3,26 @@
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="true"
android:key="acra.enable" android:key="acra.enable"
android:title="@string/pref_enable_acra"
android:summary="@string/pref_acra_summary" android:summary="@string/pref_acra_summary"
android:defaultValue="true"/> 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"/>-->
<Preference <Preference
android:key="@string/pref_version" android:key="@string/pref_version"
android:title="@string/version" android:persistent="false"
android:persistent="false" /> android:title="@string/version"/>
<Preference <Preference
android:key="@string/pref_build_time" android:key="@string/pref_build_time"
android:title="@string/build_time" android:persistent="false"
android:persistent="false" /> android:title="@string/build_time"/>
</android.support.v7.preference.PreferenceScreen> </android.support.v7.preference.PreferenceScreen>

View File

@ -3,13 +3,13 @@
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<Preference <Preference
android:title="@string/pref_clear_chapter_cache" android:key="@string/pref_clear_chapter_cache_key"
android:key="@string/pref_clear_chapter_cache_key" /> android:title="@string/pref_clear_chapter_cache"/>
<Preference <Preference
android:title="@string/pref_clear_database"
android:key="@string/pref_clear_database_key" android:key="@string/pref_clear_database_key"
android:summary="@string/pref_clear_database_summary"/> android:summary="@string/pref_clear_database_summary"
android:title="@string/pref_clear_database"/>
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="false" android:defaultValue="false"

View File

@ -24,14 +24,16 @@
android:summary="%s" android:summary="%s"
android:title="@string/pref_library_update_interval"/> android:title="@string/pref_library_update_interval"/>
<MultiSelectListPreference
android:entries="@array/library_update_restrictions"
android:entryValues="@array/library_update_restrictions_values"
android:key="@string/pref_library_update_restriction_key"
android:summary="@string/pref_library_update_restriction_summary"
android:title="@string/pref_library_update_restriction" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="false" android:defaultValue="false"
android:key="@string/pref_update_only_non_completed_key" android:key="@string/pref_update_only_non_completed_key"
android:title="@string/pref_update_only_non_completed"/> android:title="@string/pref_update_only_non_completed"/>
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/pref_update_only_when_charging_key"
android:title="@string/pref_update_only_when_charging"/>
</android.support.v7.preference.PreferenceScreen> </android.support.v7.preference.PreferenceScreen>

View File

@ -101,7 +101,7 @@ public class LibraryUpdateAlarmTest {
@Test @Test
public void testLibraryUpdateServiceIsStartedWhenUpdateIntentIsReceived() { public void testLibraryUpdateServiceIsStartedWhenUpdateIntentIsReceived() {
Intent intent = new Intent(context, LibraryUpdateService.class); Intent intent = new Intent(context, LibraryUpdateService.class);
intent.putExtra("is_forced", false); intent.putExtra("is_manual", false);
assertThat(app.getNextStartedService()).isNotEqualTo(intent); assertThat(app.getNextStartedService()).isNotEqualTo(intent);
LibraryUpdateAlarm alarm = new LibraryUpdateAlarm(); LibraryUpdateAlarm alarm = new LibraryUpdateAlarm();