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/notification.h"
15 #include "chrome/browser/notifications/persistent_notification_delegate.h"
16 #include "chrome/browser/notifications/platform_notification_service_impl.h"
17 #include "chrome/browser/notifications/profile_notification.h"
18 #include "chrome/browser/profiles/profile_manager.h"
19 #include "content/public/common/persistent_notification_status.h"
20 #include "content/public/common/platform_notification_data.h"
21 #include "jni/NotificationUIManager_jni.h"
22 #include "ui/gfx/android/java_bitmap.h"
23 #include "ui/gfx/image/image.h"
25 using base::android::AttachCurrentThread
;
26 using base::android::ConvertJavaStringToUTF8
;
27 using base::android::ConvertUTF16ToJavaString
;
28 using base::android::ConvertUTF8ToJavaString
;
32 // The maximum size of the serialized pickle that carries a notification's meta
33 // information. Notifications carrying more data will be silently dropped - with
34 // an error being written to the log.
35 const int kMaximumSerializedNotificationSizeBytes
= 1024 * 1024;
37 // Persistent notifications are likely to outlive the browser process they were
38 // created by on Android. In order to be able to re-surface the notification
39 // when the user interacts with them, all relevant notification data needs to
40 // be serialized with the notification itself.
42 // In the near future, as more features get added to Chrome's notification
43 // implementation, this will be done by storing the persistent notification data
44 // in a database. However, to support launching Chrome for Android from a
45 // notification event until that exists, serialize the data in the Intent.
47 // TODO(peter): Move towards storing notification data in a database rather than
48 // as a serialized Intent extra.
50 scoped_ptr
<Pickle
> SerializePersistentNotification(
51 const content::PlatformNotificationData
& notification_data
,
53 int64 service_worker_registration_id
) {
54 scoped_ptr
<Pickle
> pickle(new Pickle
);
56 // content::PlatformNotificationData
57 pickle
->WriteString16(notification_data
.title
);
58 pickle
->WriteInt(static_cast<int>(notification_data
.direction
));
59 pickle
->WriteString(notification_data
.lang
);
60 pickle
->WriteString16(notification_data
.body
);
61 pickle
->WriteString(notification_data
.tag
);
62 pickle
->WriteString(notification_data
.icon
.spec());
63 pickle
->WriteBool(notification_data
.silent
);
65 // The origin which is displaying the notification.
66 pickle
->WriteString(origin
.spec());
68 // The Service Worker registration this notification is associated with.
69 pickle
->WriteInt64(service_worker_registration_id
);
71 if (pickle
->size() > kMaximumSerializedNotificationSizeBytes
)
77 bool UnserializePersistentNotification(
79 content::PlatformNotificationData
* notification_data
,
81 int64
* service_worker_registration_id
) {
82 DCHECK(notification_data
&& origin
&& service_worker_registration_id
);
83 PickleIterator
iterator(pickle
);
85 std::string icon_url
, origin_url
;
88 // Unpack content::PlatformNotificationData.
89 if (!iterator
.ReadString16(¬ification_data
->title
) ||
90 !iterator
.ReadInt(&direction_value
) ||
91 !iterator
.ReadString(¬ification_data
->lang
) ||
92 !iterator
.ReadString16(¬ification_data
->body
) ||
93 !iterator
.ReadString(¬ification_data
->tag
) ||
94 !iterator
.ReadString(&icon_url
) ||
95 !iterator
.ReadBool(¬ification_data
->silent
)) {
99 notification_data
->direction
=
100 static_cast<content::PlatformNotificationData::NotificationDirection
>(
102 notification_data
->icon
= GURL(icon_url
);
104 // Unpack the origin which displayed this notification.
105 if (!iterator
.ReadString(&origin_url
))
108 *origin
= GURL(origin_url
);
110 // Unpack the Service Worker registration id.
111 if (!iterator
.ReadInt64(service_worker_registration_id
))
117 // Called when the "notificationclick" event in the Service Worker has finished
118 // executing for a notification that was created in a previous instance of the
120 void OnEventDispatchComplete(content::PersistentNotificationStatus status
) {
121 // TODO(peter): Add UMA statistics based on |status|.
122 // TODO(peter): Decide if we want to forcefully shut down the browser process
123 // if we're confident it was created for delivering this event.
128 // Called by the Java side when a notification event has been received, but the
129 // NotificationUIManager has not been initialized yet. Enforce initialization of
131 static void InitializeNotificationUIManager(JNIEnv
* env
, jclass clazz
) {
132 g_browser_process
->notification_ui_manager();
136 NotificationUIManager
* NotificationUIManager::Create(PrefService
* local_state
) {
137 return new NotificationUIManagerAndroid();
140 NotificationUIManagerAndroid::NotificationUIManagerAndroid() {
142 Java_NotificationUIManager_create(
143 AttachCurrentThread(),
144 reinterpret_cast<intptr_t>(this),
145 base::android::GetApplicationContext()));
147 // TODO(peter): Synchronize notifications with the Java side.
150 NotificationUIManagerAndroid::~NotificationUIManagerAndroid() {
151 Java_NotificationUIManager_destroy(AttachCurrentThread(),
155 bool NotificationUIManagerAndroid::OnNotificationClicked(
158 jstring notification_id
,
159 jbyteArray serialized_notification_data
) {
160 std::string id
= ConvertJavaStringToUTF8(env
, notification_id
);
162 auto iter
= profile_notifications_
.find(id
);
163 if (iter
!= profile_notifications_
.end()) {
164 const Notification
& notification
= iter
->second
->notification();
165 notification
.delegate()->Click();
170 // If the Notification were not found, it may be a persistent notification
171 // that outlived the Chrome browser process. In this case, try to
172 // unserialize the notification's serialized data and trigger the click
175 std::vector
<uint8
> bytes
;
176 base::android::JavaByteArrayToByteVector(env
, serialized_notification_data
,
181 content::PlatformNotificationData notification_data
;
183 int64 service_worker_registration_id
;
185 Pickle
pickle(reinterpret_cast<const char*>(&bytes
[0]), bytes
.size());
186 if (!UnserializePersistentNotification(pickle
, ¬ification_data
, &origin
,
187 &service_worker_registration_id
)) {
191 // Store the tag and origin of this notification so that it can later be
192 // closed using these details.
193 regenerated_notification_infos_
[id
] =
194 std::make_pair(notification_data
.tag
, origin
.spec());
196 PlatformNotificationServiceImpl
* service
=
197 PlatformNotificationServiceImpl::GetInstance();
199 // TODO(peter): Rather than assuming that the last used profile is the
200 // appropriate one for this notification, the used profile should be
201 // stored as part of the notification's data. See https://crbug.com/437574.
202 service
->OnPersistentNotificationClick(
203 ProfileManager::GetLastUsedProfile(),
204 service_worker_registration_id
,
208 base::Bind(&OnEventDispatchComplete
));
213 bool NotificationUIManagerAndroid::OnNotificationClosed(
214 JNIEnv
* env
, jobject java_object
, jstring notification_id
) {
215 std::string id
= ConvertJavaStringToUTF8(env
, notification_id
);
217 auto iter
= profile_notifications_
.find(id
);
218 if (iter
== profile_notifications_
.end())
221 const Notification
& notification
= iter
->second
->notification();
222 notification
.delegate()->Close(true /** by_user **/);
223 RemoveProfileNotification(iter
->second
, true /* close */);
227 void NotificationUIManagerAndroid::Add(const Notification
& notification
,
229 // If the given notification is replacing an older one, drop its associated
230 // profile notification object without closing the platform notification.
231 // We'll use the native Android system to perform a smoother replacement.
232 ProfileNotification
* notification_to_replace
=
233 FindNotificationToReplace(notification
, profile
);
234 if (notification_to_replace
)
235 RemoveProfileNotification(notification_to_replace
, false /* close */);
237 ProfileNotification
* profile_notification
=
238 new ProfileNotification(profile
, notification
);
240 // Takes ownership of |profile_notification|.
241 AddProfileNotification(profile_notification
);
243 JNIEnv
* env
= AttachCurrentThread();
245 ScopedJavaLocalRef
<jstring
> tag
=
246 ConvertUTF8ToJavaString(env
, notification
.tag());
247 ScopedJavaLocalRef
<jstring
> id
= ConvertUTF8ToJavaString(
248 env
, profile_notification
->notification().id());
249 ScopedJavaLocalRef
<jstring
> title
= ConvertUTF16ToJavaString(
250 env
, notification
.title());
251 ScopedJavaLocalRef
<jstring
> body
= ConvertUTF16ToJavaString(
252 env
, notification
.message());
253 ScopedJavaLocalRef
<jstring
> origin
= ConvertUTF8ToJavaString(
254 env
, notification
.origin_url().GetOrigin().spec());
256 ScopedJavaLocalRef
<jobject
> icon
;
258 SkBitmap icon_bitmap
= notification
.icon().AsBitmap();
259 if (!icon_bitmap
.isNull())
260 icon
= gfx::ConvertToJavaBitmap(&icon_bitmap
);
262 ScopedJavaLocalRef
<jbyteArray
> notification_data
;
263 if (true /** is_persistent_notification **/) {
264 PersistentNotificationDelegate
* delegate
=
265 static_cast<PersistentNotificationDelegate
*>(notification
.delegate());
266 scoped_ptr
<Pickle
> pickle
= SerializePersistentNotification(
267 delegate
->notification_data(),
268 notification
.origin_url(),
269 delegate
->service_worker_registration_id());
273 "Unable to serialize the notification, payload too large (max 1MB).";
274 RemoveProfileNotification(profile_notification
, true /* close */);
278 notification_data
= base::android::ToJavaByteArray(
279 env
, static_cast<const uint8
*>(pickle
->data()), pickle
->size());
282 Java_NotificationUIManager_displayNotification(
291 notification
.silent(),
292 notification_data
.obj());
294 regenerated_notification_infos_
[profile_notification
->notification().id()] =
295 std::make_pair(notification
.tag(),
296 notification
.origin_url().GetOrigin().spec());
298 notification
.delegate()->Display();
301 bool NotificationUIManagerAndroid::Update(const Notification
& notification
,
303 // This method is currently only called from extensions and local discovery,
304 // both of which are not supported on Android.
309 const Notification
* NotificationUIManagerAndroid::FindById(
310 const std::string
& delegate_id
,
311 ProfileID profile_id
) const {
312 std::string profile_notification_id
=
313 ProfileNotification::GetProfileNotificationId(delegate_id
, profile_id
);
314 ProfileNotification
* profile_notification
=
315 FindProfileNotification(profile_notification_id
);
316 if (!profile_notification
)
319 return &profile_notification
->notification();
322 bool NotificationUIManagerAndroid::CancelById(const std::string
& delegate_id
,
323 ProfileID profile_id
) {
324 std::string profile_notification_id
=
325 ProfileNotification::GetProfileNotificationId(delegate_id
, profile_id
);
326 ProfileNotification
* profile_notification
=
327 FindProfileNotification(profile_notification_id
);
328 if (profile_notification
) {
329 RemoveProfileNotification(profile_notification
, true /* close */);
333 // On Android, it's still possible that the notification can be closed in case
334 // the platform Id is known, even if no delegate exists. This happens when the
335 // browser process is started because of interaction with a Notification.
336 PlatformCloseNotification(delegate_id
);
340 std::set
<std::string
>
341 NotificationUIManagerAndroid::GetAllIdsByProfileAndSourceOrigin(
343 const GURL
& source
) {
344 // |profile| may be invalid, so no calls must be made based on the instance.
345 std::set
<std::string
> delegate_ids
;
347 for (auto iterator
: profile_notifications_
) {
348 ProfileNotification
* profile_notification
= iterator
.second
;
349 if (profile_notification
->notification().origin_url() == source
&&
350 profile_notification
->profile() == profile
)
351 delegate_ids
.insert(profile_notification
->notification().id());
357 bool NotificationUIManagerAndroid::CancelAllBySourceOrigin(
358 const GURL
& source_origin
) {
361 for (auto iterator
= profile_notifications_
.begin();
362 iterator
!= profile_notifications_
.end();) {
363 auto current_iterator
= iterator
++;
365 ProfileNotification
* profile_notification
= current_iterator
->second
;
366 if (profile_notification
->notification().origin_url() == source_origin
) {
367 RemoveProfileNotification(profile_notification
, true /* close */);
375 bool NotificationUIManagerAndroid::CancelAllByProfile(ProfileID profile_id
) {
378 for (auto iterator
= profile_notifications_
.begin();
379 iterator
!= profile_notifications_
.end();) {
380 auto current_iterator
= iterator
++;
382 ProfileNotification
* profile_notification
= current_iterator
->second
;
383 ProfileID current_profile_id
=
384 NotificationUIManager::GetProfileID(profile_notification
->profile());
385 if (current_profile_id
== profile_id
) {
386 RemoveProfileNotification(profile_notification
, true /* close */);
394 void NotificationUIManagerAndroid::CancelAll() {
395 for (auto iterator
: profile_notifications_
) {
396 ProfileNotification
* profile_notification
= iterator
.second
;
398 PlatformCloseNotification(profile_notification
->notification().id());
399 delete profile_notification
;
402 profile_notifications_
.clear();
405 bool NotificationUIManagerAndroid::RegisterNotificationUIManager(JNIEnv
* env
) {
406 return RegisterNativesImpl(env
);
409 void NotificationUIManagerAndroid::PlatformCloseNotification(
410 const std::string
& notification_id
) {
411 auto iterator
= regenerated_notification_infos_
.find(notification_id
);
412 if (iterator
== regenerated_notification_infos_
.end())
415 RegeneratedNotificationInfo notification_info
= iterator
->second
;
416 JNIEnv
* env
= AttachCurrentThread();
418 ScopedJavaLocalRef
<jstring
> tag
=
419 ConvertUTF8ToJavaString(env
, notification_info
.first
);
420 ScopedJavaLocalRef
<jstring
> origin
=
421 ConvertUTF8ToJavaString(env
, notification_info
.second
);
422 ScopedJavaLocalRef
<jstring
> java_notification_id
=
423 ConvertUTF8ToJavaString(env
, notification_id
);
425 regenerated_notification_infos_
.erase(notification_id
);
427 Java_NotificationUIManager_closeNotification(
428 env
, java_object_
.obj(), tag
.obj(), java_notification_id
.obj(),
432 void NotificationUIManagerAndroid::AddProfileNotification(
433 ProfileNotification
* profile_notification
) {
434 std::string id
= profile_notification
->notification().id();
436 // Notification ids should be unique.
437 DCHECK(profile_notifications_
.find(id
) == profile_notifications_
.end());
439 profile_notifications_
[id
] = profile_notification
;
442 void NotificationUIManagerAndroid::RemoveProfileNotification(
443 ProfileNotification
* profile_notification
, bool close
) {
444 std::string notification_id
= profile_notification
->notification().id();
446 PlatformCloseNotification(notification_id
);
448 profile_notifications_
.erase(notification_id
);
449 delete profile_notification
;
452 ProfileNotification
* NotificationUIManagerAndroid::FindProfileNotification(
453 const std::string
& id
) const {
454 auto iter
= profile_notifications_
.find(id
);
455 if (iter
== profile_notifications_
.end())
461 ProfileNotification
* NotificationUIManagerAndroid::FindNotificationToReplace(
462 const Notification
& notification
, Profile
* profile
) const {
463 const std::string
& tag
= notification
.tag();
467 const GURL origin_url
= notification
.origin_url();
468 DCHECK(origin_url
.is_valid());
470 for (const auto& iterator
: profile_notifications_
) {
471 ProfileNotification
* profile_notification
= iterator
.second
;
472 if (profile_notification
->notification().tag() == tag
||
473 profile_notification
->notification().origin_url() == origin_url
||
474 profile_notification
->profile() == profile
) {
475 return profile_notification
;