1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "device/nfc/nfc_peer_chromeos.h"
6
7#include <string>
8#include <vector>
9
10#include "base/logging.h"
11#include "base/stl_util.h"
12#include "chromeos/dbus/dbus_thread_manager.h"
13#include "chromeos/dbus/nfc_device_client.h"
14#include "device/nfc/nfc_ndef_record_utils_chromeos.h"
15#include "third_party/cros_system_api/dbus/service_constants.h"
16
17using device::NfcNdefMessage;
18using device::NfcNdefRecord;
19
20namespace chromeos {
21
22namespace {
23
24typedef std::vector<dbus::ObjectPath> ObjectPathVector;
25
26}  // namespace
27
28NfcPeerChromeOS::NfcPeerChromeOS(const dbus::ObjectPath& object_path)
29    : object_path_(object_path),
30      weak_ptr_factory_(this) {
31  // Create record objects for all records that were received before.
32  const ObjectPathVector& records =
33      DBusThreadManager::Get()->GetNfcRecordClient()->
34          GetRecordsForDevice(object_path_);
35  for (ObjectPathVector::const_iterator iter = records.begin();
36       iter != records.end(); ++iter) {
37    AddRecord(*iter);
38  }
39  DBusThreadManager::Get()->GetNfcRecordClient()->AddObserver(this);
40}
41
42NfcPeerChromeOS::~NfcPeerChromeOS() {
43  DBusThreadManager::Get()->GetNfcRecordClient()->RemoveObserver(this);
44  STLDeleteValues(&records_);
45}
46
47void NfcPeerChromeOS::AddObserver(device::NfcPeer::Observer* observer) {
48  DCHECK(observer);
49  observers_.AddObserver(observer);
50}
51
52void NfcPeerChromeOS::RemoveObserver(device::NfcPeer::Observer* observer) {
53  DCHECK(observer);
54  observers_.RemoveObserver(observer);
55}
56
57std::string NfcPeerChromeOS::GetIdentifier() const {
58  return object_path_.value();
59}
60
61const NfcNdefMessage& NfcPeerChromeOS::GetNdefMessage() const {
62  return message_;
63}
64
65void NfcPeerChromeOS::PushNdef(const NfcNdefMessage& message,
66                               const base::Closure& callback,
67                               const ErrorCallback& error_callback) {
68  if (message.records().empty()) {
69    LOG(ERROR) << "Given NDEF message is empty. Cannot push it.";
70    error_callback.Run();
71    return;
72  }
73  // TODO(armansito): neard currently supports pushing only one NDEF record
74  // to a remote device and won't support multiple records until 0.15. Until
75  // then, report failure if |message| contains more than one record.
76  if (message.records().size() > 1) {
77    LOG(ERROR) << "Currently, pushing only 1 NDEF record is supported.";
78    error_callback.Run();
79    return;
80  }
81  const NfcNdefRecord* record = message.records()[0];
82  base::DictionaryValue attributes;
83  if (!nfc_ndef_record_utils::NfcNdefRecordToDBusAttributes(
84          record, &attributes)) {
85    LOG(ERROR) << "Failed to extract NDEF record fields for NDEF push.";
86    error_callback.Run();
87    return;
88  }
89  DBusThreadManager::Get()->GetNfcDeviceClient()->Push(
90      object_path_,
91      attributes,
92      base::Bind(&NfcPeerChromeOS::OnPushNdef,
93                 weak_ptr_factory_.GetWeakPtr(),
94                 callback),
95      base::Bind(&NfcPeerChromeOS::OnPushNdefError,
96                 weak_ptr_factory_.GetWeakPtr(),
97                 error_callback));
98}
99
100void NfcPeerChromeOS::StartHandover(HandoverType handover_type,
101                                    const base::Closure& callback,
102                                    const ErrorCallback& error_callback) {
103  // TODO(armansito): Initiating handover with a peer is currently not
104  // supported. For now, return an error immediately.
105  LOG(ERROR) << "NFC Handover currently not supported.";
106  error_callback.Run();
107}
108
109void NfcPeerChromeOS::RecordAdded(const dbus::ObjectPath& object_path) {
110  // Don't create the record object yet. Instead, wait until all record
111  // properties have been received and contruct the object and notify observers
112  // then.
113  VLOG(1) << "Record added: " << object_path.value() << ". Waiting until "
114          << "all properties have been fetched to create record object.";
115}
116
117void NfcPeerChromeOS::RecordRemoved(const dbus::ObjectPath& object_path) {
118  NdefRecordMap::iterator iter = records_.find(object_path);
119  if (iter == records_.end())
120    return;
121  VLOG(1) << "Lost remote NDEF record object: " << object_path.value()
122          << ", removing record.";
123  NfcNdefRecord* record = iter->second;
124  message_.RemoveRecord(record);
125  delete record;
126  records_.erase(iter);
127}
128
129void NfcPeerChromeOS::RecordPropertiesReceived(
130    const dbus::ObjectPath& object_path) {
131  VLOG(1) << "Record properties received for: " << object_path.value();
132
133  // Check if the found record belongs to this device.
134  bool record_found = false;
135  const ObjectPathVector& records =
136      DBusThreadManager::Get()->GetNfcRecordClient()->
137          GetRecordsForDevice(object_path_);
138  for (ObjectPathVector::const_iterator iter = records.begin();
139       iter != records.end(); ++iter) {
140    if (*iter == object_path) {
141      record_found = true;
142      break;
143    }
144  }
145  if (!record_found) {
146    VLOG(1) << "Record \"" << object_path.value() << "\" doesn't belong to this"
147            << " device. Ignoring.";
148    return;
149  }
150
151  AddRecord(object_path);
152}
153
154void NfcPeerChromeOS::OnPushNdef(const base::Closure& callback) {
155  callback.Run();
156}
157
158void NfcPeerChromeOS::OnPushNdefError(const ErrorCallback& error_callback,
159                                      const std::string& error_name,
160                                      const std::string& error_message) {
161  LOG(ERROR) << object_path_.value() << ": Failed to Push NDEF message: "
162             << error_name << ": " << error_message;
163  error_callback.Run();
164}
165
166void NfcPeerChromeOS::AddRecord(const dbus::ObjectPath& object_path) {
167  // Ignore this call if an entry for this record already exists.
168  if (records_.find(object_path) != records_.end()) {
169    VLOG(1) << "Record object for remote \"" << object_path.value()
170            << "\" already exists.";
171    return;
172  }
173
174  NfcRecordClient::Properties* record_properties =
175      DBusThreadManager::Get()->GetNfcRecordClient()->
176          GetProperties(object_path);
177  DCHECK(record_properties);
178
179  NfcNdefRecord* record = new NfcNdefRecord();
180  if (!nfc_ndef_record_utils::RecordPropertiesToNfcNdefRecord(
181          record_properties, record)) {
182    LOG(ERROR) << "Failed to create record object for record with object "
183               << "path \"" << object_path.value() << "\"";
184    delete record;
185    return;
186  }
187
188  message_.AddRecord(record);
189  records_[object_path] = record;
190  FOR_EACH_OBSERVER(NfcPeer::Observer, observers_,
191                    RecordReceived(this, record));
192}
193
194}  // namespace chromeos
195