Index: chrome/browser/devtools/device/usb/android_usb_discovery_test.cc |
diff --git a/chrome/browser/devtools/device/usb/android_usb_discovery_test.cc b/chrome/browser/devtools/device/usb/android_usb_discovery_test.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..0c8f07b655e861c761fb182db5fe80fd336311c7 |
--- /dev/null |
+++ b/chrome/browser/devtools/device/usb/android_usb_discovery_test.cc |
@@ -0,0 +1,558 @@ |
+// Copyright (c) 2014 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 <algorithm> |
+ |
+#include "base/strings/utf_string_conversions.h" |
+#include "chrome/browser/devtools/device/devtools_android_bridge.h" |
+#include "chrome/browser/devtools/device/usb/android_usb_device.h" |
+#include "chrome/browser/devtools/device/usb/usb_device_provider.h" |
+#include "chrome/browser/ui/browser.h" |
+#include "chrome/test/base/in_process_browser_test.h" |
+#include "components/usb_service/usb_device.h" |
+#include "components/usb_service/usb_device_handle.h" |
+#include "components/usb_service/usb_interface.h" |
+#include "components/usb_service/usb_service.h" |
+#include "content/public/browser/browser_thread.h" |
+#include "content/public/test/test_utils.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+using content::BrowserThread; |
+using namespace usb_service; |
+namespace { |
+const int kClass = 0xff; |
+const int kSubclass = 0x42; |
+const int kProtocol = 0x1; |
+ |
+const uint32 kMaxPayload = 4096; |
+const uint32 kVersion = 0x01000000; |
+ |
+const char kOpenedUnixSocketsCommand[] = "shell:cat /proc/net/unix"; |
+const char kDeviceModelCommand[] = "shell:getprop ro.product.model"; |
+const char kDumpsysCommand[] = "shell:dumpsys window policy"; |
+const char kListProcessesCommand[] = "shell:ps"; |
+const char kInstalledChromePackagesCommand[] = "shell:pm list packages"; |
+const char kDeviceModel[] = "Nexus 5"; |
+const char kDeviceSerial[] = "Sample serial"; |
+ |
+const char kSampleOpenedUnixSockets[] = |
+ "Num RefCount Protocol Flags Type St Inode Path\n" |
+ "00000000: 00000004 00000000" |
+ " 00000000 0002 01 3328 /dev/socket/wpa_wlan0\n" |
+ "00000000: 00000002 00000000" |
+ " 00010000 0001 01 5394 /dev/socket/vold\n"; |
+ |
+const char kSampleListProcesses[] = |
+ "USER PID PPID VSIZE RSS WCHAN PC NAME\n" |
+ "root 1 0 688 508 ffffffff 00000000 S /init\r\n" |
+ "u0_a75 2425 123 933736 193024 ffffffff 00000000 S com.sample.feed\r\n" |
+ "nfc 741 123 706448 26316 ffffffff 00000000 S com.android.nfc\r\n" |
+ "u0_a76 1001 124 111111 222222 ffffffff 00000000 S com.android.chrome\r\n" |
+ "u0_a78 1003 126 111111 222222 ffffffff 00000000 S com.noprocess.app\r\n"; |
+ |
+const char kSampleListPackages[] = |
+ "package:com.sample.feed\r\n" |
+ "package:com.android.nfc\r\n" |
+ "package:com.android.chrome\r\n" |
+ "package:com.chrome.beta\r\n" |
+ "package:com.google.android.apps.chrome\r\n"; |
+ |
+const char kSampleDumpsys[] = |
+ "WINDOW MANAGER POLICY STATE (dumpsys window policy)\r\n" |
+ " mSafeMode=false mSystemReady=true mSystemBooted=true\r\n" |
+ " mStable=(0,50)-(720,1184)\r\n" // Only mStable parameter is parsed |
+ " mForceStatusBar=false mForceStatusBarFromKeyguard=false\r\n"; |
+ |
+const char* GetMockShellResponse(std::string command) { |
+ if (command == kDeviceModelCommand) { |
+ return kDeviceModel; |
+ } else if (command == kOpenedUnixSocketsCommand) { |
+ return kSampleOpenedUnixSockets; |
+ } else if (command == kDumpsysCommand) { |
+ return kSampleDumpsys; |
+ } else if (command == kListProcessesCommand) { |
+ return kSampleListProcesses; |
+ } else if (command == kInstalledChromePackagesCommand) { |
+ return kSampleListPackages; |
+ } |
+ |
+ DCHECK(false) << "Should not be reached"; |
+ |
+ return ""; |
+} |
+ |
+class MockUsbEndpointDescriptor : public UsbEndpointDescriptor { |
+ public: |
+ virtual int GetAddress() const OVERRIDE { return address_; } |
+ |
+ virtual UsbEndpointDirection GetDirection() const OVERRIDE { |
+ return direction_; |
+ } |
+ |
+ virtual int GetMaximumPacketSize() const OVERRIDE { |
+ return maximum_packet_size_; |
+ } |
+ |
+ virtual UsbSynchronizationType GetSynchronizationType() const OVERRIDE { |
+ return usb_synchronization_type_; |
+ } |
+ |
+ virtual UsbTransferType GetTransferType() const OVERRIDE { |
+ return usb_transfer_type_; |
+ } |
+ virtual UsbUsageType GetUsageType() const OVERRIDE { return usb_usage_type_; } |
+ |
+ virtual int GetPollingInterval() const OVERRIDE { return polling_interval_; } |
+ |
+ int address_; |
+ UsbEndpointDirection direction_; |
+ int maximum_packet_size_; |
+ UsbSynchronizationType usb_synchronization_type_; |
+ UsbTransferType usb_transfer_type_; |
+ UsbUsageType usb_usage_type_; |
+ int polling_interval_; |
+ |
+ private: |
+ virtual ~MockUsbEndpointDescriptor() {} |
+}; |
+ |
+class MockUsbInterfaceAltSettingDescriptor |
+ : public UsbInterfaceAltSettingDescriptor { |
+ public: |
+ MockUsbInterfaceAltSettingDescriptor(int interface_number, |
+ int alternate_setting) |
+ : interface_number_(interface_number), |
+ alternate_setting_(alternate_setting) {} |
+ |
+ virtual size_t GetNumEndpoints() const OVERRIDE { |
+ // See IsAndroidInterface function in android_usb_device.cc |
+ return 2; |
+ } |
+ |
+ virtual scoped_refptr<const UsbEndpointDescriptor> GetEndpoint( |
+ size_t index) const OVERRIDE { |
+ EXPECT_GT(static_cast<size_t>(2), index); |
+ MockUsbEndpointDescriptor* result = new MockUsbEndpointDescriptor(); |
+ result->address_ = index + 1; |
+ result->usb_transfer_type_ = USB_TRANSFER_BULK; |
+ result->direction_ = |
+ ((index == 0) ? USB_DIRECTION_INBOUND : USB_DIRECTION_OUTBOUND); |
+ result->maximum_packet_size_ = 1 << 20; // 1Mb maximum packet size |
+ return result; |
+ } |
+ |
+ virtual int GetInterfaceNumber() const OVERRIDE { return interface_number_; } |
+ |
+ virtual int GetAlternateSetting() const OVERRIDE { |
+ return alternate_setting_; |
+ } |
+ |
+ virtual int GetInterfaceClass() const OVERRIDE { return kClass; } |
+ |
+ virtual int GetInterfaceSubclass() const OVERRIDE { return kSubclass; } |
+ |
+ virtual int GetInterfaceProtocol() const OVERRIDE { return kProtocol; } |
+ |
+ protected: |
+ virtual ~MockUsbInterfaceAltSettingDescriptor() {}; |
+ |
+ private: |
+ const int interface_number_; |
+ const int alternate_setting_; |
+}; |
+ |
+class MockUsbInterfaceDescriptor : public UsbInterfaceDescriptor { |
+ public: |
+ explicit MockUsbInterfaceDescriptor(int interface_number) |
+ : interface_number_(interface_number) {} |
+ |
+ virtual size_t GetNumAltSettings() const OVERRIDE { |
+ // See IsAndroidInterface function in android_usb_device.cc |
+ return 1; |
+ } |
+ virtual scoped_refptr<const UsbInterfaceAltSettingDescriptor> GetAltSetting( |
+ size_t index) const OVERRIDE { |
+ EXPECT_EQ(static_cast<size_t>(0), index); |
+ return new MockUsbInterfaceAltSettingDescriptor(interface_number_, 0); |
+ } |
+ |
+ protected: |
+ const int interface_number_; |
+ virtual ~MockUsbInterfaceDescriptor() {} |
+}; |
+ |
+class MockUsbConfigDescriptor : public UsbConfigDescriptor { |
+ public: |
+ MockUsbConfigDescriptor() {} |
+ |
+ virtual size_t GetNumInterfaces() const OVERRIDE { return 1; } |
+ |
+ virtual scoped_refptr<const UsbInterfaceDescriptor> GetInterface( |
+ size_t index) const OVERRIDE { |
+ EXPECT_EQ(static_cast<size_t>(0), index); |
+ return new MockUsbInterfaceDescriptor(index); |
+ } |
+ |
+ protected: |
+ virtual ~MockUsbConfigDescriptor() {}; |
+}; |
+ |
+class MockUsbDevice; |
+ |
+class MockUsbDeviceHandle : public UsbDeviceHandle { |
+ public: |
+ explicit MockUsbDeviceHandle(MockUsbDevice* device) |
+ : device_(device), remaining_body_length_(0) {} |
+ |
+ virtual scoped_refptr<UsbDevice> GetDevice() const OVERRIDE { |
+ return device_; |
+ } |
+ |
+ virtual void Close() OVERRIDE { device_ = NULL; } |
+ |
+ virtual bool ClaimInterface(const int interface_number) OVERRIDE; |
+ virtual bool ReleaseInterface(const int interface_number) OVERRIDE; |
+ virtual bool SetInterfaceAlternateSetting( |
+ const int interface_number, |
+ const int alternate_setting) OVERRIDE { |
+ return true; |
+ } |
+ |
+ virtual bool ResetDevice() OVERRIDE { return true; } |
+ |
+ virtual bool GetSerial(base::string16* serial) OVERRIDE { |
+ *serial = base::UTF8ToUTF16(kDeviceSerial); |
+ return true; |
+ } |
+ |
+ // Async IO. Can be called on any thread. |
+ virtual void ControlTransfer(const UsbEndpointDirection direction, |
+ const TransferRequestType request_type, |
+ const TransferRecipient recipient, |
+ const uint8 request, |
+ const uint16 value, |
+ const uint16 index, |
+ net::IOBuffer* buffer, |
+ const size_t length, |
+ const unsigned int timeout, |
+ const UsbTransferCallback& callback) OVERRIDE {} |
+ |
+ virtual void BulkTransfer(const UsbEndpointDirection direction, |
+ const uint8 endpoint, |
+ net::IOBuffer* buffer, |
+ const size_t length, |
+ const unsigned int timeout, |
+ const UsbTransferCallback& callback) OVERRIDE { |
+ if (direction == USB_DIRECTION_OUTBOUND) { |
+ if (remaining_body_length_ == 0) { |
+ std::vector<uint32> header(6); |
+ memcpy(&header[0], buffer->data(), length); |
+ current_message_ = new AdbMessage(header[0], header[1], header[2], ""); |
+ remaining_body_length_ = header[3]; |
+ uint32 magic = header[5]; |
+ if ((current_message_->command ^ 0xffffffff) != magic) { |
+ DCHECK(false) << "Header checksum error"; |
+ return; |
+ } |
+ } else { |
+ DCHECK(current_message_); |
+ current_message_->body += std::string(buffer->data(), length); |
+ remaining_body_length_ -= length; |
+ } |
+ |
+ if (remaining_body_length_ == 0) { |
+ ProcessIncoming(); |
+ } |
+ |
+ base::MessageLoop::current()->PostTask( |
+ FROM_HERE, |
+ base::Bind(callback, |
+ usb_service::USB_TRANSFER_COMPLETED, |
+ scoped_refptr<net::IOBuffer>(), |
+ 0)); |
+ |
+ } else if (direction == USB_DIRECTION_INBOUND) { |
+ queries_.push(Query(callback, make_scoped_refptr(buffer), length)); |
+ ProcessQueries(); |
+ } |
+ } |
+ |
+ template <class T> |
+ void append(T data) { |
+ std::copy(reinterpret_cast<char*>(&data), |
+ (reinterpret_cast<char*>(&data)) + sizeof(T), |
+ std::back_inserter(output_buffer_)); |
+ } |
+ |
+ // Copied from AndroidUsbDevice::Checksum |
+ uint32 Checksum(const std::string& data) { |
+ unsigned char* x = (unsigned char*)data.data(); |
+ int count = data.length(); |
+ uint32 sum = 0; |
+ while (count-- > 0) |
+ sum += *x++; |
+ return sum; |
+ } |
+ |
+ void ProcessIncoming() { |
+ DCHECK(current_message_); |
+ switch (current_message_->command) { |
+ case AdbMessage::kCommandCNXN: |
+ WriteResponse(new AdbMessage(AdbMessage::kCommandCNXN, |
+ kVersion, |
+ kMaxPayload, |
+ "device::ro.product.name=SampleProduct;ro." |
+ "product.model=SampleModel;ro.product." |
+ "device=SampleDevice;")); |
+ break; |
+ case AdbMessage::kCommandOPEN: |
+ DCHECK(current_message_->arg1 == 0); |
+ DCHECK(current_message_->arg0 != 0); |
+ if (current_message_->body.find("shell:") != std::string::npos) { |
+ WriteResponse(new AdbMessage(AdbMessage::kCommandOKAY, |
+ ++next_local_socket_, |
+ current_message_->arg0, |
+ "")); |
+ WriteResponse( |
+ new AdbMessage(AdbMessage::kCommandWRTE, |
+ next_local_socket_, |
+ current_message_->arg0, |
+ GetMockShellResponse(current_message_->body.substr( |
+ 0, current_message_->body.size() - 1)))); |
+ WriteResponse(new AdbMessage( |
+ AdbMessage::kCommandCLSE, 0, current_message_->arg0, "")); |
+ } |
+ default: |
+ return; |
+ } |
+ ProcessQueries(); |
+ } |
+ |
+ void WriteResponse(scoped_refptr<AdbMessage> response) { |
+ append(response->command); |
+ append(response->arg0); |
+ append(response->arg1); |
+ bool add_zero = response->body.length() && |
+ (response->command != AdbMessage::kCommandWRTE); |
+ append(static_cast<uint32>(response->body.length() + (add_zero ? 1 : 0))); |
+ append(Checksum(response->body)); |
+ append(response->command ^ 0xffffffff); |
+ std::copy(response->body.begin(), |
+ response->body.end(), |
+ std::back_inserter(output_buffer_)); |
+ if (add_zero) { |
+ output_buffer_.push_back(0); |
+ } |
+ ProcessQueries(); |
+ } |
+ |
+ void ProcessQueries() { |
+ if (!queries_.size()) |
+ return; |
+ Query query = queries_.front(); |
+ |
+ if (query.size > output_buffer_.size()) |
+ return; |
+ |
+ queries_.pop(); |
+ std::copy(output_buffer_.begin(), |
+ output_buffer_.begin() + query.size, |
+ query.buffer->data()); |
+ output_buffer_.erase(output_buffer_.begin(), |
+ output_buffer_.begin() + query.size); |
+ base::MessageLoop::current()->PostTask( |
+ FROM_HERE, |
+ base::Bind(query.callback, |
+ usb_service::USB_TRANSFER_COMPLETED, |
+ query.buffer, |
+ query.size)); |
+ } |
+ |
+ virtual void InterruptTransfer(const UsbEndpointDirection direction, |
+ const uint8 endpoint, |
+ net::IOBuffer* buffer, |
+ const size_t length, |
+ const unsigned int timeout, |
+ const UsbTransferCallback& callback) OVERRIDE { |
+ } |
+ |
+ virtual void IsochronousTransfer( |
+ const UsbEndpointDirection direction, |
+ const uint8 endpoint, |
+ net::IOBuffer* buffer, |
+ const size_t length, |
+ const unsigned int packets, |
+ const unsigned int packet_length, |
+ const unsigned int timeout, |
+ const UsbTransferCallback& callback) OVERRIDE {} |
+ |
+ protected: |
+ virtual ~MockUsbDeviceHandle() {} |
+ |
+ struct Query { |
+ UsbTransferCallback callback; |
+ scoped_refptr<net::IOBuffer> buffer; |
+ size_t size; |
+ |
+ Query(UsbTransferCallback callback, |
+ scoped_refptr<net::IOBuffer> buffer, |
+ int size) |
+ : callback(callback), buffer(buffer), size(size) {}; |
+ }; |
+ |
+ scoped_refptr<MockUsbDevice> device_; |
+ uint32 remaining_body_length_; |
+ scoped_refptr<AdbMessage> current_message_; |
+ std::vector<char> output_buffer_; |
+ std::queue<Query> queries_; |
+ int next_local_socket_ = 1; |
+}; |
+ |
+class MockUsbDevice : public UsbDevice { |
+ public: |
+ MockUsbDevice() : UsbDevice(0, 0, 0) {} |
+ |
+ virtual scoped_refptr<UsbDeviceHandle> Open() OVERRIDE { |
+ return new MockUsbDeviceHandle(this); |
+ } |
+ |
+ virtual scoped_refptr<UsbConfigDescriptor> ListInterfaces() OVERRIDE { |
+ return new MockUsbConfigDescriptor(); |
+ } |
+ |
+ virtual bool Close(scoped_refptr<UsbDeviceHandle> handle) OVERRIDE { |
+ return true; |
+ } |
+ |
+#if defined(OS_CHROMEOS) |
+ // On ChromeOS, if an interface of a claimed device is not claimed, the |
+ // permission broker can change the owner of the device so that the unclaimed |
+ // interfaces can be used. If this argument is missing, permission broker will |
+ // not be used and this method fails if the device is claimed. |
+ virtual void RequestUsbAcess( |
+ int interface_id, |
+ const base::Callback<void(bool success)>& callback) OVERRIDE { |
+ callback.Run(true); |
+ } |
+#endif // OS_CHROMEOS |
+ |
+ std::set<int> claimed_interfaces_; |
+ |
+ protected: |
+ virtual ~MockUsbDevice() {} |
+}; |
+ |
+class MockUsbService : public UsbService { |
+ public: |
+ MockUsbService() { devices_.push_back(new MockUsbDevice()); } |
+ |
+ virtual ~MockUsbService() {} |
+ |
+ virtual scoped_refptr<UsbDevice> GetDeviceById(uint32 unique_id) OVERRIDE { |
+ NOTIMPLEMENTED(); |
+ return NULL; |
+ } |
+ |
+ virtual void GetDevices( |
+ std::vector<scoped_refptr<UsbDevice> >* devices) OVERRIDE { |
+ STLClearObject(devices); |
+ std::copy(devices_.begin(), devices_.end(), back_inserter(*devices)); |
+ } |
+ |
+ std::vector<scoped_refptr<UsbDevice> > devices_; |
+}; |
+ |
+bool MockUsbDeviceHandle::ClaimInterface(const int interface_number) { |
+ if (device_->claimed_interfaces_.find(interface_number) != |
+ device_->claimed_interfaces_.end()) |
+ return false; |
+ |
+ device_->claimed_interfaces_.insert(interface_number); |
+ return true; |
+} |
+ |
+bool MockUsbDeviceHandle::ReleaseInterface(const int interface_number) { |
+ if (device_->claimed_interfaces_.find(interface_number) == |
+ device_->claimed_interfaces_.end()) |
+ return false; |
+ |
+ device_->claimed_interfaces_.erase(interface_number); |
+ return true; |
+} |
+ |
+class AndroidUsbDiscoveryTest |
+ : public InProcessBrowserTest, |
+ public DevToolsAndroidBridge::DeviceListListener { |
+ protected: |
+ virtual void SetUpOnMainThread() OVERRIDE { |
+ scoped_refptr<content::MessageLoopRunner> runner = |
+ new content::MessageLoopRunner; |
+ |
+ BrowserThread::PostTaskAndReply( |
+ BrowserThread::FILE, |
+ FROM_HERE, |
+ base::Bind(&AndroidUsbDiscoveryTest::SetUpService, this), |
+ runner->QuitClosure()); |
+ runner->Run(); |
+ |
+ adb_bridge_ = |
+ DevToolsAndroidBridge::Factory::GetForProfile(browser()->profile()); |
+ |
+ scoped_refptr<UsbDeviceProvider> provider = |
+ new UsbDeviceProvider(browser()->profile()); |
+ |
+ AndroidDeviceManager::DeviceProviders providers; |
+ providers.push_back(provider); |
+ |
+ adb_bridge_->set_device_providers_for_test(providers); |
+ |
+ runner_ = new content::MessageLoopRunner; |
+ } |
+ |
+ void SetUpService() { |
+ service_ = new MockUsbService(); |
+ UsbService::SetInstanceForTest(service_); |
+ } |
+ |
+ virtual void CleanUpOnMainThread() OVERRIDE { |
+ scoped_refptr<content::MessageLoopRunner> runner = |
+ new content::MessageLoopRunner; |
+ UsbService* service = NULL; |
+ BrowserThread::PostTaskAndReply( |
+ BrowserThread::FILE, |
+ FROM_HERE, |
+ base::Bind(&UsbService::SetInstanceForTest, service), |
+ runner->QuitClosure()); |
+ runner->Run(); |
+ } |
+ |
+ virtual void DeviceListChanged( |
+ const DevToolsAndroidBridge::RemoteDevices& devices) OVERRIDE { |
+ if (devices.size() > 0) { |
+ if (devices[0]->is_connected()) { |
+ ASSERT_EQ(kDeviceModel, devices[0]->model()); |
+ ASSERT_EQ(kDeviceSerial, devices[0]->serial()); |
+ adb_bridge_->RemoveDeviceListListener(this); |
+ runner_->Quit(); |
+ } |
+ } |
+ } |
+ |
+ bool passed = false; |
+ scoped_refptr<content::MessageLoopRunner> runner_; |
+ MockUsbService* service_; |
+ scoped_refptr<DevToolsAndroidBridge> adb_bridge_; |
+}; |
+ |
+} // namespace |
+ |
+IN_PROC_BROWSER_TEST_F(AndroidUsbDiscoveryTest, TestDeviceDiscovery) { |
+ if (!adb_bridge_) { |
+ FAIL() << "Failed to get DevToolsAndroidBridge."; |
+ } |
+ |
+ adb_bridge_->AddDeviceListListener(this); |
+ |
+ runner_->Run(); |
+} |