package com.sunlightlabs.android.congress.notifications; import java.util.ArrayList; import java.util.List; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.Uri; import android.preference.PreferenceManager; import android.util.Log; import com.commonsware.cwac.wakeful.WakefulIntentService; import com.sunlightlabs.android.congress.NotificationSettings; import com.sunlightlabs.android.congress.R; import com.sunlightlabs.android.congress.utils.Analytics; import com.sunlightlabs.android.congress.utils.Database; import com.sunlightlabs.android.congress.utils.Utils; public class NotificationService extends WakefulIntentService { public static final int NOTIFY_UPDATES = 0; public static final String EXTRA_NEW_IDS_PREFIX = "com.sunlightlabs.android.congress.notifications.new_ids."; private NotificationManager notifyManager; private Database database; public NotificationService() { super("NotificationService"); } @Override public void onCreate() { super.onCreate(); notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); database = new Database(this); database.open(); } @Override public void onDestroy() { super.onDestroy(); database.close(); } @SuppressWarnings("deprecation") @Override protected void doWakefulWork(Intent intent) { // only proceed if background data is enabled ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); if (!cm.getBackgroundDataSetting()) { Log.i(Utils.TAG, "User has background data disabled, not polling. Alarms remain scheduled."); return; } Cursor cursor = database.getSubscriptions(); if (!cursor.moveToFirst()) { cursor.close(); return; } do { Subscription subscription = Database.loadSubscription(cursor); // load the appropriate finder for this subscription Subscriber subscriber; try { subscriber = subscription.getSubscriber(); subscriber.context = this; } catch (Exception e) { Log.e(Utils.TAG, "Could not instantiate a Subscriber of class " + subscription.notificationClass, e); continue; } Log.i(Utils.TAG, "[" + subscriber.getClass().getSimpleName() + "][" + subscription.id + "] - " + "About to fetch updates."); // ask the finder for the latest updates List<?> updates = subscriber.fetchUpdates(subscription); // if there was an error or there were no results, move on if (updates == null || updates.isEmpty()) { Log.i(Utils.TAG, "[" + subscriber.getClass().getSimpleName() + "][" + subscription.id + "] - " + "No results, or an error - moving on and not notifying."); continue; } // keep a record of the un-seen matches as we go through the updates List<String> unseenIds = new ArrayList<String>(); // in case the database got closed by now (has happened before) if (!database.isOpen()) database.open(); // debug flags boolean alwaysNotify = getResources().getString(R.string.debug_always_notify).equals("true"); boolean alwaysConsiderNew = getResources().getString(R.string.debug_always_consider_new).equals("true"); boolean sometimesConsiderNew = getResources().getString(R.string.debug_sometimes_consider_new).equals("true"); int sometimesOften = 10; // how often to flag something as artificially new int size = updates.size(); for (int i=0; i<size; i++) { String itemId = subscriber.decodeId(updates.get(i)); if (alwaysConsiderNew || (sometimesConsiderNew && (i % sometimesOften == 0)) || !database.hasSubscriptionItem(subscription.id, subscription.notificationClass, itemId)) unseenIds.add(itemId); } database.addSeenIds(subscription, unseenIds); int results = unseenIds.size(); // if there's at least one new item, notify the user if (alwaysNotify || (results > 0)) { notifyManager.notify( (subscription.id + subscription.notificationClass).hashCode(), getNotification( subscription.notificationClass, subscriber.notificationTicker(subscription), subscriber.notificationTitle(subscription), subscriber.notificationMessage(subscription, results), subscriber.notificationIntent(subscription), unseenIds, notificationUri(subscription) ) ); Log.i(Utils.TAG, "[" + subscriber.getClass().getSimpleName() + "][" + subscription.id + "] - " + "notified of " + results + " results"); } else Log.i(Utils.TAG, "[" + subscriber.getClass().getSimpleName() + "][" + subscription.id + "] - " + "0 new results, not notifying."); } while(cursor.moveToNext()); cursor.close(); } @SuppressWarnings("deprecation") private Notification getNotification(String notificationClass, String ticker, String title, String message, Intent intent, List<String> unseenIds, Uri uri) { int icon = R.drawable.notification_icon; long when = System.currentTimeMillis(); Notification notification = new Notification(icon, ticker, when); intent.setAction(Intent.ACTION_MAIN); intent.setData(uri); intent.putExtra(Analytics.EXTRA_ENTRY_FROM, Analytics.ENTRY_NOTIFICATION); intent.putExtra(EXTRA_NEW_IDS_PREFIX + notificationClass, unseenIds.toArray(new String[0])); PendingIntent contentIntent = PendingIntent .getActivity(this, (int) System.currentTimeMillis(), intent, PendingIntent.FLAG_UPDATE_CURRENT); notification.setLatestEventInfo(this, title, message, contentIntent); // Attach notification sound if the user picked one (defaults to silent) String ringtone = PreferenceManager.getDefaultSharedPreferences(this).getString(NotificationSettings.KEY_NOTIFY_RINGTONE, NotificationSettings.DEFAULT_NOTIFY_RINGTONE); if (ringtone != null) notification.sound = Uri.parse(ringtone); // Vibrate unless user disabled it boolean vibration = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(NotificationSettings.KEY_NOTIFY_VIBRATION, NotificationSettings.DEFAULT_NOTIFY_VIBRATION); if (vibration) notification.defaults |= Notification.DEFAULT_VIBRATE; // always show the light notification.ledARGB = 0xffffffff; notification.ledOnMS = 2000; notification.flags |= Notification.FLAG_SHOW_LIGHTS; notification.flags |= Notification.FLAG_AUTO_CANCEL; return notification; } // hack to make sure PendingIntents are always recognized as unique private Uri notificationUri(Subscription subscription) { return Uri.parse("congress://notifications/" + subscription.notificationClass + "/" + subscription.id); } }