blob: e37f48eeec85eb498b81164a6474855ce540f127 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/printer_detector/printer_detector.h"
#include <stdint.h>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/memory/scoped_ptr.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/notifications/notification.h"
#include "chrome/browser/notifications/notification_delegate.h"
#include "chrome/browser/notifications/notification_ui_manager.h"
#include "chrome/common/extensions/api/webstore_widget_private.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "device/core/device_client.h"
#include "device/usb/usb_device.h"
#include "device/usb/usb_device_filter.h"
#include "device/usb/usb_ids.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/api/printer_provider/usb_printer_manifest_data.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/one_shot_event.h"
#include "extensions/common/permissions/api_permission.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/permissions/usb_device_permission.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
namespace webstore_widget_private_api =
extensions::api::webstore_widget_private;
namespace {
const char kPrinterProviderFoundNotificationID[] =
"chrome://settings/printer/printer_app_found";
const char kNoPrinterProviderNotificationID[] =
"chrome://settings/printer/no_printer_app";
// Base class used for printer USB interfaces
// (https://www.usb.org/developers/defined_class).
const uint8 kPrinterInterfaceClass = 7;
enum PrinterServiceEvent {
PRINTER_ADDED,
DEPRECATED_PAGE_DISPLAYED,
NOTIFICATION_SHOWN_PRINTER_SUPPORTED,
NOTIFICATION_SHOWN_PRINTER_NOT_SUPPORTED,
WEBSTORE_WIDGET_APP_LAUNCHED,
PRINTER_SERVICE_EVENT_MAX,
};
base::string16 GetNotificationTitle(uint16 vendor_id, uint16 product_id) {
const char* vendor_name = device::UsbIds::GetVendorName(vendor_id);
if (vendor_name) {
return l10n_util::GetStringFUTF16(IDS_PRINTER_DETECTED_NOTIFICATION_TITLE,
base::UTF8ToUTF16(vendor_name));
} else {
return l10n_util::GetStringUTF16(
IDS_PRINTER_DETECTED_NOTIFICATION_TITLE_UNKNOWN_VENDOR);
}
}
std::string GetNotificationTag(const std::string& vendor_id,
const std::string& product_id) {
return vendor_id + ":" + product_id;
}
// Checks if there is an enabled extension with printerProvider permission and
// usbDevices persmission for the USB (vendor_id, product_id) pair.
bool HasAppThatSupportsPrinter(Profile* profile,
const scoped_refptr<device::UsbDevice>& device) {
const extensions::ExtensionSet& enabled_extensions =
extensions::ExtensionRegistry::Get(profile)->enabled_extensions();
for (const auto& extension : enabled_extensions) {
if (!extension->permissions_data() ||
!extension->permissions_data()->HasAPIPermission(
extensions::APIPermission::kPrinterProvider) ||
!extension->permissions_data()->HasAPIPermission(
extensions::APIPermission::kUsb)) {
continue;
}
const extensions::UsbPrinterManifestData* manifest_data =
extensions::UsbPrinterManifestData::Get(extension.get());
if (manifest_data && manifest_data->SupportsDevice(device)) {
return true;
}
extensions::UsbDevicePermission::CheckParam param(
device->vendor_id(), device->product_id(),
extensions::UsbDevicePermissionData::UNSPECIFIED_INTERFACE);
if (extension->permissions_data()->CheckAPIPermissionWithParam(
extensions::APIPermission::kUsbDevice, &param)) {
return true;
}
}
return false;
}
// Delegate for notification shown when a printer provider app for the plugged
// in printer is found.
class PrinterProviderExistsNotificationDelegate : public NotificationDelegate {
public:
PrinterProviderExistsNotificationDelegate(const std::string& vendor_id,
const std::string& product_id)
: vendor_id_(vendor_id), product_id_(product_id) {}
std::string id() const override {
return "system.printer.printer_provider_exists/" +
GetNotificationTag(vendor_id_, product_id_);
}
private:
~PrinterProviderExistsNotificationDelegate() override = default;
const std::string vendor_id_;
const std::string product_id_;
DISALLOW_COPY_AND_ASSIGN(PrinterProviderExistsNotificationDelegate);
};
// Delegate for notification shown when there are no printer provider apps that
// support the plugged in printer found.
// The notification is clickable, and clicking it is supposed to launch
// Chrome Web Store widget listing apps that can support the plugged in printer.
// (not implemented yet).
class SearchPrinterAppNotificationDelegate : public NotificationDelegate {
public:
SearchPrinterAppNotificationDelegate(content::BrowserContext* browser_context,
uint16 vendor_id,
const std::string& vendor_id_str,
uint16 product_id,
const std::string& product_id_str)
: browser_context_(browser_context),
vendor_id_(vendor_id),
vendor_id_str_(vendor_id_str),
product_id_(product_id),
product_id_str_(product_id_str) {}
std::string id() const override {
return "system.printer.no_printer_provider_found/" +
GetNotificationTag(vendor_id_str_, product_id_str_);
}
bool HasClickedListener() override { return true; }
void Click() override {
UMA_HISTOGRAM_ENUMERATION("PrinterService.PrinterServiceEvent",
WEBSTORE_WIDGET_APP_LAUNCHED,
PRINTER_SERVICE_EVENT_MAX);
webstore_widget_private_api::Options options;
options.type = webstore_widget_private_api::TYPE_PRINTER_PROVIDER;
options.usb_id.reset(new webstore_widget_private_api::UsbId());
options.usb_id->vendor_id = vendor_id_;
options.usb_id->product_id = product_id_;
extensions::EventRouter* event_router =
extensions::EventRouter::Get(browser_context_);
scoped_ptr<extensions::Event> event(new extensions::Event(
extensions::events::WEBSTORE_WIDGET_PRIVATE_ON_SHOW_WIDGET,
webstore_widget_private_api::OnShowWidget::kEventName,
webstore_widget_private_api::OnShowWidget::Create(options)));
event_router->DispatchEventToExtension(extension_misc::kWebstoreWidgetAppId,
event.Pass());
}
private:
~SearchPrinterAppNotificationDelegate() override = default;
content::BrowserContext* browser_context_;
uint16 vendor_id_;
std::string vendor_id_str_;
uint16 product_id_;
std::string product_id_str_;
DISALLOW_COPY_AND_ASSIGN(SearchPrinterAppNotificationDelegate);
};
// Shows a notification for a plugged in printer.
// If there is a printerProvider app that handles the printer's USB (vendor_id,
// product_id) pair, the notification informs the user that the printer is ready
// to be used, otherwise it offers the user to search the Chrome Web Store for
// an app that can handle the printer.
void ShowPrinterPluggedNotification(
Profile* profile,
NotificationUIManager* notification_ui_manager,
const scoped_refptr<device::UsbDevice>& device) {
ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
scoped_ptr<Notification> notification;
const std::string kVendorIdStr = base::IntToString(device->vendor_id());
const std::string kProductIdStr = base::IntToString(device->product_id());
if (HasAppThatSupportsPrinter(profile, device)) {
UMA_HISTOGRAM_ENUMERATION("PrinterService.PrinterServiceEvent",
NOTIFICATION_SHOWN_PRINTER_SUPPORTED,
PRINTER_SERVICE_EVENT_MAX);
notification.reset(new Notification(
message_center::NOTIFICATION_TYPE_SIMPLE,
GetNotificationTitle(device->vendor_id(), device->product_id()),
l10n_util::GetStringUTF16(
IDS_PRINTER_DETECTED_NOTIFICATION_PRINT_APP_FOUND_BODY),
bundle.GetImageNamed(IDR_PRINTER_NOTIFICATION),
message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT,
kPrinterProviderFoundNotificationID),
base::string16(), GURL(kPrinterProviderFoundNotificationID),
GetNotificationTag(kVendorIdStr, kProductIdStr),
message_center::RichNotificationData(),
new PrinterProviderExistsNotificationDelegate(kVendorIdStr,
kProductIdStr)));
} else {
UMA_HISTOGRAM_ENUMERATION("PrinterService.PrinterServiceEvent",
NOTIFICATION_SHOWN_PRINTER_NOT_SUPPORTED,
PRINTER_SERVICE_EVENT_MAX);
message_center::RichNotificationData options;
options.clickable = true;
notification.reset(new Notification(
message_center::NOTIFICATION_TYPE_SIMPLE,
GetNotificationTitle(device->vendor_id(), device->product_id()),
l10n_util::GetStringUTF16(
IDS_PRINTER_DETECTED_NOTIFICATION_NO_PRINT_APP_BODY),
bundle.GetImageNamed(IDR_PRINTER_NOTIFICATION),
message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT,
kNoPrinterProviderNotificationID),
base::string16(), GURL(kNoPrinterProviderNotificationID),
GetNotificationTag(kVendorIdStr, kProductIdStr), options,
new SearchPrinterAppNotificationDelegate(
profile, device->vendor_id(), kVendorIdStr, device->product_id(),
kProductIdStr)));
}
notification->SetSystemPriority();
notification_ui_manager->Add(*notification, profile);
}
} // namespace
namespace chromeos {
PrinterDetector::PrinterDetector(Profile* profile)
: profile_(profile),
notification_ui_manager_(nullptr),
observer_(this),
weak_ptr_factory_(this) {
extensions::ExtensionSystem::Get(profile)->ready().Post(
FROM_HERE,
base::Bind(&PrinterDetector::Initialize, weak_ptr_factory_.GetWeakPtr()));
}
PrinterDetector::~PrinterDetector() {
}
void PrinterDetector::Shutdown() {
}
void PrinterDetector::Initialize() {
device::UsbService* usb_service =
device::DeviceClient::Get()->GetUsbService();
if (!usb_service)
return;
observer_.Add(usb_service);
}
void PrinterDetector::OnDeviceAdded(scoped_refptr<device::UsbDevice> device) {
const user_manager::User* user =
ProfileHelper::Get()->GetUserByProfile(profile_);
if (!user || !user->HasGaiaAccount() || !user_manager::UserManager::Get() ||
user != user_manager::UserManager::Get()->GetActiveUser()) {
return;
}
device::UsbDeviceFilter printer_filter;
printer_filter.SetInterfaceClass(kPrinterInterfaceClass);
if (!printer_filter.Matches(device)) {
return;
}
UMA_HISTOGRAM_ENUMERATION("PrinterService.PrinterServiceEvent", PRINTER_ADDED,
PRINTER_SERVICE_EVENT_MAX);
ShowPrinterPluggedNotification(
profile_,
notification_ui_manager_ ? notification_ui_manager_
: g_browser_process->notification_ui_manager(),
device);
}
void PrinterDetector::SetNotificationUIManagerForTesting(
NotificationUIManager* manager) {
notification_ui_manager_ = manager;
}
} // namespace chromeos