Chromium Code Reviews| Index: chrome/android/java/src/org/chromium/chrome/browser/ntp/ContentSuggestionsNotificationHelper.java |
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/ContentSuggestionsNotificationHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/ContentSuggestionsNotificationHelper.java |
| index a149fca21d5ad3db39536bae3edcf76d8bef53c3..3067fc6a04e912dfe968250c76a8f27117a29dcb 100644 |
| --- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/ContentSuggestionsNotificationHelper.java |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/ContentSuggestionsNotificationHelper.java |
| @@ -4,7 +4,6 @@ |
| package org.chromium.chrome.browser.ntp; |
| -import android.annotation.TargetApi; |
| import android.app.AlarmManager; |
| import android.app.NotificationManager; |
| import android.app.PendingIntent; |
| @@ -14,9 +13,7 @@ import android.content.Intent; |
| import android.content.SharedPreferences; |
| import android.graphics.Bitmap; |
| import android.net.Uri; |
| -import android.os.Build; |
| import android.provider.Browser; |
| -import android.service.notification.StatusBarNotification; |
| import android.support.v4.app.NotificationCompat; |
| import org.chromium.base.ContextUtils; |
| @@ -27,6 +24,10 @@ import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.IntentHandler; |
| import org.chromium.chrome.browser.ShortcutHelper; |
| import org.chromium.chrome.browser.document.ChromeLauncherActivity; |
| +import org.chromium.chrome.browser.ntp.snippets.ContentSuggestionsNotificationAction; |
| + |
| +import java.util.HashSet; |
| +import java.util.Set; |
| /** |
| * Provides functionality needed for content suggestion notifications. |
| @@ -37,6 +38,8 @@ import org.chromium.chrome.browser.document.ChromeLauncherActivity; |
| public class ContentSuggestionsNotificationHelper { |
| private static final String NOTIFICATION_TAG = "ContentSuggestionsNotification"; |
| private static final String NOTIFICATION_ID_EXTRA = "notification_id"; |
| + private static final String NOTIFICATION_CATEGORY_EXTRA = "category"; |
| + private static final String NOTIFICATION_ID_WITHIN_CATEGORY_EXTRA = "id_within_category"; |
| private static final String PREF_CACHED_ACTION_TAP = |
| "ntp.content_suggestions.notification.cached_action_tap"; |
| @@ -44,9 +47,21 @@ public class ContentSuggestionsNotificationHelper { |
| "ntp.content_suggestions.notification.cached_action_dismissal"; |
| private static final String PREF_CACHED_ACTION_HIDE_DEADLINE = |
| "ntp.content_suggestions.notification.cached_action_hide_deadline"; |
| + private static final String PREF_CACHED_ACTION_HIDE_EXPIRY = |
| + "ntp.content_suggestions.notification.cached_action_hide_expiry"; |
| + private static final String PREF_CACHED_ACTION_HIDE_FRONTMOST = |
| + "ntp.content_suggestions.notification.cached_action_hide_frontmost"; |
| + private static final String PREF_CACHED_ACTION_HIDE_DISABLED = |
| + "ntp.content_suggestions.notification.cached_action_hide_disabled"; |
| + private static final String PREF_CACHED_ACTION_HIDE_SHUTDOWN = |
| + "ntp.content_suggestions.notification.cached_action_hide_shutdown"; |
| private static final String PREF_CACHED_CONSECUTIVE_IGNORED = |
| "ntp.content_suggestions.notification.cached_consecutive_ignored"; |
| + // Tracks which URIs there is an active notification for. |
| + private static final String PREF_ACTIVE_NOTIFICATIONS = |
| + "ntp.content_suggestions.notification.active"; |
| + |
| private ContentSuggestionsNotificationHelper() {} // Prevent instantiation |
| /** |
| @@ -54,8 +69,11 @@ public class ContentSuggestionsNotificationHelper { |
| */ |
| public static final class OpenUrlReceiver extends BroadcastReceiver { |
| public void onReceive(Context context, Intent intent) { |
| + int category = intent.getIntExtra(NOTIFICATION_CATEGORY_EXTRA, -1); |
| + String idWithinCategory = intent.getStringExtra(NOTIFICATION_ID_WITHIN_CATEGORY_EXTRA); |
| openUrl(intent.getData()); |
| - recordCachedActionMetric(PREF_CACHED_ACTION_TAP); |
| + recordCachedActionMetric(ContentSuggestionsNotificationAction.CONTENT_SUGGESTIONS_TAP); |
| + removeActiveNotification(category, idWithinCategory); |
| } |
| } |
| @@ -64,7 +82,11 @@ public class ContentSuggestionsNotificationHelper { |
| */ |
| public static final class DeleteReceiver extends BroadcastReceiver { |
| public void onReceive(Context context, Intent intent) { |
| - recordCachedActionMetric(PREF_CACHED_ACTION_DISMISSAL); |
| + int category = intent.getIntExtra(NOTIFICATION_CATEGORY_EXTRA, -1); |
| + String idWithinCategory = intent.getStringExtra(NOTIFICATION_ID_WITHIN_CATEGORY_EXTRA); |
| + recordCachedActionMetric( |
| + ContentSuggestionsNotificationAction.CONTENT_SUGGESTIONS_DISMISSAL); |
| + removeActiveNotification(category, idWithinCategory); |
| } |
| } |
| @@ -73,10 +95,15 @@ public class ContentSuggestionsNotificationHelper { |
| */ |
| public static final class TimeoutReceiver extends BroadcastReceiver { |
| public void onReceive(Context context, Intent intent) { |
| + int category = intent.getIntExtra(NOTIFICATION_CATEGORY_EXTRA, -1); |
| + String idWithinCategory = intent.getStringExtra(NOTIFICATION_ID_WITHIN_CATEGORY_EXTRA); |
| + if (findActiveNotification(category, idWithinCategory) == null) { |
| + return; // tapped or swiped |
| + } |
| + |
| int id = intent.getIntExtra(NOTIFICATION_ID_EXTRA, -1); |
| - if (id < 0) return; |
| - hideNotification(id); |
| - recordCachedActionMetric(PREF_CACHED_ACTION_HIDE_DEADLINE); |
| + hideNotification(category, idWithinCategory, |
| + ContentSuggestionsNotificationAction.CONTENT_SUGGESTIONS_HIDE_DEADLINE); |
| } |
| } |
| @@ -93,28 +120,28 @@ public class ContentSuggestionsNotificationHelper { |
| context.startActivity(intent); |
| } |
| - @TargetApi(Build.VERSION_CODES.M) |
| @CalledByNative |
| - private static void showNotification( |
| - String url, String title, String text, Bitmap image, long timeoutAtMillis) { |
| + private static boolean showNotification(int category, String idWithinCategory, String url, |
| + String title, String text, Bitmap image, long timeoutAtMillis) { |
| + if (findActiveNotification(category, idWithinCategory) != null) return false; |
| + |
| // Post notification. |
| Context context = ContextUtils.getApplicationContext(); |
| NotificationManager manager = |
| (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); |
| - // Find an available notification ID. |
| - int nextId = 0; |
| - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
| - for (StatusBarNotification activeNotification : manager.getActiveNotifications()) { |
| - if (activeNotification.getTag() != NOTIFICATION_TAG) continue; |
| - if (activeNotification.getId() >= nextId) { |
| - nextId = activeNotification.getId() + 1; |
| - } |
| - } |
| - } |
| - |
| - Intent contentIntent = new Intent(context, OpenUrlReceiver.class).setData(Uri.parse(url)); |
| - Intent deleteIntent = new Intent(context, DeleteReceiver.class).setData(Uri.parse(url)); |
| + int nextId = nextNotificationId(); |
| + Uri uri = Uri.parse(url); |
| + Intent contentIntent = |
| + new Intent(context, OpenUrlReceiver.class) |
| + .setData(uri) |
| + .putExtra(NOTIFICATION_CATEGORY_EXTRA, category) |
| + .putExtra(NOTIFICATION_ID_WITHIN_CATEGORY_EXTRA, idWithinCategory); |
| + Intent deleteIntent = |
| + new Intent(context, DeleteReceiver.class) |
| + .setData(uri) |
| + .putExtra(NOTIFICATION_CATEGORY_EXTRA, category) |
| + .putExtra(NOTIFICATION_ID_WITHIN_CATEGORY_EXTRA, idWithinCategory); |
| NotificationCompat.Builder builder = |
| new NotificationCompat.Builder(context) |
| .setAutoCancel(true) |
| @@ -126,42 +153,176 @@ public class ContentSuggestionsNotificationHelper { |
| .setLargeIcon(image) |
| .setSmallIcon(R.drawable.ic_chrome); |
| manager.notify(NOTIFICATION_TAG, nextId, builder.build()); |
| + addActiveNotification(new ActiveNotification(nextId, category, idWithinCategory, uri)); |
| // Set timeout. |
| if (timeoutAtMillis != Long.MAX_VALUE) { |
| AlarmManager alarmManager = |
| (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); |
| - Intent timeoutIntent = new Intent(context, TimeoutReceiver.class) |
| - .setData(Uri.parse(url)) |
| - .putExtra(NOTIFICATION_ID_EXTRA, nextId); |
| + Intent timeoutIntent = |
| + new Intent(context, TimeoutReceiver.class) |
| + .setData(Uri.parse(url)) |
| + .putExtra(NOTIFICATION_ID_EXTRA, nextId) |
| + .putExtra(NOTIFICATION_CATEGORY_EXTRA, category) |
| + .putExtra(NOTIFICATION_ID_WITHIN_CATEGORY_EXTRA, idWithinCategory); |
| alarmManager.set(AlarmManager.RTC, timeoutAtMillis, |
| PendingIntent.getBroadcast( |
| context, 0, timeoutIntent, PendingIntent.FLAG_UPDATE_CURRENT)); |
| } |
| + return true; |
| } |
| - private static void hideNotification(int id) { |
| + @CalledByNative |
| + private static void hideNotification(int category, String idWithinCategory, int why) { |
| Context context = ContextUtils.getApplicationContext(); |
| NotificationManager manager = |
| (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); |
| - manager.cancel(NOTIFICATION_TAG, id); |
| + if (removeActiveNotification(category, idWithinCategory)) { |
| + recordCachedActionMetric(why); |
| + } |
| } |
| - @TargetApi(Build.VERSION_CODES.M) |
| @CalledByNative |
| - private static void hideAllNotifications() { |
| + private static void hideAllNotifications(int why) { |
| Context context = ContextUtils.getApplicationContext(); |
| NotificationManager manager = |
| (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); |
| - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
| - for (StatusBarNotification activeNotification : manager.getActiveNotifications()) { |
| - if (activeNotification.getTag() == NOTIFICATION_TAG) { |
| - manager.cancel(NOTIFICATION_TAG, activeNotification.getId()); |
| - } |
| + for (ActiveNotification activeNotification : getActiveNotifications()) { |
| + manager.cancel(NOTIFICATION_TAG, activeNotification.mId); |
| + recordCachedActionMetric(why); |
| + } |
| + } |
| + |
| + private static class ActiveNotification { |
| + int mId; |
|
Bernhard Bauer
2017/01/17 15:56:55
Can you make these final?
sfiera
2017/01/17 17:32:08
Done.
|
| + int mCategory; |
| + String mIdWithinCategory; |
| + Uri mUri; |
| + |
| + ActiveNotification(int id, int category, String idWithinCategory, Uri uri) { |
| + mId = id; |
| + mCategory = category; |
| + mIdWithinCategory = idWithinCategory; |
| + mUri = uri; |
| + } |
| + |
| + /** Parses the fields out of a chrome://content-suggestions-notification URI */ |
| + static ActiveNotification fromUri(Uri notificationUri) { |
| + if (!notificationUri.getScheme().equals("chrome")) return null; |
|
Bernhard Bauer
2017/01/17 15:56:55
We shouldn't really get a malformed URL here, righ
sfiera
2017/01/17 17:32:08
Sure, done.
Bernhard Bauer
2017/01/17 18:01:06
Integer.parseInt() might choke though, right? Anyw
|
| + if (!notificationUri.getAuthority().equals("content-suggestions-notification")) { |
| + return null; |
| } |
| - } else { |
| - manager.cancel(NOTIFICATION_TAG, 0); |
| + if (notificationUri.getQueryParameter("id") == null) return null; |
| + if (notificationUri.getQueryParameter("category") == null) return null; |
| + if (notificationUri.getQueryParameter("idWithinCategory") == null) return null; |
| + if (notificationUri.getQueryParameter("uri") == null) return null; |
| + |
| + return new ActiveNotification(Integer.parseInt(notificationUri.getQueryParameter("id")), |
| + Integer.parseInt(notificationUri.getQueryParameter("category")), |
| + notificationUri.getQueryParameter("idWithinCategory"), |
| + Uri.parse(notificationUri.getQueryParameter("uri"))); |
| + } |
| + |
| + /** Serializes the fields to a chrome://content-suggestions-notification URI */ |
| + Uri toUri() { |
| + return new Uri.Builder() |
| + .scheme("chrome") |
| + .authority("content-suggestions-notification") |
| + .appendQueryParameter("id", Integer.toString(mId)) |
| + .appendQueryParameter("category", Integer.toString(mCategory)) |
| + .appendQueryParameter("idWithinCategory", mIdWithinCategory) |
| + .appendQueryParameter("uri", mUri.toString()) |
| + .build(); |
| + } |
| + } |
| + |
| + private static void addActiveNotification(ActiveNotification notification) { |
| + SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); |
| + Set<String> activeNotifications = prefs.getStringSet(PREF_ACTIVE_NOTIFICATIONS, null); |
| + if (activeNotifications == null) activeNotifications = new HashSet<String>(); |
| + activeNotifications.add(notification.toUri().toString()); |
|
Bernhard Bauer
2017/01/17 15:56:55
You are not allowed to modify the set returned by
sfiera
2017/01/17 17:32:08
Done.
|
| + prefs.edit().putStringSet(PREF_ACTIVE_NOTIFICATIONS, activeNotifications).apply(); |
| + } |
| + |
| + private static boolean removeActiveNotification(int category, String idWithinCategory) { |
| + SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); |
| + ActiveNotification notification = findActiveNotification(category, idWithinCategory); |
| + if (notification == null) return false; |
| + |
| + Set<String> activeNotifications = prefs.getStringSet(PREF_ACTIVE_NOTIFICATIONS, null); |
| + if (activeNotifications == null) return false; |
| + boolean result = activeNotifications.remove(notification.toUri().toString()); |
| + prefs.edit().putStringSet(PREF_ACTIVE_NOTIFICATIONS, activeNotifications).apply(); |
| + return result; |
| + } |
| + |
| + private static Set<ActiveNotification> getActiveNotifications() { |
| + SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); |
| + Set<String> activeNotifications = prefs.getStringSet(PREF_ACTIVE_NOTIFICATIONS, null); |
| + if (activeNotifications == null) return new HashSet<ActiveNotification>(); |
| + |
| + Set<ActiveNotification> result = new HashSet<ActiveNotification>(); |
| + for (String serialized : activeNotifications) { |
| + Uri notificationUri = Uri.parse(serialized); |
| + ActiveNotification activeNotification = ActiveNotification.fromUri(notificationUri); |
| + if (activeNotification != null) result.add(activeNotification); |
| + } |
| + return result; |
| + } |
| + |
| + private static ActiveNotification findActiveNotification( |
| + int category, String idWithinCategory) { |
| + SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); |
| + Set<String> activeNotifications = prefs.getStringSet(PREF_ACTIVE_NOTIFICATIONS, null); |
| + if (activeNotifications == null) return null; |
| + |
| + for (String serialized : activeNotifications) { |
| + Uri notificationUri = Uri.parse(serialized); |
| + ActiveNotification activeNotification = ActiveNotification.fromUri(notificationUri); |
| + if ((activeNotification != null) && (activeNotification.mCategory == category) |
| + && (activeNotification.mIdWithinCategory.equals(idWithinCategory))) { |
| + return activeNotification; |
| + } |
| + } |
| + return null; |
| + } |
| + |
| + private static int nextNotificationId() { |
| + SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); |
| + Set<String> activeNotifications = prefs.getStringSet(PREF_ACTIVE_NOTIFICATIONS, null); |
| + if (activeNotifications == null) return 0; |
| + |
| + int nextId = 0; |
| + for (String serialized : activeNotifications) { |
| + Uri notificationUri = Uri.parse(serialized); |
| + ActiveNotification activeNotification = ActiveNotification.fromUri(notificationUri); |
| + if ((activeNotification != null) && (activeNotification.mId >= nextId)) { |
| + nextId = activeNotification.mId + 1; |
| + } |
| + } |
| + return nextId; |
| + } |
| + |
| + public static String cachedMetricNameForAction( |
|
Bernhard Bauer
2017/01/17 15:56:55
Javadoc for public methods please.
sfiera
2017/01/17 17:32:08
Oops, shouldn't have been public.
|
| + @ContentSuggestionsNotificationAction.ContentSuggestionsNotificationActionEnum |
| + int action) { |
| + switch (action) { |
| + case ContentSuggestionsNotificationAction.CONTENT_SUGGESTIONS_TAP: |
| + return PREF_CACHED_ACTION_TAP; |
| + case ContentSuggestionsNotificationAction.CONTENT_SUGGESTIONS_DISMISSAL: |
| + return PREF_CACHED_ACTION_DISMISSAL; |
| + case ContentSuggestionsNotificationAction.CONTENT_SUGGESTIONS_HIDE_DEADLINE: |
| + return PREF_CACHED_ACTION_HIDE_DEADLINE; |
| + case ContentSuggestionsNotificationAction.CONTENT_SUGGESTIONS_HIDE_EXPIRY: |
| + return PREF_CACHED_ACTION_HIDE_EXPIRY; |
| + case ContentSuggestionsNotificationAction.CONTENT_SUGGESTIONS_HIDE_FRONTMOST: |
| + return PREF_CACHED_ACTION_HIDE_FRONTMOST; |
| + case ContentSuggestionsNotificationAction.CONTENT_SUGGESTIONS_HIDE_DISABLED: |
| + return PREF_CACHED_ACTION_HIDE_DISABLED; |
| + case ContentSuggestionsNotificationAction.CONTENT_SUGGESTIONS_HIDE_SHUTDOWN: |
| + return PREF_CACHED_ACTION_HIDE_SHUTDOWN; |
| } |
| + return ""; |
| } |
| /** |
| @@ -174,15 +335,18 @@ public class ContentSuggestionsNotificationHelper { |
| * will immediately be sent to C++. If not, it will cache them for a later call to |
| * flushCachedMetrics(). |
| * |
| - * @param prefName The name of the action metric pref to update (<tt>PREF_CACHED_ACTION_*</tt>) |
| + * @param action The action to update the pref for. |
| */ |
| - private static void recordCachedActionMetric(String prefName) { |
| - assert prefName.startsWith("ntp.content_suggestions.notification.cached_action_"); |
| + private static void recordCachedActionMetric( |
| + @ContentSuggestionsNotificationAction.ContentSuggestionsNotificationActionEnum |
| + int action) { |
| + String prefName = cachedMetricNameForAction(action); |
| + assert !prefName.isEmpty(); |
| SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); |
| int currentValue = prefs.getInt(prefName, 0); |
| int consecutiveIgnored = 0; |
| - if (!prefName.equals(PREF_CACHED_ACTION_TAP)) { |
| + if (action != ContentSuggestionsNotificationAction.CONTENT_SUGGESTIONS_TAP) { |
| consecutiveIgnored = 1 + prefs.getInt(PREF_CACHED_CONSECUTIVE_IGNORED, 0); |
| } |
| prefs.edit() |
| @@ -208,20 +372,31 @@ public class ContentSuggestionsNotificationHelper { |
| int tapCount = prefs.getInt(PREF_CACHED_ACTION_TAP, 0); |
| int dismissalCount = prefs.getInt(PREF_CACHED_ACTION_DISMISSAL, 0); |
| int hideDeadlineCount = prefs.getInt(PREF_CACHED_ACTION_HIDE_DEADLINE, 0); |
| + int hideExpiryCount = prefs.getInt(PREF_CACHED_ACTION_HIDE_EXPIRY, 0); |
| + int hideFrontmostCount = prefs.getInt(PREF_CACHED_ACTION_HIDE_FRONTMOST, 0); |
| + int hideDisabledCount = prefs.getInt(PREF_CACHED_ACTION_HIDE_DISABLED, 0); |
| + int hideShutdownCount = prefs.getInt(PREF_CACHED_ACTION_HIDE_SHUTDOWN, 0); |
| int consecutiveIgnored = prefs.getInt(PREF_CACHED_CONSECUTIVE_IGNORED, 0); |
| - if (tapCount > 0 || dismissalCount > 0 || hideDeadlineCount > 0) { |
| + if (tapCount > 0 || dismissalCount > 0 || hideDeadlineCount > 0 || hideExpiryCount > 0 |
| + || hideFrontmostCount > 0 || hideDisabledCount > 0 || hideShutdownCount > 0) { |
| nativeReceiveFlushedMetrics(tapCount, dismissalCount, hideDeadlineCount, |
| - consecutiveIgnored); |
| + hideExpiryCount, hideFrontmostCount, hideDisabledCount, hideShutdownCount, |
| + consecutiveIgnored); |
| prefs.edit() |
| .remove(PREF_CACHED_ACTION_TAP) |
| .remove(PREF_CACHED_ACTION_DISMISSAL) |
| .remove(PREF_CACHED_ACTION_HIDE_DEADLINE) |
| + .remove(PREF_CACHED_ACTION_HIDE_EXPIRY) |
| + .remove(PREF_CACHED_ACTION_HIDE_FRONTMOST) |
| + .remove(PREF_CACHED_ACTION_HIDE_DISABLED) |
| + .remove(PREF_CACHED_ACTION_HIDE_SHUTDOWN) |
| .remove(PREF_CACHED_CONSECUTIVE_IGNORED) |
| .apply(); |
| } |
| } |
| - private static native void nativeReceiveFlushedMetrics( |
| - int tapCount, int dismissalCount, int hideDeadlineCount, int consecutiveIgnored); |
| + private static native void nativeReceiveFlushedMetrics(int tapCount, int dismissalCount, |
| + int hideDeadlineCount, int hideExpiryCount, int hideFrontmostCount, |
| + int hideDisabledCount, int hideShutdownCount, int consecutiveIgnored); |
| } |