SubscriptionService.java (10860B)
1 /** 2 * 3 */ 4 package de.mtbnews.android.service; 5 6 import java.util.ArrayList; 7 import java.util.List; 8 import java.util.Timer; 9 import java.util.TimerTask; 10 11 import android.app.Notification; 12 import android.app.NotificationManager; 13 import android.app.PendingIntent; 14 import android.app.Service; 15 import android.content.Context; 16 import android.content.Intent; 17 import android.content.SharedPreferences; 18 import android.net.Uri; 19 import android.os.IBinder; 20 import android.preference.PreferenceManager; 21 import android.text.TextUtils; 22 import android.util.Log; 23 import de.mtbnews.android.MailboxActivity; 24 import de.mtbnews.android.R; 25 import de.mtbnews.android.SubscriptionForenActivity; 26 import de.mtbnews.android.SubscriptionTopicsActivity; 27 import de.mtbnews.android.tapatalk.TapatalkClient; 28 import de.mtbnews.android.tapatalk.TapatalkException; 29 import de.mtbnews.android.tapatalk.wrapper.Forum; 30 import de.mtbnews.android.tapatalk.wrapper.ListHolder; 31 import de.mtbnews.android.tapatalk.wrapper.Mailbox; 32 import de.mtbnews.android.tapatalk.wrapper.Topic; 33 import de.mtbnews.android.util.IBC; 34 35 /** 36 * Hintergrund-Service, der ungelesene Nachrichten, Themen und Beiträge 37 * ermittelt und im Erfolgsfall eine Notification erzeugt. 38 * 39 * @author dankert 40 * 41 */ 42 public class SubscriptionService extends Service 43 { 44 /** 45 * Timer, der das zeitgesteuerte Abholen von neuen Nachrichten steuert. 46 */ 47 // Damit der Timer nicht nur erzeugt, sondern auch gestoppt werden kann, 48 // behalten wir hier eine Referenz. 49 private Timer timer; 50 51 /** 52 * App-Einstellungen. 53 */ 54 // Notwendig für die Benutzeranmeldung. 55 private SharedPreferences prefs; 56 57 // Notification-Kategorien: 58 private static final int NOTIFICATION_EVENT_RUNNING = 1; 59 private static final int NOTIFICATION_TOPIC = 2; 60 private static final int NOTIFICATION_FORUM = 3; 61 private static final int NOTIFICATION_MESSAGES = 4; 62 63 /** 64 * {@inheritDoc} 65 * 66 * @see android.app.Service#onBind(android.content.Intent) 67 */ 68 public IBinder onBind(Intent arg0) 69 { 70 // Dieser Service läuft stets alleine und wird nicht gebunden. 71 return null; 72 } 73 74 /** 75 * Service-Start. Erzeugt für diese Serviceinstanz einen Timer, der in 76 * regelmäßigen Abständen auf neue Themen und Nachrichten prüft. 77 * 78 * @see android.app.Service#onCreate() 79 */ 80 public void onCreate() 81 { 82 Log.i(IBC.TAG, "Starting service"); 83 super.onCreate(); 84 prefs = PreferenceManager.getDefaultSharedPreferences(this); 85 86 // Intervall in Minuten (Default = 3 Stunden) 87 int intervalInMinutes = Integer.parseInt(prefs.getString("subscription_service_interval", "180")); 88 89 // Prüfen, ob Service laufen soll und ein Benutzername vorhanden ist 90 if (prefs.getBoolean("autostart_subscription_service", false) 91 && !TextUtils.isEmpty(prefs.getString("username", ""))) 92 { 93 Log.d(IBC.TAG, "Creating the timer"); 94 timer = new Timer(); 95 timer.scheduleAtFixedRate(new SubscriptionTask(), 2000, intervalInMinutes * 60 * 1000); 96 } 97 else 98 { 99 // Service soll nicht laufen, also sofort wieder stoppen 100 Log.i(IBC.TAG, "Stopping service (should not start)"); 101 stopSelf(); 102 } 103 104 } 105 106 /** 107 * Der Timer, der auf ungelesene Nachrichten und ungelesene Themen prüft. 108 * Falls gefunden, wird dies über den NotificationService gemeldet. <em>Dies 109 * darf eine interne Klasse sein, denn solange der Timer besteht, muss auch 110 * der Service dazu laufen.</em> 111 * 112 * @author dankert 113 * 114 */ 115 private class SubscriptionTask extends TimerTask 116 { 117 public void run() 118 { 119 Log.i(IBC.TAG, "Timer event fired"); 120 121 // Für diesen Timer-Event erzeugen wir einen eigene Instanz des 122 // Tapatalk-Client. Die Laufzeit ist hier unkritisch, dafür belastet 123 // der Client nicht den HEAP, da nach dem Timeevent der 124 // Tapatalk-Client durch den GC weggeräum werden kann. Dieser 125 // Hintergrundprozess wird dadurch deutlich weniger 126 // speicherintensiv. 127 final TapatalkClient client = new TapatalkClient(IBC.IBC_FORUM_CONNECTOR_URL); 128 129 // Anzeigen einer Notification, damit der Benutzer weiß, dass neue 130 // Nachrichten ab 131 final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 132 final PendingIntent emptyIntent = PendingIntent.getActivity(getApplicationContext(), 0, new Intent(), 0); 133 134 final String tickerText1 = getResources().getString(R.string.checking_new); 135 136 if (prefs.getBoolean("show_hints", false)) 137 { 138 final Notification notificationRunning = new Notification(R.drawable.ibc_logo, tickerText1, System 139 .currentTimeMillis()); 140 141 notificationRunning.setLatestEventInfo(getApplicationContext(), getResources().getString( 142 R.string.checking_new), "", emptyIntent); 143 144 notificationRunning.defaults = 0; 145 notificationRunning.flags = Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR; 146 nm.notify(NOTIFICATION_EVENT_RUNNING, notificationRunning); 147 } 148 149 try 150 { 151 // Zuerst Login 152 client.login(prefs.getString("username", ""), prefs.getString("password", "")); 153 154 List<Forum> subscribedForum = client.getSubscribedForum(true); 155 156 final List<String> forumNameList = new ArrayList<String>(); 157 for (Forum forum : subscribedForum) 158 { 159 forumNameList.add(forum.getTitle()); 160 } 161 if (!forumNameList.isEmpty()) 162 { 163 final Intent notificationIntent = new Intent(SubscriptionService.this, 164 SubscriptionForenActivity.class); 165 final PendingIntent contentIntent = PendingIntent.getActivity(SubscriptionService.this, 0, 166 notificationIntent, 0); 167 168 final String tickerText = getResources().getString(R.string.unread_forum) + "\n" 169 + TextUtils.join("\n", forumNameList); 170 171 final Notification notification = createNotification(tickerText, R.string.unread_forum, "(" 172 + subscribedForum.size() + ")", TextUtils.join(", ", forumNameList), contentIntent); 173 nm.notify(NOTIFICATION_FORUM, notification); 174 } 175 176 ListHolder<Topic> subscribedTopic = client.getSubscribedTopics(0, 10, true); 177 final List<String> topicNameList = new ArrayList<String>(); 178 for (Topic topic : subscribedTopic.getChildren()) 179 { 180 topicNameList.add(topic.getTitle()); 181 } 182 if (!topicNameList.isEmpty()) 183 { 184 185 final Intent notificationIntent = new Intent(SubscriptionService.this, 186 SubscriptionTopicsActivity.class); 187 final PendingIntent contentIntent = PendingIntent.getActivity(SubscriptionService.this, 0, 188 notificationIntent, 0); 189 190 final String tickerText = getResources().getString(R.string.unread_topic) + "\n" 191 + TextUtils.join("\n", topicNameList); 192 193 final Notification notification = createNotification(tickerText, R.string.unread_topic, "(" 194 + subscribedTopic.getChildren().size() + ")", TextUtils.join(", ", topicNameList), 195 contentIntent); 196 197 nm.notify(NOTIFICATION_TOPIC, notification); 198 } 199 200 List<Mailbox> mailboxList = client.getMailbox(); 201 int unreadCount = 0; 202 203 final List<String> unreadBoxNames = new ArrayList<String>(); 204 205 for (Mailbox mailbox : mailboxList) 206 { 207 if (mailbox.countUnread > 0) 208 { 209 210 unreadBoxNames.add(mailbox.getTitle()); 211 unreadCount += mailbox.countUnread; 212 } 213 } 214 215 if (unreadCount > 0) 216 { 217 final Intent notificationIntent = new Intent(SubscriptionService.this, MailboxActivity.class); 218 final PendingIntent contentIntent = PendingIntent.getActivity(SubscriptionService.this, 0, 219 notificationIntent, 0); 220 221 final String tickerText = getResources().getString(R.string.unread_messages) + "\n" 222 + TextUtils.join("\n", unreadBoxNames); 223 224 final Notification notification = createNotification(tickerText, R.string.unread_messages, "(" 225 + unreadCount + ")", TextUtils.join(", ", unreadBoxNames), contentIntent); 226 nm.notify(NOTIFICATION_MESSAGES, notification); 227 } 228 } 229 catch (TapatalkException e) 230 { 231 // Kann vorkommen, wenn Login fehlschlägt oder Verbindung 232 // abbricht 233 Log.w(IBC.TAG, e); 234 235 // Ganz bewusst wird hier keine Fehlermeldung erzeugt: 236 // Oft kann es passieren, dass der Empfang schlecht wird und die 237 // Verbindung einen Timeout bekommt. In diesem Fall möchten wir 238 // den Benutzer aber nicht mit Fehlermeldungen nerven. Beim 239 // nächsten Timerevent wird der Abruf sowieso wieder probiert. 240 } 241 catch (Exception e) 242 { 243 // Das sollte eigentlich nicht vorkommen. Hier scheint etwas 244 // schlimmeres kaputt zu sein, daher werfen wir hier eine 245 // RuntimeException weiter. Android wird dann den Service 246 // beenden und das Senden eines Berichtes ermöglichen. Das ist 247 // das beste, was wir hier noch tun können ;) Würden wir den 248 // Fehler nicht weiterwerfen, würde der Service eh nichts mehr 249 // machen und niemandem wäre geholfen. 250 Log.w(IBC.TAG, e); 251 throw new RuntimeException("Unrecoverable error in service", e); 252 } 253 finally 254 { 255 // In jedem Fall die Notification entfernen. 256 nm.cancel(NOTIFICATION_EVENT_RUNNING); 257 } 258 } 259 260 @Override 261 public boolean cancel() 262 { 263 Log.i(IBC.TAG, "Timer canceled"); 264 return super.cancel(); 265 } 266 } 267 268 /** 269 * Wird vom System automatisch aufgerufen bevor der Service entfernt wird. 270 * Hier räumen wir vor allem den Timer weg, damit keine weiteren Ereignisse 271 * ausgelöst werden. 272 * 273 * @see android.app.Service#onDestroy() 274 */ 275 public void onDestroy() 276 { 277 Log.d(IBC.TAG, "Destroying service"); 278 279 if (timer != null) 280 timer.cancel(); // Alle Timer-Ereignisse stoppen. 281 282 super.onDestroy(); 283 } 284 285 /** 286 * Notification erzeugen. 287 * 288 * @param tickerText 289 * Mehrzeiliger Ticker-Text, der in der Notification-Bar 290 * angezeigt wird. 291 * @param titleResId 292 * Titel-Resource-Id der Notification 293 * @param titleExtra 294 * Zusatz-Titeltext, kann <code>null</code> sein 295 * @param content 296 * Inhaltstext der Noification 297 * @param intent 298 * Auszulösender Intent 299 * @return 300 */ 301 private Notification createNotification(String tickerText, int titleResId, String titleExtra, String content, 302 PendingIntent intent) 303 { 304 305 final Notification notification = new Notification(R.drawable.ibc_logo, tickerText, System.currentTimeMillis()); 306 notification.setLatestEventInfo(getApplicationContext(), getResources().getString(titleResId) 307 + (titleExtra != null ? " " + titleExtra : ""), content, intent); 308 309 notification.defaults = Notification.DEFAULT_LIGHTS; 310 311 final String ringtone = prefs.getString("ringtone", ""); 312 313 if (!TextUtils.isEmpty(ringtone)) 314 notification.sound = Uri.parse(ringtone); 315 else 316 notification.defaults |= Notification.DEFAULT_SOUND; 317 318 // Falls so konfiguriert, den Vibrationsalarm auslösen 319 if (prefs.getBoolean("use_vibration", false)) 320 notification.defaults |= Notification.DEFAULT_VIBRATE; 321 322 notification.flags = Notification.FLAG_AUTO_CANCEL | Notification.FLAG_ONLY_ALERT_ONCE; 323 324 return notification; 325 } 326 }