瀏覽代碼

Added option to check if connected to power before updating. closes #192 (#229)

Bram van de Kerkhof 9 年之前
父節點
當前提交
b1b97c19d4

+ 4 - 4
app/src/main/AndroidManifest.xml

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="eu.kanade.tachiyomi" >
+<manifest package="eu.kanade.tachiyomi"
+          xmlns:android="http://schemas.android.com/apk/res/android">
 
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -12,9 +12,9 @@
     <application
         android:name=".App"
         android:allowBackup="true"
+        android:hardwareAccelerated="true"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
-        android:hardwareAccelerated="true"
         android:largeHeap="true"
         android:theme="@style/Theme.Tachiyomi" >
         <activity
@@ -68,7 +68,7 @@
         </receiver>
 
         <receiver
-            android:name=".data.library.LibraryUpdateService$CancelUpdateReceiver">
+            android:name=".data.library.LibraryUpdateService$LibraryUpdateReceiver">
         </receiver>
 
         <receiver

+ 90 - 25
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt

@@ -17,7 +17,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.data.source.SourceManager
 import eu.kanade.tachiyomi.ui.main.MainActivity
 import eu.kanade.tachiyomi.util.AndroidComponentUtil
-import eu.kanade.tachiyomi.util.NetworkUtil
+import eu.kanade.tachiyomi.util.DeviceUtil
 import eu.kanade.tachiyomi.util.notification
 import eu.kanade.tachiyomi.util.notificationManager
 import rx.Observable
@@ -28,13 +28,19 @@ import java.util.*
 import java.util.concurrent.atomic.AtomicInteger
 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): Intent {
-    return Intent(context, LibraryUpdateService::class.java)
+fun getIntent(context: Context, isForced: Boolean = false): Intent {
+    return Intent(context, LibraryUpdateService::class.java).apply {
+        putExtra(UPDATE_IS_FORCED, isForced)
+    }
 }
 
 /**
@@ -67,6 +73,7 @@ class LibraryUpdateService : Service() {
     // Subscription where the update is done.
     private var subscription: Subscription? = null
 
+
     companion object {
         val UPDATE_NOTIFICATION_ID = 1
 
@@ -76,9 +83,9 @@ class LibraryUpdateService : Service() {
          * @param context the application context.
          */
         @JvmStatic
-        fun start(context: Context) {
+        fun start(context: Context, isForced: Boolean = false) {
             if (!isRunning(context)) {
-                context.startService(getIntent(context))
+                context.startService(getIntent(context, isForced))
             }
         }
 
@@ -116,6 +123,7 @@ class LibraryUpdateService : Service() {
         return null
     }
 
+
     /**
      * Method called when the service receives an intent. In this case, the content of the intent
      * is irrelevant, because everything required is fetched in [updateLibrary].
@@ -127,13 +135,27 @@ class LibraryUpdateService : Service() {
     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.
-        if (!NetworkUtil.isNetworkConnected(this)) {
+        if (!DeviceUtil.isNetworkConnected(this)) {
             Timber.i("Sync canceled, connection not available")
+            showWarningNotification(getString(R.string.notification_no_connection_title),
+                    getString(R.string.notification_no_connection_body))
             AndroidComponentUtil.toggleComponent(this, SyncOnConnectionAvailable::class.java, true)
             stopSelf(startId)
             return Service.START_NOT_STICKY
         }
 
+        // If user doesn't want to update while phone is not charging, cancel sync
+        else if (preferences.updateOnlyWhenCharging() && !(intent?.getBooleanExtra(UPDATE_IS_FORCED, false) ?: false) && !DeviceUtil.isPowerConnected(this)) {
+            Timber.i("Sync canceled, not connected to ac power")
+            // 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.
         subscription?.unsubscribe()
 
@@ -146,11 +168,20 @@ class LibraryUpdateService : Service() {
                             stopSelf(startId)
                         }, {
                             stopSelf(startId)
-                        })
+                })
 
         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
      * heavy operations or network calls here.
@@ -164,8 +195,7 @@ class LibraryUpdateService : Service() {
         val newUpdates = ArrayList<Manga>()
         val failedUpdates = ArrayList<Manga>()
 
-        val cancelIntent = PendingIntent.getBroadcast(this, 0,
-                Intent(this, CancelUpdateReceiver::class.java), 0)
+        val cancelIntent = getLibraryUpdateReceiverIntent(LibraryUpdateReceiver.CANCEL_LIBRARY_UPDATE)
 
         // Get the manga list that is going to be updated.
         val allLibraryMangas = db.getFavoriteMangas().executeAsBlocking()
@@ -179,16 +209,17 @@ class LibraryUpdateService : Service() {
                 // Notify manga that will update.
                 .doOnNext { showProgressNotification(it, count.andIncrement, toUpdate.size, cancelIntent) }
                 // Update the chapters of the manga.
-                .concatMap { manga -> updateManga(manga)
-                        // If there's any error, return empty update and continue.
-                        .onErrorReturn {
-                            failedUpdates.add(manga)
-                            Pair(0, 0)
-                        }
-                        // Filter out mangas without new chapters (or failed).
-                        .filter { pair -> pair.first > 0 }
-                        // Convert to the manga that contains new chapters.
-                        .map { manga }
+                .concatMap { manga ->
+                    updateManga(manga)
+                            // If there's any error, return empty update and continue.
+                            .onErrorReturn {
+                                failedUpdates.add(manga)
+                                Pair(0, 0)
+                            }
+                            // Filter out mangas without new chapters (or failed).
+                            .filter { pair -> pair.first > 0 }
+                            // Convert to the manga that contains new chapters.
+                            .map { manga }
                 }
                 // Add manga with new chapters to the list.
                 .doOnNext { newUpdates.add(it) }
@@ -288,7 +319,27 @@ class LibraryUpdateService : Service() {
             setContentTitle(manga.title)
             setProgress(total, current, false)
             setOngoing(true)
-            addAction(R.drawable.ic_clear_grey_24dp_img, getString(R.string.action_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)
     }
@@ -341,23 +392,37 @@ class LibraryUpdateService : Service() {
          * @param intent the intent received.
          */
         override fun onReceive(context: Context, intent: Intent) {
-            if (NetworkUtil.isNetworkConnected(context)) {
+            if (DeviceUtil.isNetworkConnected(context)) {
                 AndroidComponentUtil.toggleComponent(context, this.javaClass, false)
                 context.startService(getIntent(context))
             }
         }
     }
 
-    class CancelUpdateReceiver : BroadcastReceiver() {
+    /**
+     * Class that triggers the library to update.
+     */
+    class LibraryUpdateReceiver : BroadcastReceiver() {
+        companion object {
+            // Cancel library update action
+            val CANCEL_LIBRARY_UPDATE = "eu.kanade.CANCEL_LIBRARY_UPDATE"
+            // Force library update
+            val FORCE_LIBRARY_UPDATE = "eu.kanade.FORCE_LIBRARY_UPDATE"
+        }
 
         /**
-         * Method called when user stops the update.
+         * Method called when user wants a library update.
          * @param context the application context.
          * @param intent the intent received.
          */
         override fun onReceive(context: Context, intent: Intent) {
-            LibraryUpdateService.stop(context)
-            context.notificationManager.cancel(UPDATE_NOTIFICATION_ID)
+            when (intent.action) {
+                CANCEL_LIBRARY_UPDATE -> {
+                    LibraryUpdateService.stop(context)
+                    context.notificationManager.cancel(UPDATE_NOTIFICATION_ID)
+                }
+                FORCE_LIBRARY_UPDATE -> LibraryUpdateService.start(context, true)
+            }
         }
     }
 

+ 6 - 2
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt

@@ -197,16 +197,20 @@ class PreferencesHelper(private val context: Context) {
         return prefs.getBoolean(getKey(R.string.pref_remove_after_marked_as_read_key), false)
     }
 
+    fun updateOnlyWhenCharging(): Boolean {
+        return prefs.getBoolean(getKey(R.string.pref_update_only_when_charging_key), false)
+    }
+
     fun libraryUpdateInterval(): Preference<Int> {
         return rxPrefs.getInteger(getKey(R.string.pref_library_update_interval_key), 0)
     }
 
     fun filterDownloaded(): Preference<Boolean> {
-        return rxPrefs.getBoolean(getKey(R.string.pref_filter_downloaded), false)
+        return rxPrefs.getBoolean(getKey(R.string.pref_filter_downloaded_key), false)
     }
 
     fun filterUnread(): Preference<Boolean> {
-        return rxPrefs.getBoolean(getKey(R.string.pref_filter_unread), false)
+        return rxPrefs.getBoolean(getKey(R.string.pref_filter_unread_key), false)
     }
 
 }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt

@@ -207,7 +207,7 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback
                 // Apply filter
                 onFilterCheckboxChanged()
             }
-            R.id.action_refresh -> LibraryUpdateService.start(activity)
+            R.id.action_refresh -> LibraryUpdateService.start(activity, true) // Force refresh
             R.id.action_edit_categories -> {
                 val intent = CategoryActivity.newIntent(activity)
                 startActivity(intent)

+ 24 - 0
app/src/main/java/eu/kanade/tachiyomi/util/DeviceUtil.kt

@@ -0,0 +1,24 @@
+package eu.kanade.tachiyomi.util
+
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.ConnectivityManager
+import android.os.BatteryManager
+
+object DeviceUtil {
+    fun isPowerConnected(context: Context): Boolean {
+        val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
+        intent?.let {
+            val plugged = it.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
+            return plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB
+        }
+        return false
+    }
+
+    fun isNetworkConnected(context: Context): Boolean {
+        val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+        val activeNetwork = cm.activeNetworkInfo
+        return activeNetwork != null && activeNetwork.isConnectedOrConnecting
+    }
+}

+ 0 - 16
app/src/main/java/eu/kanade/tachiyomi/util/NetworkUtil.java

@@ -1,16 +0,0 @@
-package eu.kanade.tachiyomi.util;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-
-public class NetworkUtil {
-
-    public static boolean isNetworkConnected(Context context) {
-        ConnectivityManager cm =
-                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
-        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
-        return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
-    }
-
-}

二進制
app/src/main/res/drawable-hdpi/ic_refresh_grey_24dp_img.png


二進制
app/src/main/res/drawable-hdpi/ic_warning_white_24dp_img.png


二進制
app/src/main/res/drawable-mdpi/ic_refresh_grey_24dp_img.png


二進制
app/src/main/res/drawable-mdpi/ic_warning_white_24dp_img.png


二進制
app/src/main/res/drawable-xhdpi/ic_refresh_grey_24dp_img.png


二進制
app/src/main/res/drawable-xhdpi/ic_warning_white_24dp_img.png


二進制
app/src/main/res/drawable-xxhdpi/ic_refresh_grey_24dp_img.png


二進制
app/src/main/res/drawable-xxhdpi/ic_warning_white_24dp_img.png


二進制
app/src/main/res/drawable-xxxhdpi/ic_refresh_grey_24dp_img.png


二進制
app/src/main/res/drawable-xxxhdpi/ic_warning_white_24dp_img.png


+ 3 - 2
app/src/main/res/values/keys.xml

@@ -13,6 +13,7 @@
     <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_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_ask_update_manga_sync_key">pref_ask_update_manga_sync_key</string>
     <string name="pref_theme_key">pref_theme_key</string>
@@ -30,8 +31,8 @@
     <string name="pref_reader_theme_key">pref_reader_theme_key</string>
     <string name="pref_image_decoder_key">pref_image_decoder_key</string>
     <string name="pref_seamless_mode_key">pref_seamless_mode_key</string>
-    <string name="pref_filter_downloaded">pref_filter_downloaded</string>
-    <string name="pref_filter_unread">pref_filter_unread</string>
+    <string name="pref_filter_downloaded_key">pref_filter_downloaded_key</string>
+    <string name="pref_filter_unread_key">pref_filter_unread_key</string>
 
     <string name="pref_download_directory_key">pref_download_directory_key</string>
     <string name="pref_download_slots_key">pref_download_slots_key</string>

+ 6 - 0
app/src/main/res/values/strings.xml

@@ -50,6 +50,7 @@
     <string name="action_display_mode">Change display mode</string>
     <string name="action_cancel">Cancel</string>
     <string name="action_sort">Sort</string>
+    <string name="action_force">Force refresh</string>
 
     <!-- Operations -->
     <string name="deleting">Deleting…</string>
@@ -72,6 +73,7 @@
     <string name="default_columns">Default</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_1hour">Hourly</string>
     <string name="update_2hour">Every 2 hours</string>
@@ -256,6 +258,10 @@
     <string name="notification_new_chapters">New chapters found for:</string>
     <string name="notification_manga_update_failed">Failed to update manga:</string>
     <string name="notification_first_add_to_library">Please add the manga to your library before doing this</string>
+    <string name="notification_not_connected_to_ac_title">Sync canceled</string>
+    <string name="notification_not_connected_to_ac_body">Not connected to AC power</string>
+    <string name="notification_no_connection_title">Sync canceled</string>
+    <string name="notification_no_connection_body">Connection not available</string>
 
     <!-- File Picker Titles -->
     <string name="file_select_cover">Select cover image</string>

+ 17 - 12
app/src/main/res/xml/pref_general.xml

@@ -3,30 +3,35 @@
     xmlns:android="http://schemas.android.com/apk/res/android">
 
     <eu.kanade.tachiyomi.widget.preference.SimpleDialogPreference
+        android:dialogLayout="@layout/pref_library_columns"
         android:key="@string/pref_library_columns_dialog_key"
         android:persistent="false"
-        android:title="@string/pref_library_columns"
-        android:dialogLayout="@layout/pref_library_columns"/>
+        android:title="@string/pref_library_columns"/>
 
     <eu.kanade.tachiyomi.widget.preference.IntListPreference
-        android:key="@string/pref_library_update_interval_key"
-        android:title="@string/pref_library_update_interval"
+        android:defaultValue="0"
         android:entries="@array/library_update_interval"
         android:entryValues="@array/library_update_interval_values"
-        android:defaultValue="0"
-        android:summary="%s"/>
+        android:key="@string/pref_library_update_interval_key"
+        android:summary="%s"
+        android:title="@string/pref_library_update_interval"/>
 
     <eu.kanade.tachiyomi.widget.preference.IntListPreference
-        android:title="@string/pref_theme"
-        android:key="@string/pref_theme_key"
+        android:defaultValue="1"
         android:entries="@array/themes"
         android:entryValues="@array/themes_values"
-        android:defaultValue="1"
-        android:summary="%s"/>
+        android:key="@string/pref_theme_key"
+        android:summary="%s"
+        android:title="@string/pref_theme"/>
 
     <SwitchPreferenceCompat
+        android:defaultValue="false"
         android:key="@string/pref_update_only_non_completed_key"
-        android:title="@string/pref_update_only_non_completed"
-        android:defaultValue="false"/>
+        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>

+ 1 - 0
app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateAlarmTest.java

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