aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Sliwowski <psliwowski@google.com>2013-06-17 18:06:51 -0700
committerMichael Bestas <mkbestas@lineageos.org>2020-06-24 23:14:05 +0300
commitb09b3452e08935be5b4ebb4671f8817a2619350f (patch)
tree5bff7c665471688bfe52da4425107c58c4dfee74
parentc3af36b95e87d25eba27370b68c7d1fcea97622b (diff)
downloadandroid_packages_apps_Etar-b09b3452e08935be5b4ebb4671f8817a2619350f.tar.gz
android_packages_apps_Etar-b09b3452e08935be5b4ebb4671f8817a2619350f.tar.bz2
android_packages_apps_Etar-b09b3452e08935be5b4ebb4671f8817a2619350f.zip
Add in-memory cache to dismiss alerts that we haven't seen yet.
Bug: 9018194 This adds an in-memory cache of alerts that we haven't seen yet, so that we can dismiss them once the calendar data provider syncs. Change-Id: I9490f478681783200a5cb8309a58243723bda4c1
-rw-r--r--src/com/android/calendar/alerts/AlertService.java11
-rw-r--r--src/com/android/calendar/alerts/GlobalDismissManager.java379
2 files changed, 281 insertions, 109 deletions
diff --git a/src/com/android/calendar/alerts/AlertService.java b/src/com/android/calendar/alerts/AlertService.java
index dabf693d..a3b53d61 100644
--- a/src/com/android/calendar/alerts/AlertService.java
+++ b/src/com/android/calendar/alerts/AlertService.java
@@ -184,6 +184,8 @@ public class AlertService extends Service {
return true;
}
+ // Sync CalendarAlerts with global dismiss cache before query it
+ GlobalDismissManager.syncReceiverDismissCache(context);
Cursor alertCursor = cr.query(CalendarAlerts.CONTENT_URI, ALERT_PROJECTION,
(ACTIVE_ALERTS_SELECTION + currentTime), ACTIVE_ALERTS_SELECTION_ARGS,
ACTIVE_ALERTS_SORT);
@@ -655,7 +657,7 @@ public class AlertService extends Service {
lowPriorityEvents.add(newInfo);
}
}
- // TODO(cwren) add beginTime/startTime
+ // TODO(psliwowski): move this to account synchronization
GlobalDismissManager.processEventIds(context, eventIds.keySet());
} finally {
if (alertCursor != null) {
@@ -875,6 +877,13 @@ public class AlertService extends Service {
}
}
+ // If we dismissed a notification for a new event, then we need to sync the cache when
+ // an ACTION_PROVIDER_CHANGED event has been sent. Unfortunately, the data provider
+ // has a delay of CalendarProvider2.SYNC_UPDATE_BROADCAST_TIMEOUT_MILLIS (ie. 30 sec.)
+ // until it notifies us that the sync adapter has finished.
+ // TODO(psliwowski): Find a quicker way to be notified when the data provider has the
+ // syncId for event.
+ GlobalDismissManager.syncSenderDismissCache(this);
updateAlertNotification(this);
} else if (action.equals(Intent.ACTION_TIME_CHANGED)) {
doTimeChanged();
diff --git a/src/com/android/calendar/alerts/GlobalDismissManager.java b/src/com/android/calendar/alerts/GlobalDismissManager.java
index 051b7014..89a06919 100644
--- a/src/com/android/calendar/alerts/GlobalDismissManager.java
+++ b/src/com/android/calendar/alerts/GlobalDismissManager.java
@@ -42,6 +42,7 @@ import com.android.calendar.ExtensionsFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -53,12 +54,131 @@ import ws.xsoh.etar.R;
* Utilities for managing notification dismissal across devices.
*/
public class GlobalDismissManager extends BroadcastReceiver {
+ private static class GlobalDismissId {
+ public final String mAccountName;
+ public final String mSyncId;
+ public final long mStartTime;
+
+ private GlobalDismissId(String accountName, String syncId, long startTime) {
+ // TODO(psliwowski): Add guava library to use Preconditions class
+ if (accountName == null) {
+ throw new IllegalArgumentException("Account Name can not be set to null");
+ } else if (syncId == null) {
+ throw new IllegalArgumentException("SyncId can not be set to null");
+ }
+ mAccountName = accountName;
+ mSyncId = syncId;
+ mStartTime = startTime;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ GlobalDismissId that = (GlobalDismissId) o;
+
+ if (mStartTime != that.mStartTime) {
+ return false;
+ }
+ if (!mAccountName.equals(that.mAccountName)) {
+ return false;
+ }
+ if (!mSyncId.equals(that.mSyncId)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mAccountName.hashCode();
+ result = 31 * result + mSyncId.hashCode();
+ result = 31 * result + (int) (mStartTime ^ (mStartTime >>> 32));
+ return result;
+ }
+ }
+
+ public static class LocalDismissId {
+ public final String mAccountType;
+ public final String mAccountName;
+ public final long mEventId;
+ public final long mStartTime;
+
+ public LocalDismissId(String accountType, String accountName, long eventId,
+ long startTime) {
+ if (accountType == null) {
+ throw new IllegalArgumentException("Account Type can not be null");
+ } else if (accountName == null) {
+ throw new IllegalArgumentException("Account Name can not be null");
+ }
+
+ mAccountType = accountType;
+ mAccountName = accountName;
+ mEventId = eventId;
+ mStartTime = startTime;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ LocalDismissId that = (LocalDismissId) o;
+
+ if (mEventId != that.mEventId) {
+ return false;
+ }
+ if (mStartTime != that.mStartTime) {
+ return false;
+ }
+ if (!mAccountName.equals(that.mAccountName)) {
+ return false;
+ }
+ if (!mAccountType.equals(that.mAccountType)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mAccountType.hashCode();
+ result = 31 * result + mAccountName.hashCode();
+ result = 31 * result + (int) (mEventId ^ (mEventId >>> 32));
+ result = 31 * result + (int) (mStartTime ^ (mStartTime >>> 32));
+ return result;
+ }
+ }
+
+ public static class AlarmId {
+ public long mEventId;
+ public long mStart;
+
+ public AlarmId(long id, long start) {
+ mEventId = id;
+ mStart = start;
+ }
+ }
+
+ private static final long TIME_TO_LIVE = 1 * 60 * 60 * 1000; // 1 hour
+
public static final String KEY_PREFIX = "com.android.calendar.alerts.";
public static final String SYNC_ID = KEY_PREFIX + "sync_id";
public static final String START_TIME = KEY_PREFIX + "start_time";
public static final String ACCOUNT_NAME = KEY_PREFIX + "account_name";
public static final String DISMISS_INTENT = KEY_PREFIX + "DISMISS";
- protected static final long FOUR_WEEKS = 60 * 60 * 24 * 7 * 4;
+
static final String[] EVENT_PROJECTION = new String[] {
Events._ID,
Events.CALENDAR_ID
@@ -77,6 +197,12 @@ public class GlobalDismissManager extends BroadcastReceiver {
private static final String GLOBAL_DISMISS_MANAGER_PREFS = "com.android.calendar.alerts.GDM";
private static final String ACCOUNT_KEY = "known_accounts";
+ // TODO(psliwowski): Look into persisting these like AlertUtils.ALERTS_SHARED_PREFS_NAME
+ private static HashMap<GlobalDismissId, Long> sReceiverDismissCache =
+ new HashMap<GlobalDismissId, Long>();
+ private static HashMap<LocalDismissId, Long> sSenderDismissCache =
+ new HashMap<LocalDismissId, Long>();
+
/**
* Look for unknown accounts in a set of events and associate with them.
* Returns immediately, processing happens in the background.
@@ -85,7 +211,7 @@ public class GlobalDismissManager extends BroadcastReceiver {
* @param eventIds IDs for events that have posted notifications that may be
* dismissed.
*/
- public static void processEventIds(final Context context, final Set<Long> eventIds) {
+ public static void processEventIds(Context context, Set<Long> eventIds) {
final String senderId = context.getResources().getString(R.string.notification_sender_id);
if (senderId == null || senderId.isEmpty()) {
Log.i(TAG, "no sender configured");
@@ -149,25 +275,84 @@ public class GlobalDismissManager extends BroadcastReceiver {
}
/**
- * Globally dismiss notifications that are backed by the same events.
- *
- * @param context application context
- * @param alarmIds Unique identifiers for events that have been dismissed by the user.
- * @return true if notification_sender_id is available
+ * Some events don't have a global sync_id when they are dismissed. We need to wait
+ * until the data provider is updated before we can send the global dismiss message.
*/
- public static void dismissGlobally(final Context context, final List<AlarmId> alarmIds) {
+ public static void syncSenderDismissCache(Context context) {
final String senderId = context.getResources().getString(R.string.notification_sender_id);
if ("".equals(senderId)) {
Log.i(TAG, "no sender configured");
return;
}
+ CloudNotificationBackplane cnb = ExtensionsFactory.getCloudNotificationBackplane();
+ if (!cnb.open(context)) {
+ Log.i(TAG, "Unable to open could notification backplane");
+
+ }
+
+ long currentTime = System.currentTimeMillis();
+ ContentResolver resolver = context.getContentResolver();
+ synchronized (sSenderDismissCache) {
+ Iterator<Map.Entry<LocalDismissId, Long>> it =
+ sSenderDismissCache.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<LocalDismissId, Long> entry = it.next();
+ LocalDismissId dismissId = entry.getKey();
+
+ Uri uri = asSync(Events.CONTENT_URI, dismissId.mAccountType,
+ dismissId.mAccountName);
+ Cursor cursor = resolver.query(uri, EVENT_SYNC_PROJECTION,
+ Events._ID + " = " + dismissId.mEventId, null, null);
+ try {
+ cursor.moveToPosition(-1);
+ int sync_id_idx = cursor.getColumnIndex(Events._SYNC_ID);
+ if (sync_id_idx != -1) {
+ while (cursor.moveToNext()) {
+ String syncId = cursor.getString(sync_id_idx);
+ if (syncId != null) {
+ Bundle data = new Bundle();
+ long startTime = dismissId.mStartTime;
+ String accountName = dismissId.mAccountName;
+ data.putString(SYNC_ID, syncId);
+ data.putString(START_TIME, Long.toString(startTime));
+ data.putString(ACCOUNT_NAME, accountName);
+ try {
+ cnb.send(accountName, syncId + ":" + startTime, data);
+ it.remove();
+ } catch (IOException e) {
+ // If we couldn't send, then leave dismissal in cache
+ }
+ }
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+
+ // Remove old dismissals from cache after a certain time period
+ if (currentTime - entry.getValue() > TIME_TO_LIVE) {
+ it.remove();
+ }
+ }
+ }
+
+ cnb.close();
+ }
+
+ /**
+ * Globally dismiss notifications that are backed by the same events.
+ *
+ * @param context application context
+ * @param alarmIds Unique identifiers for events that have been dismissed by the user.
+ * @return true if notification_sender_id is available
+ */
+ public static void dismissGlobally(Context context, List<AlarmId> alarmIds) {
Set<Long> eventIds = new HashSet<Long>(alarmIds.size());
for (AlarmId alarmId: alarmIds) {
eventIds.add(alarmId.mEventId);
}
// find the mapping between calendars and events
Map<Long, Long> eventsToCalendars = lookupEventToCalendarMap(context, eventIds);
-
if (eventsToCalendars.isEmpty()) {
Log.d(TAG, "found no calendars for events");
return;
@@ -179,62 +364,24 @@ public class GlobalDismissManager extends BroadcastReceiver {
// find the accounts associated with those calendars
Map<Long, Pair<String, String>> calendarsToAccounts =
lookupCalendarToAccountMap(context, calendars);
-
if (calendarsToAccounts.isEmpty()) {
Log.d(TAG, "found no accounts for calendars");
return;
}
- // TODO group by account to reduce queries
- Map<String, String> syncIdToAccount = new HashMap<String, String>();
- Map<Long, String> eventIdToSyncId = new HashMap<Long, String>();
- ContentResolver resolver = context.getContentResolver();
- for (Long eventId : eventsToCalendars.keySet()) {
- Long calendar = eventsToCalendars.get(eventId);
+ long currentTime = System.currentTimeMillis();
+ for (AlarmId alarmId : alarmIds) {
+ Long calendar = eventsToCalendars.get(alarmId.mEventId);
Pair<String, String> account = calendarsToAccounts.get(calendar);
if (GOOGLE_ACCOUNT_TYPE.equals(account.first)) {
- Uri uri = asSync(Events.CONTENT_URI, account.first, account.second);
- Cursor cursor = resolver.query(uri, EVENT_SYNC_PROJECTION,
- Events._ID + " = " + eventId, null, null);
- try {
- cursor.moveToPosition(-1);
- int sync_id_idx = cursor.getColumnIndex(Events._SYNC_ID);
- if (sync_id_idx != -1) {
- while (cursor.moveToNext()) {
- String syncId = cursor.getString(sync_id_idx);
- syncIdToAccount.put(syncId, account.second);
- eventIdToSyncId.put(eventId, syncId);
- }
- }
- } finally {
- cursor.close();
- }
- }
- }
-
- if (syncIdToAccount.isEmpty()) {
- Log.d(TAG, "found no syncIds for events");
- return;
- }
-
- // TODO group by account to reduce packets
- CloudNotificationBackplane cnb = ExtensionsFactory.getCloudNotificationBackplane();
- if (cnb.open(context)) {
- for (AlarmId alarmId: alarmIds) {
- String syncId = eventIdToSyncId.get(alarmId.mEventId);
- String account = syncIdToAccount.get(syncId);
- Bundle data = new Bundle();
- data.putString(SYNC_ID, syncId);
- data.putString(START_TIME, Long.toString(alarmId.mStart));
- data.putString(ACCOUNT_NAME, account);
- try {
- cnb.send(account, syncId + ":" + alarmId.mStart, data);
- } catch (IOException e) {
- // TODO save a note to try again later
+ LocalDismissId dismissId = new LocalDismissId(account.first, account.second,
+ alarmId.mEventId, alarmId.mStart);
+ synchronized (sSenderDismissCache) {
+ sSenderDismissCache.put(dismissId, currentTime);
}
}
- cnb.close();
}
+ syncSenderDismissCache(context);
}
private static Uri asSync(Uri uri, String accountType, String account) {
@@ -274,8 +421,7 @@ public class GlobalDismissManager extends BroadcastReceiver {
* @param eventIds Event row IDs to query.
* @return a map from event to calendar
*/
- private static Map<Long, Long> lookupEventToCalendarMap(final Context context,
- final Set<Long> eventIds) {
+ private static Map<Long, Long> lookupEventToCalendarMap(Context context, Set<Long> eventIds) {
Map<Long, Long> eventsToCalendars = new HashMap<Long, Long>();
ContentResolver resolver = context.getContentResolver();
String eventSelection = buildMultipleIdQuery(eventIds, Events._ID);
@@ -309,7 +455,7 @@ public class GlobalDismissManager extends BroadcastReceiver {
* @param calendars Calendar row IDs to query.
* @return a map from Calendar to a pair (account type, account name)
*/
- private static Map<Long, Pair<String, String>> lookupCalendarToAccountMap(final Context context,
+ private static Map<Long, Pair<String, String>> lookupCalendarToAccountMap(Context context,
Set<Long> calendars) {
Map<Long, Pair<String, String>> calendarsToAccounts =
new HashMap<Long, Pair<String, String>>();
@@ -334,7 +480,9 @@ public class GlobalDismissManager extends BroadcastReceiver {
Long id = calendarCursor.getLong(calendar_id_idx);
String name = calendarCursor.getString(account_name_idx);
String type = calendarCursor.getString(account_type_idx);
- calendarsToAccounts.put(id, new Pair<String, String>(type, name));
+ if (name != null && type != null) {
+ calendarsToAccounts.put(id, new Pair<String, String>(type, name));
+ }
}
}
} finally {
@@ -343,67 +491,82 @@ public class GlobalDismissManager extends BroadcastReceiver {
return calendarsToAccounts;
}
- @SuppressWarnings("unchecked")
+ /**
+ * We can get global dismisses for events we don't know exists yet, so sync our cache
+ * with the data provider whenever it updates.
+ */
+ public static void syncReceiverDismissCache(Context context) {
+ ContentResolver resolver = context.getContentResolver();
+ long currentTime = System.currentTimeMillis();
+ synchronized (sReceiverDismissCache) {
+ Iterator<Map.Entry<GlobalDismissId, Long>> it =
+ sReceiverDismissCache.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<GlobalDismissId, Long> entry = it.next();
+ GlobalDismissId globalDismissId = entry.getKey();
+ Uri uri = GlobalDismissManager.asSync(Events.CONTENT_URI,
+ GlobalDismissManager.GOOGLE_ACCOUNT_TYPE, globalDismissId.mAccountName);
+ Cursor cursor = resolver.query(uri, GlobalDismissManager.EVENT_SYNC_PROJECTION,
+ Events._SYNC_ID + " = '" + globalDismissId.mSyncId + "'",
+ null, null);
+ try {
+ int event_id_idx = cursor.getColumnIndex(Events._ID);
+ cursor.moveToFirst();
+ if (event_id_idx != -1 && !cursor.isAfterLast()) {
+ long eventId = cursor.getLong(event_id_idx);
+ ContentValues values = new ContentValues();
+ String selection = "(" + CalendarAlerts.STATE + "=" +
+ CalendarAlerts.STATE_FIRED + " OR " +
+ CalendarAlerts.STATE + "=" +
+ CalendarAlerts.STATE_SCHEDULED + ") AND " +
+ CalendarAlerts.EVENT_ID + "=" + eventId + " AND " +
+ CalendarAlerts.BEGIN + "=" + globalDismissId.mStartTime;
+ values.put(CalendarAlerts.STATE, CalendarAlerts.STATE_DISMISSED);
+ int rows = resolver.update(CalendarAlerts.CONTENT_URI, values,
+ selection, null);
+ if (rows > 0) {
+ it.remove();
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+
+ if (currentTime - entry.getValue() > TIME_TO_LIVE) {
+ it.remove();
+ }
+ }
+ }
+ }
+
@Override
+ @SuppressWarnings("unchecked")
public void onReceive(Context context, Intent intent) {
new AsyncTask<Pair<Context, Intent>, Void, Void>() {
@Override
protected Void doInBackground(Pair<Context, Intent>... params) {
Context context = params[0].first;
Intent intent = params[0].second;
- boolean updated = false;
- if (intent.hasExtra(SYNC_ID) && intent.hasExtra(ACCOUNT_NAME)) {
- String syncId = intent.getStringExtra(SYNC_ID);
- long startTime = Long.parseLong(intent.getStringExtra(START_TIME));
- ContentResolver resolver = context.getContentResolver();
-
- Uri uri = asSync(Events.CONTENT_URI, GOOGLE_ACCOUNT_TYPE,
- intent.getStringExtra(ACCOUNT_NAME));
- Cursor cursor = resolver.query(uri, EVENT_SYNC_PROJECTION,
- Events._SYNC_ID + " = '" + syncId + "'", null, null);
- try {
- int event_id_idx = cursor.getColumnIndex(Events._ID);
- cursor.moveToFirst();
- if (event_id_idx != -1 && !cursor.isAfterLast()) {
- long eventId = cursor.getLong(event_id_idx);
- ContentValues values = new ContentValues();
- String selection = CalendarAlerts.STATE + "=" +
- CalendarAlerts.STATE_FIRED + " AND " +
- CalendarAlerts.EVENT_ID + "=" + eventId + " AND " +
- CalendarAlerts.BEGIN + "=" + startTime;
- values.put(CalendarAlerts.STATE, CalendarAlerts.STATE_DISMISSED);
- if (Build.VERSION.SDK_INT >= 23 && ContextCompat.checkSelfPermission(context,
- Manifest.permission.WRITE_CALENDAR)
- != PackageManager.PERMISSION_GRANTED) {
- //If permission is not granted then just return.
- Log.d(TAG, "Manifest.permission.READ_CALENDAR is not granted");
- return null;
- }
- int rows = resolver.update(CalendarAlerts.CONTENT_URI, values,
- selection, null);
- updated = rows > 0;
- }
- } finally {
- cursor.close();
- }
+ if (Build.VERSION.SDK_INT >= 23 && ContextCompat.checkSelfPermission(context,
+ Manifest.permission.WRITE_CALENDAR)
+ != PackageManager.PERMISSION_GRANTED) {
+ //If permission is not granted then just return.
+ Log.d(TAG, "Manifest.permission.READ_CALENDAR is not granted");
+ return null;
}
-
- if (updated) {
- Log.d(TAG, "updating alarm state");
+ if (intent.hasExtra(SYNC_ID) && intent.hasExtra(ACCOUNT_NAME)
+ && intent.hasExtra(START_TIME)) {
+ synchronized (sReceiverDismissCache) {
+ sReceiverDismissCache.put(new GlobalDismissId(
+ intent.getStringExtra(ACCOUNT_NAME),
+ intent.getStringExtra(SYNC_ID),
+ Long.parseLong(intent.getStringExtra(START_TIME))
+ ), System.currentTimeMillis());
+ }
AlertService.updateAlertNotification(context);
}
return null;
}
}.execute(new Pair<Context, Intent>(context, intent));
}
-
- public static class AlarmId {
- public long mEventId;
- public long mStart;
-
- public AlarmId(long id, long start) {
- mEventId = id;
- mStart = start;
- }
- }
}