| 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..6a0ad7b4a160c9bc7c287949d2fae433074e5cc4 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,12 @@ 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.Collection;
|
| +import java.util.Collections;
|
| +import java.util.HashSet;
|
| +import java.util.Set;
|
|
|
| /**
|
| * Provides functionality needed for content suggestion notifications.
|
| @@ -37,6 +40,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 +49,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 +71,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 +84,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 +97,14 @@ public class ContentSuggestionsNotificationHelper {
|
| */
|
| public static final class TimeoutReceiver extends BroadcastReceiver {
|
| public void onReceive(Context context, Intent intent) {
|
| - int id = intent.getIntExtra(NOTIFICATION_ID_EXTRA, -1);
|
| - if (id < 0) return;
|
| - hideNotification(id);
|
| - recordCachedActionMetric(PREF_CACHED_ACTION_HIDE_DEADLINE);
|
| + 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
|
| + }
|
| +
|
| + hideNotification(category, idWithinCategory,
|
| + ContentSuggestionsNotificationAction.CONTENT_SUGGESTIONS_HIDE_DEADLINE);
|
| }
|
| }
|
|
|
| @@ -93,28 +121,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 +154,189 @@ 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);
|
| + ActiveNotification activeNotification = findActiveNotification(category, idWithinCategory);
|
| + if (activeNotification == null) return;
|
| + manager.cancel(NOTIFICATION_TAG, activeNotification.mId);
|
| + 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 {
|
| + final int mId;
|
| + final int mCategory;
|
| + final String mIdWithinCategory;
|
| + final 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) {
|
| + assert notificationUri.getScheme().equals("chrome");
|
| + assert notificationUri.getAuthority().equals("content-suggestions-notification");
|
| + assert notificationUri.getQueryParameter("id") != null;
|
| + assert notificationUri.getQueryParameter("category") != null;
|
| + assert notificationUri.getQueryParameter("idWithinCategory") != null;
|
| + assert notificationUri.getQueryParameter("uri") != 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();
|
| + }
|
| + }
|
| +
|
| + /** Returns a mutable copy of the named pref. Never returns null. */
|
| + private static Set<String> getMutableStringSetPreference(
|
| + SharedPreferences prefs, String prefName) {
|
| + Set<String> prefValue = prefs.getStringSet(prefName, null);
|
| + if (prefValue == null) {
|
| + return new HashSet<String>();
|
| + }
|
| + return new HashSet<String>(prefValue);
|
| + }
|
| +
|
| + private static void addActiveNotification(ActiveNotification notification) {
|
| + SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
|
| + Set<String> activeNotifications =
|
| + getMutableStringSetPreference(prefs, PREF_ACTIVE_NOTIFICATIONS);
|
| + activeNotifications.add(notification.toUri().toString());
|
| + 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 =
|
| + getMutableStringSetPreference(prefs, PREF_ACTIVE_NOTIFICATIONS);
|
| + boolean result = activeNotifications.remove(notification.toUri().toString());
|
| + prefs.edit().putStringSet(PREF_ACTIVE_NOTIFICATIONS, activeNotifications).apply();
|
| + return result;
|
| + }
|
| +
|
| + private static Collection<ActiveNotification> getActiveNotifications() {
|
| + SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
|
| + Set<String> activeNotifications = prefs.getStringSet(PREF_ACTIVE_NOTIFICATIONS, null);
|
| + if (activeNotifications == null) return Collections.emptySet();
|
| +
|
| + 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;
|
| + }
|
| +
|
| + /** Returns an ActiveNotification if a corresponding one is found, otherwise null. */
|
| + 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;
|
| }
|
| - } else {
|
| - manager.cancel(NOTIFICATION_TAG, 0);
|
| }
|
| + return null;
|
| + }
|
| +
|
| + /** Returns a non-negative integer greater than any active notification's notification ID. */
|
| + 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;
|
| + }
|
| +
|
| + private static String cachedMetricNameForAction(
|
| + @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 +349,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 +386,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);
|
| }
|
|
|