Add a manifest property declaring USB printers supported by an app.

This new manifest property "usb_printers" declares a set of USB device
filters matching the printers supported by an app. This will be used by
the printerProvider API to allow the user to select a printer that can
be supported by the app and in so doing grant the app permission to
connect to the printer.

BUG=468955

Review URL: https://codereview.chromium.org/1123093003

Cr-Commit-Position: refs/heads/master@{#328845}
diff --git a/chrome/common/extensions/docs/templates/articles/manifest/usb_printers.html b/chrome/common/extensions/docs/templates/articles/manifest/usb_printers.html
new file mode 100644
index 0000000..c8b9635
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/manifest/usb_printers.html
@@ -0,0 +1,28 @@
+<h1>Manifest - USB Printers</h1>
+
+<p>
+The <code>usbPrinters</code> manifest property declares which USB printers are supported by an app using the $(ref:printerProvider) API.
+</p>
+
+<h2 id="manifest">Sample manifest.json</h2>
+<pre data-filename="manifest.json">
+{
+  "name": "My printer {{platform}}",
+  "usb_printers": {
+    "filters": [
+      // This app can print to the Nexus One and any printer made by Google.
+      { "vendorId": 6353, "productId": 19985 },
+      { "vendorId": 6353, "interfaceClass": 7 }
+    ]
+  },
+  ...
+}
+</pre>
+
+<section>
+<h2 id="reference">Reference</h2>
+<p class="api_reference">
+{{+partials.manifest_type
+  type:apis.apps.extensionsManifestTypes.byName.UsbPrinters/}}
+</p>
+</section>
diff --git a/chrome/common/extensions/docs/templates/json/manifest.json b/chrome/common/extensions/docs/templates/json/manifest.json
index 1df8724..1b66baf 100644
--- a/chrome/common/extensions/docs/templates/json/manifest.json
+++ b/chrome/common/extensions/docs/templates/json/manifest.json
@@ -214,6 +214,12 @@
     "documentation": "manifest/url_handlers",
     "example": {}
   },
+  "usb_printers": {
+    "documentation": "manifest/usb_printers",
+    "example": {
+      "filters": []
+    }
+  },
   "version": {
     "documentation": "manifest/version",
     "example": "versionString",
diff --git a/chrome/common/extensions/docs/templates/public/apps/manifest/usb_printers.html b/chrome/common/extensions/docs/templates/public/apps/manifest/usb_printers.html
new file mode 100644
index 0000000..c2bf377
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/public/apps/manifest/usb_printers.html
@@ -0,0 +1 @@
+{{+partials.standard_apps_article article:articles.manifest/usb_printers/}}
diff --git a/device/usb/usb_device_filter.cc b/device/usb/usb_device_filter.cc
index 970bbd0..b52a4a3 100644
--- a/device/usb/usb_device_filter.cc
+++ b/device/usb/usb_device_filter.cc
@@ -96,7 +96,7 @@
   return true;
 }
 
-base::Value* UsbDeviceFilter::ToValue() const {
+scoped_ptr<base::Value> UsbDeviceFilter::ToValue() const {
   scoped_ptr<base::DictionaryValue> obj(new base::DictionaryValue());
 
   if (vendor_id_set_) {
@@ -116,7 +116,7 @@
     }
   }
 
-  return obj.release();
+  return obj.Pass();
 }
 
 // static
diff --git a/device/usb/usb_device_filter.h b/device/usb/usb_device_filter.h
index 6525f8f..a40c85968 100644
--- a/device/usb/usb_device_filter.h
+++ b/device/usb/usb_device_filter.h
@@ -8,6 +8,7 @@
 #include <vector>
 
 #include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
 
 namespace base {
 class Value;
@@ -29,7 +30,7 @@
   void SetInterfaceProtocol(uint8 interface_protocol);
 
   bool Matches(scoped_refptr<UsbDevice> device) const;
-  base::Value* ToValue() const;
+  scoped_ptr<base::Value> ToValue() const;
 
   static bool MatchesAny(scoped_refptr<UsbDevice> device,
                          const std::vector<UsbDeviceFilter>& filters);
diff --git a/extensions/common/api/_manifest_features.json b/extensions/common/api/_manifest_features.json
index 68a02d8..b9950f04 100644
--- a/extensions/common/api/_manifest_features.json
+++ b/extensions/common/api/_manifest_features.json
@@ -269,6 +269,10 @@
     "channel": "stable",
     "extension_types": ["platform_app"]
   },
+  "usb_printers": {
+    "channel": "dev",
+    "extension_types": ["platform_app"]
+  },
   "version": {
     "channel": "stable",
     "extension_types": "all"
diff --git a/extensions/common/api/extensions_manifest_types.json b/extensions/common/api/extensions_manifest_types.json
index cbf735c..d698273 100644
--- a/extensions/common/api/extensions_manifest_types.json
+++ b/extensions/common/api/extensions_manifest_types.json
@@ -167,6 +167,46 @@
             "optional": true
           }
         }
+      },
+      {
+        "id": "UsbPrinters",
+        "type": "object",
+        "description": "The <code>usb_printers</code> manifest property lists the USB printers supported by an app implementing the $(ref:printerProvider) API.",
+        "properties": {
+          "filters": {
+            "description": "A list of $(ref:usb.DeviceFilter USB device filters) matching supported devices. A device only needs to match one of the provided filters. A <code>vendorId</code> is required and only one of <code>productId</code> or <code>interfaceClass</code> may be provided.",
+            "type": "array",
+            "items": {
+              "type": "object",
+              "properties": {
+                "vendorId": {
+                  "description": "USB vendor ID of matching devices",
+                  "type": "integer"
+                },
+                "productId": {
+                  "description": "USB product ID of matching devices",
+                  "type": "integer",
+                  "optional": true
+                },
+                "interfaceClass": {
+                  "description": "USB interface class implemented by any interface of a matching device.",
+                  "type": "integer",
+                  "optional": true
+                },
+                "interfaceSubclass": {
+                  "description": "USB interface sub-class implemented by the interface matching $(ref:interfaceClass).",
+                  "type": "integer",
+                  "optional": true
+                },
+                "interfaceProtocol": {
+                  "description": "USB interface protocol implemented by the interface matching $(ref:interfaceClass) and $(ref:interfaceSubclass).",
+                  "type": "integer",
+                  "optional": true
+                }
+              }
+            }
+          }
+        }
       }
     ]
   }
diff --git a/extensions/common/api/printer_provider/usb_printer_manifest_data.cc b/extensions/common/api/printer_provider/usb_printer_manifest_data.cc
new file mode 100644
index 0000000..61dc9e83
--- /dev/null
+++ b/extensions/common/api/printer_provider/usb_printer_manifest_data.cc
@@ -0,0 +1,64 @@
+// 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 "extensions/common/api/printer_provider/usb_printer_manifest_data.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "device/usb/usb_device_filter.h"
+#include "extensions/common/api/extensions_manifest_types.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+UsbPrinterManifestData::UsbPrinterManifestData() {
+}
+
+UsbPrinterManifestData::~UsbPrinterManifestData() {
+}
+
+// static
+const UsbPrinterManifestData* UsbPrinterManifestData::Get(
+    const Extension* extension) {
+  return static_cast<UsbPrinterManifestData*>(
+      extension->GetManifestData(manifest_keys::kUsbPrinters));
+}
+
+// static
+scoped_ptr<UsbPrinterManifestData> UsbPrinterManifestData::FromValue(
+    const base::Value& value,
+    base::string16* error) {
+  scoped_ptr<core_api::extensions_manifest_types::UsbPrinters> usb_printers =
+      core_api::extensions_manifest_types::UsbPrinters::FromValue(value, error);
+  if (!usb_printers) {
+    return nullptr;
+  }
+
+  scoped_ptr<UsbPrinterManifestData> result(new UsbPrinterManifestData());
+  for (const auto& input : usb_printers->filters) {
+    DCHECK(input.get());
+    device::UsbDeviceFilter output;
+    output.SetVendorId(input->vendor_id);
+    if (input->product_id && input->interface_class) {
+      *error = base::ASCIIToUTF16(
+          "Only one of productId or interfaceClass may be specified.");
+      return nullptr;
+    }
+    if (input->product_id) {
+      output.SetProductId(*input->product_id);
+    }
+    if (input->interface_class) {
+      output.SetInterfaceClass(*input->interface_class);
+      if (input->interface_subclass) {
+        output.SetInterfaceSubclass(*input->interface_subclass);
+        if (input->interface_protocol) {
+          output.SetInterfaceProtocol(*input->interface_protocol);
+        }
+      }
+    }
+    result->filters_.push_back(output);
+  }
+  return result.Pass();
+}
+
+}  // namespace extensions
diff --git a/extensions/common/api/printer_provider/usb_printer_manifest_data.h b/extensions/common/api/printer_provider/usb_printer_manifest_data.h
new file mode 100644
index 0000000..fae6053
--- /dev/null
+++ b/extensions/common/api/printer_provider/usb_printer_manifest_data.h
@@ -0,0 +1,43 @@
+// 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.
+
+#ifndef EXTENSIONS_COMMON_API_PRINTER_PROVIDER_USB_PRINTER_MANIFEST_DATA_H_
+#define EXTENSIONS_COMMON_API_PRINTER_PROVIDER_USB_PRINTER_MANIFEST_DATA_H_
+
+#include <vector>
+
+#include "extensions/common/extension.h"
+
+namespace device {
+class UsbDeviceFilter;
+}
+
+namespace extensions {
+
+// The parsed form of the "usb_printers" manifest entry.
+class UsbPrinterManifestData : public Extension::ManifestData {
+ public:
+  UsbPrinterManifestData();
+  ~UsbPrinterManifestData() override;
+
+  // Gets the UsbPrinterManifestData for |extension|, or NULL if none was
+  // specified.
+  static const UsbPrinterManifestData* Get(const Extension* extension);
+
+  // Parses the data stored in |value|. Sets |error| and returns an empty
+  // scoped_ptr on failure.
+  static scoped_ptr<UsbPrinterManifestData> FromValue(const base::Value& value,
+                                                      base::string16* error);
+
+  const std::vector<device::UsbDeviceFilter>& filters() const {
+    return filters_;
+  }
+
+ private:
+  std::vector<device::UsbDeviceFilter> filters_;
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_COMMON_API_PRINTER_PROVIDER_USB_PRINTER_MANIFEST_DATA_H_
diff --git a/extensions/common/api/printer_provider/usb_printer_manifest_handler.cc b/extensions/common/api/printer_provider/usb_printer_manifest_handler.cc
new file mode 100644
index 0000000..e356050b
--- /dev/null
+++ b/extensions/common/api/printer_provider/usb_printer_manifest_handler.cc
@@ -0,0 +1,37 @@
+// 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 "extensions/common/api/printer_provider/usb_printer_manifest_handler.h"
+
+#include "extensions/common/api/printer_provider/usb_printer_manifest_data.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/manifest_constants.h"
+
+namespace extensions {
+
+UsbPrinterManifestHandler::UsbPrinterManifestHandler() {
+}
+
+UsbPrinterManifestHandler::~UsbPrinterManifestHandler() {
+}
+
+bool UsbPrinterManifestHandler::Parse(Extension* extension,
+                                      base::string16* error) {
+  const base::Value* usb_printers = nullptr;
+  CHECK(extension->manifest()->Get(manifest_keys::kUsbPrinters, &usb_printers));
+  scoped_ptr<UsbPrinterManifestData> data =
+      UsbPrinterManifestData::FromValue(*usb_printers, error);
+  if (!data) {
+    return false;
+  }
+
+  extension->SetManifestData(manifest_keys::kUsbPrinters, data.release());
+  return true;
+}
+
+const std::vector<std::string> UsbPrinterManifestHandler::Keys() const {
+  return SingleKey(manifest_keys::kUsbPrinters);
+}
+
+}  // namespace extensions
diff --git a/extensions/common/api/printer_provider/usb_printer_manifest_handler.h b/extensions/common/api/printer_provider/usb_printer_manifest_handler.h
new file mode 100644
index 0000000..beccce2
--- /dev/null
+++ b/extensions/common/api/printer_provider/usb_printer_manifest_handler.h
@@ -0,0 +1,26 @@
+// 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.
+
+#ifndef EXTENSIONS_COMMON_API_PRINTER_PROVIDER_USB_PRINTER_MANIFEST_HANDLER_H_
+#define EXTENSIONS_COMMON_API_PRINTER_PROVIDER_USB_PRINTER_MANIFEST_HANDLER_H_
+
+#include "extensions/common/manifest_handler.h"
+
+namespace extensions {
+
+// Parses the "usb_printers" manifest key.
+class UsbPrinterManifestHandler : public ManifestHandler {
+ public:
+  UsbPrinterManifestHandler();
+  ~UsbPrinterManifestHandler() override;
+
+ private:
+  // ManifestHandler overrides.
+  bool Parse(Extension* extension, base::string16* error) override;
+  const std::vector<std::string> Keys() const override;
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_COMMON_API_PRINTER_PROVIDER_USB_PRINTER_MANIFEST_HANDLER_H_
diff --git a/extensions/common/api/printer_provider/usb_printer_manifest_unittest.cc b/extensions/common/api/printer_provider/usb_printer_manifest_unittest.cc
new file mode 100644
index 0000000..cd35af1
--- /dev/null
+++ b/extensions/common/api/printer_provider/usb_printer_manifest_unittest.cc
@@ -0,0 +1,45 @@
+// 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 "device/usb/usb_device_filter.h"
+#include "extensions/common/api/printer_provider/usb_printer_manifest_data.h"
+#include "extensions/common/manifest_test.h"
+#include "extensions/common/value_builder.h"
+
+namespace extensions {
+
+class UsbPrinterManifestTest : public ManifestTest {
+ public:
+  UsbPrinterManifestTest() {}
+  ~UsbPrinterManifestTest() override {}
+};
+
+TEST_F(UsbPrinterManifestTest, Filters) {
+  scoped_refptr<Extension> extension =
+      LoadAndExpectSuccess("usb_printers_filters.json");
+  const UsbPrinterManifestData* manifest_data =
+      UsbPrinterManifestData::Get(extension.get());
+  ASSERT_TRUE(manifest_data);
+  EXPECT_EQ(2u, manifest_data->filters().size());
+  EXPECT_TRUE(DictionaryBuilder()
+                  .Set("vendorId", 1)
+                  .Set("productId", 2)
+                  .Build()
+                  ->Equals(manifest_data->filters()[0].ToValue().get()));
+  EXPECT_TRUE(DictionaryBuilder()
+                  .Set("vendorId", 1)
+                  .Set("interfaceClass", 2)
+                  .Set("interfaceSubclass", 3)
+                  .Set("interfaceProtocol", 4)
+                  .Build()
+                  ->Equals(manifest_data->filters()[1].ToValue().get()));
+}
+
+TEST_F(UsbPrinterManifestTest, InvalidFilter) {
+  LoadAndExpectError(
+      "usb_printers_invalid_filter.json",
+      "Only one of productId or interfaceClass may be specified.");
+}
+
+}  // namespace extensions
diff --git a/extensions/common/common_manifest_handlers.cc b/extensions/common/common_manifest_handlers.cc
index db137ffc..3331a0d5 100644
--- a/extensions/common/common_manifest_handlers.cc
+++ b/extensions/common/common_manifest_handlers.cc
@@ -5,6 +5,7 @@
 #include "extensions/common/common_manifest_handlers.h"
 
 #include "extensions/common/api/bluetooth/bluetooth_manifest_handler.h"
+#include "extensions/common/api/printer_provider/usb_printer_manifest_handler.h"
 #include "extensions/common/api/sockets/sockets_manifest_handler.h"
 #include "extensions/common/manifest_handler.h"
 #include "extensions/common/manifest_handlers/background_info.h"
@@ -51,6 +52,7 @@
   (new SandboxedPageHandler)->Register();
   (new SharedModuleHandler)->Register();
   (new SocketsManifestHandler)->Register();
+  (new UsbPrinterManifestHandler)->Register();
   (new WebAccessibleResourcesHandler)->Register();
   (new WebviewHandler)->Register();
 }
diff --git a/extensions/common/manifest_constants.cc b/extensions/common/manifest_constants.cc
index 528307b..534cc5b 100644
--- a/extensions/common/manifest_constants.cc
+++ b/extensions/common/manifest_constants.cc
@@ -174,6 +174,7 @@
 const char kUpdateURL[] = "update_url";
 const char kUrlHandlers[] = "url_handlers";
 const char kUrlHandlerTitle[] = "title";
+const char kUsbPrinters[] = "usb_printers";
 const char kVersion[] = "version";
 const char kVersionName[] = "version_name";
 const char kWebAccessibleResources[] = "web_accessible_resources";
diff --git a/extensions/common/manifest_constants.h b/extensions/common/manifest_constants.h
index fa587015..bed5b91a 100644
--- a/extensions/common/manifest_constants.h
+++ b/extensions/common/manifest_constants.h
@@ -175,6 +175,7 @@
 extern const char kUpdateURL[];
 extern const char kUrlHandlers[];
 extern const char kUrlHandlerTitle[];
+extern const char kUsbPrinters[];
 extern const char kVersion[];
 extern const char kVersionName[];
 extern const char kWebAccessibleResources[];
diff --git a/extensions/extensions.gypi b/extensions/extensions.gypi
index 106fc44..3ec8a4d 100644
--- a/extensions/extensions.gypi
+++ b/extensions/extensions.gypi
@@ -20,6 +20,10 @@
       'common/api/bluetooth/bluetooth_manifest_permission.cc',
       'common/api/bluetooth/bluetooth_manifest_permission.h',
       'common/api/messaging/message.h',
+      'common/api/printer_provider/usb_printer_manifest_data.cc',
+      'common/api/printer_provider/usb_printer_manifest_data.h',
+      'common/api/printer_provider/usb_printer_manifest_handler.cc',
+      'common/api/printer_provider/usb_printer_manifest_handler.h',
       'common/api/sockets/sockets_manifest_data.cc',
       'common/api/sockets/sockets_manifest_data.h',
       'common/api/sockets/sockets_manifest_handler.cc',
diff --git a/extensions/extensions_tests.gypi b/extensions/extensions_tests.gypi
index deb11cc..9220e91 100644
--- a/extensions/extensions_tests.gypi
+++ b/extensions/extensions_tests.gypi
@@ -101,6 +101,7 @@
       'browser/value_store/value_store_unittest.h',
       'browser/verified_contents_unittest.cc',
       'browser/warning_service_unittest.cc',
+      'common/api/printer_provider/usb_printer_manifest_unittest.cc',
       'common/api/sockets/sockets_manifest_permission_unittest.cc',
       'common/csp_validator_unittest.cc',
       'common/event_filter_unittest.cc',
diff --git a/extensions/test/data/manifest_tests/usb_printers_filters.json b/extensions/test/data/manifest_tests/usb_printers_filters.json
new file mode 100644
index 0000000..673c684
--- /dev/null
+++ b/extensions/test/data/manifest_tests/usb_printers_filters.json
@@ -0,0 +1,21 @@
+{
+  "name": "extensions_unittests --gtest_filter=UsbPrinterManifestTest.InvalidFilter",
+  "version": "1",
+  "manifest_version": 2,
+  "app": {
+    "background": {
+      "scripts": ["background.js"]
+    }
+  },
+  "usb_printers": {
+    "filters": [
+      { "vendorId": 1, "productId": 2 },
+      {
+        "vendorId": 1,
+        "interfaceClass": 2,
+        "interfaceSubclass": 3,
+        "interfaceProtocol": 4
+      }
+    ]
+  }
+}
diff --git a/extensions/test/data/manifest_tests/usb_printers_invalid_filter.json b/extensions/test/data/manifest_tests/usb_printers_invalid_filter.json
new file mode 100644
index 0000000..e117779
--- /dev/null
+++ b/extensions/test/data/manifest_tests/usb_printers_invalid_filter.json
@@ -0,0 +1,16 @@
+{
+  "name": "extensions_unittests --gtest_filter=UsbPrinterManifestTest.InvalidFilter",
+  "version": "1",
+  "manifest_version": 2,
+  "app": {
+    "background": {
+      "scripts": ["background.js"]
+    }
+  },
+  "usb_printers": {
+    "filters": [
+      // Cannot specify both productId and interfaceClass.
+      { "vendorId": 0, "productId": 0, "interfaceClass": 0 }
+    ]
+  }
+}