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

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

Issue 2872053002: Linux native notifications: Support image notifications (Closed)
Patch Set: base::Optional<bool> 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
« no previous file with comments | « no previous file | chrome/browser/notifications/notification_platform_bridge_linux_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 <sstream>
10 #include <unordered_map> 11 #include <unordered_map>
11 #include <unordered_set> 12 #include <unordered_set>
12 #include <utility> 13 #include <utility>
13 #include <vector> 14 #include <vector>
14 15
15 #include "base/barrier_closure.h" 16 #include "base/barrier_closure.h"
16 #include "base/files/file_util.h" 17 #include "base/files/file_util.h"
17 #include "base/i18n/number_formatting.h" 18 #include "base/i18n/number_formatting.h"
18 #include "base/metrics/histogram_macros.h" 19 #include "base/metrics/histogram_macros.h"
19 #include "base/strings/nullable_string16.h" 20 #include "base/strings/nullable_string16.h"
20 #include "base/strings/string_number_conversions.h" 21 #include "base/strings/string_number_conversions.h"
21 #include "base/strings/string_split.h" 22 #include "base/strings/string_split.h"
22 #include "base/strings/string_util.h" 23 #include "base/strings/string_util.h"
23 #include "base/strings/utf_string_conversions.h" 24 #include "base/strings/utf_string_conversions.h"
24 #include "base/task_scheduler/post_task.h" 25 #include "base/task_scheduler/post_task.h"
25 #include "base/version.h" 26 #include "base/version.h"
26 #include "chrome/browser/browser_process.h" 27 #include "chrome/browser/browser_process.h"
27 #include "chrome/browser/chrome_notification_types.h" 28 #include "chrome/browser/chrome_notification_types.h"
28 #include "chrome/browser/notifications/native_notification_display_service.h" 29 #include "chrome/browser/notifications/native_notification_display_service.h"
29 #include "chrome/browser/notifications/notification.h" 30 #include "chrome/browser/notifications/notification.h"
30 #include "chrome/browser/notifications/notification_display_service_factory.h" 31 #include "chrome/browser/notifications/notification_display_service_factory.h"
31 #include "chrome/browser/profiles/profile_manager.h" 32 #include "chrome/browser/profiles/profile_manager.h"
32 #include "chrome/browser/shell_integration_linux.h" 33 #include "chrome/browser/shell_integration_linux.h"
33 #include "chrome/grit/generated_resources.h" 34 #include "chrome/grit/generated_resources.h"
34 #include "content/public/browser/browser_thread.h" 35 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/notification_service.h" 36 #include "content/public/browser/notification_service.h"
36 #include "dbus/bus.h" 37 #include "dbus/bus.h"
37 #include "dbus/message.h" 38 #include "dbus/message.h"
38 #include "dbus/object_proxy.h" 39 #include "dbus/object_proxy.h"
40 #include "net/base/escape.h"
41 #include "skia/ext/image_operations.h"
39 #include "ui/base/l10n/l10n_util.h" 42 #include "ui/base/l10n/l10n_util.h"
40 #include "ui/gfx/image/image_skia.h" 43 #include "ui/gfx/image/image_skia.h"
41 44
42 namespace { 45 namespace {
43 46
44 // DBus name / path. 47 // DBus name / path.
45 const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications"; 48 const char kFreedesktopNotificationsName[] = "org.freedesktop.Notifications";
46 const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications"; 49 const char kFreedesktopNotificationsPath[] = "/org/freedesktop/Notifications";
47 50
48 // DBus methods. 51 // DBus methods.
(...skipping 15 matching lines...) Expand all
64 const char kCapabilityBodyMarkup[] = "body-markup"; 67 const char kCapabilityBodyMarkup[] = "body-markup";
65 const char kCapabilityIconMulti[] = "icon-multi"; 68 const char kCapabilityIconMulti[] = "icon-multi";
66 const char kCapabilityIconStatic[] = "icon-static"; 69 const char kCapabilityIconStatic[] = "icon-static";
67 const char kCapabilityPersistence[] = "persistence"; 70 const char kCapabilityPersistence[] = "persistence";
68 const char kCapabilitySound[] = "sound"; 71 const char kCapabilitySound[] = "sound";
69 72
70 // Button IDs. 73 // Button IDs.
71 const char kDefaultButtonId[] = "default"; 74 const char kDefaultButtonId[] = "default";
72 const char kSettingsButtonId[] = "settings"; 75 const char kSettingsButtonId[] = "settings";
73 76
77 // Max image size; specified in the FDO notification specification.
78 const int kMaxImageWidth = 200;
79 const int kMaxImageHeight = 100;
80
74 // The values in this enumeration correspond to those of the 81 // The values in this enumeration correspond to those of the
75 // Linux.NotificationPlatformBridge.InitializationStatus histogram, so 82 // Linux.NotificationPlatformBridge.InitializationStatus histogram, so
76 // the ordering should not be changed. New error codes should be 83 // the ordering should not be changed. New error codes should be
77 // added at the end, before NUM_ITEMS. 84 // added at the end, before NUM_ITEMS.
78 enum class ConnectionInitializationStatusCode { 85 enum class ConnectionInitializationStatusCode {
79 SUCCESS = 0, 86 SUCCESS = 0,
80 NATIVE_NOTIFICATIONS_NOT_SUPPORTED = 1, 87 NATIVE_NOTIFICATIONS_NOT_SUPPORTED = 1,
81 MISSING_REQUIRED_CAPABILITIES = 2, 88 MISSING_REQUIRED_CAPABILITIES = 2,
82 COULD_NOT_CONNECT_TO_SIGNALS = 3, 89 COULD_NOT_CONNECT_TO_SIGNALS = 3,
83 INCOMPATIBLE_SPEC_VERSION = 4, 90 INCOMPATIBLE_SPEC_VERSION = 4,
84 NUM_ITEMS 91 NUM_ITEMS
85 }; 92 };
86 93
94 int ClampInt(int value, int low, int hi) {
95 return std::max(std::min(value, hi), low);
96 }
97
87 base::string16 CreateNotificationTitle(const Notification& notification) { 98 base::string16 CreateNotificationTitle(const Notification& notification) {
88 base::string16 title; 99 base::string16 title;
89 if (notification.type() == message_center::NOTIFICATION_TYPE_PROGRESS) { 100 if (notification.type() == message_center::NOTIFICATION_TYPE_PROGRESS) {
90 title += base::FormatPercent(notification.progress()); 101 title += base::FormatPercent(notification.progress());
91 title += base::UTF8ToUTF16(" - "); 102 title += base::UTF8ToUTF16(" - ");
92 } 103 }
93 title += notification.title(); 104 title += notification.title();
94 return title; 105 return title;
95 } 106 }
96 107
(...skipping 17 matching lines...) Expand all
114 case message_center::HIGH_PRIORITY: 125 case message_center::HIGH_PRIORITY:
115 case message_center::MAX_PRIORITY: 126 case message_center::MAX_PRIORITY:
116 return CRITICAL; 127 return CRITICAL;
117 default: 128 default:
118 NOTREACHED(); 129 NOTREACHED();
119 case message_center::DEFAULT_PRIORITY: 130 case message_center::DEFAULT_PRIORITY:
120 return NORMAL; 131 return NORMAL;
121 } 132 }
122 } 133 }
123 134
135 // Constrain |image|'s size to |kMaxImageWidth|x|kMaxImageHeight|. If
136 // the image does not need to be resized, or the image is empty,
137 // returns |image| directly.
138 gfx::Image ResizeImageToFdoMaxSize(const gfx::Image& image) {
139 if (image.IsEmpty())
140 return image;
141 int width = image.Width();
142 int height = image.Height();
143 if (width <= kMaxImageWidth && height <= kMaxImageHeight) {
144 return image;
145 }
146 const SkBitmap* image_bitmap = image.ToSkBitmap();
147 double scale = std::min(static_cast<double>(kMaxImageWidth) / width,
148 static_cast<double>(kMaxImageHeight) / height);
149 width = ClampInt(scale * width, 1, kMaxImageWidth);
150 height = ClampInt(scale * height, 1, kMaxImageHeight);
151 return gfx::Image(
152 gfx::ImageSkia::CreateFrom1xBitmap(skia::ImageOperations::Resize(
153 *image_bitmap, skia::ImageOperations::RESIZE_LANCZOS3, width,
154 height)));
155 }
156
124 // Runs once the profile has been loaded in order to perform a given 157 // Runs once the profile has been loaded in order to perform a given
125 // |operation| on a notification. 158 // |operation| on a notification.
126 void ProfileLoadedCallback(NotificationCommon::Operation operation, 159 void ProfileLoadedCallback(NotificationCommon::Operation operation,
127 NotificationCommon::Type notification_type, 160 NotificationCommon::Type notification_type,
128 const std::string& origin, 161 const std::string& origin,
129 const std::string& notification_id, 162 const std::string& notification_id,
130 int action_index, 163 int action_index,
131 const base::NullableString16& reply, 164 const base::NullableString16& reply,
132 Profile* profile) { 165 Profile* profile) {
133 if (!profile) 166 if (!profile)
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
228 bool is_incognito, 261 bool is_incognito,
229 const Notification& notification) override { 262 const Notification& notification) override {
230 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 263 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
231 // Notifications contain gfx::Image's which have reference counts 264 // Notifications contain gfx::Image's which have reference counts
232 // that are not thread safe. Because of this, we duplicate the 265 // that are not thread safe. Because of this, we duplicate the
233 // notification and its images. Wrap the notification in a 266 // notification and its images. Wrap the notification in a
234 // unique_ptr to transfer ownership of the notification (and the 267 // unique_ptr to transfer ownership of the notification (and the
235 // non-thread-safe reference counts) to the task runner thread. 268 // non-thread-safe reference counts) to the task runner thread.
236 auto notification_copy = base::MakeUnique<Notification>(notification); 269 auto notification_copy = base::MakeUnique<Notification>(notification);
237 notification_copy->set_icon(DeepCopyImage(notification_copy->icon())); 270 notification_copy->set_icon(DeepCopyImage(notification_copy->icon()));
238 notification_copy->set_image(gfx::Image()); 271 notification_copy->set_image(body_images_supported_.value()
272 ? DeepCopyImage(notification_copy->image())
273 : gfx::Image());
239 notification_copy->set_small_image(gfx::Image()); 274 notification_copy->set_small_image(gfx::Image());
240 for (size_t i = 0; i < notification_copy->buttons().size(); i++) 275 for (size_t i = 0; i < notification_copy->buttons().size(); i++)
241 notification_copy->SetButtonIcon(i, gfx::Image()); 276 notification_copy->SetButtonIcon(i, gfx::Image());
242 277
243 PostTaskToTaskRunnerThread(base::BindOnce( 278 PostTaskToTaskRunnerThread(base::BindOnce(
244 &NotificationPlatformBridgeLinuxImpl::DisplayOnTaskRunner, this, 279 &NotificationPlatformBridgeLinuxImpl::DisplayOnTaskRunner, this,
245 notification_type, notification_id, profile_id, is_incognito, 280 notification_type, notification_id, profile_id, is_incognito,
246 base::Passed(&notification_copy))); 281 base::Passed(&notification_copy)));
247 } 282 }
248 283
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
330 void Observe(int type, 365 void Observe(int type,
331 const content::NotificationSource& source, 366 const content::NotificationSource& source,
332 const content::NotificationDetails& details) override { 367 const content::NotificationDetails& details) override {
333 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 368 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
334 DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type); 369 DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type);
335 // The browser process is about to exit. Post the CleanUp() task 370 // The browser process is about to exit. Post the CleanUp() task
336 // while we still can. 371 // while we still can.
337 CleanUp(); 372 CleanUp();
338 } 373 }
339 374
375 void SetBodyImagesSupported(bool body_images_supported) {
376 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
377 body_images_supported_ = body_images_supported;
378 }
379
340 void PostTaskToUiThread(base::OnceClosure closure) const { 380 void PostTaskToUiThread(base::OnceClosure closure) const {
341 DCHECK(task_runner_->RunsTasksOnCurrentThread()); 381 DCHECK(task_runner_->RunsTasksOnCurrentThread());
342 bool success = content::BrowserThread::PostTask( 382 bool success = content::BrowserThread::PostTask(
343 content::BrowserThread::UI, FROM_HERE, std::move(closure)); 383 content::BrowserThread::UI, FROM_HERE, std::move(closure));
344 DCHECK(success); 384 DCHECK(success);
345 } 385 }
346 386
347 void PostTaskToTaskRunnerThread(base::OnceClosure closure) const { 387 void PostTaskToTaskRunnerThread(base::OnceClosure closure) const {
348 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 388 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
349 DCHECK(task_runner_); 389 DCHECK(task_runner_);
(...skipping 29 matching lines...) Expand all
379 notification_proxy_->CallMethodAndBlock( 419 notification_proxy_->CallMethodAndBlock(
380 &get_capabilities_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); 420 &get_capabilities_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
381 if (capabilities_response) { 421 if (capabilities_response) {
382 dbus::MessageReader reader(capabilities_response.get()); 422 dbus::MessageReader reader(capabilities_response.get());
383 std::vector<std::string> capabilities; 423 std::vector<std::string> capabilities;
384 reader.PopArrayOfStrings(&capabilities); 424 reader.PopArrayOfStrings(&capabilities);
385 for (const std::string& capability : capabilities) 425 for (const std::string& capability : capabilities)
386 capabilities_.insert(capability); 426 capabilities_.insert(capability);
387 } 427 }
388 RecordMetricsForCapabilities(); 428 RecordMetricsForCapabilities();
429 PostTaskToUiThread(base::BindOnce(
430 &NotificationPlatformBridgeLinuxImpl::SetBodyImagesSupported, this,
431 base::ContainsKey(capabilities_, kCapabilityBodyImages)));
389 432
390 dbus::MethodCall get_server_information_call(kFreedesktopNotificationsName, 433 dbus::MethodCall get_server_information_call(kFreedesktopNotificationsName,
391 kMethodGetServerInformation); 434 kMethodGetServerInformation);
392 std::unique_ptr<dbus::Response> server_information_response = 435 std::unique_ptr<dbus::Response> server_information_response =
393 notification_proxy_->CallMethodAndBlock( 436 notification_proxy_->CallMethodAndBlock(
394 &get_server_information_call, 437 &get_server_information_call,
395 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); 438 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
396 if (server_information_response) { 439 if (server_information_response) {
397 dbus::MessageReader reader(server_information_response.get()); 440 dbus::MessageReader reader(server_information_response.get());
398 std::string spec_version; 441 std::string spec_version;
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
465 writer.AppendString(""); 508 writer.AppendString("");
466 509
467 writer.AppendUint32(data->dbus_id); 510 writer.AppendUint32(data->dbus_id);
468 511
469 // app_icon passed implicitly via desktop-entry. 512 // app_icon passed implicitly via desktop-entry.
470 writer.AppendString(""); 513 writer.AppendString("");
471 514
472 writer.AppendString( 515 writer.AppendString(
473 base::UTF16ToUTF8(CreateNotificationTitle(*notification))); 516 base::UTF16ToUTF8(CreateNotificationTitle(*notification)));
474 517
475 std::string body; 518 std::ostringstream body;
476 if (base::ContainsKey(capabilities_, kCapabilityBody)) { 519 if (base::ContainsKey(capabilities_, kCapabilityBody)) {
477 body = base::UTF16ToUTF8(notification->message());
478 const bool body_markup = 520 const bool body_markup =
479 base::ContainsKey(capabilities_, kCapabilityBodyMarkup); 521 base::ContainsKey(capabilities_, kCapabilityBodyMarkup);
522 std::string message = base::UTF16ToUTF8(notification->message());
480 if (body_markup) { 523 if (body_markup) {
481 base::ReplaceSubstringsAfterOffset(&body, 0, "&", "&amp;"); 524 base::ReplaceSubstringsAfterOffset(&message, 0, "&", "&amp;");
482 base::ReplaceSubstringsAfterOffset(&body, 0, "<", "&lt;"); 525 base::ReplaceSubstringsAfterOffset(&message, 0, "<", "&lt;");
483 base::ReplaceSubstringsAfterOffset(&body, 0, ">", "&gt;"); 526 base::ReplaceSubstringsAfterOffset(&message, 0, ">", "&gt;");
484 } 527 }
528 body << message;
529
485 if (notification->type() == message_center::NOTIFICATION_TYPE_MULTIPLE) { 530 if (notification->type() == message_center::NOTIFICATION_TYPE_MULTIPLE) {
486 for (const auto& item : notification->items()) { 531 for (const auto& item : notification->items()) {
487 if (!body.empty()) 532 if (body.tellp())
488 body += "\n"; 533 body << "\n";
489 const std::string title = base::UTF16ToUTF8(item.title); 534 const std::string title = base::UTF16ToUTF8(item.title);
490 const std::string message = base::UTF16ToUTF8(item.message); 535 const std::string message = base::UTF16ToUTF8(item.message);
491 // TODO(peter): Figure out the right way to internationalize 536 // TODO(peter): Figure out the right way to internationalize
492 // this for RTL languages. 537 // this for RTL languages.
493 if (body_markup) 538 if (body_markup)
494 body += "<b>" + title + "</b> " + message; 539 body << "<b>" << title << "</b> " << message;
495 else 540 else
496 body += title + " - " + message; 541 body << title << " - " << message;
542 }
543 } else if (notification->type() ==
544 message_center::NOTIFICATION_TYPE_IMAGE &&
545 base::ContainsKey(capabilities_, kCapabilityBodyImages)) {
546 std::unique_ptr<ResourceFile> image_file = WriteDataToTmpFile(
547 ResizeImageToFdoMaxSize(notification->image()).As1xPNGBytes());
548 if (image_file) {
549 if (body.tellp())
550 body << "\n";
551 body << "<img src=\""
552 << net::EscapePath(image_file->file_path().value())
553 << "\" alt=\"\"/>";
554 data->resource_files.push_back(std::move(image_file));
497 } 555 }
498 } 556 }
499 } 557 }
500 writer.AppendString(body); 558 writer.AppendString(body.str());
501 559
502 // Even-indexed elements in this vector are action IDs passed back to 560 // Even-indexed elements in this vector are action IDs passed back to
503 // us in OnActionInvoked(). Odd-indexed ones contain the button text. 561 // us in OnActionInvoked(). Odd-indexed ones contain the button text.
504 std::vector<std::string> actions; 562 std::vector<std::string> actions;
505 if (base::ContainsKey(capabilities_, kCapabilityActions)) { 563 if (base::ContainsKey(capabilities_, kCapabilityActions)) {
506 data->action_start = data->action_end; 564 data->action_start = data->action_end;
507 for (const auto& button_info : notification->buttons()) { 565 for (const auto& button_info : notification->buttons()) {
508 // FDO notification buttons can contain either an icon or a label, 566 // FDO notification buttons can contain either an icon or a label,
509 // but not both, and the type of all buttons must be the same (all 567 // but not both, and the type of all buttons must be the same (all
510 // labels or all icons), so always use labels. 568 // labels or all icons), so always use labels.
(...skipping 265 matching lines...) Expand 10 before | Expand all | Expand 10 after
776 834
777 scoped_refptr<base::SequencedTaskRunner> task_runner_; 835 scoped_refptr<base::SequencedTaskRunner> task_runner_;
778 836
779 content::NotificationRegistrar registrar_; 837 content::NotificationRegistrar registrar_;
780 838
781 // State necessary for OnConnectionInitializationFinished() and 839 // State necessary for OnConnectionInitializationFinished() and
782 // SetReadyCallback(). 840 // SetReadyCallback().
783 base::Optional<bool> connected_; 841 base::Optional<bool> connected_;
784 std::vector<NotificationBridgeReadyCallback> on_connected_callbacks_; 842 std::vector<NotificationBridgeReadyCallback> on_connected_callbacks_;
785 843
844 // Notification servers very rarely have the 'body-images'
845 // capability, so try to avoid an image copy if possible.
846 base::Optional<bool> body_images_supported_;
847
786 ////////////////////////////////////////////////////////////////////////////// 848 //////////////////////////////////////////////////////////////////////////////
787 // Members used only on the task runner thread. 849 // Members used only on the task runner thread.
788 850
789 scoped_refptr<dbus::Bus> bus_; 851 scoped_refptr<dbus::Bus> bus_;
790 852
791 dbus::ObjectProxy* notification_proxy_ = nullptr; 853 dbus::ObjectProxy* notification_proxy_ = nullptr;
792 854
793 std::unordered_set<std::string> capabilities_; 855 std::unordered_set<std::string> capabilities_;
794 856
795 base::Version spec_version_; 857 base::Version spec_version_;
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
842 } 904 }
843 905
844 void NotificationPlatformBridgeLinux::SetReadyCallback( 906 void NotificationPlatformBridgeLinux::SetReadyCallback(
845 NotificationBridgeReadyCallback callback) { 907 NotificationBridgeReadyCallback callback) {
846 impl_->SetReadyCallback(std::move(callback)); 908 impl_->SetReadyCallback(std::move(callback));
847 } 909 }
848 910
849 void NotificationPlatformBridgeLinux::CleanUp() { 911 void NotificationPlatformBridgeLinux::CleanUp() {
850 impl_->CleanUp(); 912 impl_->CleanUp();
851 } 913 }
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/notifications/notification_platform_bridge_linux_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698