From d1fa3c0e0a8f45538c867c4f56bb629e1219ed23 Mon Sep 17 00:00:00 2001 From: Sara Ting Date: Thu, 30 Aug 2012 16:05:52 -0700 Subject: Stored recently fired alerts in SharedPrefs so multiple calendar apps can coexist without eating each other's alerts. Bug:6750428 Change-Id: I790b4beea1f25c7ef94381a2fd151416e0f72343 --- src/com/android/calendar/alerts/AlertReceiver.java | 2 +- src/com/android/calendar/alerts/AlertService.java | 47 +++++++- src/com/android/calendar/alerts/AlertUtils.java | 125 +++++++++++++++++++++ 3 files changed, 168 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/com/android/calendar/alerts/AlertReceiver.java b/src/com/android/calendar/alerts/AlertReceiver.java index 16c42b50..b8c38770 100644 --- a/src/com/android/calendar/alerts/AlertReceiver.java +++ b/src/com/android/calendar/alerts/AlertReceiver.java @@ -256,7 +256,7 @@ public class AlertReceiver extends BroadcastReceiver { // Turn off timestamp. notificationBuilder.setWhen(0); - + if (Utils.isJellybeanOrLater()) { // Setting to a higher priority will encourage notification manager to expand the // notification. diff --git a/src/com/android/calendar/alerts/AlertService.java b/src/com/android/calendar/alerts/AlertService.java index 8a4563e5..0408a8a0 100644 --- a/src/com/android/calendar/alerts/AlertService.java +++ b/src/com/android/calendar/alerts/AlertService.java @@ -379,6 +379,9 @@ public class AlertService extends Service { Log.e(TAG, "Illegal state: next notification refresh time found to be in the past."); } + // Flushes old fired alerts from internal storage, if needed. + AlertUtils.flushOldAlertsFromInternalStorage(context); + return true; } @@ -507,11 +510,37 @@ public class AlertService extends Service { int state = alertCursor.getInt(ALERT_INDEX_STATE); final boolean allDay = alertCursor.getInt(ALERT_INDEX_ALL_DAY) != 0; + // Use app local storage to keep track of fired alerts to fix problem of multiple + // installed calendar apps potentially causing missed alarms. + boolean newAlertOverride = false; + String alertIdStr = Long.toString(alertId); + if (AlertUtils.BYPASS_DB && ((currentTime - alarmTime) / MINUTE_MS < 1)) { + // To avoid re-firing alerts, only fire if alarmTime is very recent. Otherwise + // we can get refires for non-dismissed alerts after app installation, or if the + // SharedPrefs was cleared too early. This means alerts that were timed while + // the phone was off may show up silently in the notification bar. + boolean alreadyFired = AlertUtils.hasAlertFiredInSharedPrefs(context, eventId, + beginTime, alarmTime); + if (!alreadyFired) { + newAlertOverride = true; + } + } + if (DEBUG) { - Log.d(TAG, "alertCursor result: alarmTime:" + alarmTime + " alertId:" + alertId - + " eventId:" + eventId + " state: " + state + " minutes:" + minutes - + " declined:" + declined + " beginTime:" + beginTime - + " endTime:" + endTime + " allDay:" + allDay); + StringBuilder msgBuilder = new StringBuilder(); + msgBuilder.append("alertCursor result: alarmTime:").append(alarmTime) + .append(" alertId:").append(alertId) + .append(" eventId:").append(eventId) + .append(" state: ").append(state) + .append(" minutes:").append(minutes) + .append(" declined:").append(declined) + .append(" beginTime:").append(beginTime) + .append(" endTime:").append(endTime) + .append(" allDay:").append(allDay); + if (AlertUtils.BYPASS_DB) { + msgBuilder.append(" newAlertOverride: " + newAlertOverride); + } + Log.d(TAG, msgBuilder.toString()); } ContentValues values = new ContentValues(); @@ -527,7 +556,7 @@ public class AlertService extends Service { // Remove declined events if (!declined) { - if (state == CalendarAlerts.STATE_SCHEDULED) { + if (state == CalendarAlerts.STATE_SCHEDULED || newAlertOverride) { newState = CalendarAlerts.STATE_FIRED; numFired++; newAlert = true; @@ -545,6 +574,11 @@ public class AlertService extends Service { if (newState != -1) { values.put(CalendarAlerts.STATE, newState); state = newState; + + if (AlertUtils.BYPASS_DB) { + AlertUtils.setAlertFiredInSharedPrefs(context, eventId, beginTime, + alarmTime); + } } if (state == CalendarAlerts.STATE_FIRED) { @@ -947,6 +981,9 @@ public class AlertService extends Service { mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); + + // Flushes old fired alerts from internal storage, if needed. + AlertUtils.flushOldAlertsFromInternalStorage(getApplication()); } @Override diff --git a/src/com/android/calendar/alerts/AlertUtils.java b/src/com/android/calendar/alerts/AlertUtils.java index 1777a2d4..9f160c6b 100644 --- a/src/com/android/calendar/alerts/AlertUtils.java +++ b/src/com/android/calendar/alerts/AlertUtils.java @@ -22,6 +22,7 @@ import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.net.Uri; import android.provider.CalendarContract; import android.provider.CalendarContract.CalendarAlerts; @@ -29,15 +30,19 @@ import android.text.TextUtils; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.text.format.Time; +import android.util.Log; import com.android.calendar.EventInfoActivity; import com.android.calendar.R; import com.android.calendar.Utils; import java.util.Locale; +import java.util.Map; import java.util.TimeZone; public class AlertUtils { + private static final String TAG = "AlertUtils"; + static final boolean DEBUG = true; public static final long SNOOZE_DELAY = 5 * 60 * 1000L; @@ -52,6 +57,28 @@ public class AlertUtils { public static final String NOTIFICATION_ID_KEY = "notificationid"; public static final String EVENT_IDS_KEY = "eventids"; + // A flag for using local storage to save alert state instead of the alerts DB table. + // This allows the unbundled app to run alongside other calendar apps without eating + // alerts from other apps. + static boolean BYPASS_DB = true; + + // SharedPrefs table name for storing fired alerts. This prevents other installed + // Calendar apps from eating the alerts. + private static final String ALERTS_SHARED_PREFS_NAME = "calendar_alerts"; + + // Keyname prefix for the alerts data in SharedPrefs. The key will contain a combo + // of event ID, begin time, and alarm time. The value will be the fired time. + private static final String KEY_FIRED_ALERT_PREFIX = "preference_alert_"; + + // The last time the SharedPrefs was scanned and flushed of old alerts data. + private static final String KEY_LAST_FLUSH_TIME_MS = "preference_flushTimeMs"; + + // The # of days to save alert states in the shared prefs table, before flushing. This + // can be any value, since AlertService will also check for a recent alertTime before + // ringing the alert. + private static final int FLUSH_INTERVAL_DAYS = 1; + private static final int FLUSH_INTERVAL_MS = FLUSH_INTERVAL_DAYS * 24 * 60 * 60 * 1000; + /** * Schedules an alarm intent with the system AlarmManager that will notify * listeners when a reminder should be fired. The provider will keep @@ -184,4 +211,102 @@ public class AlertUtils { return i; } + public static SharedPreferences getFiredAlertsTable(Context context) { + return context.getSharedPreferences(ALERTS_SHARED_PREFS_NAME, Context.MODE_PRIVATE); + } + + private static String getFiredAlertsKey(long eventId, long beginTime, + long alarmTime) { + StringBuilder sb = new StringBuilder(KEY_FIRED_ALERT_PREFIX); + sb.append(eventId); + sb.append("_"); + sb.append(beginTime); + sb.append("_"); + sb.append(alarmTime); + return sb.toString(); + } + + /** + * Returns whether the SharedPrefs storage indicates we have fired the alert before. + */ + static boolean hasAlertFiredInSharedPrefs(Context context, long eventId, long beginTime, + long alarmTime) { + SharedPreferences prefs = getFiredAlertsTable(context); + return prefs.contains(getFiredAlertsKey(eventId, beginTime, alarmTime)); + } + + /** + * Store fired alert info in the SharedPrefs. + */ + static void setAlertFiredInSharedPrefs(Context context, long eventId, long beginTime, + long alarmTime) { + // Store alarm time as the value too so we don't have to parse all the keys to flush + // old alarms out of the table later. + SharedPreferences prefs = getFiredAlertsTable(context); + SharedPreferences.Editor editor = prefs.edit(); + editor.putLong(getFiredAlertsKey(eventId, beginTime, alarmTime), alarmTime); + editor.apply(); + } + + /** + * Scans and flushes the internal storage of old alerts. Looks up the previous flush + * time in SharedPrefs, and performs the flush if overdue. Otherwise, no-op. + */ + static void flushOldAlertsFromInternalStorage(Context context) { + if (BYPASS_DB) { + SharedPreferences prefs = getFiredAlertsTable(context); + + // Only flush if it hasn't been done in a while. + long nowTime = System.currentTimeMillis(); + long lastFlushTimeMs = prefs.getLong(KEY_LAST_FLUSH_TIME_MS, 0); + if (nowTime - lastFlushTimeMs > FLUSH_INTERVAL_MS) { + if (DEBUG) { + Log.d(TAG, "Flushing old alerts from shared prefs table"); + } + + // Scan through all fired alert entries, removing old ones. + SharedPreferences.Editor editor = prefs.edit(); + Time timeObj = new Time(); + for (Map.Entry entry : prefs.getAll().entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (key.startsWith(KEY_FIRED_ALERT_PREFIX)) { + long alertTime; + if (value instanceof Long) { + alertTime = (Long) value; + } else { + // Should never occur. + Log.e(TAG,"SharedPrefs key " + key + " did not have Long value: " + + value); + continue; + } + + if (nowTime - alertTime >= FLUSH_INTERVAL_MS) { + editor.remove(key); + if (DEBUG) { + int ageInDays = getIntervalInDays(alertTime, nowTime, timeObj); + Log.d(TAG, "SharedPrefs key " + key + ": removed (" + ageInDays + + " days old)"); + } + } else { + if (DEBUG) { + int ageInDays = getIntervalInDays(alertTime, nowTime, timeObj); + Log.d(TAG, "SharedPrefs key " + key + ": keep (" + ageInDays + + " days old)"); + } + } + } + } + editor.putLong(KEY_LAST_FLUSH_TIME_MS, nowTime); + editor.apply(); + } + } + } + + private static int getIntervalInDays(long startMillis, long endMillis, Time timeObj) { + timeObj.set(startMillis); + int startDay = Time.getJulianDay(startMillis, timeObj.gmtoff); + timeObj.set(endMillis); + return Time.getJulianDay(endMillis, timeObj.gmtoff) - startDay; + } } -- cgit v1.2.3