From 8745fc364cbddd1f00c93d1f9519e0f9845bdb44 Mon Sep 17 00:00:00 2001 From: Florens Douwes Date: Sun, 8 Jun 2014 15:23:29 +0200 Subject: [PATCH] Refactor the pin watcher. --- Clover/app/src/main/AndroidManifest.xml | 2 +- .../java/org/floens/chan/ChanApplication.java | 69 +++- .../org/floens/chan/core/ChanPreferences.java | 10 +- .../chan/core/manager/PinnedManager.java | 163 --------- .../chan/core/manager/ThreadManager.java | 6 +- .../chan/core/manager/WatchManager.java | 320 ++++++++++++++++++ .../java/org/floens/chan/core/model/Pin.java | 5 +- .../floens/chan/core/watch/PinWatcher.java | 44 ++- .../org/floens/chan/service/WatchService.java | 231 ------------- .../floens/chan/ui/activity/BaseActivity.java | 21 +- .../chan/ui/activity/BoardActivity.java | 47 ++- .../ui/activity/WatchSettingsActivity.java | 11 + .../floens/chan/ui/adapter/PinnedAdapter.java | 2 +- .../chan/ui/fragment/ReplyFragment.java | 2 +- .../watch => ui/service}/WatchNotifier.java | 131 ++++--- 15 files changed, 554 insertions(+), 510 deletions(-) delete mode 100644 Clover/app/src/main/java/org/floens/chan/core/manager/PinnedManager.java create mode 100644 Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java delete mode 100644 Clover/app/src/main/java/org/floens/chan/service/WatchService.java rename Clover/app/src/main/java/org/floens/chan/{core/watch => ui/service}/WatchNotifier.java (58%) diff --git a/Clover/app/src/main/AndroidManifest.xml b/Clover/app/src/main/AndroidManifest.xml index 0ffe1cf0..3eb26f25 100644 --- a/Clover/app/src/main/AndroidManifest.xml +++ b/Clover/app/src/main/AndroidManifest.xml @@ -136,7 +136,7 @@ along with this program. If not, see . diff --git a/Clover/app/src/main/java/org/floens/chan/ChanApplication.java b/Clover/app/src/main/java/org/floens/chan/ChanApplication.java index 982f5baf..4630c98b 100644 --- a/Clover/app/src/main/java/org/floens/chan/ChanApplication.java +++ b/Clover/app/src/main/java/org/floens/chan/ChanApplication.java @@ -28,27 +28,31 @@ import com.android.volley.toolbox.ImageLoader; import com.android.volley.toolbox.Volley; import org.floens.chan.core.manager.BoardManager; -import org.floens.chan.core.manager.PinnedManager; -import org.floens.chan.core.manager.PinnedManager.PinListener; import org.floens.chan.core.manager.ReplyManager; +import org.floens.chan.core.manager.WatchManager; import org.floens.chan.core.net.BitmapLruImageCache; import org.floens.chan.database.DatabaseManager; -import org.floens.chan.service.WatchService; import org.floens.chan.utils.IconCache; -import org.floens.chan.utils.ThemeHelper; +import org.floens.chan.utils.Logger; import java.io.File; import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +public class ChanApplication extends Application { + private static final String TAG = "ChanApplication"; -public class ChanApplication extends Application implements PinListener { private static ChanApplication instance; private static RequestQueue volleyRequestQueue; private static ImageLoader imageLoader; private static BoardManager boardManager; - private static PinnedManager pinnedManager; + private static WatchManager watchManager; private static ReplyManager replyManager; private static DatabaseManager databaseManager; - private static ThemeHelper themeHelper; + + private List foregroundChangedListeners = new ArrayList<>(); + private int activityForegroundCounter = 0; public ChanApplication() { instance = this; @@ -70,8 +74,8 @@ public class ChanApplication extends Application implements PinListener { return boardManager; } - public static PinnedManager getPinnedManager() { - return pinnedManager; + public static WatchManager getWatchManager() { + return watchManager; } public static ReplyManager getReplyManager() { @@ -115,15 +119,50 @@ public class ChanApplication extends Application implements PinListener { databaseManager = new DatabaseManager(this); boardManager = new BoardManager(); - pinnedManager = new PinnedManager(this); - pinnedManager.addPinListener(this); + watchManager = new WatchManager(this); replyManager = new ReplyManager(this); + } - WatchService.updateRunningState(this); + public void activityEnteredForeground() { + boolean lastForeground = getApplicationInForeground(); + + activityForegroundCounter++; + + if (getApplicationInForeground() != lastForeground) { + for (ForegroundChangedListener listener : foregroundChangedListeners) { + listener.onForegroundChanged(getApplicationInForeground()); + } + } } - @Override - public void onPinsChanged() { - WatchService.updateRunningState(this); + public void activityEnteredBackground() { + boolean lastForeground = getApplicationInForeground(); + + activityForegroundCounter--; + if (activityForegroundCounter < 0) { + Logger.wtf(TAG, "activityForegroundCounter below 0"); + } + + if (getApplicationInForeground() != lastForeground) { + for (ForegroundChangedListener listener : foregroundChangedListeners) { + listener.onForegroundChanged(getApplicationInForeground()); + } + } + } + + public boolean getApplicationInForeground() { + return activityForegroundCounter > 0; + } + + public void addForegroundChangedListener(ForegroundChangedListener listener) { + foregroundChangedListeners.add(listener); + } + + public void removeForegroundChangedListener(ForegroundChangedListener listener) { + foregroundChangedListeners.remove(listener); + } + + public static interface ForegroundChangedListener { + public void onForegroundChanged(boolean foreground); } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/ChanPreferences.java b/Clover/app/src/main/java/org/floens/chan/core/ChanPreferences.java index 858534d3..039e1138 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/ChanPreferences.java +++ b/Clover/app/src/main/java/org/floens/chan/core/ChanPreferences.java @@ -20,7 +20,6 @@ package org.floens.chan.core; import android.content.SharedPreferences; import org.floens.chan.ChanApplication; -import org.floens.chan.service.WatchService; public class ChanPreferences { private static SharedPreferences p() { @@ -64,8 +63,7 @@ public class ChanPreferences { public static void setWatchEnabled(boolean enabled) { if (getWatchEnabled() != enabled) { p().edit().putBoolean("preference_watch_enabled", enabled).commit(); - WatchService.updateRunningState(ChanApplication.getInstance()); - ChanApplication.getPinnedManager().onPinsChanged(); + ChanApplication.getWatchManager().onWatchEnabledChanged(enabled); } } @@ -73,9 +71,9 @@ public class ChanPreferences { return p().getBoolean("preference_watch_background_enabled", false); } - public static long getWatchBackgroundTimeout() { - String number = p().getString("preference_watch_background_timeout", "0"); - return Integer.parseInt(number) * 1000L; + public static int getWatchBackgroundTimeout() { + String number = p().getString("preference_watch_background_timeout", "60"); + return Integer.parseInt(number); } public static boolean getVideoAutoPlay() { diff --git a/Clover/app/src/main/java/org/floens/chan/core/manager/PinnedManager.java b/Clover/app/src/main/java/org/floens/chan/core/manager/PinnedManager.java deleted file mode 100644 index b7af3626..00000000 --- a/Clover/app/src/main/java/org/floens/chan/core/manager/PinnedManager.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Clover - 4chan browser https://github.com/Floens/Clover/ - * Copyright (C) 2014 Floens - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.floens.chan.core.manager; - -import android.content.Context; - -import org.floens.chan.ChanApplication; -import org.floens.chan.core.model.Loadable; -import org.floens.chan.core.model.Pin; - -import java.util.ArrayList; -import java.util.List; - -public class PinnedManager { - private final List listeners = new ArrayList<>(); - private final List pins; - - public PinnedManager(Context context) { - pins = ChanApplication.getDatabaseManager().getPinned(); - } - - public void addPinListener(PinListener l) { - listeners.add(l); - } - - public void removePinListener(PinListener l) { - listeners.remove(l); - } - - /** - * Look for a pin that has an loadable that is equal to the supplied - * loadable. - * - * @param other - * @return The pin whose loadable is equal to the supplied loadable, or null - * if no pin was found. - */ - public Pin findPinByLoadable(Loadable other) { - for (Pin pin : pins) { - if (pin.loadable.equals(other)) { - return pin; - } - } - - return null; - } - - public Pin findPinById(int id) { - for (Pin pin : pins) { - if (pin.id == id) { - return pin; - } - } - - return null; - } - - public List getPins() { - return pins; - } - - public List getWatchingPins() { - List l = new ArrayList<>(); - - for (Pin p : pins) { - if (p.watching) - l.add(p); - } - - return l; - } - - /** - * Add a pin - * - * @param pin - * @return true if it was added, false if it wasn't (duplicated) - */ - public boolean add(Pin pin) { - // No duplicates - for (Pin e : pins) { - if (e.loadable.equals(pin.loadable)) { - return false; - } - } - - pins.add(pin); - ChanApplication.getDatabaseManager().addPin(pin); - - onPinsChanged(); - - return true; - } - - /** - * Remove a pin - * - * @param pin - */ - public void remove(Pin pin) { - pins.remove(pin); - ChanApplication.getDatabaseManager().removePin(pin); - pin.destroyWatcher(); - - onPinsChanged(); - } - - /** - * Update the pin in the database - * - * @param pin - */ - public void update(Pin pin) { - ChanApplication.getDatabaseManager().updatePin(pin); - - onPinsChanged(); - } - - /** - * Updates all the pins to the database. This will run in a new thread - * because it can be an expensive operation. (this will be an huge headache - * later on when we get concurrent problems) - */ - public void updateAll() { - new Thread(new Runnable() { - @Override - public void run() { - ChanApplication.getDatabaseManager().updatePins(pins); - } - }).start(); - } - - public void onPinViewed(Pin pin) { - pin.getPinWatcher().onViewed(); - - onPinsChanged(); - } - - public void onPinsChanged() { - for (PinListener l : listeners) { - l.onPinsChanged(); - } - } - - public static interface PinListener { - public void onPinsChanged(); - } -} diff --git a/Clover/app/src/main/java/org/floens/chan/core/manager/ThreadManager.java b/Clover/app/src/main/java/org/floens/chan/core/manager/ThreadManager.java index 766e8060..cdb8600f 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/manager/ThreadManager.java +++ b/Clover/app/src/main/java/org/floens/chan/core/manager/ThreadManager.java @@ -124,9 +124,9 @@ public class ThreadManager implements Loader.LoaderListener { public void bottomPostViewed() { if (loader != null && loader.getLoadable().isThreadMode()) { - Pin pin = ChanApplication.getPinnedManager().findPinByLoadable(loader.getLoadable()); + Pin pin = ChanApplication.getWatchManager().findPinByLoadable(loader.getLoadable()); if (pin != null) { - ChanApplication.getPinnedManager().onPinViewed(pin); + ChanApplication.getWatchManager().onPinViewed(pin); } updateLastSeen(); @@ -565,7 +565,7 @@ public class ThreadManager implements Loader.LoaderListener { } private void updateLastSeen() { - Pin pin = ChanApplication.getPinnedManager().findPinByLoadable(loader.getLoadable()); + Pin pin = ChanApplication.getWatchManager().findPinByLoadable(loader.getLoadable()); if (pin != null) { Post last = pin.getLastSeenPost(); if (last != null) { diff --git a/Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java b/Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java new file mode 100644 index 00000000..c1d0bb7d --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java @@ -0,0 +1,320 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan.core.manager; + +import android.content.Context; +import android.content.Intent; + +import org.floens.chan.ChanApplication; +import org.floens.chan.core.ChanPreferences; +import org.floens.chan.core.model.Loadable; +import org.floens.chan.core.model.Pin; +import org.floens.chan.ui.service.WatchNotifier; +import org.floens.chan.utils.Logger; +import org.floens.chan.utils.Utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class WatchManager implements ChanApplication.ForegroundChangedListener { + private static final String TAG = "WatchManager"; + private static final int FOREGROUND_TIME = 10; + + private final Context context; + private final List listeners = new ArrayList<>(); + private final List pins; + private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + private PendingTimer pendingTimer; + + public WatchManager(Context context) { + this.context = context; + + pins = ChanApplication.getDatabaseManager().getPinned(); + + ChanApplication.getInstance().addForegroundChangedListener(this); + + updateTimerState(); + updateNotificationServiceState(); + } + + /** + * Look for a pin that has an loadable that is equal to the supplied + * loadable. + * + * @param other + * @return The pin whose loadable is equal to the supplied loadable, or null + * if no pin was found. + */ + public Pin findPinByLoadable(Loadable other) { + for (Pin pin : pins) { + if (pin.loadable.equals(other)) { + return pin; + } + } + + return null; + } + + public Pin findPinById(int id) { + for (Pin pin : pins) { + if (pin.id == id) { + return pin; + } + } + + return null; + } + + public List getPins() { + return pins; + } + + public List getWatchingPins() { + if (ChanPreferences.getWatchEnabled()) { + List l = new ArrayList<>(); + + for (Pin p : pins) { + if (p.watching) + l.add(p); + } + + return l; + } else { + return Collections.emptyList(); + } + } + + /** + * Add a pin + * + * @param pin + * @return true if it was added, false if it wasn't (duplicated) + */ + public boolean addPin(Pin pin) { + // No duplicates + for (Pin e : pins) { + if (e.loadable.equals(pin.loadable)) { + return false; + } + } + + pins.add(pin); + ChanApplication.getDatabaseManager().addPin(pin); + + onPinsChanged(); + + return true; + } + + /** + * Remove a pin + * + * @param pin + */ + public void removePin(Pin pin) { + pins.remove(pin); + ChanApplication.getDatabaseManager().removePin(pin); + pin.destroyWatcher(); + + onPinsChanged(); + } + + /** + * Update the pin in the database + * + * @param pin + */ + public void updatePin(Pin pin) { + ChanApplication.getDatabaseManager().updatePin(pin); + + onPinsChanged(); + } + + /** + * Updates all the pins to the database. This will run in a new thread + * because it can be an expensive operation. (this will be an huge headache + * later on when we get concurrent problems) + */ + public void updateDatabase() { + new Thread(new Runnable() { + @Override + public void run() { + ChanApplication.getDatabaseManager().updatePins(pins); + } + }).start(); + } + + public void onPinViewed(Pin pin) { + pin.getPinWatcher().onViewed(); + + onPinsChanged(); + } + + public void addPinListener(PinListener l) { + listeners.add(l); + } + + public void removePinListener(PinListener l) { + listeners.remove(l); + } + + public void onPinsChanged() { + for (PinListener l : listeners) { + l.onPinsChanged(); + } + + updateTimerState(); + updateNotificationServiceState(); + } + + public void pausePins() { + List watchingPins = getWatchingPins(); + for (Pin pin : watchingPins) { + pin.watching = false; + } + + onPinsChanged(); + updateDatabase(); + } + + public void onWatchEnabledChanged(boolean watchEnabled) { + updateNotificationServiceState(watchEnabled, getWatchBackgroundEnabled()); + updateTimerState(watchEnabled, getWatchBackgroundEnabled()); + } + + public void onBackgroundWatchingChanged(boolean backgroundEnabled) { + updateNotificationServiceState(getWatchEnabled(), backgroundEnabled); + updateTimerState(getWatchEnabled(), backgroundEnabled); + } + + @Override + public void onForegroundChanged(final boolean foreground) { + updateNotificationServiceState(); + updateTimerState(); + } + + public boolean getWatchEnabled() { + // getWatchingPins returns an empty list when ChanPreferences.getWatchEnabled() is false + return getWatchingPins().size() > 0; + } + + public boolean getWatchBackgroundEnabled() { + return ChanPreferences.getWatchBackgroundEnabled(); + } + + private void updateNotificationServiceState() { + updateNotificationServiceState(getWatchEnabled(), getWatchBackgroundEnabled()); + } + + private void updateNotificationServiceState(boolean watchEnabled, boolean backgroundEnabled) { + if (watchEnabled && backgroundEnabled) { + // Also calls onStartCommand, which updates the notification + context.startService(new Intent(context, WatchNotifier.class)); + } else { + context.stopService(new Intent(context, WatchNotifier.class)); + } + } + + private void updateTimerState() { + updateTimerState(getWatchEnabled(), getWatchBackgroundEnabled()); + } + + private void updateTimerState(boolean watchEnabled, boolean backgroundEnabled) { + if (watchEnabled) { + if (ChanApplication.getInstance().getApplicationInForeground()) { + setTimer(FOREGROUND_TIME); + } else { + if (backgroundEnabled) { + setTimer(ChanPreferences.getWatchBackgroundTimeout()); + } else { + if (pendingTimer != null) { + pendingTimer.cancel(); + pendingTimer = null; + Logger.d(TAG, "Canceled timer"); + } + } + } + } else { + if (pendingTimer != null) { + pendingTimer.cancel(); + pendingTimer = null; + Logger.d(TAG, "Canceled timer"); + } + } + } + + private void setTimer(int time) { + if (pendingTimer != null && pendingTimer.time == time) { + return; + } + + if (pendingTimer != null) { + pendingTimer.cancel(); + pendingTimer = null; + Logger.d(TAG, "Canceled timer"); + } + + ScheduledFuture scheduledFuture = executor.schedule(new Runnable() { + @Override + public void run() { + Utils.runOnUiThread(new Runnable() { + @Override + public void run() { + timerFired(); + } + }); + } + }, time, TimeUnit.SECONDS); + pendingTimer = new PendingTimer(scheduledFuture, time); + Logger.d(TAG, "Timer firing in " + time + " seconds"); + } + + private void timerFired() { + Logger.d(TAG, "Timer fired"); + pendingTimer = null; + + for (Pin pin : getWatchingPins()) { + pin.updateWatch(); + } + + updateTimerState(); + } + + public static interface PinListener { + public void onPinsChanged(); + } + + private static class PendingTimer { + public ScheduledFuture scheduledFuture; + public int time; + + public PendingTimer(ScheduledFuture scheduledFuture, int time) { + this.scheduledFuture = scheduledFuture; + this.time = time; + } + + public void cancel() { + scheduledFuture.cancel(false); + } + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/Pin.java b/Clover/app/src/main/java/org/floens/chan/core/model/Pin.java index 3ff9a0da..a909d5ee 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/Pin.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/Pin.java @@ -79,9 +79,10 @@ public class Pin { public void toggleWatch() { watching = !watching; - ChanApplication.getPinnedManager().onPinsChanged(); + ChanApplication.getWatchManager().onPinsChanged(); + if (watching) { - updateWatch(); + getPinWatcher().update(); } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/watch/PinWatcher.java b/Clover/app/src/main/java/org/floens/chan/core/watch/PinWatcher.java index d324ae35..b52e0109 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/watch/PinWatcher.java +++ b/Clover/app/src/main/java/org/floens/chan/core/watch/PinWatcher.java @@ -19,12 +19,13 @@ package org.floens.chan.core.watch; import com.android.volley.VolleyError; +import org.floens.chan.ChanApplication; import org.floens.chan.core.loader.Loader; import org.floens.chan.core.loader.LoaderPool; import org.floens.chan.core.model.Pin; import org.floens.chan.core.model.Post; -import org.floens.chan.service.WatchService; import org.floens.chan.utils.Logger; +import org.floens.chan.utils.Utils; import java.util.ArrayList; import java.util.List; @@ -37,6 +38,7 @@ public class PinWatcher implements Loader.LoaderListener { private final List posts = new ArrayList<>(); private boolean wereNewQuotes = false; + private boolean wereNewPosts = false; public PinWatcher(Pin pin) { this.pin = pin; @@ -64,6 +66,7 @@ public class PinWatcher implements Loader.LoaderListener { pin.quoteLastCount = pin.quoteNewCount; wereNewQuotes = false; + wereNewPosts = false; } public List getNewPosts() { @@ -92,6 +95,15 @@ public class PinWatcher implements Loader.LoaderListener { } } + public boolean getWereNewPosts() { + if (wereNewPosts) { + wereNewPosts = false; + return true; + } else { + return false; + } + } + public Post getLastSeenPost() { int i = posts.size() - pin.getNewPostsCount() - 1; if (i >= 0 && i < posts.size()) { @@ -106,7 +118,12 @@ public class PinWatcher implements Loader.LoaderListener { Logger.e(TAG, "PinWatcher onError: ", error); pin.isError = true; - WatchService.onPinWatcherResult(); + Utils.runOnUiThread(new Runnable() { + @Override + public void run() { + ChanApplication.getWatchManager().onPinsChanged(); + } + }); } @Override @@ -116,14 +133,14 @@ public class PinWatcher implements Loader.LoaderListener { posts.clear(); posts.addAll(result); + int lastQuoteNewCount = pin.quoteNewCount; + int lastWatchNewCount = pin.watchNewCount; + if (pin.watchLastCount < 0) pin.watchLastCount = result.size(); pin.watchNewCount = result.size(); - // If there are more replies than last time, let the notification make a sound - int lastCounterForSoundNotification = pin.quoteNewCount; - // Get list of saved posts int total = 0; for (Post saved : result) { @@ -134,15 +151,24 @@ public class PinWatcher implements Loader.LoaderListener { pin.quoteNewCount = total; - if (pin.quoteNewCount > lastCounterForSoundNotification) { + if (pin.quoteNewCount > lastQuoteNewCount) { wereNewQuotes = true; } + if (pin.watchNewCount > lastWatchNewCount) { + wereNewPosts = true; + } + if (Logger.debugEnabled()) { - Logger.d(TAG, String.format("postlast=%d postnew=%d quotelast=%d quotenew=%d werenewquotes=%b", - pin.watchLastCount, pin.watchNewCount, pin.quoteLastCount, pin.quoteNewCount, wereNewQuotes)); + Logger.d(TAG, String.format("postlast=%d postnew=%d werenewposts=%b quotelast=%d quotenew=%d werenewquotes=%b", + pin.watchLastCount, pin.watchNewCount, wereNewPosts, pin.quoteLastCount, pin.quoteNewCount, wereNewQuotes)); } - WatchService.onPinWatcherResult(); + Utils.runOnUiThread(new Runnable() { + @Override + public void run() { + ChanApplication.getWatchManager().onPinsChanged(); + } + }); } } diff --git a/Clover/app/src/main/java/org/floens/chan/service/WatchService.java b/Clover/app/src/main/java/org/floens/chan/service/WatchService.java deleted file mode 100644 index b0c15ca0..00000000 --- a/Clover/app/src/main/java/org/floens/chan/service/WatchService.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Clover - 4chan browser https://github.com/Floens/Clover/ - * Copyright (C) 2014 Floens - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.floens.chan.service; - -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.IBinder; - -import org.floens.chan.ChanApplication; -import org.floens.chan.core.ChanPreferences; -import org.floens.chan.core.model.Pin; -import org.floens.chan.core.watch.WatchNotifier; -import org.floens.chan.utils.Logger; -import org.floens.chan.utils.Utils; - -import java.util.List; - -public class WatchService extends Service { - private static final String TAG = "WatchService"; - - private static final long FOREGROUND_INTERVAL = 10000L; - - private static WatchService instance; - private static boolean activityInForeground = false; - - private Thread loadThread; - private boolean running = true; - private WatchNotifier watchNotifier; - - public static void onActivityStart() { - activityInForeground = true; - if (instance != null) { - instance.onActivityInForeground(); - } - } - - public static void onActivityStop() { - activityInForeground = false; - if (instance != null) { - instance.onActivityInBackground(); - } - } - - public static boolean getActivityInForeground() { - return activityInForeground; - } - - public static void updateRunningState(Context context) { - if (ChanPreferences.getWatchEnabled()) { - if (ChanApplication.getPinnedManager().getWatchingPins().size() == 0) { - if (getRunning()) { - disable(context); - } - } else { - if (!getRunning()) { - enable(context); - } - } - } else { - if (getRunning()) { - disable(context); - } - } - } - - public static void enable(Context context) { - if (!getRunning()) { - context.startService(new Intent(context, WatchService.class)); - } - } - - public static void disable(Context context) { - if (getRunning()) { - context.stopService(new Intent(context, WatchService.class)); - - List pins = ChanApplication.getPinnedManager().getWatchingPins(); - for (Pin pin : pins) { - pin.destroyWatcher(); - } - - instance.watchNotifier.destroy(); - } - } - - public static boolean getRunning() { - return instance != null; - } - - public static void onPinWatcherResult() { - Utils.runOnUiThread(new Runnable() { - @Override - public void run() { - ChanApplication.getPinnedManager().onPinsChanged(); - if (instance != null) { - instance.watchNotifier.update(); - } - } - }); - } - - @Override - public void onCreate() { - super.onCreate(); - - instance = this; - - watchNotifier = new WatchNotifier(this); - - Logger.i(TAG, "WatchService created"); - - startThread(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - - instance = null; - - running = false; - if (loadThread != null) { - loadThread.interrupt(); - } - - Logger.i(TAG, "WatchService destroyed"); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (intent != null && intent.getExtras() != null && intent.getExtras().getBoolean("pause_pins", false)) { - if (watchNotifier != null) { - watchNotifier.onPausePinsClicked(); - } - } - - return START_STICKY; - } - - private void startThread() { - running = true; - - if (loadThread == null) { - loadThread = new Thread(new Runnable() { - @Override - public void run() { - while (running) { - Logger.d(TAG, "Loadthread loop"); - - if (!running) - return; - - long timeout = activityInForeground ? FOREGROUND_INTERVAL : getBackgroundTimeout(); - if (timeout < 0L) { - Logger.d(TAG, "Waiting for interrupt..."); - try { - Object o = new Object(); - synchronized (o) { - o.wait(); - } - } catch (InterruptedException e) { - Logger.d(TAG, "Interrupted!"); - } - } else { - update(); - - try { - Thread.sleep(timeout); - } catch (InterruptedException e) { - Logger.d(TAG, "Interrupted!"); - } - } - } - } - }); - - loadThread.start(); - } - } - - private void onActivityInForeground() { - if (loadThread != null) { - loadThread.interrupt(); - } - watchNotifier.onForegroundChanged(); - } - - private void onActivityInBackground() { - watchNotifier.onForegroundChanged(); - } - - /** - * Returns the sleep time the user specified for background iteration - * - * @return the sleep time in ms, or -1 if background reloading is disabled - */ - private long getBackgroundTimeout() { - if (ChanPreferences.getWatchBackgroundEnabled()) { - return ChanPreferences.getWatchBackgroundTimeout(); - } else { - return -1; - } - } - - private void update() { - List pins = ChanApplication.getPinnedManager().getWatchingPins(); - for (Pin pin : pins) { - pin.updateWatch(); - } - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } -} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/BaseActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/BaseActivity.java index 2fcc434b..791a5106 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/activity/BaseActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/BaseActivity.java @@ -46,7 +46,7 @@ import android.widget.ShareActionProvider; import org.floens.chan.ChanApplication; import org.floens.chan.R; -import org.floens.chan.core.manager.PinnedManager; +import org.floens.chan.core.manager.WatchManager; import org.floens.chan.core.model.Pin; import org.floens.chan.core.model.Post; import org.floens.chan.ui.BadgeDrawable; @@ -58,7 +58,7 @@ import org.floens.chan.utils.Utils; import java.util.List; -public abstract class BaseActivity extends Activity implements PanelSlideListener, PinnedManager.PinListener { +public abstract class BaseActivity extends Activity implements PanelSlideListener, WatchManager.PinListener { public static boolean doRestartOnResume = false; private final static int ACTION_OPEN_URL = 1; @@ -102,7 +102,7 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene threadPane = (SlidingPaneLayout) findViewById(R.id.pane_container); initPane(); - ChanApplication.getPinnedManager().addPinListener(this); + ChanApplication.getWatchManager().addPinListener(this); updateIcon(); } @@ -111,7 +111,7 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene protected void onDestroy() { super.onDestroy(); - ChanApplication.getPinnedManager().removePinListener(this); + ChanApplication.getWatchManager().removePinListener(this); } @Override @@ -203,7 +203,7 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene } private void updateIcon() { - List list = ChanApplication.getPinnedManager().getWatchingPins(); + List list = ChanApplication.getWatchManager().getWatchingPins(); if (list.size() > 0) { int count = 0; boolean color = false; @@ -226,15 +226,15 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene } public void addPin(Pin pin) { - ChanApplication.getPinnedManager().add(pin); + ChanApplication.getWatchManager().addPin(pin); } public void removePin(Pin pin) { - ChanApplication.getPinnedManager().remove(pin); + ChanApplication.getWatchManager().removePin(pin); } public void updatePin(Pin pin) { - ChanApplication.getPinnedManager().update(pin); + ChanApplication.getWatchManager().updatePin(pin); } private void changePinTitle(final Pin pin) { @@ -318,7 +318,10 @@ public abstract class BaseActivity extends Activity implements PanelSlideListene } NdefMessage message = new NdefMessage(new NdefRecord[]{record}); - adapter.setNdefPushMessage(message, this); + try { + adapter.setNdefPushMessage(message, this); + } catch (Exception e) { + } } Intent share = new Intent(android.content.Intent.ACTION_SEND); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/BoardActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/BoardActivity.java index b39a69c5..93d41fe9 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/activity/BoardActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/BoardActivity.java @@ -48,7 +48,6 @@ import org.floens.chan.core.ChanPreferences; import org.floens.chan.core.model.Loadable; import org.floens.chan.core.model.Pin; import org.floens.chan.core.model.Post; -import org.floens.chan.service.WatchService; import org.floens.chan.ui.fragment.ThreadFragment; import org.floens.chan.utils.Logger; import org.floens.chan.utils.Utils; @@ -118,24 +117,22 @@ public class BoardActivity extends BaseActivity implements AdapterView.OnItemSel } } - Bundle extras = startIntent.getExtras(); - if (extras != null) { - int pinId = extras.getInt("pin_id", -2); - if (pinId != -2) { - if (pinId == -1) { - pinDrawer.openDrawer(pinDrawerView); - } else { - Pin pin = ChanApplication.getPinnedManager().findPinById(pinId); - if (pin != null) { - startLoadingThread(pin.loadable); - } - } - } + if (startIntent.getExtras() != null) { + handleExtraBundle(startIntent.getExtras()); } ignoreNextOnItemSelected = true; } + @Override + protected void onNewIntent(final Intent intent) { + super.onNewIntent(intent); + + if (intent.getExtras() != null) { + handleExtraBundle(intent.getExtras()); + } + } + @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -148,21 +145,21 @@ public class BoardActivity extends BaseActivity implements AdapterView.OnItemSel protected void onStart() { super.onStart(); - WatchService.onActivityStart(); + ChanApplication.getInstance().activityEnteredForeground(); } @Override protected void onStop() { super.onStop(); - WatchService.onActivityStop(); + ChanApplication.getInstance().activityEnteredBackground(); } @Override protected void onPause() { super.onPause(); - ChanApplication.getPinnedManager().updateAll(); + ChanApplication.getWatchManager().updateDatabase(); } @Override @@ -236,6 +233,20 @@ public class BoardActivity extends BaseActivity implements AdapterView.OnItemSel updateActionBarState(); } + private void handleExtraBundle(Bundle extras) { + int pinId = extras.getInt("pin_id", -2); + if (pinId != -2) { + if (pinId == -1) { + pinDrawer.openDrawer(pinDrawerView); + } else { + Pin pin = ChanApplication.getWatchManager().findPinById(pinId); + if (pin != null) { + startLoadingThread(pin.loadable); + } + } + } + } + private void updatePaneState() { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); @@ -458,7 +469,7 @@ public class BoardActivity extends BaseActivity implements AdapterView.OnItemSel if (loadable.mode == Loadable.Mode.INVALID) return; - Pin pin = ChanApplication.getPinnedManager().findPinByLoadable(loadable); + Pin pin = ChanApplication.getWatchManager().findPinByLoadable(loadable); if (pin != null) { // Use the loadable from the pin. // This way we can store the listview position in the pin loadable, diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/WatchSettingsActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/WatchSettingsActivity.java index 6b12577f..fae4c297 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/activity/WatchSettingsActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/WatchSettingsActivity.java @@ -23,6 +23,7 @@ import android.app.FragmentTransaction; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; @@ -38,6 +39,7 @@ import android.widget.LinearLayout; import android.widget.Switch; import android.widget.TextView; +import org.floens.chan.ChanApplication; import org.floens.chan.R; import org.floens.chan.core.ChanPreferences; import org.floens.chan.utils.ThemeHelper; @@ -153,6 +155,15 @@ public class WatchSettingsActivity extends Activity implements OnCheckedChangeLi return true; } }); + + ((CheckBoxPreference)findPreference("preference_watch_background_enabled")).setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(final Preference preference, final Object newValue) { + ChanApplication.getWatchManager().onBackgroundWatchingChanged((Boolean)newValue); + + return true; + } + }); } private void updateListSummary(ListPreference backgroundTimeout, String value) { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/adapter/PinnedAdapter.java b/Clover/app/src/main/java/org/floens/chan/ui/adapter/PinnedAdapter.java index ba2f30d2..f5864cdd 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/adapter/PinnedAdapter.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/adapter/PinnedAdapter.java @@ -163,7 +163,7 @@ public class PinnedAdapter extends BaseAdapter { public void reload() { pins.clear(); - pins.addAll(ChanApplication.getPinnedManager().getPins()); + pins.addAll(ChanApplication.getWatchManager().getPins()); notifyDataSetChanged(); } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java b/Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java index 83260c7e..5ad3cbfe 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java @@ -520,7 +520,7 @@ public class ReplyFragment extends DialogFragment { // Pin thread on successful post Pin pin = new Pin(); pin.loadable = loadable; - ChanApplication.getPinnedManager().add(pin); + ChanApplication.getWatchManager().addPin(pin); closeReply(); } else { diff --git a/Clover/app/src/main/java/org/floens/chan/core/watch/WatchNotifier.java b/Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java similarity index 58% rename from Clover/app/src/main/java/org/floens/chan/core/watch/WatchNotifier.java rename to Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java index 1e371a04..ab7f9369 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/watch/WatchNotifier.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java @@ -15,72 +15,85 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.floens.chan.core.watch; +package org.floens.chan.ui.service; +import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.Service; import android.content.Context; import android.content.Intent; import android.media.RingtoneManager; +import android.os.IBinder; import android.support.v4.app.NotificationCompat; import org.floens.chan.ChanApplication; import org.floens.chan.R; -import org.floens.chan.core.ChanPreferences; +import org.floens.chan.core.manager.WatchManager; import org.floens.chan.core.model.Pin; import org.floens.chan.core.model.Post; -import org.floens.chan.service.WatchService; +import org.floens.chan.core.watch.PinWatcher; import org.floens.chan.ui.activity.BoardActivity; -import org.floens.chan.utils.Logger; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -public class WatchNotifier { +public class WatchNotifier extends Service { private static final String TAG = "WatchNotifier"; + private static final int NOTIFICATION_ID = 1; - private final int NOTIFICATION_ID = 1; + private NotificationManager nm; + private WatchManager wm; - private final Context context; - private final NotificationManager nm; + @Override + public IBinder onBind(final Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); - public WatchNotifier(Context context) { - this.context = context; - nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + wm = ChanApplication.getWatchManager(); + + startForeground(NOTIFICATION_ID, getIdleNotification()); } - public void destroy() { + @Override + public void onDestroy() { + super.onDestroy(); nm.cancel(NOTIFICATION_ID); } - public void update() { - if (!WatchService.getActivityInForeground() && ChanPreferences.getWatchBackgroundEnabled()) { - prepareNotification(); + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent != null && intent.getExtras() != null && intent.getExtras().getBoolean("pause_pins", false)) { + pausePins(); + } else { + updateNotification(); } - } - public void onForegroundChanged() { - if (WatchService.getActivityInForeground()) { - nm.cancel(NOTIFICATION_ID); - } + return START_STICKY; } - public void onPausePinsClicked() { - nm.cancel(NOTIFICATION_ID); - - List watchingPins = ChanApplication.getPinnedManager().getWatchingPins(); - for (Pin pin : watchingPins) { - pin.watching = false; + public void updateNotification() { + Notification notification = createNotification(); + if (notification != null) { + nm.notify(NOTIFICATION_ID, notification); + } else { + nm.notify(NOTIFICATION_ID, getIdleNotification()); } + } - ChanApplication.getPinnedManager().onPinsChanged(); - ChanApplication.getPinnedManager().updateAll(); // Can be the last thing this app does + public void pausePins() { + wm.pausePins(); } - private void prepareNotification() { - List watchingPins = ChanApplication.getPinnedManager().getWatchingPins(); + private Notification createNotification() { + List watchingPins = wm.getWatchingPins(); List pins = new ArrayList<>(); int newPostsCount = 0; @@ -88,6 +101,7 @@ public class WatchNotifier { List posts = new ArrayList<>(); boolean makeSound = false; boolean show = false; + boolean wereNewPosts = false; for (Pin pin : watchingPins) { PinWatcher watcher = pin.getPinWatcher(); @@ -97,6 +111,9 @@ public class WatchNotifier { boolean add = false; if (pin.getNewPostsCount() > 0) { + if (watcher.getWereNewPosts()) { + wereNewPosts = true; + } newPostsCount += pin.getNewPostsCount(); for (Post p : watcher.getNewPosts()) { p.title = pin.loadable.title; @@ -129,7 +146,6 @@ public class WatchNotifier { } String tickerText = title + " in "; - if (pins.size() == 1) { tickerText += pins.get(0).loadable.title; } else { @@ -159,54 +175,67 @@ public class WatchNotifier { targetPin = pins.get(0); } - showNotification(tickerText, title, tickerText, Integer.toString(newPostsCount), lines, makeSound, targetPin); + boolean showTickerText = !ChanApplication.getInstance().getApplicationInForeground() && wereNewPosts; + return getNotificationFor(showTickerText ? tickerText : null, title, tickerText, Integer.toString(newPostsCount), lines, makeSound, targetPin); + } else { + return null; } } + private Notification getIdleNotification() { + List watchingPins = wm.getWatchingPins(); + int s = watchingPins.size(); + String message = "Watching " + s + " thread" + (s != 1 ? "s" : ""); + + return getNotificationFor(null, message, message, "", null, false, null); + } + @SuppressWarnings("deprecation") - private void showNotification(String tickerText, String title, String content, String contentInfo, - List lines, boolean makeSound, Pin targetPin) { + private Notification getNotificationFor(String tickerText, String title, String content, String count, + List lines, boolean makeSound, Pin targetPin) { - Intent intent = new Intent(context, BoardActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + Intent intent = new Intent(this, BoardActivity.class); + intent.setAction(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); intent.putExtra("pin_id", targetPin == null ? -1 : targetPin.id); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - NotificationCompat.Builder builder = new NotificationCompat.Builder(context); + NotificationCompat.Builder builder = new NotificationCompat.Builder(this); builder.setContentIntent(pendingIntent); builder.setTicker(tickerText); builder.setContentTitle(title); builder.setContentText(content); - builder.setContentInfo(contentInfo); + builder.setContentInfo(count); builder.setSmallIcon(R.drawable.ic_stat_notify); - Intent pauseWatching = new Intent(context, WatchService.class); + Intent pauseWatching = new Intent(this, WatchNotifier.class); pauseWatching.putExtra("pause_pins", true); - PendingIntent pauseWatchingPending = PendingIntent.getService(context, 0, pauseWatching, + PendingIntent pauseWatchingPending = PendingIntent.getService(this, 0, pauseWatching, PendingIntent.FLAG_UPDATE_CURRENT); - builder.addAction(R.drawable.ic_action_pause, context.getString(R.string.watch_pause_pins), + builder.addAction(R.drawable.ic_action_pause, getString(R.string.watch_pause_pins), pauseWatchingPending); if (makeSound) { builder.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); } - NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); - for (CharSequence line : lines.subList(Math.max(0, lines.size() - 10), lines.size())) { - style.addLine(line); + if (lines != null) { + NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); + for (CharSequence line : lines.subList(Math.max(0, lines.size() - 10), lines.size())) { + style.addLine(line); + } + style.setBigContentTitle(title); + builder.setStyle(style); } - style.setBigContentTitle(title); - // style.setSummaryText(content); - - builder.setStyle(style); - Logger.i(TAG, "Showing notification"); - nm.notify(NOTIFICATION_ID, builder.getNotification()); + return builder.getNotification(); } private static class PostAgeComparer implements Comparator {