Commit 5a568c2c authored by sfiera's avatar sfiera Committed by Commit bot

Hide notifications after their deadline

Right now, this code will hide the notification at the end of the
timeout, even we switched to showing a different article in the
notification in the meantime. Fixing that will mean switching to
different tags for different articles.

Notifications should also implicitly be hidden if the articles
themselves expire, since the notifier service will watch for their
removal.

BUG=675561

Review-Url: https://codereview.chromium.org/2616893003
Cr-Commit-Position: refs/heads/master@{#442010}
parent 80dc1ec7
......@@ -875,6 +875,9 @@ android:value="true" />
</intent-filter>
</receiver>
<receiver android:name="org.chromium.chrome.browser.ntp.ContentSuggestionsNotificationHelper$TimeoutReceiver"
android:exported="false"/>
<receiver android:name="org.chromium.base.PowerStatusReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
......
......@@ -4,13 +4,18 @@
package org.chromium.chrome.browser.ntp;
import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
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,10 +32,21 @@ import org.chromium.chrome.browser.document.ChromeLauncherActivity;
*/
public class ContentSuggestionsNotificationHelper {
private static final String NOTIFICATION_TAG = "ContentSuggestionsNotification";
private static final int NOTIFICATION_ID = 0;
private static final String NOTIFICATION_ID_EXTRA = "notification_id";
private ContentSuggestionsNotificationHelper() {} // Prevent instantiation
/**
* Removes the notification after a timeout period.
*/
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);
}
}
@CalledByNative
private static void openUrl(String url) {
Context context = ContextUtils.getApplicationContext();
......@@ -45,11 +61,26 @@ public class ContentSuggestionsNotificationHelper {
context.startActivity(intent);
}
@TargetApi(Build.VERSION_CODES.M)
@CalledByNative
private static void showNotification(String url, String title, String text, Bitmap image) {
private static void showNotification(
String url, String title, String text, Bitmap image, long timeoutAtMillis) {
// 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 intent = new Intent()
.setAction(Intent.ACTION_VIEW)
.setData(Uri.parse(url))
......@@ -65,14 +96,42 @@ public class ContentSuggestionsNotificationHelper {
.setGroup(NOTIFICATION_TAG)
.setLargeIcon(image)
.setSmallIcon(R.drawable.ic_chrome);
manager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
manager.notify(NOTIFICATION_TAG, nextId, builder.build());
// 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);
alarmManager.set(AlarmManager.RTC, timeoutAtMillis,
PendingIntent.getBroadcast(
context, 0, timeoutIntent, PendingIntent.FLAG_UPDATE_CURRENT));
}
}
private static void hideNotification(int id) {
Context context = ContextUtils.getApplicationContext();
NotificationManager manager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.cancel(NOTIFICATION_TAG, id);
}
@TargetApi(Build.VERSION_CODES.M)
@CalledByNative
private static void hideNotification() {
private static void hideAllNotifications() {
Context context = ContextUtils.getApplicationContext();
NotificationManager manager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
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());
}
}
} else {
manager.cancel(NOTIFICATION_TAG, 0);
}
}
}
......@@ -4,6 +4,8 @@
#include "chrome/browser/android/ntp/content_suggestions_notification_helper.h"
#include <limits>
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/strings/utf_string_conversions.h"
......@@ -24,22 +26,28 @@ void ContentSuggestionsNotificationHelper::SendNotification(
const GURL& url,
const base::string16& title,
const base::string16& text,
const gfx::Image& image) {
const gfx::Image& image,
base::Time timeout_at) {
JNIEnv* env = base::android::AttachCurrentThread();
SkBitmap skimage = image.AsImageSkia().GetRepresentation(1.0f).sk_bitmap();
if (skimage.empty())
return;
jint timeout_at_millis = timeout_at.ToJavaTime();
if (timeout_at == base::Time::Max()) {
timeout_at_millis = std::numeric_limits<jint>::max();
}
Java_ContentSuggestionsNotificationHelper_showNotification(
env, base::android::ConvertUTF8ToJavaString(env, url.spec()),
base::android::ConvertUTF16ToJavaString(env, title),
base::android::ConvertUTF16ToJavaString(env, text),
gfx::ConvertToJavaBitmap(&skimage));
gfx::ConvertToJavaBitmap(&skimage), timeout_at_millis);
}
void ContentSuggestionsNotificationHelper::HideNotification() {
void ContentSuggestionsNotificationHelper::HideAllNotifications() {
JNIEnv* env = base::android::AttachCurrentThread();
Java_ContentSuggestionsNotificationHelper_hideNotification(env);
Java_ContentSuggestionsNotificationHelper_hideAllNotifications(env);
}
} // namespace ntp_snippets
......@@ -9,6 +9,7 @@
#include "base/macros.h"
#include "base/strings/string16.h"
#include "base/time/time.h"
#include "url/gurl.h"
namespace gfx {
......@@ -23,8 +24,9 @@ class ContentSuggestionsNotificationHelper {
static void SendNotification(const GURL& url,
const base::string16& title,
const base::string16& text,
const gfx::Image& image);
static void HideNotification();
const gfx::Image& image,
base::Time timeout_at);
static void HideAllNotifications();
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(ContentSuggestionsNotificationHelper);
......
......@@ -81,12 +81,15 @@ class ContentSuggestionsNotifierService::NotifyingObserver
if (!suggestion) {
return;
}
base::Time timeout_at = suggestion->notification_extra()
? suggestion->notification_extra()->deadline
: base::Time::Max();
service_->FetchSuggestionImage(
suggestion->id(),
base::Bind(&NotifyingObserver::ImageFetched,
weak_ptr_factory_.GetWeakPtr(), suggestion->id(),
suggestion->url(), suggestion->title(),
suggestion->publisher_name()));
suggestion->publisher_name(), timeout_at));
}
void OnCategoryStatusChanged(Category category,
......@@ -104,7 +107,7 @@ class ContentSuggestionsNotifierService::NotifyingObserver
case CategoryStatus::LOADING_ERROR:
case CategoryStatus::NOT_PROVIDED:
case CategoryStatus::SIGNED_OUT:
ContentSuggestionsNotificationHelper::HideNotification();
ContentSuggestionsNotificationHelper::HideAllNotifications();
break;
}
}
......@@ -114,16 +117,16 @@ class ContentSuggestionsNotifierService::NotifyingObserver
if (suggestion_id.category().IsKnownCategory(KnownCategories::ARTICLES) &&
(suggestion_id.id_within_category() ==
prefs_->GetString(kNotificationIDWithinCategory))) {
ContentSuggestionsNotificationHelper::HideNotification();
ContentSuggestionsNotificationHelper::HideAllNotifications();
}
}
void OnFullRefreshRequired() override {
ContentSuggestionsNotificationHelper::HideNotification();
ContentSuggestionsNotificationHelper::HideAllNotifications();
}
void ContentSuggestionsServiceShutdown() override {
ContentSuggestionsNotificationHelper::HideNotification();
ContentSuggestionsNotificationHelper::HideAllNotifications();
}
private:
......@@ -150,7 +153,7 @@ class ContentSuggestionsNotifierService::NotifyingObserver
void AppStatusChanged(base::android::ApplicationState state) {
if (!ShouldNotifyInState(state)) {
ContentSuggestionsNotificationHelper::HideNotification();
ContentSuggestionsNotificationHelper::HideAllNotifications();
}
}
......@@ -158,6 +161,7 @@ class ContentSuggestionsNotifierService::NotifyingObserver
const GURL& url,
const base::string16& title,
const base::string16& publisher,
base::Time timeout_at,
const gfx::Image& image) {
if (!ShouldNotifyInState(app_status_listener_.GetState())) {
return; // Became foreground while we were fetching the image; forget it.
......@@ -167,7 +171,7 @@ class ContentSuggestionsNotifierService::NotifyingObserver
<< image.Size().height() << " image for " << url.spec();
prefs_->SetString(kNotificationIDWithinCategory, id.id_within_category());
ContentSuggestionsNotificationHelper::SendNotification(
url, title, publisher, CropSquare(image));
url, title, publisher, CropSquare(image), timeout_at);
}
ContentSuggestionsService* const service_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment