Refactor the pin watcher.

captchafix
Florens Douwes 11 years ago
parent 0b0ac8748b
commit 8745fc364c
  1. 2
      Clover/app/src/main/AndroidManifest.xml
  2. 69
      Clover/app/src/main/java/org/floens/chan/ChanApplication.java
  3. 10
      Clover/app/src/main/java/org/floens/chan/core/ChanPreferences.java
  4. 163
      Clover/app/src/main/java/org/floens/chan/core/manager/PinnedManager.java
  5. 6
      Clover/app/src/main/java/org/floens/chan/core/manager/ThreadManager.java
  6. 320
      Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java
  7. 5
      Clover/app/src/main/java/org/floens/chan/core/model/Pin.java
  8. 44
      Clover/app/src/main/java/org/floens/chan/core/watch/PinWatcher.java
  9. 231
      Clover/app/src/main/java/org/floens/chan/service/WatchService.java
  10. 21
      Clover/app/src/main/java/org/floens/chan/ui/activity/BaseActivity.java
  11. 47
      Clover/app/src/main/java/org/floens/chan/ui/activity/BoardActivity.java
  12. 11
      Clover/app/src/main/java/org/floens/chan/ui/activity/WatchSettingsActivity.java
  13. 2
      Clover/app/src/main/java/org/floens/chan/ui/adapter/PinnedAdapter.java
  14. 2
      Clover/app/src/main/java/org/floens/chan/ui/fragment/ReplyFragment.java
  15. 131
      Clover/app/src/main/java/org/floens/chan/ui/service/WatchNotifier.java

@ -136,7 +136,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</activity>
<service
android:name="org.floens.chan.service.WatchService"
android:name=".ui.service.WatchNotifier"
android:exported="false">
</service>
</application>

@ -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<ForegroundChangedListener> 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);
}
}

@ -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() {

@ -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 <http://www.gnu.org/licenses/>.
*/
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<PinListener> listeners = new ArrayList<>();
private final List<Pin> 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<Pin> getPins() {
return pins;
}
public List<Pin> getWatchingPins() {
List<Pin> 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();
}
}

@ -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) {

@ -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 <http://www.gnu.org/licenses/>.
*/
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<PinListener> listeners = new ArrayList<>();
private final List<Pin> 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<Pin> getPins() {
return pins;
}
public List<Pin> getWatchingPins() {
if (ChanPreferences.getWatchEnabled()) {
List<Pin> 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<Pin> 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);
}
}
}

@ -79,9 +79,10 @@ public class Pin {
public void toggleWatch() {
watching = !watching;
ChanApplication.getPinnedManager().onPinsChanged();
ChanApplication.getWatchManager().onPinsChanged();
if (watching) {
updateWatch();
getPinWatcher().update();
}
}

@ -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<Post> 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<Post> 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();
}
});
}
}

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Pin> 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<Pin> pins = ChanApplication.getPinnedManager().getWatchingPins();
for (Pin pin : pins) {
pin.updateWatch();
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

@ -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<Pin> list = ChanApplication.getPinnedManager().getWatchingPins();
List<Pin> 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);

@ -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,

@ -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) {

@ -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();
}

@ -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 {

@ -15,72 +15,85 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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<Pin> 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<Pin> watchingPins = ChanApplication.getPinnedManager().getWatchingPins();
private Notification createNotification() {
List<Pin> watchingPins = wm.getWatchingPins();
List<Pin> pins = new ArrayList<>();
int newPostsCount = 0;
@ -88,6 +101,7 @@ public class WatchNotifier {
List<Post> 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<Pin> 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<CharSequence> lines, boolean makeSound, Pin targetPin) {
private Notification getNotificationFor(String tickerText, String title, String content, String count,
List<CharSequence> 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<Post> {
Loading…
Cancel
Save