1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/notifications/notification_ui_manager_android.h"
9 #include "base/android/jni_array.h"
10 #include "base/android/jni_string.h"
11 #include "base/logging.h"
12 #include "base/pickle.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/notifications/persistent_notification_delegate.h"
15 #include "chrome/browser/notifications/platform_notification_service_impl.h"
16 #include "chrome/browser/notifications/profile_notification.h"
17 #include "chrome/browser/profiles/profile_manager.h"
18 #include "content/public/common/persistent_notification_status.h"
19 #include "content/public/common/platform_notification_data.h"
20 #include "jni/NotificationUIManager_jni.h"
21 #include "ui/gfx/android/java_bitmap.h"
22 #include "ui/gfx/image/image.h"
24 using base::android::AttachCurrentThread
;
25 using base::android::ConvertJavaStringToUTF8
;
26 using base::android::ConvertUTF16ToJavaString
;
27 using base::android::ConvertUTF8ToJavaString
;
31 // The maximum size of the serialized pickle that carries a notification's meta
32 // information. Notifications carrying more data will be silently dropped - with
33 // an error being written to the log.
34 const int kMaximumSerializedNotificationSizeBytes
= 1024 * 1024;
36 // Persistent notifications are likely to outlive the browser process they were
37 // created by on Android. In order to be able to re-surface the notification
38 // when the user interacts with them, all relevant notification data needs to
39 // be serialized with the notification itself.
41 // In the near future, as more features get added to Chrome's notification
42 // implementation, this will be done by storing the persistent notification data
43 // in a database. However, to support launching Chrome for Android from a
44 // notification event until that exists, serialize the data in the Intent.
46 // TODO(peter): Move towards storing notification data in a database rather than
47 // as a serialized Intent extra.
49 scoped_ptr
<Pickle
> SerializePersistentNotification(
50 const content::PlatformNotificationData
& notification_data
,
52 int64 service_worker_registration_id
) {
53 scoped_ptr
<Pickle
> pickle(new Pickle
);
55 // content::PlatformNotificationData
56 pickle
->WriteString16(notification_data
.title
);
57 pickle
->WriteInt(static_cast<int>(notification_data
.direction
));
58 pickle
->WriteString(notification_data
.lang
);
59 pickle
->WriteString16(notification_data
.body
);
60 pickle
->WriteString16(notification_data
.tag
);
61 pickle
->WriteString(notification_data
.icon
.spec());
63 // The origin which is displaying the notification.
64 pickle
->WriteString(origin
.spec());
66 // The Service Worker registration this notification is associated with.
67 pickle
->WriteInt64(service_worker_registration_id
);
69 if (pickle
->size() > kMaximumSerializedNotificationSizeBytes
)
75 bool UnserializePersistentNotification(
77 content::PlatformNotificationData
* notification_data
,
79 int64
* service_worker_registration_id
) {
80 DCHECK(notification_data
&& origin
&& service_worker_registration_id
);
81 PickleIterator
iterator(pickle
);
83 std::string icon_url
, origin_url
;
86 // Unpack content::PlatformNotificationData.
87 if (!iterator
.ReadString16(¬ification_data
->title
) ||
88 !iterator
.ReadInt(&direction_value
) ||
89 !iterator
.ReadString(¬ification_data
->lang
) ||
90 !iterator
.ReadString16(¬ification_data
->body
) ||
91 !iterator
.ReadString16(¬ification_data
->tag
) ||
92 !iterator
.ReadString(&icon_url
)) {
96 notification_data
->direction
=
97 static_cast<content::PlatformNotificationData::NotificationDirection
>(
99 notification_data
->icon
= GURL(icon_url
);
101 // Unpack the origin which displayed this notification.
102 if (!iterator
.ReadString(&origin_url
))
105 *origin
= GURL(origin_url
);
107 // Unpack the Service Worker registration id.
108 if (!iterator
.ReadInt64(service_worker_registration_id
))
114 // Called when the "notificationclick" event in the Service Worker has finished
115 // executing for a notification that was created in a previous instance of the
117 void OnEventDispatchComplete(content::PersistentNotificationStatus status
) {
118 // TODO(peter): Add UMA statistics based on |status|.
119 // TODO(peter): Decide if we want to forcefully shut down the browser process
120 // if we're confident it was created for delivering this event.
125 // Called by the Java side when a notification event has been received, but the
126 // NotificationUIManager has not been initialized yet. Enforce initialization of
128 static void InitializeNotificationUIManager(JNIEnv
* env
, jclass clazz
) {
129 g_browser_process
->notification_ui_manager();
133 NotificationUIManager
* NotificationUIManager::Create(PrefService
* local_state
) {
134 return new NotificationUIManagerAndroid();
137 NotificationUIManagerAndroid::NotificationUIManagerAndroid() {
139 Java_NotificationUIManager_create(
140 AttachCurrentThread(),
141 reinterpret_cast<intptr_t>(this),
142 base::android::GetApplicationContext()));
144 // TODO(peter): Synchronize notifications with the Java side.
147 NotificationUIManagerAndroid::~NotificationUIManagerAndroid() {
148 Java_NotificationUIManager_destroy(AttachCurrentThread(),
152 bool NotificationUIManagerAndroid::OnNotificationClicked(
155 jstring notification_id
,
157 jbyteArray serialized_notification_data
) {
158 std::string id
= ConvertJavaStringToUTF8(env
, notification_id
);
160 auto iter
= profile_notifications_
.find(id
);
161 if (iter
!= profile_notifications_
.end()) {
162 const Notification
& notification
= iter
->second
->notification();
163 notification
.delegate()->Click();
168 // If the Notification were not found, it may be a persistent notification
169 // that outlived the Chrome browser process. In this case, try to
170 // unserialize the notification's serialized data and trigger the click
173 std::vector
<uint8
> bytes
;
174 base::android::JavaByteArrayToByteVector(env
, serialized_notification_data
,
179 content::PlatformNotificationData notification_data
;
181 int64 service_worker_registration_id
;
183 Pickle
pickle(reinterpret_cast<const char*>(&bytes
[0]), bytes
.size());
184 if (!UnserializePersistentNotification(pickle
, ¬ification_data
, &origin
,
185 &service_worker_registration_id
)) {
189 // Store the platform id, tag, and origin of this notification so that it can
191 RegeneratedNotificationInfo
notification_info(notification_data
.tag
,
192 platform_id
, origin
.spec());
193 regenerated_notification_infos_
.insert(std::make_pair(id
, notification_info
));
195 PlatformNotificationServiceImpl
* service
=
196 PlatformNotificationServiceImpl::GetInstance();
198 // TODO(peter): Rather than assuming that the last used profile is the
199 // appropriate one for this notification, the used profile should be
200 // stored as part of the notification's data. See https://crbug.com/437574.
201 service
->OnPersistentNotificationClick(
202 ProfileManager::GetLastUsedProfile(),
203 service_worker_registration_id
,
207 base::Bind(&OnEventDispatchComplete
));
212 bool NotificationUIManagerAndroid::OnNotificationClosed(
213 JNIEnv
* env
, jobject java_object
, jstring notification_id
) {
214 std::string id
= ConvertJavaStringToUTF8(env
, notification_id
);
216 auto iter
= profile_notifications_
.find(id
);
217 if (iter
== profile_notifications_
.end())
220 const Notification
& notification
= iter
->second
->notification();
221 notification
.delegate()->Close(true /** by_user **/);
225 void NotificationUIManagerAndroid::Add(const Notification
& notification
,
227 if (Update(notification
, profile
))
230 ProfileNotification
* profile_notification
=
231 new ProfileNotification(profile
, notification
);
233 // Takes ownership of |profile_notification|.
234 AddProfileNotification(profile_notification
);
236 JNIEnv
* env
= AttachCurrentThread();
238 ScopedJavaLocalRef
<jstring
> tag
=
239 ConvertUTF16ToJavaString(env
, notification
.replace_id());
240 ScopedJavaLocalRef
<jstring
> id
= ConvertUTF8ToJavaString(
241 env
, profile_notification
->notification().id());
242 ScopedJavaLocalRef
<jstring
> title
= ConvertUTF16ToJavaString(
243 env
, notification
.title());
244 ScopedJavaLocalRef
<jstring
> body
= ConvertUTF16ToJavaString(
245 env
, notification
.message());
246 ScopedJavaLocalRef
<jstring
> origin
= ConvertUTF8ToJavaString(
247 env
, notification
.origin_url().GetOrigin().spec());
249 ScopedJavaLocalRef
<jobject
> icon
;
251 SkBitmap icon_bitmap
= notification
.icon().AsBitmap();
252 if (!icon_bitmap
.isNull())
253 icon
= gfx::ConvertToJavaBitmap(&icon_bitmap
);
255 ScopedJavaLocalRef
<jbyteArray
> notification_data
;
256 if (true /** is_persistent_notification **/) {
257 PersistentNotificationDelegate
* delegate
=
258 static_cast<PersistentNotificationDelegate
*>(notification
.delegate());
259 scoped_ptr
<Pickle
> pickle
= SerializePersistentNotification(
260 delegate
->notification_data(),
261 notification
.origin_url(),
262 delegate
->service_worker_registration_id());
266 "Unable to serialize the notification, payload too large (max 1MB).";
267 RemoveProfileNotification(profile_notification
);
271 notification_data
= base::android::ToJavaByteArray(
272 env
, static_cast<const uint8
*>(pickle
->data()), pickle
->size());
275 int platform_id
= Java_NotificationUIManager_displayNotification(
276 env
, java_object_
.obj(), tag
.obj(), id
.obj(), title
.obj(), body
.obj(),
277 icon
.obj(), origin
.obj(), notification_data
.obj());
279 RegeneratedNotificationInfo
notification_info(
280 notification
.replace_id(), platform_id
,
281 notification
.origin_url().GetOrigin().spec());
282 regenerated_notification_infos_
.insert(std::make_pair(
283 profile_notification
->notification().id(), notification_info
));
285 notification
.delegate()->Display();
288 bool NotificationUIManagerAndroid::Update(const Notification
& notification
,
290 const base::string16
& replace_id
= notification
.replace_id();
291 if (replace_id
.empty())
294 const GURL origin_url
= notification
.origin_url();
295 DCHECK(origin_url
.is_valid());
297 for (const auto& iterator
: profile_notifications_
) {
298 ProfileNotification
* profile_notification
= iterator
.second
;
299 if (profile_notification
->notification().replace_id() != replace_id
||
300 profile_notification
->notification().origin_url() != origin_url
||
301 profile_notification
->profile() != profile
)
304 std::string notification_id
= profile_notification
->notification().id();
306 // TODO(peter): Use Android's native notification replacement mechanism.
307 // Right now FALSE is returned from this function even when we would be
308 // able to update the notification, so that Add() creates a new one.
309 RemoveProfileNotification(profile_notification
);
316 const Notification
* NotificationUIManagerAndroid::FindById(
317 const std::string
& delegate_id
,
318 ProfileID profile_id
) const {
319 std::string profile_notification_id
=
320 ProfileNotification::GetProfileNotificationId(delegate_id
, profile_id
);
321 ProfileNotification
* profile_notification
=
322 FindProfileNotification(profile_notification_id
);
323 if (!profile_notification
)
326 return &profile_notification
->notification();
329 bool NotificationUIManagerAndroid::CancelById(const std::string
& delegate_id
,
330 ProfileID profile_id
) {
331 std::string profile_notification_id
=
332 ProfileNotification::GetProfileNotificationId(delegate_id
, profile_id
);
333 ProfileNotification
* profile_notification
=
334 FindProfileNotification(profile_notification_id
);
335 if (profile_notification
) {
336 RemoveProfileNotification(profile_notification
);
340 // On Android, it's still possible that the notification can be closed in case
341 // the platform Id is known, even if no delegate exists. This happens when the
342 // browser process is started because of interaction with a Notification.
343 PlatformCloseNotification(delegate_id
);
347 std::set
<std::string
>
348 NotificationUIManagerAndroid::GetAllIdsByProfileAndSourceOrigin(
350 const GURL
& source
) {
351 // |profile| may be invalid, so no calls must be made based on the instance.
352 std::set
<std::string
> delegate_ids
;
354 for (auto iterator
: profile_notifications_
) {
355 ProfileNotification
* profile_notification
= iterator
.second
;
356 if (profile_notification
->notification().origin_url() == source
&&
357 profile_notification
->profile() == profile
)
358 delegate_ids
.insert(profile_notification
->notification().id());
364 bool NotificationUIManagerAndroid::CancelAllBySourceOrigin(
365 const GURL
& source_origin
) {
368 for (auto iterator
= profile_notifications_
.begin();
369 iterator
!= profile_notifications_
.end();) {
370 auto current_iterator
= iterator
++;
372 ProfileNotification
* profile_notification
= current_iterator
->second
;
373 if (profile_notification
->notification().origin_url() == source_origin
) {
374 RemoveProfileNotification(profile_notification
);
382 bool NotificationUIManagerAndroid::CancelAllByProfile(ProfileID profile_id
) {
385 for (auto iterator
= profile_notifications_
.begin();
386 iterator
!= profile_notifications_
.end();) {
387 auto current_iterator
= iterator
++;
389 ProfileNotification
* profile_notification
= current_iterator
->second
;
390 ProfileID current_profile_id
=
391 NotificationUIManager::GetProfileID(profile_notification
->profile());
392 if (current_profile_id
== profile_id
) {
393 RemoveProfileNotification(profile_notification
);
401 void NotificationUIManagerAndroid::CancelAll() {
402 for (auto iterator
: profile_notifications_
) {
403 ProfileNotification
* profile_notification
= iterator
.second
;
405 PlatformCloseNotification(profile_notification
->notification().id());
406 delete profile_notification
;
409 profile_notifications_
.clear();
412 bool NotificationUIManagerAndroid::RegisterNotificationUIManager(JNIEnv
* env
) {
413 return RegisterNativesImpl(env
);
416 void NotificationUIManagerAndroid::PlatformCloseNotification(
417 const std::string
& notification_id
) {
418 auto iterator
= regenerated_notification_infos_
.find(notification_id
);
419 if (iterator
== regenerated_notification_infos_
.end())
422 RegeneratedNotificationInfo notification_info
= iterator
->second
;
423 int platform_id
= notification_info
.platform_id
;
424 JNIEnv
* env
= AttachCurrentThread();
426 ScopedJavaLocalRef
<jstring
> tag
=
427 ConvertUTF16ToJavaString(env
, notification_info
.tag
);
428 ScopedJavaLocalRef
<jstring
> origin
=
429 ConvertUTF8ToJavaString(env
, notification_info
.origin
);
431 regenerated_notification_infos_
.erase(notification_id
);
433 Java_NotificationUIManager_closeNotification(
434 env
, java_object_
.obj(), tag
.obj(), platform_id
, origin
.obj());
437 void NotificationUIManagerAndroid::AddProfileNotification(
438 ProfileNotification
* profile_notification
) {
439 std::string id
= profile_notification
->notification().id();
441 // Notification ids should be unique.
442 DCHECK(profile_notifications_
.find(id
) == profile_notifications_
.end());
444 profile_notifications_
[id
] = profile_notification
;
447 void NotificationUIManagerAndroid::RemoveProfileNotification(
448 ProfileNotification
* profile_notification
) {
449 std::string notification_id
= profile_notification
->notification().id();
450 PlatformCloseNotification(notification_id
);
452 profile_notifications_
.erase(notification_id
);
453 delete profile_notification
;
456 ProfileNotification
* NotificationUIManagerAndroid::FindProfileNotification(
457 const std::string
& id
) const {
458 auto iter
= profile_notifications_
.find(id
);
459 if (iter
== profile_notifications_
.end())