Chromium Code Reviews| Index: chrome/browser/chromeos/printer_detector/cups_printer_detector.cc |
| diff --git a/chrome/browser/chromeos/printer_detector/cups_printer_detector.cc b/chrome/browser/chromeos/printer_detector/cups_printer_detector.cc |
| index 4532256dc3c09642ac36568617d8fce6372f67d8..1fab4cc31b08a38fda24798e1f2f91e6f36e061c 100644 |
| --- a/chrome/browser/chromeos/printer_detector/cups_printer_detector.cc |
| +++ b/chrome/browser/chromeos/printer_detector/cups_printer_detector.cc |
| @@ -15,17 +15,17 @@ |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chromeos/printer_detector/printer_detector.h" |
| +#include "chrome/browser/chromeos/printing/ppd_provider_factory.h" |
| +#include "chrome/browser/chromeos/printing/printer_configurer.h" |
| +#include "chrome/browser/chromeos/printing/printers_manager_factory.h" |
| +#include "chrome/browser/chromeos/printing/usb_util.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/browser/ui/browser_navigator.h" |
| -#include "chrome/browser/ui/browser_navigator_params.h" |
| -#include "chrome/common/url_constants.h" |
| -#include "chrome/grit/generated_resources.h" |
| -#include "chrome/grit/theme_resources.h" |
| +#include "chromeos/dbus/dbus_thread_manager.h" |
| +#include "chromeos/dbus/debug_daemon_client.h" |
| +#include "chromeos/printing/ppd_provider.h" |
| #include "components/user_manager/user.h" |
| #include "components/user_manager/user_manager.h" |
| +#include "content/public/browser/browser_thread.h" |
| #include "device/base/device_client.h" |
| #include "device/usb/usb_device.h" |
| #include "device/usb/usb_device_filter.h" |
| @@ -33,44 +33,33 @@ |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/common/one_shot_event.h" |
| -#include "ui/base/l10n/l10n_util.h" |
| -#include "ui/base/resource/resource_bundle.h" |
| namespace chromeos { |
| namespace { |
| -const char kUSBPrinterFoundNotificationID[] = |
| - "chrome://settings/cupsPrinters/printer_found"; |
| +using printing::PpdProvider; |
| -// Base class used for printer USB interfaces |
| -// (https://www.usb.org/developers/defined_class). |
| -const uint8_t kPrinterInterfaceClass = 7; |
| +// We have to carry quite a bit of data through to the callbacks for |
| +// getting a printer set up. Rather than have a bunch of parameters |
| +// in a bunch of functions below, this struct aggregates everything |
| +// we need to keep around during the setup. |
| +struct SetUpPrinterData { |
| + // The configurer running the SetUpPrinter call. |
| + std::unique_ptr<PrinterConfigurer> configurer; |
| -// USBPrinterSetupNotificationDelegate takes a pointer to the Impl class, so |
| -// we have to forward declare it. |
| -class CupsPrinterDetectorImpl; |
| + // The printer being set up. |
| + std::unique_ptr<Printer> printer; |
| -class USBPrinterSetupNotificationDelegate : public NotificationDelegate { |
| - public: |
| - explicit USBPrinterSetupNotificationDelegate( |
| - CupsPrinterDetectorImpl* printer_detector) |
| - : printer_detector_(printer_detector) {} |
| - |
| - // NotificationDelegate override: |
| - std::string id() const override { return kUSBPrinterFoundNotificationID; } |
| - |
| - // This is defined out of line because it needs the PrinterDetectorImpl |
| - // full class declaration, not just the forward declaration. |
| - void ButtonClick(int button_index) override; |
| - |
| - bool HasClickedListener() override { return true; } |
| - |
| - private: |
| - ~USBPrinterSetupNotificationDelegate() override = default; |
| + // The usb device causing this setup flow. |
| + scoped_refptr<device::UsbDevice> device; |
| - CupsPrinterDetectorImpl* printer_detector_; |
| + // True if this printer is one that the user doesn't already have a |
| + // configuration for. |
| + bool is_new; |
| - DISALLOW_COPY_AND_ASSIGN(USBPrinterSetupNotificationDelegate); |
| + // True if this was a printer that was plugged in during the session, false if |
| + // it was already plugged in when the session started. |
| + bool hotplugged; |
| }; |
| // The PrinterDetector that drives the flow for setting up a USB printer to use |
| @@ -86,149 +75,210 @@ class CupsPrinterDetectorImpl : public PrinterDetector, |
| } |
| ~CupsPrinterDetectorImpl() override = default; |
| - void ClickOnNotificationButton(int button_index) { |
| - // Remove the old notification first. |
| - const ProfileID profile_id = NotificationUIManager::GetProfileID(profile_); |
| - g_browser_process->notification_ui_manager()->CancelById( |
| - kUSBPrinterFoundNotificationID, profile_id); |
| - |
| - if (command_ == ButtonCommand::SETUP) { |
| - OnSetUpUSBPrinterStarted(); |
| - // TODO(skau/xdai): call the CUPS backend to set up the USB printer and |
| - // then call OnSetUpPrinterDone() or OnSetUpPrinterError() depending on |
| - // the setup result. |
| - } else if (command_ == ButtonCommand::CANCEL_SETUP) { |
| - // TODO(skau/xdai): call the CUPS backend to cancel the printer setup. |
| - } else if (command_ == ButtonCommand::GET_HELP) { |
| - chrome::NavigateParams params(profile_, |
| - GURL(chrome::kChromeUIMdCupsSettingsURL), |
| - ui::PAGE_TRANSITION_LINK); |
| - params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; |
| - params.window_action = chrome::NavigateParams::SHOW_WINDOW; |
| - chrome::Navigate(¶ms); |
| - } |
| - } |
| - |
| private: |
| - // Action that should be performed when a notification button is clicked. |
| - enum class ButtonCommand { |
| - SETUP, |
| - CANCEL_SETUP, |
| - CLOSE, |
| - GET_HELP, |
| - }; |
| - |
| - // UsbService::observer override: |
| - void OnDeviceAdded(scoped_refptr<device::UsbDevice> device) override { |
| + // Called when the extension system is "ready", meaning everything has been |
| + // created. |
| + void Initialize() { |
| const user_manager::User* user = |
| ProfileHelper::Get()->GetUserByProfile(profile_); |
| + // Don't bother to listen for USB events unless we are the active user. |
| + // |
| // TODO(justincarlson) - See if it's appropriate to relax any of these |
| // constraints. |
| if (!user || !user->HasGaiaAccount() || !user_manager::UserManager::Get() || |
| user != user_manager::UserManager::Get()->GetActiveUser()) { |
| return; |
| } |
| - |
| - device::UsbDeviceFilter printer_filter; |
| - printer_filter.interface_class = kPrinterInterfaceClass; |
| - if (!printer_filter.Matches(device)) |
| + device::UsbService* usb_service = |
| + device::DeviceClient::Get()->GetUsbService(); |
| + if (!usb_service) { |
| return; |
| + } |
| + observer_.Add(usb_service); |
| + usb_service->GetDevices(base::Bind(&CupsPrinterDetectorImpl::OnGetDevices, |
| + weak_ptr_factory_.GetWeakPtr())); |
| + } |
| - ShowUSBPrinterSetupNotification(device); |
| + // Callback for initial enumeration of usb devices. UsbService invokes |
| + // this on the UI thread. |
| + void OnGetDevices( |
| + const std::vector<scoped_refptr<device::UsbDevice>>& devices) { |
| + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| + for (const auto& device : devices) { |
| + if (!UsbDeviceIsPrinter(device)) { |
| + continue; |
| + } |
| + if (!known_printers_.insert(device->guid()).second) { |
|
skau
2017/03/11 00:00:34
Is this a common idiom? I didn't know that map::i
Carlson
2017/03/11 01:19:17
I would have guessed yes, but apparently not:
htt
|
| + // Already dealt with this, don't need to do so again. |
| + continue; |
| + } |
| + SetUpDevice(device, false); |
| + } |
| } |
| - // Initializes the printer detector. |
| - void Initialize() { |
| - device::UsbService* usb_service = |
| - device::DeviceClient::Get()->GetUsbService(); |
| - if (!usb_service) |
| + // UsbService::observer override. This runs on the UI thread. |
| + void OnDeviceAdded(scoped_refptr<device::UsbDevice> device) override { |
|
skau
2017/03/11 00:00:34
nit: DRY
Carlson
2017/03/11 01:19:16
Not sure which part you're referring to. You mean
skau
2017/03/13 21:56:25
Yes. I was referring to the code above. Sorry fo
Carlson
2017/03/15 00:31:15
Done.
|
| + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| + if (!UsbDeviceIsPrinter(device)) { |
| return; |
| - observer_.Add(usb_service); |
| + } |
| + if (!known_printers_.insert(device->guid()).second) { |
| + // Already dealt with this, don't need to do so again. |
| + return; |
| + } |
| + SetUpDevice(device, true); |
| } |
| - void SetNotificationUIManagerForTesting( |
| - NotificationUIManager* manager) override { |
| - LOG(FATAL) << "Not implemented for CUPS"; |
| + // UsbService::observer override. This runs on the UI thread. |
| + void OnDeviceRemoved(scoped_refptr<device::UsbDevice> device) override { |
| + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| + if (!UsbDeviceIsPrinter(device)) { |
| + return; |
| + } |
| + known_printers_.erase(device->guid()); |
| + |
| + // If this printer appears in the list of printers we've failed to set up, |
| + // remove it. |
| + failed_setup_printers_.erase(device->guid()); |
| } |
| - void ShowUSBPrinterSetupNotification( |
| - scoped_refptr<device::UsbDevice> device) { |
| - // TODO(justincarlson) - Test this notification across a wide variety of |
| - // less-than-sane printers to make sure the notification text stays as |
| - // intelligible as possible. |
| - base::string16 printer_name = device->manufacturer_string() + |
| - base::UTF8ToUTF16(" ") + |
| - device->product_string(); |
| - ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); |
| - message_center::RichNotificationData data; |
| - data.buttons.push_back(message_center::ButtonInfo(l10n_util::GetStringUTF16( |
| - IDS_PRINTER_DETECTED_NOTIFICATION_SET_UP_BUTTON))); |
| - notification_.reset(new Notification( |
| - message_center::NOTIFICATION_TYPE_SIMPLE, |
| - l10n_util::GetStringUTF16( |
| - IDS_PRINTER_DETECTED_NOTIFICATION_SET_UP_TITLE), // title |
| - printer_name, // body |
| - bundle.GetImageNamed(IDR_PRINTER_DETECTED_NOTIFICATION), // icon |
| - message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT, |
| - kUSBPrinterFoundNotificationID), |
| - base::string16(), // display_source |
| - GURL(), kUSBPrinterFoundNotificationID, data, |
| - new USBPrinterSetupNotificationDelegate(this))); |
| - notification_->SetSystemPriority(); |
| - command_ = ButtonCommand::SETUP; |
| - |
| - g_browser_process->notification_ui_manager()->Add(*notification_, profile_); |
| + // This is called on the UI thread. |
| + void SetUpDevice(scoped_refptr<device::UsbDevice> printer_device, |
| + bool hotplugged) { |
| + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| + auto data = base::MakeUnique<SetUpPrinterData>(); |
| + data->configurer = PrinterConfigurer::Create(profile_); |
| + data->printer = base::MakeUnique<Printer>(); |
| + data->device = printer_device; |
| + data->is_new = true; |
| + data->hotplugged = hotplugged; |
| + if (!UsbDeviceToPrinter(*printer_device, data->printer.get())) { |
| + LOG(ERROR) |
| + << "Failed to convert usb device to printer, abandoning setup. " |
| + << "Usb device details follow:"; |
| + LOG(ERROR) << UsbPrinterDeviceDetailsAsString(*printer_device); |
| + return; |
| + } |
| + auto existing_printers = |
| + PrintersManagerFactory::GetForBrowserContext(profile_)->GetPrinters(); |
|
skau
2017/03/11 00:00:34
TODO: add a GetPrinterByUri to PrintersManagerFact
Carlson
2017/03/11 01:19:16
Done.
|
| + for (std::unique_ptr<Printer>& printer : existing_printers) { |
| + if (printer->uri() == data->printer->uri()) { |
| + // Found a match, so use the existing configuration. |
| + *(data->printer) = *printer; |
| + data->is_new = false; |
| + |
| + // Copy fields that will be invalidated by std::move. |
| + auto* configurer_tmp = data->configurer.get(); |
| + const Printer& printer_tmp = *(data->printer); |
| + // off to the configurer. |
| + configurer_tmp->SetUpPrinter( |
| + printer_tmp, base::Bind(&CupsPrinterDetectorImpl::SetUpPrinterDone, |
| + weak_ptr_factory_.GetWeakPtr(), |
| + base::Passed(std::move(data)))); |
| + return; |
| + } |
| + } |
| + |
| + // It's not a device we know about. First we see if we can get an exact |
| + // driver match based on USB ids. |
| + // |
| + // TODO(justincarlson): Add a notification that we are attempting to set up |
| + // this printer at this point. |
| + auto ppd_provider = printing::CreateProvider(profile_); |
|
skau
2017/03/11 00:00:34
scoped_refptf<PpdProvider>. In this case, it's im
Carlson
2017/03/11 01:19:16
Done.
|
| + ppd_provider->ResolveUsbIds( |
| + printer_device->vendor_id(), printer_device->product_id(), |
| + base::Bind(&CupsPrinterDetectorImpl::ResolveUsbIdsDone, |
| + weak_ptr_factory_.GetWeakPtr(), ppd_provider, |
| + base::Passed(std::move(data)))); |
| } |
| - void OnSetUpUSBPrinterStarted() { |
| - notification_->set_title(l10n_util::GetStringUTF16( |
| - IDS_PRINTER_DETECTED_NOTIFICATION_SET_UP_IN_PROGRESS_TITLE)); |
| - notification_->set_type(message_center::NOTIFICATION_TYPE_PROGRESS); |
| - notification_->set_progress(-1); |
| - std::vector<message_center::ButtonInfo> buttons; |
| - buttons.push_back(message_center::ButtonInfo(l10n_util::GetStringUTF16( |
| - IDS_PRINTER_DETECTED_NOTIFICATION_SET_UP_CANCEL_BUTTON))); |
| - notification_->set_buttons(buttons); |
| - command_ = ButtonCommand::CANCEL_SETUP; |
| - g_browser_process->notification_ui_manager()->Add(*notification_, profile_); |
| + // Called when the query for a driver based on usb ids completes. |
| + void ResolveUsbIdsDone(scoped_refptr<PpdProvider> provider, |
| + std::unique_ptr<SetUpPrinterData> data, |
| + PpdProvider::CallbackResultCode result, |
| + const std::string& effective_make_and_model) { |
| + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| + if (result == PpdProvider::SUCCESS) { |
| + // Got something based on usb ids. Go with it. |
| + data->printer->mutable_ppd_reference()->effective_make_and_model = |
| + effective_make_and_model; |
| + } else { |
| + // Couldn't figure this printer out based on usb ids, fall back to |
| + // guessing the make/model string from what the USB system reports. |
| + // |
| + // TODO(justincarlson): Consider adding a mechanism for aggregating data |
| + // about which usb devices are in the wild but unsupported? |
| + // |
| + // TODO(justincarlson): Possibly go deeper and query the IEEE1284 fields |
| + // for make and model if we determine those are more likely to contain |
| + // what we want. |
| + data->printer->mutable_ppd_reference()->effective_make_and_model = |
| + base::UTF16ToUTF8(data->device->manufacturer_string()) + " " + |
| + base::UTF16ToUTF8(data->device->product_string()); |
|
skau
2017/03/11 00:00:34
This function should have a name.
Carlson
2017/03/11 01:19:16
A function had no name. (Done.)
|
| + } |
| + |
| + // Have to keep copies of some parameters that will be moved in the |
| + // arguments and used in the same statement. |
| + auto* configurer = data->configurer.get(); |
| + auto* printer = data->printer.get(); |
| + |
| + configurer->SetUpPrinter( |
| + *printer, base::Bind(&CupsPrinterDetectorImpl::SetUpPrinterDone, |
| + weak_ptr_factory_.GetWeakPtr(), |
| + base::Passed(std::move(data)))); |
| } |
| - void OnSetUpUSBPrinterDone() { |
| - notification_->set_title(l10n_util::GetStringUTF16( |
| - IDS_PRINTER_DETECTED_NOTIFICATION_SET_UP_SUCCESS_TITLE)); |
| - notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE); |
| - std::vector<message_center::ButtonInfo> buttons; |
| - buttons.push_back(message_center::ButtonInfo(l10n_util::GetStringUTF16( |
| - IDS_PRINTER_DETECTED_NOTIFICATION_SET_UP_CLOSE_BUTTON))); |
| - notification_->set_buttons(buttons); |
| - command_ = ButtonCommand::CLOSE; |
| - g_browser_process->notification_ui_manager()->Add(*notification_, profile_); |
| + // Called on the UI thread with the result of asking to have a printer |
| + // configured for cups. If |printer_to_register| is non-null and we |
|
skau
2017/03/11 00:00:34
nit: s/cups/CUPS/
|
| + // successfully configured, then the printer is registered with the printers |
| + // manager. |
| + void SetUpPrinterDone(std::unique_ptr<SetUpPrinterData> data, |
| + PrinterSetupResult result) { |
| + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| + if (result == PrinterSetupResult::SUCCESS) { |
| + if (data->is_new) { |
| + PrintersManagerFactory::GetForBrowserContext(profile_)->RegisterPrinter( |
| + std::move(data->printer)); |
| + } |
| + if (data->hotplugged) { |
| + // TODO(justincarlson): Pop a timed notification that says the printer |
| + // is now available for printing. |
| + } |
| + } else { |
| + // TODO(justincarlson): Pop a notification that tells the user automatic |
| + // setup failed and offers to open the CUPS printer configuration |
| + // settings. |
| + DCHECK(!base::ContainsKey(failed_setup_printers_, data->device->guid())); |
| + failed_setup_printers_.insert({data->device->guid(), *(data->printer)}); |
| + } |
| } |
| - void OnSetUpUSBPrinterError() { |
| - notification_->set_title(l10n_util::GetStringUTF16( |
| - IDS_PRINTER_DETECTED_NOTIFICATION_SET_UP_FAILED_TITLE)); |
| - notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE); |
| - std::vector<message_center::ButtonInfo> buttons; |
| - buttons.push_back(message_center::ButtonInfo(l10n_util::GetStringUTF16( |
| - IDS_PRINTER_DETECTED_NOTIFICATION_SET_UP_GET_HELP_BUTTON))); |
| - notification_->set_buttons(buttons); |
| - command_ = ButtonCommand::GET_HELP; |
| - g_browser_process->notification_ui_manager()->Add(*notification_, profile_); |
| + void SetNotificationUIManagerForTesting( |
| + NotificationUIManager* manager) override { |
| + LOG(FATAL) << "Not implemented for CUPS"; |
| } |
| - std::unique_ptr<Notification> notification_; |
| - ButtonCommand command_ = ButtonCommand::SETUP; |
| + // USB GUIDs of printers we've already dealt with. There's an inherent race |
| + // between initially querying all usb devices and receiving a notification |
| + // about a new device, so this set lets us guarantee that we handle a given |
| + // printer exactly once. |
| + std::set<std::string> known_printers_; |
| + |
| + // Printers that this class failed to set up automatically. This is provided |
| + // to the PrinterDiscoverer used in the settings flow to determine which |
| + // printers are available to be set up. This is a map from the USB GUID |
| + // to Printer structure. |
| + std::unordered_map<std::string, Printer> failed_setup_printers_; |
| + |
| + // The active ppd_provider, if any. References to ppd_provider_ are managed |
| + // on the UI thread. |
| + scoped_refptr<printing::PpdProvider> ppd_provider_; |
|
skau
2017/03/11 00:00:34
This member appears unused.
Carlson
2017/03/11 01:19:16
Thanks, was doing this differently before.
|
| Profile* profile_; |
| ScopedObserver<device::UsbService, device::UsbService::Observer> observer_; |
| base::WeakPtrFactory<CupsPrinterDetectorImpl> weak_ptr_factory_; |
| }; |
| -void USBPrinterSetupNotificationDelegate::ButtonClick(int button_index) { |
| - printer_detector_->ClickOnNotificationButton(button_index); |
| -} |
| - |
| } // namespace |
| // static |