diff options
author | armansito@chromium.org <armansito@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-20 23:24:19 +0000 |
---|---|---|
committer | armansito@chromium.org <armansito@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-20 23:24:19 +0000 |
commit | 13aa72068571a09f84d6bd2524cf6d9ca190f214 (patch) | |
tree | 77b4587e072bdfdde5ac0e95db8292acbf0211f9 /device | |
parent | 57afa12e2f3d503b2cea20ecd50a47930156305a (diff) | |
download | chromium_src-13aa72068571a09f84d6bd2524cf6d9ca190f214.zip chromium_src-13aa72068571a09f84d6bd2524cf6d9ca190f214.tar.gz chromium_src-13aa72068571a09f84d6bd2524cf6d9ca190f214.tar.bz2 |
nfc: Implement device::NfcPeerChromeOS.
Implemented device::NfcPeer for Chrome OS. With this, Chrome can now
interact with remote NFC adapters.
BUG=316471
TEST=device_unittests
Review URL: https://codereview.chromium.org/112183002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@242205 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'device')
-rw-r--r-- | device/nfc/nfc.gyp | 5 | ||||
-rw-r--r-- | device/nfc/nfc_adapter_chromeos.cc | 83 | ||||
-rw-r--r-- | device/nfc/nfc_adapter_chromeos.h | 2 | ||||
-rw-r--r-- | device/nfc/nfc_chromeos_unittest.cc | 466 | ||||
-rw-r--r-- | device/nfc/nfc_ndef_record.cc | 55 | ||||
-rw-r--r-- | device/nfc/nfc_ndef_record.h | 8 | ||||
-rw-r--r-- | device/nfc/nfc_ndef_record_unittest.cc | 39 | ||||
-rw-r--r-- | device/nfc/nfc_ndef_record_utils_chromeos.cc | 236 | ||||
-rw-r--r-- | device/nfc/nfc_ndef_record_utils_chromeos.h | 36 | ||||
-rw-r--r-- | device/nfc/nfc_peer.h | 12 | ||||
-rw-r--r-- | device/nfc/nfc_peer_chromeos.cc | 196 | ||||
-rw-r--r-- | device/nfc/nfc_peer_chromeos.h | 81 |
12 files changed, 1171 insertions, 48 deletions
diff --git a/device/nfc/nfc.gyp b/device/nfc/nfc.gyp index d5e519b..2495a1c 100644 --- a/device/nfc/nfc.gyp +++ b/device/nfc/nfc.gyp @@ -12,6 +12,7 @@ 'type': 'static_library', 'dependencies': [ '../../base/base.gyp:base', + '../../url/url.gyp:url_lib', ], 'sources': [ 'nfc_adapter.cc', @@ -22,8 +23,12 @@ 'nfc_adapter_factory.h', 'nfc_ndef_record.cc', 'nfc_ndef_record.h', + 'nfc_ndef_record_utils_chromeos.cc', + 'nfc_ndef_record_utils_chromeos.h', 'nfc_peer.cc', 'nfc_peer.h', + 'nfc_peer_chromeos.cc', + 'nfc_peer_chromeos.h', 'nfc_tag.cc', 'nfc_tag.h', 'nfc_tag_technology.cc', diff --git a/device/nfc/nfc_adapter_chromeos.cc b/device/nfc/nfc_adapter_chromeos.cc index 071b080..b78ded2 100644 --- a/device/nfc/nfc_adapter_chromeos.cc +++ b/device/nfc/nfc_adapter_chromeos.cc @@ -9,19 +9,25 @@ #include "base/callback.h" #include "base/logging.h" #include "chromeos/dbus/dbus_thread_manager.h" -#include "device/nfc/nfc_peer.h" +#include "device/nfc/nfc_peer_chromeos.h" #include "device/nfc/nfc_tag.h" #include "third_party/cros_system_api/dbus/service_constants.h" namespace chromeos { +namespace { + +typedef std::vector<dbus::ObjectPath> ObjectPathVector; + +} // namespace + NfcAdapterChromeOS::NfcAdapterChromeOS() : weak_ptr_factory_(this) { DBusThreadManager::Get()->GetNfcAdapterClient()->AddObserver(this); DBusThreadManager::Get()->GetNfcDeviceClient()->AddObserver(this); DBusThreadManager::Get()->GetNfcTagClient()->AddObserver(this); - const std::vector<dbus::ObjectPath>& object_paths = + const ObjectPathVector& object_paths = DBusThreadManager::Get()->GetNfcAdapterClient()->GetAdapters(); if (!object_paths.empty()) { VLOG(1) << object_paths.size() << " NFC adapter(s) available."; @@ -126,9 +132,9 @@ void NfcAdapterChromeOS::AdapterRemoved(const dbus::ObjectPath& object_path) { // There may still be other adapters present on the system. Set the next // available adapter as the current one. - const std::vector<dbus::ObjectPath>& object_paths = + const ObjectPathVector& object_paths = DBusThreadManager::Get()->GetNfcAdapterClient()->GetAdapters(); - for (std::vector<dbus::ObjectPath>::const_iterator iter = + for (ObjectPathVector::const_iterator iter = object_paths.begin(); iter != object_paths.end(); ++iter) { // The removed object will still be available until the call to @@ -155,19 +161,54 @@ void NfcAdapterChromeOS::AdapterPropertyChanged( } void NfcAdapterChromeOS::DeviceAdded(const dbus::ObjectPath& object_path) { + if (!IsPresent()) + return; + + if (peers_.find(object_path.value()) != peers_.end()) { + VLOG(1) << "Peer object for device \"" << object_path.value() + << "\" already exists."; + return; + } + VLOG(1) << "NFC device found: " << object_path.value(); - // TODO(armansito): Implement device logic. + + // Check to see if the device belongs to this adapter. + const ObjectPathVector& devices = + DBusThreadManager::Get()->GetNfcDeviceClient()-> + GetDevicesForAdapter(object_path_); + bool device_found = false; + for (ObjectPathVector::const_iterator iter = devices.begin(); + iter != devices.end(); ++iter) { + if (*iter == object_path) { + device_found = true; + break; + } + } + if (!device_found) { + VLOG(1) << "Found peer device does not belong to the current adapter."; + return; + } + + // Create the peer object. + NfcPeerChromeOS* peer_chromeos = new NfcPeerChromeOS(object_path); + peers_[object_path.value()] = peer_chromeos; + FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_, + PeerFound(this, peer_chromeos)); } void NfcAdapterChromeOS::DeviceRemoved(const dbus::ObjectPath& object_path) { VLOG(1) << "NFC device lost: " << object_path.value(); - // TODO(armansito): Implement device logic. -} + PeersMap::iterator iter = peers_.find(object_path.value()); + if (iter == peers_.end()) { + VLOG(1) << "Removed peer device does not belong to the current adapter."; + return; + } -void NfcAdapterChromeOS::DevicePropertyChanged( - const dbus::ObjectPath& object_path, - const std::string& property_name) { - // TODO(armansito): Implement device logic. + // Remove the peer. + device::NfcPeer* peer = iter->second; + peers_.erase(iter); + FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_, PeerLost(this, peer)); + delete peer; } void NfcAdapterChromeOS::TagAdded(const dbus::ObjectPath& object_path) { @@ -200,9 +241,23 @@ void NfcAdapterChromeOS::SetAdapter(const dbus::ObjectPath& object_path) { if (properties->polling.value()) PollingChanged(true); - // TODO(armansito): Create device::NfcPeer and device::NfcTag instances for - // all peers and tags that exist, once they have been implemented for - // ChromeOS. + // Create peer objects for peers that were added before the adapter was set. + const ObjectPathVector& devices = + DBusThreadManager::Get()->GetNfcDeviceClient()-> + GetDevicesForAdapter(object_path_); + for (ObjectPathVector::const_iterator iter = devices.begin(); + iter != devices.end(); ++iter) { + const dbus::ObjectPath& object_path = *iter; + if (peers_.find(object_path.value()) != peers_.end()) + continue; + NfcPeerChromeOS* peer_chromeos = new NfcPeerChromeOS(object_path); + peers_[object_path.value()] = peer_chromeos; + FOR_EACH_OBSERVER(NfcAdapter::Observer, observers_, + PeerFound(this, peer_chromeos)); + } + + // TODO(armansito): Create device::NfcTag instances for all tags that exist, + // once they have been implemented for ChromeOS. } void NfcAdapterChromeOS::RemoveAdapter() { diff --git a/device/nfc/nfc_adapter_chromeos.h b/device/nfc/nfc_adapter_chromeos.h index d70a98d..7ee99b0 100644 --- a/device/nfc/nfc_adapter_chromeos.h +++ b/device/nfc/nfc_adapter_chromeos.h @@ -60,8 +60,6 @@ class NfcAdapterChromeOS : public device::NfcAdapter, // NfcDeviceClient::Observer overrides. virtual void DeviceAdded(const dbus::ObjectPath& object_path) OVERRIDE; virtual void DeviceRemoved(const dbus::ObjectPath& object_path) OVERRIDE; - virtual void DevicePropertyChanged(const dbus::ObjectPath& object_path, - const std::string& property_name) OVERRIDE; // NfcTagClient::Observer overrides. virtual void TagAdded(const dbus::ObjectPath& object_path) OVERRIDE; diff --git a/device/nfc/nfc_chromeos_unittest.cc b/device/nfc/nfc_chromeos_unittest.cc index dd94beb..c6155e0 100644 --- a/device/nfc/nfc_chromeos_unittest.cc +++ b/device/nfc/nfc_chromeos_unittest.cc @@ -2,23 +2,48 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/values.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/fake_nfc_adapter_client.h" +#include "chromeos/dbus/fake_nfc_device_client.h" +#include "chromeos/dbus/fake_nfc_record_client.h" #include "device/nfc/nfc_adapter_chromeos.h" +#include "device/nfc/nfc_ndef_record.h" +#include "device/nfc/nfc_ndef_record_utils_chromeos.h" +#include "device/nfc/nfc_peer.h" #include "testing/gtest/include/gtest/gtest.h" +#include "third_party/cros_system_api/dbus/service_constants.h" using device::NfcAdapter; +using device::NfcNdefMessage; +using device::NfcNdefRecord; +using device::NfcPeer; namespace chromeos { namespace { -class TestObserver : public NfcAdapter::Observer { +// Callback passed to property structures. +void OnPropertyChangedCallback(const std::string& property_name) { +} + +// Callback passed to dbus::PropertyBase::Set. +void OnSet(bool success) { +} + +class TestObserver : public NfcAdapter::Observer, + public NfcPeer::Observer { public: TestObserver(scoped_refptr<NfcAdapter> adapter) : present_changed_count_(0), powered_changed_count_(0), + polling_changed_count_(0), + records_received_count_(0), + peer_count_(0), adapter_(adapter) { } @@ -38,8 +63,40 @@ class TestObserver : public NfcAdapter::Observer { powered_changed_count_++; } + // NfcAdapter::Observer override. + virtual void AdapterPollingChanged(NfcAdapter* adapter, + bool powered) OVERRIDE { + EXPECT_EQ(adapter_, adapter); + polling_changed_count_++; + } + + // NfcAdapter::Observer override. + virtual void PeerFound(NfcAdapter* adapter, NfcPeer* peer) OVERRIDE { + EXPECT_EQ(adapter_, adapter); + peer_count_++; + peer_identifier_ = peer->GetIdentifier(); + } + + // NfcAdapter::Observer override. + virtual void PeerLost(NfcAdapter* adapter, NfcPeer* peer) OVERRIDE { + EXPECT_EQ(adapter_, adapter); + peer_count_--; + peer_identifier_.clear(); + } + + // NfcPeer::Observer override. + virtual void RecordsReceived( + NfcPeer* peer, const NfcNdefRecord* record) OVERRIDE { + EXPECT_EQ(peer, adapter_->GetPeer(peer_identifier_)); + records_received_count_++; + } + int present_changed_count_; int powered_changed_count_; + int polling_changed_count_; + int records_received_count_; + int peer_count_; + std::string peer_identifier_; scoped_refptr<NfcAdapter> adapter_; }; @@ -51,9 +108,13 @@ class NfcChromeOSTest : public testing::Test { DBusThreadManager::InitializeWithStub(); fake_nfc_adapter_client_ = static_cast<FakeNfcAdapterClient*>( DBusThreadManager::Get()->GetNfcAdapterClient()); - SetAdapter(); - message_loop_.RunUntilIdle(); + fake_nfc_device_client_ = static_cast<FakeNfcDeviceClient*>( + DBusThreadManager::Get()->GetNfcDeviceClient()); + fake_nfc_record_client_ = static_cast<FakeNfcRecordClient*>( + DBusThreadManager::Get()->GetNfcRecordClient()); + fake_nfc_adapter_client_->EnablePairingOnPoll(false); + fake_nfc_device_client_->DisableSimulationTimeout(); success_callback_count_ = 0; error_callback_count_ = 0; } @@ -68,6 +129,7 @@ class NfcChromeOSTest : public testing::Test { adapter_ = new NfcAdapterChromeOS(); ASSERT_TRUE(adapter_.get() != NULL); ASSERT_TRUE(adapter_->IsInitialized()); + base::RunLoop().RunUntilIdle(); } // Generic callbacks for success and error. @@ -79,23 +141,35 @@ class NfcChromeOSTest : public testing::Test { error_callback_count_++; } + void ErrorCallbackWithParameters(const std::string& error_name, + const std::string& error_message) { + LOG(INFO) << "Error callback called: " << error_name << ", " + << error_message; + error_callback_count_++; + } + protected: + // MessageLoop instance, used to simulate asynchronous behavior. + base::MessageLoop message_loop_; + // Fields for storing the number of times SuccessCallback and ErrorCallback // have been called. int success_callback_count_; int error_callback_count_; - // A message loop to emulate asynchronous behavior. - base::MessageLoop message_loop_; - // The NfcAdapter instance under test. scoped_refptr<NfcAdapter> adapter_; // The fake D-Bus client instances used for testing. FakeNfcAdapterClient* fake_nfc_adapter_client_; + FakeNfcDeviceClient* fake_nfc_device_client_; + FakeNfcRecordClient* fake_nfc_record_client_; }; +// Tests that the adapter updates correctly to reflect the current "default" +// adapter, when multiple adapters appear and disappear. TEST_F(NfcChromeOSTest, PresentChanged) { + SetAdapter(); EXPECT_TRUE(adapter_->IsPresent()); TestObserver observer(adapter_); @@ -122,7 +196,9 @@ TEST_F(NfcChromeOSTest, PresentChanged) { EXPECT_FALSE(adapter_->IsPresent()); } +// Tests that the adapter correctly reflects the power state. TEST_F(NfcChromeOSTest, SetPowered) { + SetAdapter(); TestObserver observer(adapter_); adapter_->AddObserver(&observer); @@ -177,7 +253,9 @@ TEST_F(NfcChromeOSTest, SetPowered) { EXPECT_EQ(2, error_callback_count_); } +// Tests that the power state updates correctly when the adapter disappears. TEST_F(NfcChromeOSTest, PresentChangedWhilePowered) { + SetAdapter(); TestObserver observer(adapter_); adapter_->AddObserver(&observer); @@ -199,4 +277,380 @@ TEST_F(NfcChromeOSTest, PresentChangedWhilePowered) { EXPECT_FALSE(adapter_->IsPresent()); } +// Tests that peer and record objects are created for all peers and records +// that already exist when the adapter is created. +TEST_F(NfcChromeOSTest, PeersInitializedWhenAdapterCreated) { + // Set up the adapter client. + NfcAdapterClient::Properties* properties = + fake_nfc_adapter_client_->GetProperties( + dbus::ObjectPath(FakeNfcAdapterClient::kAdapterPath0)); + properties->powered.Set(true, base::Bind(&OnSet)); + + fake_nfc_adapter_client_->StartPollLoop( + dbus::ObjectPath(FakeNfcAdapterClient::kAdapterPath0), + nfc_adapter::kModeInitiator, + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallbackWithParameters, + base::Unretained(this))); + EXPECT_EQ(1, success_callback_count_); + EXPECT_TRUE(properties->powered.value()); + EXPECT_TRUE(properties->polling.value()); + + // Start pairing simulation, which will add a fake device and fake records. + fake_nfc_device_client_->BeginPairingSimulation(0, 0); + base::RunLoop().RunUntilIdle(); + + // Create the adapter. + SetAdapter(); + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + // Observer shouldn't have received any calls, as it got created AFTER the + // notifications were sent. + EXPECT_EQ(0, observer.present_changed_count_); + EXPECT_EQ(0, observer.present_changed_count_); + EXPECT_EQ(0, observer.present_changed_count_); + EXPECT_EQ(0, observer.peer_count_); + + EXPECT_TRUE(adapter_->IsPresent()); + EXPECT_TRUE(adapter_->IsPowered()); + EXPECT_FALSE(adapter_->IsPolling()); + + NfcAdapter::PeerList peers; + adapter_->GetPeers(&peers); + EXPECT_EQ(static_cast<size_t>(1), peers.size()); + + NfcPeer* peer = peers[0]; + const NfcNdefMessage& message = peer->GetNdefMessage(); + EXPECT_EQ(static_cast<size_t>(3), message.records().size()); +} + +// Tests that the adapter correctly updates its state when polling is started +// and stopped. +TEST_F(NfcChromeOSTest, StartAndStopPolling) { + SetAdapter(); + EXPECT_TRUE(adapter_->IsPresent()); + + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + // Start polling while not powered. Should fail. + EXPECT_FALSE(adapter_->IsPowered()); + adapter_->StartPolling( + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_FALSE(adapter_->IsPolling()); + + // Start polling while powered. Should succeed. + adapter_->SetPowered( + true, + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_TRUE(adapter_->IsPowered()); + + adapter_->StartPolling( + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_TRUE(adapter_->IsPolling()); + + // Start polling while already polling. Should fail. + adapter_->StartPolling( + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(2, error_callback_count_); + EXPECT_TRUE(adapter_->IsPolling()); + + // Stop polling. Should succeed. + adapter_->StopPolling( + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(3, success_callback_count_); + EXPECT_EQ(2, error_callback_count_); + EXPECT_FALSE(adapter_->IsPolling()); + + // Stop polling while not polling. Should fail. + adapter_->StopPolling( + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(3, success_callback_count_); + EXPECT_EQ(3, error_callback_count_); + EXPECT_FALSE(adapter_->IsPolling()); +} + +// Tests a simple peer pairing simulation. +TEST_F(NfcChromeOSTest, PeerTest) { + SetAdapter(); + TestObserver observer(adapter_); + adapter_->AddObserver(&observer); + + adapter_->SetPowered( + true, + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + adapter_->StartPolling( + base::Bind(&NfcChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&NfcChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + + EXPECT_TRUE(adapter_->IsPowered()); + EXPECT_TRUE(adapter_->IsPolling()); + EXPECT_EQ(0, observer.peer_count_); + + // Add the fake device. + fake_nfc_device_client_->BeginPairingSimulation(0, -1); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, observer.peer_count_); + EXPECT_EQ(FakeNfcDeviceClient::kDevicePath, observer.peer_identifier_); + + NfcPeer* peer = adapter_->GetPeer(observer.peer_identifier_); + CHECK(peer); + peer->AddObserver(&observer); + + // Peer should have no records on it. + EXPECT_TRUE(peer->GetNdefMessage().records().empty()); + EXPECT_EQ(0, observer.records_received_count_); + + // Make records visible. + fake_nfc_record_client_->SetRecordsVisible(true); + EXPECT_EQ(3, observer.records_received_count_); + EXPECT_EQ(static_cast<size_t>(3), peer->GetNdefMessage().records().size()); + + // End the simulation. Record should have been removed. + fake_nfc_device_client_->EndPairingSimulation(); + EXPECT_EQ(0, observer.peer_count_); + EXPECT_TRUE(observer.peer_identifier_.empty()); + + peer = adapter_->GetPeer(observer.peer_identifier_); + EXPECT_FALSE(peer); + + // No record related notifications will be sent when a peer gets removed. + EXPECT_EQ(3, observer.records_received_count_); +} + +// Unit tests for nfc_ndef_record_utils methods. +TEST_F(NfcChromeOSTest, NfcNdefRecordToDBusAttributes) { + const char kText[] = "text"; + const char kURI[] = "test://uri"; + const char kEncoding[] = "encoding"; + const char kLanguageCode[] = "en"; + const char kMimeType[] = "mime-type"; + const double kSize = 5; + + // Text record. + base::DictionaryValue data; + data.SetString(NfcNdefRecord::kFieldText, kText); + data.SetString(NfcNdefRecord::kFieldLanguageCode, kLanguageCode); + data.SetString(NfcNdefRecord::kFieldEncoding, kEncoding); + + scoped_ptr<NfcNdefRecord> record(new NfcNdefRecord()); + ASSERT_TRUE(record->Populate(NfcNdefRecord::kTypeText, &data)); + + base::DictionaryValue result; + EXPECT_TRUE(nfc_ndef_record_utils::NfcNdefRecordToDBusAttributes( + record.get(), &result)); + + std::string string_value; + EXPECT_TRUE(result.GetString( + nfc_record::kTypeProperty, &string_value)); + EXPECT_EQ(nfc_record::kTypeText, string_value); + EXPECT_TRUE(result.GetString( + nfc_record::kRepresentationProperty, &string_value)); + EXPECT_EQ(kText, string_value); + EXPECT_TRUE(result.GetString( + nfc_record::kLanguageProperty, &string_value)); + EXPECT_EQ(kLanguageCode, string_value); + EXPECT_TRUE(result.GetString( + nfc_record::kEncodingProperty, &string_value)); + EXPECT_EQ(kEncoding, string_value); + + // URI record. + data.Clear(); + data.SetString(NfcNdefRecord::kFieldURI, kURI); + data.SetString(NfcNdefRecord::kFieldMimeType, kMimeType); + data.SetDouble(NfcNdefRecord::kFieldTargetSize, kSize); + + record.reset(new NfcNdefRecord()); + ASSERT_TRUE(record->Populate(NfcNdefRecord::kTypeURI, &data)); + + result.Clear(); + EXPECT_TRUE(nfc_ndef_record_utils::NfcNdefRecordToDBusAttributes( + record.get(), &result)); + + EXPECT_TRUE(result.GetString(nfc_record::kTypeProperty, &string_value)); + EXPECT_EQ(nfc_record::kTypeUri, string_value); + EXPECT_TRUE(result.GetString(nfc_record::kUriProperty, &string_value)); + EXPECT_EQ(kURI, string_value); + EXPECT_TRUE(result.GetString(nfc_record::kMimeTypeProperty, &string_value)); + EXPECT_EQ(kMimeType, string_value); + double double_value; + EXPECT_TRUE(result.GetDouble(nfc_record::kSizeProperty, &double_value)); + EXPECT_EQ(kSize, double_value); + + // SmartPoster record. + base::DictionaryValue* title = new base::DictionaryValue(); + title->SetString(NfcNdefRecord::kFieldText, kText); + title->SetString(NfcNdefRecord::kFieldLanguageCode, kLanguageCode); + title->SetString(NfcNdefRecord::kFieldEncoding, kEncoding); + + base::ListValue* titles = new base::ListValue(); + titles->Append(title); + data.Set(NfcNdefRecord::kFieldTitles, titles); + + record.reset(new NfcNdefRecord()); + ASSERT_TRUE(record->Populate(NfcNdefRecord::kTypeSmartPoster, &data)); + + result.Clear(); + EXPECT_TRUE(nfc_ndef_record_utils::NfcNdefRecordToDBusAttributes( + record.get(), &result)); + + EXPECT_TRUE(result.GetString( + nfc_record::kTypeProperty, &string_value)); + EXPECT_EQ(nfc_record::kTypeSmartPoster, string_value); + EXPECT_TRUE(result.GetString( + nfc_record::kRepresentationProperty, &string_value)); + EXPECT_EQ(kText, string_value); + EXPECT_TRUE(result.GetString( + nfc_record::kLanguageProperty, &string_value)); + EXPECT_EQ(kLanguageCode, string_value); + EXPECT_TRUE(result.GetString( + nfc_record::kEncodingProperty, &string_value)); + EXPECT_EQ(kEncoding, string_value); + EXPECT_TRUE(result.GetString(nfc_record::kUriProperty, &string_value)); + EXPECT_EQ(kURI, string_value); + EXPECT_TRUE(result.GetString(nfc_record::kMimeTypeProperty, &string_value)); + EXPECT_EQ(kMimeType, string_value); + EXPECT_TRUE(result.GetDouble(nfc_record::kSizeProperty, &double_value)); + EXPECT_EQ(kSize, double_value); +} + +TEST_F(NfcChromeOSTest, RecordPropertiesToNfcNdefRecord) { + const char kText[] = "text"; + const char kURI[] = "test://uri"; + const char kEncoding[] = "encoding"; + const char kLanguageCode[] = "en"; + const char kMimeType[] = "mime-type"; + const uint32 kSize = 5; + + FakeNfcRecordClient::Properties record_properties( + base::Bind(&OnPropertyChangedCallback)); + + // Text record. + record_properties.type.ReplaceValue(nfc_record::kTypeText); + record_properties.representation.ReplaceValue(kText); + record_properties.language.ReplaceValue(kLanguageCode); + record_properties.encoding.ReplaceValue(kEncoding); + + scoped_ptr<NfcNdefRecord> record(new NfcNdefRecord()); + EXPECT_TRUE(nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord( + &record_properties, record.get())); + EXPECT_TRUE(record->IsPopulated()); + + std::string string_value; + EXPECT_EQ(NfcNdefRecord::kTypeText, record->type()); + EXPECT_TRUE(record->data().GetString( + NfcNdefRecord::kFieldText, &string_value)); + EXPECT_EQ(kText, string_value); + EXPECT_TRUE(record->data().GetString( + NfcNdefRecord::kFieldLanguageCode, &string_value)); + EXPECT_EQ(kLanguageCode, string_value); + EXPECT_TRUE(record->data().GetString( + NfcNdefRecord::kFieldEncoding, &string_value)); + EXPECT_EQ(kEncoding, string_value); + + // URI record. + record_properties.representation.ReplaceValue(""); + record_properties.language.ReplaceValue(""); + record_properties.encoding.ReplaceValue(""); + + record_properties.type.ReplaceValue(nfc_record::kTypeUri); + record_properties.uri.ReplaceValue(kURI); + record_properties.mime_type.ReplaceValue(kMimeType); + record_properties.size.ReplaceValue(kSize); + + record.reset(new NfcNdefRecord()); + EXPECT_TRUE(nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord( + &record_properties, record.get())); + EXPECT_TRUE(record->IsPopulated()); + + EXPECT_EQ(NfcNdefRecord::kTypeURI, record->type()); + EXPECT_TRUE(record->data().GetString( + NfcNdefRecord::kFieldURI, &string_value)); + EXPECT_EQ(kURI, string_value); + EXPECT_TRUE(record->data().GetString( + NfcNdefRecord::kFieldMimeType, &string_value)); + EXPECT_EQ(kMimeType, string_value); + double double_value; + EXPECT_TRUE(record->data().GetDouble( + NfcNdefRecord::kFieldTargetSize, &double_value)); + EXPECT_EQ(kSize, double_value); + + // Contents not matching type. + record_properties.representation.ReplaceValue(kText); + record_properties.language.ReplaceValue(kLanguageCode); + record_properties.encoding.ReplaceValue(kEncoding); + + record.reset(new NfcNdefRecord()); + EXPECT_FALSE(nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord( + &record_properties, record.get())); + EXPECT_FALSE(record->IsPopulated()); + + // SmartPoster record. + record_properties.type.ReplaceValue(nfc_record::kTypeSmartPoster); + EXPECT_TRUE(nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord( + &record_properties, record.get())); + EXPECT_TRUE(record->IsPopulated()); + + EXPECT_EQ(NfcNdefRecord::kTypeSmartPoster, record->type()); + EXPECT_TRUE(record->data().GetString( + NfcNdefRecord::kFieldURI, &string_value)); + EXPECT_EQ(kURI, string_value); + EXPECT_TRUE(record->data().GetString( + NfcNdefRecord::kFieldMimeType, &string_value)); + EXPECT_EQ(kMimeType, string_value); + EXPECT_TRUE(record->data().GetDouble( + NfcNdefRecord::kFieldTargetSize, &double_value)); + EXPECT_EQ(kSize, double_value); + + const base::ListValue* titles = NULL; + EXPECT_TRUE(record->data().GetList(NfcNdefRecord::kFieldTitles, &titles)); + EXPECT_EQ(static_cast<size_t>(1), titles->GetSize()); + ASSERT_TRUE(titles); + const base::DictionaryValue* title = NULL; + EXPECT_TRUE(titles->GetDictionary(0, &title)); + CHECK(title); + + EXPECT_TRUE(title->GetString(NfcNdefRecord::kFieldText, &string_value)); + EXPECT_EQ(kText, string_value); + EXPECT_TRUE(title->GetString( + NfcNdefRecord::kFieldLanguageCode, &string_value)); + EXPECT_EQ(kLanguageCode, string_value); + EXPECT_TRUE(title->GetString(NfcNdefRecord::kFieldEncoding, &string_value)); + EXPECT_EQ(kEncoding, string_value); +} + } // namespace chromeos diff --git a/device/nfc/nfc_ndef_record.cc b/device/nfc/nfc_ndef_record.cc index ec13c2f..5b8bb99 100644 --- a/device/nfc/nfc_ndef_record.cc +++ b/device/nfc/nfc_ndef_record.cc @@ -7,6 +7,7 @@ #include <map> #include "base/logging.h" +#include "url/gurl.h" using base::DictionaryValue; using base::ListValue; @@ -17,6 +18,23 @@ namespace { typedef std::map<std::string, base::Value::Type> FieldValueMap; +bool ValidateURI(const DictionaryValue* data) { + std::string uri; + if (!data->GetString(NfcNdefRecord::kFieldURI, &uri)) { + VLOG(1) << "No URI entry in data."; + return false; + } + DCHECK(!uri.empty()); + + // Use GURL to check validity. + GURL url(uri); + if (!url.is_valid()) { + LOG(ERROR) << "Invalid URI given: " << uri; + return false; + } + return true; +} + bool CheckFieldsAreValid( const FieldValueMap& required_fields, const FieldValueMap& optional_fields, @@ -47,6 +65,12 @@ bool CheckFieldsAreValid( << field_iter->second; return false; } + // Make sure that the value is non-empty, if the value is a string. + std::string string_value; + if (iter.value().GetAsString(&string_value) && string_value.empty()) { + VLOG(1) << "Empty value given for field of type string: " << iter.key(); + return false; + } } // Check for required fields. if (required_count != required_fields.size()) { @@ -63,13 +87,23 @@ bool HandleTypeText(const DictionaryValue* data) { VLOG(1) << "Populating record with type \"Text\"."; FieldValueMap required_fields; required_fields[NfcNdefRecord::kFieldText] = base::Value::TYPE_STRING; + required_fields[NfcNdefRecord::kFieldEncoding] = base::Value::TYPE_STRING; + required_fields[NfcNdefRecord::kFieldLanguageCode] = base::Value::TYPE_STRING; FieldValueMap optional_fields; - optional_fields[NfcNdefRecord::kFieldEncoding] = base::Value::TYPE_STRING; - optional_fields[NfcNdefRecord::kFieldLanguageCode] = base::Value::TYPE_STRING; if (!CheckFieldsAreValid(required_fields, optional_fields, data)) { VLOG(1) << "Failed to populate record."; return false; } + + // Verify that the "Encoding" property has valid values. + std::string encoding; + if (!data->GetString(NfcNdefRecord::kFieldEncoding, &encoding)) { + if (encoding != NfcNdefRecord::kEncodingUtf8 || + encoding != NfcNdefRecord::kEncodingUtf16) { + VLOG(1) << "Invalid \"Encoding\" value:" << encoding; + return false; + } + } return true; } @@ -113,7 +147,7 @@ bool HandleTypeSmartPoster(const DictionaryValue* data) { } } } - return true; + return ValidateURI(data); } // Verifies that the contents of |data| conform to the fields of NDEF type @@ -125,11 +159,13 @@ bool HandleTypeUri(const DictionaryValue* data) { FieldValueMap optional_fields; optional_fields[NfcNdefRecord::kFieldMimeType] = base::Value::TYPE_STRING; optional_fields[NfcNdefRecord::kFieldTargetSize] = base::Value::TYPE_DOUBLE; + + // Allow passing TargetSize as an integer, but convert it to a double. if (!CheckFieldsAreValid(required_fields, optional_fields, data)) { VLOG(1) << "Failed to populate record."; return false; } - return true; + return ValidateURI(data); } } // namespace @@ -210,4 +246,15 @@ void NfcNdefMessage::AddRecord(NfcNdefRecord* record) { records_.push_back(record); } +bool NfcNdefMessage::RemoveRecord(NfcNdefRecord* record) { + for (RecordList::iterator iter = records_.begin(); + iter != records_.end(); ++iter) { + if (*iter == record) { + records_.erase(iter); + return true; + } + } + return false; +} + } // namespace device diff --git a/device/nfc/nfc_ndef_record.h b/device/nfc/nfc_ndef_record.h index 2be66b1..24cbc12 100644 --- a/device/nfc/nfc_ndef_record.h +++ b/device/nfc/nfc_ndef_record.h @@ -146,9 +146,15 @@ class NfcNdefMessage { const RecordList& records() const { return records_; } // Adds the NDEF record |record| to the sequence of records that this - // NdefMessage contains. + // NfcNdefMessage contains. This method simply adds the record to this message + // and does NOT take ownership of it. void AddRecord(NfcNdefRecord* record); + // Removes the NDEF record |record| from this message. Returns true, if the + // record was removed, otherwise returns false if |record| was not contained + // in this message. + bool RemoveRecord(NfcNdefRecord* record); + private: // The NDEF records that are contained by this message. RecordList records_; diff --git a/device/nfc/nfc_ndef_record_unittest.cc b/device/nfc/nfc_ndef_record_unittest.cc index 00c2d2f5..2b6ef48 100644 --- a/device/nfc/nfc_ndef_record_unittest.cc +++ b/device/nfc/nfc_ndef_record_unittest.cc @@ -21,7 +21,7 @@ const char kTestLanguageCode[] = "test-language-code"; const char kTestMimeType[] = "test-mime-type"; const uint32 kTestTargetSize = 0; const char kTestText[] = "test-text"; -const char kTestURI[] = "test-uri"; +const char kTestURI[] = "test://uri"; } // namespace @@ -33,35 +33,32 @@ TEST(NfcNdefRecordTest, PopulateTextRecord) { EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data)); EXPECT_FALSE(record->IsPopulated()); - // Text field with incorrect entry. Should fail. + // Text field with incorrect type. Should fail. data.SetInteger(NfcNdefRecord::kFieldText, 0); EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data)); EXPECT_FALSE(record->IsPopulated()); - // Text field with correct entry. Should succeed. + // Text field with correct type but missing encoding and language. + // Should fail. data.SetString(NfcNdefRecord::kFieldText, kTestText); - EXPECT_TRUE(record->Populate(NfcNdefRecord::kTypeText, &data)); - EXPECT_TRUE(record->IsPopulated()); - EXPECT_EQ(NfcNdefRecord::kTypeText, record->type()); + EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data)); + EXPECT_FALSE(record->IsPopulated()); // Populating a successfully populated record should fail. EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data)); - // Recycle the record. - record.reset(new NfcNdefRecord()); - EXPECT_FALSE(record->IsPopulated()); - - // Incorrect optional fields. Should fail. + // Incorrect type for language code. data.SetInteger(NfcNdefRecord::kFieldLanguageCode, 0); EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data)); EXPECT_FALSE(record->IsPopulated()); + // Correct type for language code, invalid encoding. data.SetString(NfcNdefRecord::kFieldLanguageCode, kTestLanguageCode); data.SetInteger(NfcNdefRecord::kFieldEncoding, 0); EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeText, &data)); EXPECT_FALSE(record->IsPopulated()); - // Optional fields are correct. Should succeed. + // All entries valid. Should succeed. data.SetString(NfcNdefRecord::kFieldEncoding, kTestEncoding); EXPECT_TRUE(record->Populate(NfcNdefRecord::kTypeText, &data)); EXPECT_TRUE(record->IsPopulated()); @@ -88,12 +85,16 @@ TEST(NfcNdefRecordTest, PopulateUriRecord) { EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeURI, &data)); EXPECT_FALSE(record->IsPopulated()); - // URI field with incorrect entry. Should fail. + // URI field with incorrect type. Should fail. data.SetInteger(NfcNdefRecord::kFieldURI, 0); EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeURI, &data)); EXPECT_FALSE(record->IsPopulated()); - // URI field with correct entry. Should succeed. + // URI field with correct type but invalid format. + data.SetString(NfcNdefRecord::kFieldURI, "test.uri"); + EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeURI, &data)); + EXPECT_FALSE(record->IsPopulated()); + data.SetString(NfcNdefRecord::kFieldURI, kTestURI); EXPECT_TRUE(record->Populate(NfcNdefRecord::kTypeURI, &data)); EXPECT_TRUE(record->IsPopulated()); @@ -203,8 +204,10 @@ TEST(NfcNdefRecordTest, PopulateSmartPoster) { EXPECT_FALSE(record->Populate(NfcNdefRecord::kTypeSmartPoster, &data)); EXPECT_FALSE(record->IsPopulated()); - // Title value with valid "text" field. + // Title value with valid entries. title_value->SetString(NfcNdefRecord::kFieldText, kTestText); + title_value->SetString(NfcNdefRecord::kFieldLanguageCode, kTestLanguageCode); + title_value->SetString(NfcNdefRecord::kFieldEncoding, kTestEncoding); EXPECT_TRUE(record->Populate(NfcNdefRecord::kTypeSmartPoster, &data)); EXPECT_TRUE(record->IsPopulated()); @@ -233,6 +236,12 @@ TEST(NfcNdefRecordTest, PopulateSmartPoster) { EXPECT_TRUE(const_dictionary_value->GetString( NfcNdefRecord::kFieldText, &string_value)); EXPECT_EQ(kTestText, string_value); + EXPECT_TRUE(const_dictionary_value->GetString( + NfcNdefRecord::kFieldLanguageCode, &string_value)); + EXPECT_EQ(kTestLanguageCode, string_value); + EXPECT_TRUE(const_dictionary_value->GetString( + NfcNdefRecord::kFieldEncoding, &string_value)); + EXPECT_EQ(kTestEncoding, string_value); } } // namespace device diff --git a/device/nfc/nfc_ndef_record_utils_chromeos.cc b/device/nfc/nfc_ndef_record_utils_chromeos.cc new file mode 100644 index 0000000..3bc6f95 --- /dev/null +++ b/device/nfc/nfc_ndef_record_utils_chromeos.cc @@ -0,0 +1,236 @@ +// Copyright 2013 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/nfc/nfc_ndef_record_utils_chromeos.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "device/nfc/nfc_ndef_record.h" +#include "third_party/cros_system_api/dbus/service_constants.h" + +using device::NfcNdefRecord; + +namespace chromeos { +namespace nfc_ndef_record_utils { + +namespace { + +// Maps the NDEF type |type| as returned by neard to the corresponding +// device::NfcNdefRecord::Type value. +NfcNdefRecord::Type DBusRecordTypeValueToNfcNdefRecordType( + const std::string& type) { + if (type == nfc_record::kTypeSmartPoster) + return NfcNdefRecord::kTypeSmartPoster; + if (type == nfc_record::kTypeText) + return NfcNdefRecord::kTypeText; + if (type == nfc_record::kTypeUri) + return NfcNdefRecord::kTypeURI; + if (type == nfc_record::kTypeHandoverRequest) + return NfcNdefRecord::kTypeHandoverRequest; + if (type == nfc_record::kTypeHandoverSelect) + return NfcNdefRecord::kTypeHandoverSelect; + if (type == nfc_record::kTypeHandoverCarrier) + return NfcNdefRecord::kTypeHandoverCarrier; + return NfcNdefRecord::kTypeUnknown; +} + +// Maps the NDEF type |type| given as a NFC C++ API enumeration to the +// corresponding string value defined by neard. +std::string NfcRecordTypeEnumToPropertyValue(NfcNdefRecord::Type type) { + switch (type) { + case NfcNdefRecord::kTypeSmartPoster: + return nfc_record::kTypeSmartPoster; + case NfcNdefRecord::kTypeText: + return nfc_record::kTypeText; + case NfcNdefRecord::kTypeURI: + return nfc_record::kTypeUri; + case NfcNdefRecord::kTypeHandoverRequest: + return nfc_record::kTypeHandoverRequest; + case NfcNdefRecord::kTypeHandoverSelect: + return nfc_record::kTypeHandoverSelect; + case NfcNdefRecord::kTypeHandoverCarrier: + return nfc_record::kTypeHandoverCarrier; + default: + return ""; + } +} + +// Maps the field name |field_name| given as defined in NfcNdefRecord to the +// Neard Record D-Bus property name. This handles all fields except for +// NfcNdefRecord::kFieldTitles and NfcNdefRecord::kFieldAction, which need +// special handling. +std::string NdefRecordFieldToDBusProperty(const std::string& field_name) { + if (field_name == NfcNdefRecord::kFieldEncoding) + return nfc_record::kEncodingProperty; + if (field_name == NfcNdefRecord::kFieldLanguageCode) + return nfc_record::kLanguageProperty; + if (field_name == NfcNdefRecord::kFieldText) + return nfc_record::kRepresentationProperty; + if (field_name == NfcNdefRecord::kFieldURI) + return nfc_record::kUriProperty; + if (field_name == NfcNdefRecord::kFieldMimeType) + return nfc_record::kMimeTypeProperty; + if (field_name == NfcNdefRecord::kFieldTargetSize) + return nfc_record::kSizeProperty; + return ""; +} + +std::string NfcNdefRecordActionValueToDBusActionValue( + const std::string& action) { + // TODO(armansito): Add bindings for values returned by neard to + // service_constants.h. + if (action == device::NfcNdefRecord::kSmartPosterActionDo) + return "Do"; + if (action == device::NfcNdefRecord::kSmartPosterActionSave) + return "Save"; + if (action == device::NfcNdefRecord::kSmartPosterActionOpen) + return "Edit"; + return ""; +} + +std::string DBusActionValueToNfcNdefRecordActionValue( + const std::string& action) { + // TODO(armansito): Add bindings for values returned by neard to + // service_constants.h. + if (action == "Do") + return device::NfcNdefRecord::kSmartPosterActionDo; + if (action == "Save") + return device::NfcNdefRecord::kSmartPosterActionSave; + if (action == "Edit") + return device::NfcNdefRecord::kSmartPosterActionOpen; + return ""; +} + + +// Translates the given dictionary of NDEF fields by recursively converting +// each key in |record_data| to its corresponding Record property name defined +// by the Neard D-Bus API. The output is stored in |out|. Returns false if an +// error occurs. +bool ConvertNdefFieldsToDBusAttributes( + const base::DictionaryValue& fields, + base::DictionaryValue* out) { + DCHECK(out); + for (base::DictionaryValue::Iterator iter(fields); + !iter.IsAtEnd(); iter.Advance()) { + // Special case the "titles" and "action" fields. + if (iter.key() == NfcNdefRecord::kFieldTitles) { + const base::ListValue* titles = NULL; + bool value_result = iter.value().GetAsList(&titles); + DCHECK(value_result); + DCHECK(titles->GetSize() != 0); + // TODO(armansito): For now, pick the first title in the list and write + // its contents directly to the top level of the field. This is due to an + // error in the Neard D-Bus API design. This code will need to be updated + // if the neard API changes to correct this. + const base::DictionaryValue* first_title = NULL; + value_result = titles->GetDictionary(0, &first_title); + DCHECK(value_result); + if (!ConvertNdefFieldsToDBusAttributes(*first_title, out)) { + LOG(ERROR) << "Invalid title field."; + return false; + } + } else if (iter.key() == NfcNdefRecord::kFieldAction) { + // The value of the action field needs to be translated. + std::string action_value; + bool value_result = iter.value().GetAsString(&action_value); + DCHECK(value_result); + std::string action = + NfcNdefRecordActionValueToDBusActionValue(action_value); + if (action.empty()) { + VLOG(1) << "Invalid action value: \"" << action_value << "\""; + return false; + } + out->SetString(nfc_record::kActionProperty, action); + } else { + std::string dbus_property = NdefRecordFieldToDBusProperty(iter.key()); + if (dbus_property.empty()) { + LOG(ERROR) << "Invalid field: " << iter.key(); + return false; + } + out->Set(dbus_property, iter.value().DeepCopy()); + } + } + return true; +} + +} // namespace + +bool NfcNdefRecordToDBusAttributes( + const NfcNdefRecord* record, + base::DictionaryValue* out) { + DCHECK(record); + DCHECK(out); + if (!record->IsPopulated()) { + LOG(ERROR) << "Record is not populated."; + return false; + } + out->SetString(nfc_record::kTypeProperty, + NfcRecordTypeEnumToPropertyValue(record->type())); + return ConvertNdefFieldsToDBusAttributes(record->data(), out); +} + +bool RecordPropertiesToNfcNdefRecord( + const NfcRecordClient::Properties* properties, + device::NfcNdefRecord* out) { + if (out->IsPopulated()) { + LOG(ERROR) << "Record is already populated!"; + return false; + } + NfcNdefRecord::Type type = + DBusRecordTypeValueToNfcNdefRecordType(properties->type.value()); + if (type == NfcNdefRecord::kTypeUnknown) { + LOG(ERROR) << "Record type is unknown."; + return false; + } + + // Extract each property. + base::DictionaryValue attributes; + if (!properties->uri.value().empty()) + attributes.SetString(NfcNdefRecord::kFieldURI, properties->uri.value()); + if (!properties->mime_type.value().empty()) { + attributes.SetString(NfcNdefRecord::kFieldMimeType, + properties->mime_type.value()); + } + if (properties->size.value() != 0) { + attributes.SetDouble(NfcNdefRecord::kFieldTargetSize, + static_cast<double>(properties->size.value())); + } + std::string action_value = + DBusActionValueToNfcNdefRecordActionValue(properties->action.value()); + if (!action_value.empty()) + attributes.SetString(NfcNdefRecord::kFieldAction, action_value); + + // The "representation", "encoding", and "language" properties will be stored + // differently, depending on whether the record type is "SmartPoster" or + // "Text". + { + scoped_ptr<base::DictionaryValue> text_attributes( + new base::DictionaryValue()); + if (!properties->representation.value().empty()) { + text_attributes->SetString(NfcNdefRecord::kFieldText, + properties->representation.value()); + } + if (!properties->encoding.value().empty()) { + text_attributes->SetString(NfcNdefRecord::kFieldEncoding, + properties->encoding.value()); + } + if (!properties->language.value().empty()) { + text_attributes->SetString(NfcNdefRecord::kFieldLanguageCode, + properties->language.value()); + } + if (type == NfcNdefRecord::kTypeSmartPoster) { + base::ListValue* titles = new base::ListValue(); + titles->Append(text_attributes.release()); + attributes.Set(NfcNdefRecord::kFieldTitles, titles); + } else { + attributes.MergeDictionary(text_attributes.get()); + } + } + + // Populate the given record. + return out->Populate(type, &attributes); +} + +} // namespace nfc_ndef_record_utils +} // namespace chromeos diff --git a/device/nfc/nfc_ndef_record_utils_chromeos.h b/device/nfc/nfc_ndef_record_utils_chromeos.h new file mode 100644 index 0000000..2d96b97a --- /dev/null +++ b/device/nfc/nfc_ndef_record_utils_chromeos.h @@ -0,0 +1,36 @@ +// Copyright 2013 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 "base/values.h" +#include "chromeos/dbus/nfc_record_client.h" + +#ifndef DEVICE_NFC_CHROMEOS_NDEF_RECORD_UTILS_CHROMEOS_H_ +#define DEVICE_NFC_CHROMEOS_NDEF_RECORD_UTILS_CHROMEOS_H_ + +namespace device { +class NfcNdefRecord; +} // namespace device; + +namespace chromeos { +namespace nfc_ndef_record_utils { + +// Converts the NfcNdefRecord |record| to a dictionary that can be passed to +// NfcDeviceClient::Push and NfcTagClient::Write and stores it in |out|. +// Returns false, if an error occurs during conversion. +bool NfcNdefRecordToDBusAttributes( + const device::NfcNdefRecord* record, + base::DictionaryValue* out); + +// Converts an NDEF record D-Bus properties structure to an NfcNdefRecord +// instance by populating the instance passed in |out|. |out| must not be NULL +// and must not be already populated. Returns false, if an error occurs during +// conversion. +bool RecordPropertiesToNfcNdefRecord( + const NfcRecordClient::Properties* properties, + device::NfcNdefRecord* out); + +} // namespace nfc_ndef_record_utils +} // namespace chromeos + +#endif // DEVICE_NFC_CHROMEOS_NDEF_RECORD_UTILS_CHROMEOS_H_ diff --git a/device/nfc/nfc_peer.h b/device/nfc/nfc_peer.h index 1578738..710641e 100644 --- a/device/nfc/nfc_peer.h +++ b/device/nfc/nfc_peer.h @@ -37,12 +37,12 @@ class NfcPeer { public: virtual ~Observer() {} - // This method will be called when an NDEF message |message| from the peer + // This method will be called when an NDEF record |record| from the peer // device |peer| is received. Users can use this method to be notified of // new records on the device and when the initial set of records are - // received from it, if any. - virtual void RecordsReceived(NfcPeer* peer, - const NfcNdefMessage& message) {} + // received from it, if any. All records received from |peer| can be + // accessed by calling |peer->GetNdefMessage()|. + virtual void RecordsReceived(NfcPeer* peer, const NfcNdefRecord* record) {} }; // The ErrorCallback is used by methods to asynchronously report errors. @@ -64,12 +64,12 @@ class NfcPeer { // this only means that no records have yet been received from the device. // Users should use this method in conjunction with the Observer methods // to be notified when the records are ready. - virtual NfcNdefMessage GetNdefMessage() const = 0; + virtual const NfcNdefMessage& GetNdefMessage() const = 0; // Sends the NDEF records contained in |message| to the peer device. On // success, |callback| will be invoked. On failure, |error_callback| will be // invoked. - virtual void PushNdef(NfcNdefMessage* message, + virtual void PushNdef(const NfcNdefMessage& message, const base::Closure& callback, const ErrorCallback& error_callback) = 0; diff --git a/device/nfc/nfc_peer_chromeos.cc b/device/nfc/nfc_peer_chromeos.cc new file mode 100644 index 0000000..a6e864c --- /dev/null +++ b/device/nfc/nfc_peer_chromeos.cc @@ -0,0 +1,196 @@ +// Copyright 2013 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/nfc/nfc_peer_chromeos.h" + +#include <string> +#include <vector> + +#include "base/logging.h" +#include "base/stl_util.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "chromeos/dbus/nfc_device_client.h" +#include "device/nfc/nfc_ndef_record_utils_chromeos.h" +#include "third_party/cros_system_api/dbus/service_constants.h" + +using device::NfcNdefMessage; +using device::NfcNdefRecord; + +namespace chromeos { + +namespace { + +typedef std::vector<dbus::ObjectPath> ObjectPathVector; + +} // namespace + +NfcPeerChromeOS::NfcPeerChromeOS(const dbus::ObjectPath& object_path) + : object_path_(object_path), + weak_ptr_factory_(this) { + // Create record objects for all records that were received before. + const ObjectPathVector& records = + DBusThreadManager::Get()->GetNfcRecordClient()-> + GetRecordsForDevice(object_path_); + for (ObjectPathVector::const_iterator iter = records.begin(); + iter != records.end(); ++iter) { + AddRecord(*iter); + } + DBusThreadManager::Get()->GetNfcRecordClient()->AddObserver(this); +} + +NfcPeerChromeOS::~NfcPeerChromeOS() { + DBusThreadManager::Get()->GetNfcRecordClient()->RemoveObserver(this); + STLDeleteValues(&records_); +} + +void NfcPeerChromeOS::AddObserver(device::NfcPeer::Observer* observer) { + DCHECK(observer); + observers_.AddObserver(observer); +} + +void NfcPeerChromeOS::RemoveObserver(device::NfcPeer::Observer* observer) { + DCHECK(observer); + observers_.RemoveObserver(observer); +} + +std::string NfcPeerChromeOS::GetIdentifier() const { + return object_path_.value(); +} + +const NfcNdefMessage& NfcPeerChromeOS::GetNdefMessage() const { + return message_; +} + +void NfcPeerChromeOS::PushNdef(const NfcNdefMessage& message, + const base::Closure& callback, + const ErrorCallback& error_callback) { + if (message.records().empty()) { + LOG(ERROR) << "Given NDEF message is empty. Cannot push it."; + error_callback.Run(); + return; + } + // TODO(armansito): neard currently supports pushing only one NDEF record + // to a remote device and won't support multiple records until 0.15. Until + // then, report failure if |message| contains more than one record. + if (message.records().size() > 1) { + LOG(ERROR) << "Currently, pushing only 1 NDEF record is supported."; + error_callback.Run(); + return; + } + const NfcNdefRecord* record = message.records()[0]; + base::DictionaryValue attributes; + if (!nfc_ndef_record_utils::NfcNdefRecordToDBusAttributes( + record, &attributes)) { + LOG(ERROR) << "Failed to extract NDEF record fields for NDEF push."; + error_callback.Run(); + return; + } + DBusThreadManager::Get()->GetNfcDeviceClient()->Push( + object_path_, + attributes, + base::Bind(&NfcPeerChromeOS::OnPushNdef, + weak_ptr_factory_.GetWeakPtr(), + callback), + base::Bind(&NfcPeerChromeOS::OnPushNdefError, + weak_ptr_factory_.GetWeakPtr(), + error_callback)); +} + +void NfcPeerChromeOS::StartHandover(HandoverType handover_type, + const base::Closure& callback, + const ErrorCallback& error_callback) { + // TODO(armansito): Initiating handover with a peer is currently not + // supported. For now, return an error immediately. + LOG(ERROR) << "NFC Handover currently not supported."; + error_callback.Run(); +} + +void NfcPeerChromeOS::RecordAdded(const dbus::ObjectPath& object_path) { + // Don't create the record object yet. Instead, wait until all record + // properties have been received and contruct the object and notify observers + // then. + VLOG(1) << "Record added: " << object_path.value() << ". Waiting until " + << "all properties have been fetched to create record object."; +} + +void NfcPeerChromeOS::RecordRemoved(const dbus::ObjectPath& object_path) { + NdefRecordMap::iterator iter = records_.find(object_path); + if (iter == records_.end()) + return; + VLOG(1) << "Lost remote NDEF record object: " << object_path.value() + << ", removing record."; + NfcNdefRecord* record = iter->second; + message_.RemoveRecord(record); + delete record; + records_.erase(iter); +} + +void NfcPeerChromeOS::RecordPropertiesReceived( + const dbus::ObjectPath& object_path) { + VLOG(1) << "Record properties received for: " << object_path.value(); + + // Check if the found record belongs to this device. + NfcDeviceClient::Properties* device_properties = + DBusThreadManager::Get()->GetNfcDeviceClient()-> + GetProperties(object_path_); + DCHECK(device_properties); + bool record_found = false; + const ObjectPathVector& records = device_properties->records.value(); + for (ObjectPathVector::const_iterator iter = records.begin(); + iter != records.end(); ++iter) { + if (*iter == object_path) { + record_found = true; + break; + } + } + if (!record_found) { + VLOG(1) << "Record \"" << object_path.value() << "\" doesn't belong to this" + << " device. Ignoring."; + return; + } + + AddRecord(object_path); +} + +void NfcPeerChromeOS::OnPushNdef(const base::Closure& callback) { + callback.Run(); +} + +void NfcPeerChromeOS::OnPushNdefError(const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + LOG(ERROR) << object_path_.value() << ": Failed to Push NDEF message: " + << error_name << ": " << error_message; + error_callback.Run(); +} + +void NfcPeerChromeOS::AddRecord(const dbus::ObjectPath& object_path) { + // Ignore this call if an entry for this record already exists. + if (records_.find(object_path) != records_.end()) { + VLOG(1) << "Record object for remote \"" << object_path.value() + << "\" already exists."; + return; + } + + NfcRecordClient::Properties* record_properties = + DBusThreadManager::Get()->GetNfcRecordClient()-> + GetProperties(object_path); + DCHECK(record_properties); + + NfcNdefRecord* record = new NfcNdefRecord(); + if (!nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord( + record_properties, record)) { + LOG(ERROR) << "Failed to create record object for record with object " + << "path \"" << object_path.value() << "\""; + delete record; + return; + } + + message_.AddRecord(record); + records_[object_path] = record; + FOR_EACH_OBSERVER(NfcPeer::Observer, observers_, + RecordsReceived(this, record)); +} + +} // namespace chromeos diff --git a/device/nfc/nfc_peer_chromeos.h b/device/nfc/nfc_peer_chromeos.h new file mode 100644 index 0000000..2bd664a --- /dev/null +++ b/device/nfc/nfc_peer_chromeos.h @@ -0,0 +1,81 @@ +// Copyright 2013 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 DEVICE_NFC_NFC_PEER_CHROMEOS_H_ +#define DEVICE_NFC_NFC_PEER_CHROMEOS_H_ + +#include <map> + +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "chromeos/dbus/nfc_record_client.h" +#include "dbus/object_path.h" +#include "device/nfc/nfc_ndef_record.h" +#include "device/nfc/nfc_peer.h" + +namespace chromeos { + +// The NfcPeerChromeOS class implements NfcPeer for the Chrome OS platform. +class NfcPeerChromeOS : public device::NfcPeer, + public NfcRecordClient::Observer { + public: + // NfcPeer overrides. + virtual void AddObserver(device::NfcPeer::Observer* observer) OVERRIDE; + virtual void RemoveObserver(device::NfcPeer::Observer* observer) OVERRIDE; + virtual std::string GetIdentifier() const OVERRIDE; + virtual const device::NfcNdefMessage& GetNdefMessage() const OVERRIDE; + virtual void PushNdef(const device::NfcNdefMessage& message, + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + virtual void StartHandover(HandoverType handover_type, + const base::Closure& callback, + const ErrorCallback& error_callback) OVERRIDE; + + private: + friend class NfcAdapterChromeOS; + + // Mapping from D-Bus object paths to NfcNdefRecord objects. + typedef std::map<dbus::ObjectPath, device::NfcNdefRecord*> NdefRecordMap; + + NfcPeerChromeOS(const dbus::ObjectPath& object_path); + virtual ~NfcPeerChromeOS(); + + // NfcRecordClient::Observer overrides. + virtual void RecordAdded(const dbus::ObjectPath& object_path) OVERRIDE; + virtual void RecordRemoved(const dbus::ObjectPath& object_path) OVERRIDE; + virtual void RecordPropertiesReceived( + const dbus::ObjectPath& object_path) OVERRIDE; + + // Called by dbus:: on completion of the D-Bus method call to push an NDEF. + void OnPushNdef(const base::Closure& callback); + void OnPushNdefError(const ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message); + + // Creates a record object for the record with object path |object_path| and + // notifies the observers, if a record object did not already exist for it. + void AddRecord(const dbus::ObjectPath& object_path); + + // Object path of the peer that we are currently tracking. + dbus::ObjectPath object_path_; + + // A map containing the NDEF records that were received from the peer. + NdefRecordMap records_; + + // Message instance that contains pointers to all created records. + device::NfcNdefMessage message_; + + // List of observers interested in event notifications from us. + ObserverList<device::NfcPeer::Observer> observers_; + + // Note: This should remain the last member so it'll be destroyed and + // invalidate its weak pointers before any other members are destroyed. + base::WeakPtrFactory<NfcPeerChromeOS> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(NfcPeerChromeOS); +}; + +} // namespace chromeos + +#endif // DEVICE_NFC_NFC_PEER_CHROMEOS_H_ |