1 // Copyright (c) 2012 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/chromeos/dbus/printer_service_provider.h"
11 #include "ash/session/session_state_delegate.h"
12 #include "ash/shell.h"
13 #include "ash/wm/window_util.h"
14 #include "base/bind.h"
15 #include "base/bind_helpers.h"
16 #include "base/command_line.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/metrics/histogram.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/sys_info.h"
24 #include "chrome/browser/browser_process.h"
25 #include "chrome/browser/chromeos/profiles/profile_helper.h"
26 #include "chrome/browser/notifications/notification.h"
27 #include "chrome/browser/notifications/notification_delegate.h"
28 #include "chrome/browser/notifications/notification_ui_manager.h"
29 #include "chrome/browser/profiles/profile_manager.h"
30 #include "chrome/browser/ui/browser.h"
31 #include "chrome/browser/ui/browser_tabstrip.h"
32 #include "chrome/browser/ui/browser_window.h"
33 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
34 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h"
35 #include "chrome/browser/ui/tabs/tab_strip_model.h"
36 #include "chrome/common/chrome_version_info.h"
37 #include "chrome/grit/generated_resources.h"
38 #include "chromeos/chromeos_switches.h"
39 #include "components/user_manager/user.h"
40 #include "components/user_manager/user_manager.h"
41 #include "content/public/browser/browser_thread.h"
42 #include "content/public/browser/web_contents.h"
44 #include "dbus/exported_object.h"
45 #include "dbus/message.h"
46 #include "device/usb/usb_ids.h"
47 #include "extensions/browser/extension_registry.h"
48 #include "extensions/common/extension.h"
49 #include "extensions/common/extension_set.h"
50 #include "extensions/common/permissions/api_permission.h"
51 #include "extensions/common/permissions/permissions_data.h"
52 #include "extensions/common/permissions/usb_device_permission.h"
53 #include "grit/theme_resources.h"
54 #include "net/base/escape.h"
55 #include "third_party/cros_system_api/dbus/service_constants.h"
56 #include "ui/aura/window.h"
57 #include "ui/base/l10n/l10n_util.h"
58 #include "ui/base/resource/resource_bundle.h"
62 const char kPrinterAdded
[] = "PrinterAdded";
64 const char kPrinterProviderFoundNotificationID
[] =
65 "chrome://settings/printer/printer_app_found";
67 const char kNoPrinterProviderNotificationID
[] =
68 "chrome://settings/printer/no_printer_app";
70 enum PrinterServiceEvent
{
73 PRINTER_SERVICE_EVENT_MAX
,
76 // TODO(vitalybuka): update URL with more relevant information.
77 const char kCloudPrintLearnUrl
[] =
78 "https://www.google.com/landing/cloudprint/index.html";
80 void ActivateContents(Browser
* browser
, content::WebContents
* contents
) {
81 browser
->tab_strip_model()->ActivateTabAt(
82 browser
->tab_strip_model()->GetIndexOfWebContents(contents
), false);
85 Browser
* ActivateAndGetBrowserForUrl(GURL url
) {
86 for (TabContentsIterator it
; !it
.done(); it
.Next()) {
87 if (it
->GetLastCommittedURL() == url
) {
88 ActivateContents(it
.browser(), *it
);
95 bool HexStringToUInt16(const std::string
& input
, uint16
* output
) {
96 uint32 output_uint
= 0;
97 if (!base::HexStringToUInt(input
, &output_uint
) ||
98 output_uint
> std::numeric_limits
<uint16
>::max())
100 *output
= static_cast<uint16
>(output_uint
);
104 void FindOrOpenCloudPrintPage(const std::string
& /* vendor */,
105 const std::string
& /* product */) {
106 UMA_HISTOGRAM_ENUMERATION("PrinterService.PrinterServiceEvent", PRINTER_ADDED
,
107 PRINTER_SERVICE_EVENT_MAX
);
108 if (!ash::Shell::GetInstance()->session_state_delegate()->
109 IsActiveUserSessionStarted() ||
110 ash::Shell::GetInstance()->session_state_delegate()->IsScreenLocked()) {
114 Profile
* profile
= ProfileManager::GetLastUsedProfile();
118 GURL
url(kCloudPrintLearnUrl
);
120 if (!ActivateAndGetBrowserForUrl(url
)) {
121 chrome::ScopedTabbedBrowserDisplayer
displayer(
122 profile
, chrome::HOST_DESKTOP_TYPE_ASH
);
123 UMA_HISTOGRAM_ENUMERATION("PrinterService.PrinterServiceEvent",
124 PAGE_DISPLAYED
, PRINTER_SERVICE_EVENT_MAX
);
125 chrome::AddSelectedTabWithURL(displayer
.browser(), url
,
126 ui::PAGE_TRANSITION_LINK
);
130 base::string16
GetNotificationTitle(uint16 vendor_id
, uint16 product_id
) {
131 const char* vendor_name
= device::UsbIds::GetVendorName(vendor_id
);
133 return l10n_util::GetStringFUTF16(IDS_PRINTER_DETECTED_NOTIFICATION_TITLE
,
134 base::UTF8ToUTF16(vendor_name
));
136 return l10n_util::GetStringUTF16(
137 IDS_PRINTER_DETECTED_NOTIFICATION_TITLE_UNKNOWN_VENDOR
);
141 std::string
GetNotificationTag(const std::string
& vendor_id
,
142 const std::string
& product_id
) {
143 return vendor_id
+ ":" + product_id
;
146 // Checks if there is an enabled extension with printerProvider permission and
147 // usbDevices persmission for the USB (vendor_id, product_id) pair.
148 bool HasAppThatSupportsPrinter(Profile
* profile
,
151 const extensions::ExtensionSet
& enabled_extensions
=
152 extensions::ExtensionRegistry::Get(profile
)->enabled_extensions();
153 for (const auto& extension
: enabled_extensions
) {
154 if (!extension
->permissions_data() ||
155 !extension
->permissions_data()->HasAPIPermission(
156 extensions::APIPermission::kPrinterProvider
) ||
157 !extension
->permissions_data()->HasAPIPermission(
158 extensions::APIPermission::kUsb
)) {
162 extensions::UsbDevicePermission::CheckParam
param(
163 vendor_id
, product_id
,
164 extensions::UsbDevicePermissionData::UNSPECIFIED_INTERFACE
);
165 if (extension
->permissions_data()->CheckAPIPermissionWithParam(
166 extensions::APIPermission::kUsbDevice
, ¶m
)) {
173 // Delegate for notification shown when a printer provider app for the plugged
174 // in printer is found.
175 class PrinterProviderExistsNotificationDelegate
: public NotificationDelegate
{
177 PrinterProviderExistsNotificationDelegate(const std::string
& vendor_id
,
178 const std::string
& product_id
)
179 : vendor_id_(vendor_id
), product_id_(product_id
) {}
181 std::string
id() const override
{
182 return "system.printer.printer_provider_exists/" +
183 GetNotificationTag(vendor_id_
, product_id_
);
187 ~PrinterProviderExistsNotificationDelegate() override
= default;
189 const std::string vendor_id_
;
190 const std::string product_id_
;
192 DISALLOW_COPY_AND_ASSIGN(PrinterProviderExistsNotificationDelegate
);
195 // Delegate for notification shown when there are no printer provider apps that
196 // support the plugged in printer found.
197 // The notification is clickable, and clicking it is supposed to launch
198 // Chrome Web Store widget listing apps that can support the plugged in printer.
199 // (not implemented yet).
200 class SearchPrinterAppNotificationDelegate
: public NotificationDelegate
{
202 SearchPrinterAppNotificationDelegate(const std::string
& vendor_id
,
203 const std::string
& product_id
)
204 : vendor_id_(vendor_id
), product_id_(product_id
) {}
206 std::string
id() const override
{
207 return "system.printer.no_printer_provider_found/" +
208 GetNotificationTag(vendor_id_
, product_id_
);
210 bool HasClickedListener() override
{ return true; }
212 void Click() override
{
213 // TODO(tbarzic): Implement this (http://crbug.com/439448).
217 ~SearchPrinterAppNotificationDelegate() override
= default;
219 std::string vendor_id_
;
220 std::string product_id_
;
222 DISALLOW_COPY_AND_ASSIGN(SearchPrinterAppNotificationDelegate
);
225 // Shows a notification for a plugged in printer.
226 // If there is a printerProvider app that handles the printer's USB (vendor_id,
227 // product_id) pair, the notification informs the user that the printer is ready
228 // to be used, otherwise it offers the user to search the Chrome Web Store for
229 // an app that can handle the printer.
230 void ShowPrinterPluggedNotification(
231 NotificationUIManager
* notification_ui_manager
,
232 const std::string
& vendor_id_str
,
233 const std::string
& product_id_str
) {
234 uint16 vendor_id
= 0;
235 uint16 product_id
= 0;
236 if (!HexStringToUInt16(vendor_id_str
, &vendor_id
) ||
237 !HexStringToUInt16(product_id_str
, &product_id
)) {
238 LOG(WARNING
) << "Invalid USB ID " << vendor_id_str
<< ":" << product_id_str
;
242 const user_manager::User
* user
=
243 user_manager::UserManager::Get()
244 ? user_manager::UserManager::Get()->GetActiveUser()
246 if (!user
|| !user
->HasGaiaAccount())
249 Profile
* profile
= chromeos::ProfileHelper::Get()->GetProfileByUser(user
);
253 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
254 scoped_ptr
<Notification
> notification
;
256 if (HasAppThatSupportsPrinter(profile
, vendor_id
, product_id
)) {
257 notification
.reset(new Notification(
258 message_center::NOTIFICATION_TYPE_SIMPLE
,
259 GURL(kPrinterProviderFoundNotificationID
),
260 GetNotificationTitle(vendor_id
, product_id
),
261 l10n_util::GetStringUTF16(
262 IDS_PRINTER_DETECTED_NOTIFICATION_PRINT_APP_FOUND_BODY
),
263 bundle
.GetImageNamed(IDR_PRINTER_NOTIFICATION
),
264 message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT
,
265 kPrinterProviderFoundNotificationID
),
266 base::string16(), GetNotificationTag(vendor_id_str
, product_id_str
),
267 message_center::RichNotificationData(),
268 new PrinterProviderExistsNotificationDelegate(vendor_id_str
,
271 message_center::RichNotificationData options
;
272 options
.clickable
= true;
273 notification
.reset(new Notification(
274 message_center::NOTIFICATION_TYPE_SIMPLE
,
275 GURL(kNoPrinterProviderNotificationID
),
276 GetNotificationTitle(vendor_id
, product_id
),
277 l10n_util::GetStringUTF16(
278 IDS_PRINTER_DETECTED_NOTIFICATION_NO_PRINT_APP_BODY
),
279 bundle
.GetImageNamed(IDR_PRINTER_NOTIFICATION
),
280 message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT
,
281 kNoPrinterProviderNotificationID
),
282 base::string16(), GetNotificationTag(vendor_id_str
, product_id_str
),
283 options
, new SearchPrinterAppNotificationDelegate(vendor_id_str
,
287 notification
->SetSystemPriority();
288 notification_ui_manager
->Add(*notification
, profile
);
295 PrinterServiceProvider::PrinterServiceProvider()
296 : notification_ui_manager_(nullptr), weak_ptr_factory_(this) {
299 PrinterServiceProvider::~PrinterServiceProvider() {
302 void PrinterServiceProvider::Start(
303 scoped_refptr
<dbus::ExportedObject
> exported_object
) {
304 exported_object_
= exported_object
;
306 DVLOG(1) << "PrinterServiceProvider started";
307 exported_object_
->ExportMethod(
308 kLibCrosServiceInterface
,
310 base::Bind(&PrinterServiceProvider::PrinterAdded
,
311 weak_ptr_factory_
.GetWeakPtr()),
312 base::Bind(&PrinterServiceProvider::OnExported
,
313 weak_ptr_factory_
.GetWeakPtr()));
316 void PrinterServiceProvider::SetNotificationUIManagerForTesting(
317 NotificationUIManager
* manager
) {
318 notification_ui_manager_
= manager
;
321 void PrinterServiceProvider::OnExported(
322 const std::string
& interface_name
,
323 const std::string
& method_name
,
326 LOG(ERROR
) << "Failed to export " << interface_name
<< "."
329 DVLOG(1) << "Method exported: " << interface_name
<< "." << method_name
;
332 void PrinterServiceProvider::ShowCloudPrintHelp(const std::string
& vendor
,
333 const std::string
& product
) {
334 content::BrowserThread::PostTask(content::BrowserThread::UI
, FROM_HERE
,
335 base::Bind(&FindOrOpenCloudPrintPage
, vendor
,
339 void PrinterServiceProvider::PrinterAdded(
340 dbus::MethodCall
* method_call
,
341 dbus::ExportedObject::ResponseSender response_sender
) {
342 DVLOG(1) << "PrinterAdded " << method_call
->ToString();
344 dbus::MessageReader
reader(method_call
);
346 std::string vendor_id
;
347 reader
.PopString(&vendor_id
);
348 StringToUpperASCII(&vendor_id
);
350 std::string product_id
;
351 reader
.PopString(&product_id
);
352 StringToUpperASCII(&product_id
);
354 // Send an empty response.
355 response_sender
.Run(dbus::Response::FromMethodCall(method_call
));
357 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
358 switches::kEnablePrinterAppSearch
)) {
359 ShowPrinterPluggedNotification(
360 notification_ui_manager_
? notification_ui_manager_
361 : g_browser_process
->notification_ui_manager(),
362 vendor_id
, product_id
);
366 // Disable showing Cloudprint help on canary and dev channel, as these have
367 // support for printerProvider API.
368 // TODO(tbarzic): Remove this and offer the user to search for an extension
369 // that can act as a print driver (using printerProvider API) for USB printers
370 // detected by this service. http://crbug.com/439448
371 if (base::SysInfo::IsRunningOnChromeOS() &&
372 chrome::VersionInfo::GetChannel() <= chrome::VersionInfo::CHANNEL_DEV
)
375 ShowCloudPrintHelp(vendor_id
, product_id
);
378 } // namespace chromeos