Fire an error if a pref used in the UI is missing once all prefs are fetched.
[chromium-blink-merge.git] / chrome / browser / notifications / notification_ui_manager_android.cc
blobc94a86d8ef5bec87d2804013dc9be16faeac7332
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"
7 #include <utility>
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;
29 namespace {
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,
51 const GURL& origin,
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->WriteString(notification_data.tag);
61 pickle->WriteString(notification_data.icon.spec());
62 pickle->WriteBool(notification_data.silent);
64 // The origin which is displaying the notification.
65 pickle->WriteString(origin.spec());
67 // The Service Worker registration this notification is associated with.
68 pickle->WriteInt64(service_worker_registration_id);
70 if (pickle->size() > kMaximumSerializedNotificationSizeBytes)
71 return nullptr;
73 return pickle.Pass();
76 bool UnserializePersistentNotification(
77 const Pickle& pickle,
78 content::PlatformNotificationData* notification_data,
79 GURL* origin,
80 int64* service_worker_registration_id) {
81 DCHECK(notification_data && origin && service_worker_registration_id);
82 PickleIterator iterator(pickle);
84 std::string icon_url, origin_url;
85 int direction_value;
87 // Unpack content::PlatformNotificationData.
88 if (!iterator.ReadString16(&notification_data->title) ||
89 !iterator.ReadInt(&direction_value) ||
90 !iterator.ReadString(&notification_data->lang) ||
91 !iterator.ReadString16(&notification_data->body) ||
92 !iterator.ReadString(&notification_data->tag) ||
93 !iterator.ReadString(&icon_url) ||
94 !iterator.ReadBool(&notification_data->silent)) {
95 return false;
98 notification_data->direction =
99 static_cast<content::PlatformNotificationData::NotificationDirection>(
100 direction_value);
101 notification_data->icon = GURL(icon_url);
103 // Unpack the origin which displayed this notification.
104 if (!iterator.ReadString(&origin_url))
105 return false;
107 *origin = GURL(origin_url);
109 // Unpack the Service Worker registration id.
110 if (!iterator.ReadInt64(service_worker_registration_id))
111 return false;
113 return true;
116 // Called when the "notificationclick" event in the Service Worker has finished
117 // executing for a notification that was created in a previous instance of the
118 // browser.
119 void OnEventDispatchComplete(content::PersistentNotificationStatus status) {
120 // TODO(peter): Add UMA statistics based on |status|.
121 // TODO(peter): Decide if we want to forcefully shut down the browser process
122 // if we're confident it was created for delivering this event.
125 } // namespace
127 // Called by the Java side when a notification event has been received, but the
128 // NotificationUIManager has not been initialized yet. Enforce initialization of
129 // the class.
130 static void InitializeNotificationUIManager(JNIEnv* env, jclass clazz) {
131 g_browser_process->notification_ui_manager();
134 // static
135 NotificationUIManager* NotificationUIManager::Create(PrefService* local_state) {
136 return new NotificationUIManagerAndroid();
139 NotificationUIManagerAndroid::NotificationUIManagerAndroid() {
140 java_object_.Reset(
141 Java_NotificationUIManager_create(
142 AttachCurrentThread(),
143 reinterpret_cast<intptr_t>(this),
144 base::android::GetApplicationContext()));
146 // TODO(peter): Synchronize notifications with the Java side.
149 NotificationUIManagerAndroid::~NotificationUIManagerAndroid() {
150 Java_NotificationUIManager_destroy(AttachCurrentThread(),
151 java_object_.obj());
154 bool NotificationUIManagerAndroid::OnNotificationClicked(
155 JNIEnv* env,
156 jobject java_object,
157 jstring notification_id,
158 jint platform_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();
167 return true;
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
173 // event manually.
175 std::vector<uint8> bytes;
176 base::android::JavaByteArrayToByteVector(env, serialized_notification_data,
177 &bytes);
178 if (!bytes.size())
179 return false;
181 content::PlatformNotificationData notification_data;
182 GURL origin;
183 int64 service_worker_registration_id;
185 Pickle pickle(reinterpret_cast<const char*>(&bytes[0]), bytes.size());
186 if (!UnserializePersistentNotification(pickle, &notification_data, &origin,
187 &service_worker_registration_id)) {
188 return false;
191 // Store the platform id, tag, and origin of this notification so that it can
192 // be closed.
193 RegeneratedNotificationInfo notification_info(notification_data.tag,
194 platform_id, origin.spec());
195 regenerated_notification_infos_.insert(std::make_pair(id, notification_info));
197 PlatformNotificationServiceImpl* service =
198 PlatformNotificationServiceImpl::GetInstance();
200 // TODO(peter): Rather than assuming that the last used profile is the
201 // appropriate one for this notification, the used profile should be
202 // stored as part of the notification's data. See https://crbug.com/437574.
203 service->OnPersistentNotificationClick(
204 ProfileManager::GetLastUsedProfile(),
205 service_worker_registration_id,
207 origin,
208 notification_data,
209 base::Bind(&OnEventDispatchComplete));
211 return true;
214 bool NotificationUIManagerAndroid::OnNotificationClosed(
215 JNIEnv* env, jobject java_object, jstring notification_id) {
216 std::string id = ConvertJavaStringToUTF8(env, notification_id);
218 auto iter = profile_notifications_.find(id);
219 if (iter == profile_notifications_.end())
220 return false;
222 const Notification& notification = iter->second->notification();
223 notification.delegate()->Close(true /** by_user **/);
224 RemoveProfileNotification(iter->second);
225 return true;
228 void NotificationUIManagerAndroid::Add(const Notification& notification,
229 Profile* profile) {
230 if (Update(notification, profile))
231 return;
233 ProfileNotification* profile_notification =
234 new ProfileNotification(profile, notification);
236 // Takes ownership of |profile_notification|.
237 AddProfileNotification(profile_notification);
239 JNIEnv* env = AttachCurrentThread();
241 ScopedJavaLocalRef<jstring> tag =
242 ConvertUTF8ToJavaString(env, notification.tag());
243 ScopedJavaLocalRef<jstring> id = ConvertUTF8ToJavaString(
244 env, profile_notification->notification().id());
245 ScopedJavaLocalRef<jstring> title = ConvertUTF16ToJavaString(
246 env, notification.title());
247 ScopedJavaLocalRef<jstring> body = ConvertUTF16ToJavaString(
248 env, notification.message());
249 ScopedJavaLocalRef<jstring> origin = ConvertUTF8ToJavaString(
250 env, notification.origin_url().GetOrigin().spec());
252 ScopedJavaLocalRef<jobject> icon;
254 SkBitmap icon_bitmap = notification.icon().AsBitmap();
255 if (!icon_bitmap.isNull())
256 icon = gfx::ConvertToJavaBitmap(&icon_bitmap);
258 ScopedJavaLocalRef<jbyteArray> notification_data;
259 if (true /** is_persistent_notification **/) {
260 PersistentNotificationDelegate* delegate =
261 static_cast<PersistentNotificationDelegate*>(notification.delegate());
262 scoped_ptr<Pickle> pickle = SerializePersistentNotification(
263 delegate->notification_data(),
264 notification.origin_url(),
265 delegate->service_worker_registration_id());
267 if (!pickle) {
268 LOG(ERROR) <<
269 "Unable to serialize the notification, payload too large (max 1MB).";
270 RemoveProfileNotification(profile_notification);
271 return;
274 notification_data = base::android::ToJavaByteArray(
275 env, static_cast<const uint8*>(pickle->data()), pickle->size());
278 int platform_id = Java_NotificationUIManager_displayNotification(
279 env,
280 java_object_.obj(),
281 tag.obj(),
282 id.obj(),
283 title.obj(),
284 body.obj(),
285 icon.obj(),
286 origin.obj(),
287 notification.silent(),
288 notification_data.obj());
290 RegeneratedNotificationInfo notification_info(
291 notification.tag(), platform_id,
292 notification.origin_url().GetOrigin().spec());
293 regenerated_notification_infos_.insert(std::make_pair(
294 profile_notification->notification().id(), notification_info));
296 notification.delegate()->Display();
299 bool NotificationUIManagerAndroid::Update(const Notification& notification,
300 Profile* profile) {
301 const std::string& tag = notification.tag();
302 if (tag.empty())
303 return false;
305 const GURL origin_url = notification.origin_url();
306 DCHECK(origin_url.is_valid());
308 for (const auto& iterator : profile_notifications_) {
309 ProfileNotification* profile_notification = iterator.second;
310 if (profile_notification->notification().tag() != tag ||
311 profile_notification->notification().origin_url() != origin_url ||
312 profile_notification->profile() != profile)
313 continue;
315 std::string notification_id = profile_notification->notification().id();
317 // TODO(peter): Use Android's native notification replacement mechanism.
318 // Right now FALSE is returned from this function even when we would be
319 // able to update the notification, so that Add() creates a new one.
320 RemoveProfileNotification(profile_notification);
321 break;
324 return false;
327 const Notification* NotificationUIManagerAndroid::FindById(
328 const std::string& delegate_id,
329 ProfileID profile_id) const {
330 std::string profile_notification_id =
331 ProfileNotification::GetProfileNotificationId(delegate_id, profile_id);
332 ProfileNotification* profile_notification =
333 FindProfileNotification(profile_notification_id);
334 if (!profile_notification)
335 return 0;
337 return &profile_notification->notification();
340 bool NotificationUIManagerAndroid::CancelById(const std::string& delegate_id,
341 ProfileID profile_id) {
342 std::string profile_notification_id =
343 ProfileNotification::GetProfileNotificationId(delegate_id, profile_id);
344 ProfileNotification* profile_notification =
345 FindProfileNotification(profile_notification_id);
346 if (profile_notification) {
347 RemoveProfileNotification(profile_notification);
348 return true;
351 // On Android, it's still possible that the notification can be closed in case
352 // the platform Id is known, even if no delegate exists. This happens when the
353 // browser process is started because of interaction with a Notification.
354 PlatformCloseNotification(delegate_id);
355 return true;
358 std::set<std::string>
359 NotificationUIManagerAndroid::GetAllIdsByProfileAndSourceOrigin(
360 Profile* profile,
361 const GURL& source) {
362 // |profile| may be invalid, so no calls must be made based on the instance.
363 std::set<std::string> delegate_ids;
365 for (auto iterator : profile_notifications_) {
366 ProfileNotification* profile_notification = iterator.second;
367 if (profile_notification->notification().origin_url() == source &&
368 profile_notification->profile() == profile)
369 delegate_ids.insert(profile_notification->notification().id());
372 return delegate_ids;
375 bool NotificationUIManagerAndroid::CancelAllBySourceOrigin(
376 const GURL& source_origin) {
377 bool removed = true;
379 for (auto iterator = profile_notifications_.begin();
380 iterator != profile_notifications_.end();) {
381 auto current_iterator = iterator++;
383 ProfileNotification* profile_notification = current_iterator->second;
384 if (profile_notification->notification().origin_url() == source_origin) {
385 RemoveProfileNotification(profile_notification);
386 removed = true;
390 return removed;
393 bool NotificationUIManagerAndroid::CancelAllByProfile(ProfileID profile_id) {
394 bool removed = true;
396 for (auto iterator = profile_notifications_.begin();
397 iterator != profile_notifications_.end();) {
398 auto current_iterator = iterator++;
400 ProfileNotification* profile_notification = current_iterator->second;
401 ProfileID current_profile_id =
402 NotificationUIManager::GetProfileID(profile_notification->profile());
403 if (current_profile_id == profile_id) {
404 RemoveProfileNotification(profile_notification);
405 removed = true;
409 return removed;
412 void NotificationUIManagerAndroid::CancelAll() {
413 for (auto iterator : profile_notifications_) {
414 ProfileNotification* profile_notification = iterator.second;
416 PlatformCloseNotification(profile_notification->notification().id());
417 delete profile_notification;
420 profile_notifications_.clear();
423 bool NotificationUIManagerAndroid::RegisterNotificationUIManager(JNIEnv* env) {
424 return RegisterNativesImpl(env);
427 void NotificationUIManagerAndroid::PlatformCloseNotification(
428 const std::string& notification_id) {
429 auto iterator = regenerated_notification_infos_.find(notification_id);
430 if (iterator == regenerated_notification_infos_.end())
431 return;
433 RegeneratedNotificationInfo notification_info = iterator->second;
434 int platform_id = notification_info.platform_id;
435 JNIEnv* env = AttachCurrentThread();
437 ScopedJavaLocalRef<jstring> tag =
438 ConvertUTF8ToJavaString(env, notification_info.tag);
439 ScopedJavaLocalRef<jstring> origin =
440 ConvertUTF8ToJavaString(env, notification_info.origin);
442 regenerated_notification_infos_.erase(notification_id);
444 Java_NotificationUIManager_closeNotification(
445 env, java_object_.obj(), tag.obj(), platform_id, origin.obj());
448 void NotificationUIManagerAndroid::AddProfileNotification(
449 ProfileNotification* profile_notification) {
450 std::string id = profile_notification->notification().id();
452 // Notification ids should be unique.
453 DCHECK(profile_notifications_.find(id) == profile_notifications_.end());
455 profile_notifications_[id] = profile_notification;
458 void NotificationUIManagerAndroid::RemoveProfileNotification(
459 ProfileNotification* profile_notification) {
460 std::string notification_id = profile_notification->notification().id();
461 PlatformCloseNotification(notification_id);
463 profile_notifications_.erase(notification_id);
464 delete profile_notification;
467 ProfileNotification* NotificationUIManagerAndroid::FindProfileNotification(
468 const std::string& id) const {
469 auto iter = profile_notifications_.find(id);
470 if (iter == profile_notifications_.end())
471 return nullptr;
473 return iter->second;