Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(250)

Side by Side Diff: chrome/browser/notifications/notification_platform_bridge_linux.cc

Issue 2872053002: Linux native notifications: Support image notifications (Closed)
Patch Set: Rebase Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2017 The Chromium Authors. All rights reserved. 1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/notifications/notification_platform_bridge_linux.h" 5 #include "chrome/browser/notifications/notification_platform_bridge_linux.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <memory> 8 #include <memory>
9 #include <set> 9 #include <set>
10 #include <unordered_map> 10 #include <unordered_map>
(...skipping 18 matching lines...) Expand all
29 #include "chrome/browser/notifications/notification.h" 29 #include "chrome/browser/notifications/notification.h"
30 #include "chrome/browser/notifications/notification_display_service_factory.h" 30 #include "chrome/browser/notifications/notification_display_service_factory.h"
31 #include "chrome/browser/profiles/profile_manager.h" 31 #include "chrome/browser/profiles/profile_manager.h"
32 #include "chrome/browser/shell_integration_linux.h" 32 #include "chrome/browser/shell_integration_linux.h"
33 #include "chrome/grit/generated_resources.h" 33 #include "chrome/grit/generated_resources.h"
34 #include "content/public/browser/browser_thread.h" 34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/notification_service.h" 35 #include "content/public/browser/notification_service.h"
36 #include "dbus/bus.h" 36 #include "dbus/bus.h"
37 #include "dbus/message.h" 37 #include "dbus/message.h"
38 #include "dbus/object_proxy.h" 38 #include "dbus/object_proxy.h"
39 #include "skia/ext/image_operations.h"
39 #include "ui/base/l10n/l10n_util.h" 40 #include "ui/base/l10n/l10n_util.h"
40 #include "ui/gfx/image/image_skia.h" 41 #include "ui/gfx/image/image_skia.h"
41 42
42 namespace { 43 namespace {
43 44
44 // DBus name / path. 45 // DBus name / path.
45 const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications"; 46 const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications";
46 const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications"; 47 const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications";
47 48
48 // DBus methods. 49 // DBus methods.
(...skipping 15 matching lines...) Expand all
64 const char kCapabilityBodyMarkup[] = "body-markup"; 65 const char kCapabilityBodyMarkup[] = "body-markup";
65 const char kCapabilityIconMulti[] = "icon-multi"; 66 const char kCapabilityIconMulti[] = "icon-multi";
66 const char kCapabilityIconStatic[] = "icon-static"; 67 const char kCapabilityIconStatic[] = "icon-static";
67 const char kCapabilityPersistence[] = "persistence"; 68 const char kCapabilityPersistence[] = "persistence";
68 const char kCapabilitySound[] = "sound"; 69 const char kCapabilitySound[] = "sound";
69 70
70 // Button IDs. 71 // Button IDs.
71 const char kDefaultButtonId[] = "default"; 72 const char kDefaultButtonId[] = "default";
72 const char kSettingsButtonId[] = "settings"; 73 const char kSettingsButtonId[] = "settings";
73 74
75 // Max image size; specified in the FDO notification specification.
76 const int kMaxImageWidth = 200;
77 const int kMaxImageHeight = 100;
78
74 // The values in this enumeration correspond to those of the 79 // The values in this enumeration correspond to those of the
75 // Linux.NotificationPlatformBridge.InitializationStatus histogram, so 80 // Linux.NotificationPlatformBridge.InitializationStatus histogram, so
76 // the ordering should not be changed. New error codes should be 81 // the ordering should not be changed. New error codes should be
77 // added at the end, before NUM_ITEMS. 82 // added at the end, before NUM_ITEMS.
78 enum class ConnectionInitializationStatusCode { 83 enum class ConnectionInitializationStatusCode {
79 SUCCESS = 0, 84 SUCCESS = 0,
80 NATIVE_NOTIFICATIONS_NOT_SUPPORTED = 1, 85 NATIVE_NOTIFICATIONS_NOT_SUPPORTED = 1,
81 MISSING_REQUIRED_CAPABILITIES = 2, 86 MISSING_REQUIRED_CAPABILITIES = 2,
82 COULD_NOT_CONNECT_TO_SIGNALS = 3, 87 COULD_NOT_CONNECT_TO_SIGNALS = 3,
83 INCOMPATIBLE_SPEC_VERSION = 4, 88 INCOMPATIBLE_SPEC_VERSION = 4,
84 NUM_ITEMS 89 NUM_ITEMS
85 }; 90 };
86 91
92 int ClampInt(int value, int low, int hi) {
93 return std::max(std::min(value, hi), low);
94 }
95
87 base::string16 CreateNotificationTitle(const Notification& notification) { 96 base::string16 CreateNotificationTitle(const Notification& notification) {
88 base::string16 title; 97 base::string16 title;
89 if (notification.type() == message_center::NOTIFICATION_TYPE_PROGRESS) { 98 if (notification.type() == message_center::NOTIFICATION_TYPE_PROGRESS) {
90 title += base::FormatPercent(notification.progress()); 99 title += base::FormatPercent(notification.progress());
91 title += base::UTF8ToUTF16(" - "); 100 title += base::UTF8ToUTF16(" - ");
92 } 101 }
93 title += notification.title(); 102 title += notification.title();
94 return title; 103 return title;
95 } 104 }
96 105
(...skipping 17 matching lines...) Expand all
114 case message_center::HIGH_PRIORITY: 123 case message_center::HIGH_PRIORITY:
115 case message_center::MAX_PRIORITY: 124 case message_center::MAX_PRIORITY:
116 return CRITICAL; 125 return CRITICAL;
117 default: 126 default:
118 NOTREACHED(); 127 NOTREACHED();
119 case message_center::DEFAULT_PRIORITY: 128 case message_center::DEFAULT_PRIORITY:
120 return NORMAL; 129 return NORMAL;
121 } 130 }
122 } 131 }
123 132
133 // Constrain |image|'s size to |kMaxImageWidth|x|kMaxImageHeight|. If
134 // the image does not need to be resized, or the image is empty,
135 // returns |image| directly.
136 gfx::Image ResizeImageToFdoMaxSize(const gfx::Image& image) {
137 if (image.IsEmpty())
138 return image;
139 int width = image.Width();
140 int height = image.Height();
141 if (width <= kMaxImageWidth && height <= kMaxImageHeight) {
142 return image;
143 }
144 const SkBitmap* image_bitmap = image.ToSkBitmap();
145 double scale = std::min(static_cast<double>(kMaxImageWidth) / width,
146 static_cast<double>(kMaxImageHeight) / height);
147 width = ClampInt(scale * width, 1, kMaxImageWidth);
148 height = ClampInt(scale * height, 1, kMaxImageHeight);
149 return gfx::Image(
150 gfx::ImageSkia::CreateFrom1xBitmap(skia::ImageOperations::Resize(
151 *image_bitmap, skia::ImageOperations::RESIZE_LANCZOS3, width,
152 height)));
153 }
154
124 // Runs once the profile has been loaded in order to perform a given 155 // Runs once the profile has been loaded in order to perform a given
125 // |operation| on a notification. 156 // |operation| on a notification.
126 void ProfileLoadedCallback(NotificationCommon::Operation operation, 157 void ProfileLoadedCallback(NotificationCommon::Operation operation,
127 NotificationCommon::Type notification_type, 158 NotificationCommon::Type notification_type,
128 const std::string& origin, 159 const std::string& origin,
129 const std::string& notification_id, 160 const std::string& notification_id,
130 int action_index, 161 int action_index,
131 const base::NullableString16& reply, 162 const base::NullableString16& reply,
132 Profile* profile) { 163 Profile* profile) {
133 if (!profile) 164 if (!profile)
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
228 bool is_incognito, 259 bool is_incognito,
229 const Notification& notification) override { 260 const Notification& notification) override {
230 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 261 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
231 // Notifications contain gfx::Image's which have reference counts 262 // Notifications contain gfx::Image's which have reference counts
232 // that are not thread safe. Because of this, we duplicate the 263 // that are not thread safe. Because of this, we duplicate the
233 // notification and its images. Wrap the notification in a 264 // notification and its images. Wrap the notification in a
234 // unique_ptr to transfer ownership of the notification (and the 265 // unique_ptr to transfer ownership of the notification (and the
235 // non-thread-safe reference counts) to the task runner thread. 266 // non-thread-safe reference counts) to the task runner thread.
236 auto notification_copy = base::MakeUnique<Notification>(notification); 267 auto notification_copy = base::MakeUnique<Notification>(notification);
237 notification_copy->set_icon(DeepCopyImage(notification_copy->icon())); 268 notification_copy->set_icon(DeepCopyImage(notification_copy->icon()));
238 notification_copy->set_image(gfx::Image()); 269 notification_copy->set_image(body_images_supported_
270 ? DeepCopyImage(notification_copy->image())
271 : gfx::Image());
239 notification_copy->set_small_image(gfx::Image()); 272 notification_copy->set_small_image(gfx::Image());
240 for (size_t i = 0; i < notification_copy->buttons().size(); i++) 273 for (size_t i = 0; i < notification_copy->buttons().size(); i++)
241 notification_copy->SetButtonIcon(i, gfx::Image()); 274 notification_copy->SetButtonIcon(i, gfx::Image());
242 275
243 PostTaskToTaskRunnerThread(base::BindOnce( 276 PostTaskToTaskRunnerThread(base::BindOnce(
244 &NotificationPlatformBridgeLinuxImpl::DisplayOnTaskRunner, this, 277 &NotificationPlatformBridgeLinuxImpl::DisplayOnTaskRunner, this,
245 notification_type, notification_id, profile_id, is_incognito, 278 notification_type, notification_id, profile_id, is_incognito,
246 base::Passed(&notification_copy))); 279 base::Passed(&notification_copy)));
247 } 280 }
248 281
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
330 void Observe(int type, 363 void Observe(int type,
331 const content::NotificationSource& source, 364 const content::NotificationSource& source,
332 const content::NotificationDetails& details) override { 365 const content::NotificationDetails& details) override {
333 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 366 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
334 DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type); 367 DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type);
335 // The browser process is about to exit. Post the CleanUp() task 368 // The browser process is about to exit. Post the CleanUp() task
336 // while we still can. 369 // while we still can.
337 CleanUp(); 370 CleanUp();
338 } 371 }
339 372
373 void SetBodyImagesSupported(bool body_images_supported) {
374 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
375 body_images_supported_ = body_images_supported;
376 }
377
340 void PostTaskToUiThread(base::OnceClosure closure) const { 378 void PostTaskToUiThread(base::OnceClosure closure) const {
341 DCHECK(task_runner_->RunsTasksOnCurrentThread()); 379 DCHECK(task_runner_->RunsTasksOnCurrentThread());
342 bool success = content::BrowserThread::PostTask( 380 bool success = content::BrowserThread::PostTask(
343 content::BrowserThread::UI, FROM_HERE, std::move(closure)); 381 content::BrowserThread::UI, FROM_HERE, std::move(closure));
344 DCHECK(success); 382 DCHECK(success);
345 } 383 }
346 384
347 void PostTaskToTaskRunnerThread(base::OnceClosure closure) const { 385 void PostTaskToTaskRunnerThread(base::OnceClosure closure) const {
348 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 386 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
349 DCHECK(task_runner_); 387 DCHECK(task_runner_);
(...skipping 29 matching lines...) Expand all
379 notification_proxy_->CallMethodAndBlock( 417 notification_proxy_->CallMethodAndBlock(
380 &get_capabilities_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); 418 &get_capabilities_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
381 if (capabilities_response) { 419 if (capabilities_response) {
382 dbus::MessageReader reader(capabilities_response.get()); 420 dbus::MessageReader reader(capabilities_response.get());
383 std::vector<std::string> capabilities; 421 std::vector<std::string> capabilities;
384 reader.PopArrayOfStrings(&capabilities); 422 reader.PopArrayOfStrings(&capabilities);
385 for (const std::string& capability : capabilities) 423 for (const std::string& capability : capabilities)
386 capabilities_.insert(capability); 424 capabilities_.insert(capability);
387 } 425 }
388 RecordMetricsForCapabilities(); 426 RecordMetricsForCapabilities();
427 PostTaskToUiThread(base::BindOnce(
428 &NotificationPlatformBridgeLinuxImpl::SetBodyImagesSupported, this,
429 base::ContainsKey(capabilities_, kCapabilityBodyImages)));
389 430
390 dbus::MethodCall get_server_information_call(kFreedesktopNotificationsName, 431 dbus::MethodCall get_server_information_call(kFreedesktopNotificationsName,
391 kMethodGetServerInformation); 432 kMethodGetServerInformation);
392 std::unique_ptr<dbus::Response> server_information_response = 433 std::unique_ptr<dbus::Response> server_information_response =
393 notification_proxy_->CallMethodAndBlock( 434 notification_proxy_->CallMethodAndBlock(
394 &get_server_information_call, 435 &get_server_information_call,
395 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); 436 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
396 if (server_information_response) { 437 if (server_information_response) {
397 dbus::MessageReader reader(server_information_response.get()); 438 dbus::MessageReader reader(server_information_response.get());
398 std::string spec_version; 439 std::string spec_version;
(...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after
468 509
469 // app_icon passed implicitly via desktop-entry. 510 // app_icon passed implicitly via desktop-entry.
470 writer.AppendString(""); 511 writer.AppendString("");
471 512
472 writer.AppendString( 513 writer.AppendString(
473 base::UTF16ToUTF8(CreateNotificationTitle(*notification))); 514 base::UTF16ToUTF8(CreateNotificationTitle(*notification)));
474 515
475 std::string body; 516 std::string body;
476 if (base::ContainsKey(capabilities_, kCapabilityBody)) { 517 if (base::ContainsKey(capabilities_, kCapabilityBody)) {
477 body = base::UTF16ToUTF8(notification->message()); 518 body = base::UTF16ToUTF8(notification->message());
478 if (base::ContainsKey(capabilities_, kCapabilityBodyMarkup)) { 519 const bool body_markup =
520 base::ContainsKey(capabilities_, kCapabilityBodyMarkup);
521 if (body_markup) {
479 base::ReplaceSubstringsAfterOffset(&body, 0, "&", "&amp;"); 522 base::ReplaceSubstringsAfterOffset(&body, 0, "&", "&amp;");
480 base::ReplaceSubstringsAfterOffset(&body, 0, "<", "&lt;"); 523 base::ReplaceSubstringsAfterOffset(&body, 0, "<", "&lt;");
481 base::ReplaceSubstringsAfterOffset(&body, 0, ">", "&gt;"); 524 base::ReplaceSubstringsAfterOffset(&body, 0, ">", "&gt;");
482 } 525 }
526 if (notification->type() == message_center::NOTIFICATION_TYPE_MULTIPLE) {
527 for (const auto& item : notification->items()) {
528 if (!body.empty())
529 body += "\n";
530 const std::string title = base::UTF16ToUTF8(item.title);
531 const std::string message = base::UTF16ToUTF8(item.message);
532 // TODO(peter): Figure out the right way to internationalize
533 // this for RTL languages.
534 if (body_markup)
535 body += "<b>" + title + "</b> " + message;
536 else
537 body += title + " - " + message;
538 }
539 } else if (notification->type() ==
540 message_center::NOTIFICATION_TYPE_IMAGE &&
541 base::ContainsKey(capabilities_, "body-images")) {
542 std::unique_ptr<ResourceFile> image_file = WriteDataToTmpFile(
543 ResizeImageToFdoMaxSize(notification->image()).As1xPNGBytes());
544 if (image_file) {
545 if (!body.empty())
546 body += "\n";
547 body +=
548 "<img src=\"" + image_file->file_path().value() + "\" alt=\"\"/>";
Peter Beverloo 2017/05/15 16:16:00 Let's use an std::stringstream for building |body|
Tom (Use chromium acct) 2017/05/15 19:18:39 Done.
549 data->resource_files.push_back(std::move(image_file));
550 }
551 }
483 } 552 }
484 writer.AppendString(body); 553 writer.AppendString(body);
485 554
486 // Even-indexed elements in this vector are action IDs passed back to 555 // Even-indexed elements in this vector are action IDs passed back to
487 // us in OnActionInvoked(). Odd-indexed ones contain the button text. 556 // us in OnActionInvoked(). Odd-indexed ones contain the button text.
488 std::vector<std::string> actions; 557 std::vector<std::string> actions;
489 if (base::ContainsKey(capabilities_, kCapabilityActions)) { 558 if (base::ContainsKey(capabilities_, kCapabilityActions)) {
490 data->action_start = data->action_end; 559 data->action_start = data->action_end;
491 for (const auto& button_info : notification->buttons()) { 560 for (const auto& button_info : notification->buttons()) {
492 // FDO notification buttons can contain either an icon or a label, 561 // FDO notification buttons can contain either an icon or a label,
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
542 ? "image_path" 611 ? "image_path"
543 : "image-path"); 612 : "image-path");
544 image_path_writer.AppendVariantOfString(icon_file->file_path().value()); 613 image_path_writer.AppendVariantOfString(icon_file->file_path().value());
545 hints_writer.CloseContainer(&image_path_writer); 614 hints_writer.CloseContainer(&image_path_writer);
546 data->resource_files.push_back(std::move(icon_file)); 615 data->resource_files.push_back(std::move(icon_file));
547 } 616 }
548 617
549 writer.CloseContainer(&hints_writer); 618 writer.CloseContainer(&hints_writer);
550 619
551 const int32_t kExpireTimeoutDefault = -1; 620 const int32_t kExpireTimeoutDefault = -1;
552 writer.AppendInt32(kExpireTimeoutDefault); 621 const int32_t kExpireTimeoutNever = 0;
622 writer.AppendInt32(notification->never_timeout() ? kExpireTimeoutNever
Peter Beverloo 2017/05/15 16:16:00 As a general comment, some of your CLs are a bit t
Tom (Use chromium acct) 2017/05/15 19:18:39 Done. Sorry, the dependent patch set somehow got
623 : kExpireTimeoutDefault);
553 624
554 std::unique_ptr<dbus::Response> response = 625 std::unique_ptr<dbus::Response> response =
555 notification_proxy_->CallMethodAndBlock( 626 notification_proxy_->CallMethodAndBlock(
556 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); 627 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
557 if (response) { 628 if (response) {
558 dbus::MessageReader reader(response.get()); 629 dbus::MessageReader reader(response.get());
559 reader.PopUint32(&data->dbus_id); 630 reader.PopUint32(&data->dbus_id);
560 } 631 }
561 if (!data->dbus_id) { 632 if (!data->dbus_id) {
562 // There was some sort of error with creating the notification. 633 // There was some sort of error with creating the notification.
(...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after
758 829
759 scoped_refptr<base::SequencedTaskRunner> task_runner_; 830 scoped_refptr<base::SequencedTaskRunner> task_runner_;
760 831
761 content::NotificationRegistrar registrar_; 832 content::NotificationRegistrar registrar_;
762 833
763 // State necessary for OnConnectionInitializationFinished() and 834 // State necessary for OnConnectionInitializationFinished() and
764 // SetReadyCallback(). 835 // SetReadyCallback().
765 base::Optional<bool> connected_; 836 base::Optional<bool> connected_;
766 std::vector<NotificationBridgeReadyCallback> on_connected_callbacks_; 837 std::vector<NotificationBridgeReadyCallback> on_connected_callbacks_;
767 838
839 // Notification servers very rarely have the 'body-images'
840 // capability, so try to avoid an image copy if possible. It is
841 // intialized to true so that the UI thread will conservatively copy
842 // the image before the GetCapabilities message completes.
843 bool body_images_supported_ = true;
Peter Beverloo 2017/05/15 16:16:00 Technically we should never call Display() before
Tom (Use chromium acct) 2017/05/15 19:18:39 In the case of unit testing, calls to Display() ar
Lei Zhang 2017/05/15 21:48:40 Is it a lot of work to change the unit tests to no
Tom (Use chromium acct) 2017/05/16 00:22:00 Nope, just a one line change in CreatePlatformBrid
844
768 ////////////////////////////////////////////////////////////////////////////// 845 //////////////////////////////////////////////////////////////////////////////
769 // Members used only on the task runner thread. 846 // Members used only on the task runner thread.
770 847
771 scoped_refptr<dbus::Bus> bus_; 848 scoped_refptr<dbus::Bus> bus_;
772 849
773 dbus::ObjectProxy* notification_proxy_ = nullptr; 850 dbus::ObjectProxy* notification_proxy_ = nullptr;
774 851
775 std::unordered_set<std::string> capabilities_; 852 std::unordered_set<std::string> capabilities_;
776 853
777 base::Version spec_version_; 854 base::Version spec_version_;
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
824 } 901 }
825 902
826 void NotificationPlatformBridgeLinux::SetReadyCallback( 903 void NotificationPlatformBridgeLinux::SetReadyCallback(
827 NotificationBridgeReadyCallback callback) { 904 NotificationBridgeReadyCallback callback) {
828 impl_->SetReadyCallback(std::move(callback)); 905 impl_->SetReadyCallback(std::move(callback));
829 } 906 }
830 907
831 void NotificationPlatformBridgeLinux::CleanUp() { 908 void NotificationPlatformBridgeLinux::CleanUp() {
832 impl_->CleanUp(); 909 impl_->CleanUp();
833 } 910 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698