forked from andijakl/nfcinteractor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
nfcinfo.cpp
1144 lines (1041 loc) · 46.2 KB
/
nfcinfo.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Andreas Jakl ([email protected])
**
** Released under Nokia Example Code License.
** See license.txt in the main project folder.
**
****************************************************************************/
#include "nfcinfo.h"
NfcInfo::NfcInfo(QObject *parent) :
QObject(parent),
m_nfcManager(NULL),
m_cachedTarget(NULL),
m_reportingLevel(AppSettings::OnlyImportantReporting),
m_pendingWriteNdef(false),
m_currentActivity(NfcUninitialized),
m_writeOneTagOnly(true),
m_cachedNdefMessage(NULL),
m_cachedNdefMessageSize(0),
m_cachedRequestType(NfcIdle),
m_unlimitedAdvancedMsgs(true),
m_harmattanPr10(false),
m_usePeerToPeer(true),
m_nfcPeerToPeer(NULL)
{
#if defined(MEEGO_EDITION_HARMATTAN)
// Determine Harmattan FW version
// PR 1.0 doesn't support activating read and write NDEF access at the same time,
// so we need to switch between both modes depending on what the app intends to do.
QSystemInfo* sysInfo = new QSystemInfo(this);
if (sysInfo->version(QSystemInfo::Os) == "1.2" && sysInfo->version(QSystemInfo::Firmware).contains("10.2011.34")) {
qDebug() << "Running Harmattan PR 1.0";
m_harmattanPr10 = true;
}
#endif
// Record model and stats module
m_nfcRecordModel = new NfcRecordModel(this);
m_nfcStats = new NfcStats(this);
m_nfcRecordModel->setNfcStats(m_nfcStats);
connect(m_nfcRecordModel, SIGNAL(recordItemsModified()), this, SLOT(nfcRecordModelChanged()));
// Target analyzer and Ndef parser
m_nfcTargetAnalyzer = new NfcTargetAnalyzer(this);
m_nfcNdefParser = new NfcNdefParser(m_nfcRecordModel, this);
// Relay the signal when the private ndef parser found an image,
// so that the QML UI can react to this.
// Images are stored in the m_imgCache variable; both this and
// the m_nfcNdefParser have a reference to it.
connect(m_nfcNdefParser, SIGNAL(nfcTagImage(int)), this, SIGNAL(nfcTagImage(int)));
}
NfcInfo::~NfcInfo() {
delete m_cachedNdefMessage;
}
void NfcInfo::initAndStartNfcAsync()
{
QTimer::singleShot(50, this, SLOT(initAndStartNfc()));
}
/*!
\brief Initialize the NFC access for NDEF targets and start target detection.
This method emits nfcStatusUpdate signals containing the textual results of
the operation.
\return true if starting target detection was successful.
*/
bool NfcInfo::initAndStartNfc()
{
bool success = false;
if (!m_nfcPeerToPeer)
{
// Peer to peer for SNEP
m_nfcPeerToPeer = new NfcPeerToPeer(this);
m_nfcPeerToPeer->setAppSettings(m_appSettings);
// TODO: Connect all signals of m_nfcPeerToPeer
connect(m_nfcPeerToPeer, SIGNAL(rawMessage(QString)), this, SIGNAL(nfcInfoUpdate(QString)));
connect(m_nfcPeerToPeer, SIGNAL(ndefMessage(QNdefMessage)), this, SLOT(ndefMessageRead(QNdefMessage)));
connect(m_nfcPeerToPeer, SIGNAL(statusMessage(QString)), this, SIGNAL(nfcStatusUpdate(QString)));
connect(m_nfcPeerToPeer, SIGNAL(nfcSendNdefSuccess()), this, SIGNAL(nfcTagWritten()));
}
if (m_nfcRecordModel->size() == 0) {
nfcRecordModelChanged();
// Populate write view with items (for development time only, comment out for release)
// nfcRecordModel->addRecordItem(new NfcRecordItem("Smart Poster", NfcTypes::MsgSmartPoster, NfcTypes::RecordHeader, "", true, true, 1));
// nfcRecordModel->addRecordItem(new NfcRecordItem("URI", NfcTypes::MsgSmartPoster, NfcTypes::RecordUri, "http://www.nokia.com/", false, false, 1));
}
// NfcInfo (this) is the parent; will automaically delete nfcManager
if (!m_nfcManager) {
m_nfcManager = new QNearFieldManager(this);
m_nfcPeerToPeer->setNfcManager(m_nfcManager);
}
const bool nfcAvailable = m_nfcManager->isAvailable();
if (nfcAvailable) {
emit nfcStatusUpdate("Qt reports: NFC is available");
} else {
emit nfcStatusError("Qt reports: NFC is not available");
}
if (m_harmattanPr10) {
// MeeGo Harmattan PR 1.0 only allows one target access mode to be active at the same time
m_nfcManager->setTargetAccessModes(QNearFieldManager::NdefReadTargetAccess);
} else {
m_nfcManager->setTargetAccessModes(QNearFieldManager::NdefReadTargetAccess | QNearFieldManager::NdefWriteTargetAccess | QNearFieldManager::TagTypeSpecificTargetAccess);
}
// Required for autostart tags
m_nfcManager->registerNdefMessageHandler(this, SLOT(targetMessageDetected(QNdefMessage,QNearFieldTarget*)));
// Get notified when the tag gets out of range
connect(m_nfcManager, SIGNAL(targetLost(QNearFieldTarget*)),
this, SLOT(targetLost(QNearFieldTarget*)));
connect(m_nfcManager, SIGNAL(targetDetected(QNearFieldTarget*)),
this, SLOT(targetDetected(QNearFieldTarget*)));
if (m_usePeerToPeer) {
m_nfcPeerToPeer->initAndStartNfc();
}
// Start detecting targets
bool activationSuccessful = m_nfcManager->startTargetDetection();
if (activationSuccessful) {
emit nfcStatusUpdate("Successfully started target detection");
success = true;
} else {
emit nfcStatusError("Error starting NFC target detection");
}
m_currentActivity = NfcIdle;
emit nfcInitialized(success);
return success;
}
/*!
\brief Get a pointer to the NfcRecordModel instance used by this class.
*/
NfcRecordModel * NfcInfo::recordModel() const
{
return m_nfcRecordModel;
}
void NfcInfo::setUnlimitedAdvancedMsgs(const bool unlimited)
{
m_unlimitedAdvancedMsgs = unlimited;
}
/*!
\brief Slot to be called whenever the contents of the record model changed,
so that the contents can be converted to an NDEF message to see if the
resulting size changed.
If the NDEF message size does change, the method will emit the
storedMessageSizeChanged signal, passing the byte-size of the message as
parameter.
*/
void NfcInfo::nfcRecordModelChanged()
{
// Calculate new message size
QNdefMessage* message = recordModel()->convertToNdefMessage();
QByteArray rawMessage = message->toByteArray();
const int bytesize = rawMessage.size();
delete message;
emit storedMessageSizeChanged(bytesize);
}
/*!
\brief Check if NFC is supported by the device and if it
is activated.
Note: currently, this is only implemented for Symbian using
nfcsettings component (which uses native Symbian code to query
the NFC status).
\return true if and only if NFC is available and activated on Symbian.
The nfcStatusError / nfcStatusUpdate signals contain more information
about the actual status (e.g., if the device would support NFC but
the user needs to activate it).
*/
bool NfcInfo::checkNfcStatus()
{
#ifdef Q_OS_SYMBIAN
// Construct a new instance.
nfcSettings = new NfcSettings(this);
// Retrieve the NFC feature support information.
NfcSettings::NfcFeature nfcFeature = nfcSettings->nfcFeature();
if (nfcFeature == NfcSettings::NfcFeatureSupported) {
// Connect signals for receiving mode change and error notifications.
connect(nfcSettings, SIGNAL(nfcModeChanged(NfcSettings::NfcMode)), SLOT(handleNfcModeChange(NfcSettings::NfcMode)));
connect(nfcSettings, SIGNAL(nfcErrorOccurred(NfcSettings::NfcError, int)), SLOT(handleNfcError(NfcSettings::NfcError, int)));
// Retrieve the initial value of the NFC mode setting.
NfcSettings::NfcMode nfcMode = nfcSettings->nfcMode();
if (nfcMode != NfcSettings::NfcModeOn) {
// NFC is supported but not switched on, prompt the user to enable it.
emit nfcStatusError(tr("NFC hardware is available but currently switched off"));
return false;
} else {
emit nfcStatusUpdate(tr("NFC is supported and switched on"));
return true;
}
}
else if (nfcFeature == NfcSettings::NfcFeatureSupportedViaFirmwareUpdate) {
// Display message to user to update device firmware
emit nfcStatusError(tr("Update device firmware to enable NFC support"));
return false;
} else {
// Display message informing the user that NFC is not supported by this device.
emit nfcStatusError(tr("NFC not supported by this device"));
return false;
}
#endif
return false;
}
/*!
\brief Set the image cache to use for storing images retrieved
from tags.
\a tagImageCache instance of the image cache. This
class will not take ownership of the instance!
*/
void NfcInfo::setImageCache(TagImageCache *tagImageCache)
{
m_nfcNdefParser->setImageCache(tagImageCache);
}
/*!
\brief Called by the NearFieldManager whenever it finds a target.
This method will create connections between the target and this class
to be informed about its status. It also attempts to analyze the target
and emits information through nfcStatusUpdate signals. If a write operation
is pending, it will be written to the tag. Otherwise, the tag contents will be
read (if possible).
*/
void NfcInfo::targetDetected(QNearFieldTarget *target)
{
// Handle potential errors emitted by the target
connect(target, SIGNAL(error(QNearFieldTarget::Error,QNearFieldTarget::RequestId)),
this, SLOT(targetError(QNearFieldTarget::Error,QNearFieldTarget::RequestId)));
connect(target, SIGNAL(requestCompleted(const QNearFieldTarget::RequestId)),
this, SLOT(requestCompleted(QNearFieldTarget::RequestId)));
connect(target, SIGNAL(ndefMessagesWritten()),
this, SLOT(ndefMessageWritten()));
m_currentActivity = NfcTargetAnalysis;
// Cache the target in any case for future writing
// (so that we can also write on tags that are empty as of now)
m_cachedTarget = target;
startedTagInteraction();
// Check if the target includes a NDEF message
bool targetHasNdefMessage = target->hasNdefMessage();
if (targetHasNdefMessage) {
emit nfcStatusUpdate("NDEF target detected");
} else {
emit nfcStatusUpdate("Target detected");
}
// Analyze the target and send the info to the UI
emit nfcInfoUpdate(m_nfcTargetAnalyzer->analyzeTarget(target));
m_currentActivity = NfcIdle;
if (!m_pendingWriteNdef) {
// Count number of tags read with the app
// (don't count when in write mode)
m_nfcStats->incTagReadCount();
}
// Check if we have NDEF access and can read or write to the tag
QNearFieldTarget::AccessMethods accessMethods = target->accessMethods();
// Work with NDEF both when NdefAccess or TagTypeSpecificAccess is set
// This is due to the way the enum is defined (NdefAccess = 0x0,
// TagTypeSpecificAccess = 0x1).
// When testing for a flag, it is therefore impossible for a tag that has
// tag type specific access to also report NdefAccess.
// See: https://bugreports.qt-project.org/browse/QTMOBILITY-2024
if (accessMethods.testFlag(QNearFieldTarget::NdefAccess) ||
accessMethods.testFlag(QNearFieldTarget::TagTypeSpecificAccess)) {
#ifdef Q_OS_SYMBIAN
// Bug workaround on Symbian: hasNdefMessage() always returns false
// for a NFC Forum Tag Type 4, even if an NDEF message is present on the tag.
// See: https://bugreports.qt.nokia.com/browse/QTMOBILITY-2018
if (target->type() == QNearFieldTarget::NfcTagType4 && !targetHasNdefMessage) {
targetHasNdefMessage = true;
}
#endif
// Is a write operation pending?
if (!m_pendingWriteNdef)
{
// NO write operation pending, so read the tag if possible
// If target has an NDEF message...
if (targetHasNdefMessage)
{
m_currentActivity = NfcNdefReading;
m_cachedRequestType = NfcNdefReading;
// Target has NDEF messages: read them (asynchronous)
connect(target, SIGNAL(ndefMessageRead(QNdefMessage)),
this, SLOT(ndefMessageRead(QNdefMessage)));
m_cachedRequestId = target->readNdefMessages();
} else {
// No NDEF message detected
qDebug() << "No NDEF message detected";
emit nfcTagContents(tr("No NDEF message detected"), "");
stoppedTagInteraction();
}
} else {
// Write operation is pending, so attempt writing the message.
if (m_harmattanPr10) {
m_nfcManager->setTargetAccessModes(QNearFieldManager::NdefWriteTargetAccess);
}
writeCachedNdefMessage();
}
} else if (accessMethods.testFlag(QNearFieldTarget::LlcpAccess) && m_usePeerToPeer) {
// Establish peer to peer connection
m_nfcPeerToPeer->targetDetected(target);
// Is a write operation pending?
if (m_pendingWriteNdef)
{
writeCachedNdefMessage();
}
} else {
// No NDEF access - no further interaction
qDebug() << "No NDEF access to the target";
stoppedTagInteraction();
}
}
/*!
\brief Slot needed for the registerNdefMessageHandler() method.
This isn't used by the app directly, as it reads NDEF messages through the
two-step process of first detecting targets, and then reading the messages.
However, for registering an app for autostart on nfc tag touch, Qt Mobility
requires to also use the registerNdefMessageHandler() handler, as this
slot will get called when the app has been autostarted through a tag.
*/
void NfcInfo::targetMessageDetected(const QNdefMessage &message, QNearFieldTarget* target)
{
// TODO: on Symbian, target isn't set -> check and maybe create a bug report.
qDebug() << "NDEF Message detected (-> Autostart)!";
emit nfcStatusUpdate("NDEF Message detected");
if (target) {
// Analyze the target and send the info to the UI
emit nfcInfoUpdate(m_nfcTargetAnalyzer->analyzeTarget(target));
}
// Go through all records in the message
ndefMessageRead(message);
#ifdef MEEGO_EDITION_HARMATTAN
// MeeGo: raise the app to the foreground in case it was autostarted
// by touching the tag AND it was already running in the background.
// If we wouldn't do it, the app would receive the tag, but remain
// in the background.
if (m_declarativeView) {
m_declarativeView->raise();
}
#endif
}
/*!
\brief Emits the nfcTagContents containing a textual description of the
contents of the NDEF message.
In case pictures are found, these are added to the image cache and the
nfcTagImage signal is emitted together with the image ID.
\param message the NDEF message to analyze.
*/
void NfcInfo::ndefMessageRead(const QNdefMessage &message)
{
QString fileName = storeNdefToFile(QString(), message, true);
emit nfcTagContents(m_nfcNdefParser->parseNdefMessage(message), fileName);
stoppedTagInteraction();
}
/*!
\brief Store a raw NDEF message to a file.
The file will be stored inside the directory specified by AppSettings,
which is further adapted by the collected parameter.
\param fileName if specified, the filename to use. The ".txt" extension
will be added to the file name automatically if it does not have a ".txt"
extension already. If an empty QString is passed ("QString()"), a default
file name will be created, based on the current date, time and (if available)
the tag type.
\param message the NDEF message to store as raw byte array in the file.
\param collected influences the sub directory of the data directory.
If set to true, it will go to the collected subdirectory for
auto-collected/saved tags. If set to true, it will go to the subdirectory
for manually saved messages.
*/
QString NfcInfo::storeNdefToFile(const QString& fileName, const QNdefMessage &message, const bool collected)
{
QString fullFileName = "";
if (m_appSettings && m_appSettings->logNdefToFile()) {
// Store tag contents to the log file if enabled
const QString writeDir = m_appSettings->logNdefDir(collected);
QDir dir("/");
dir.mkpath(writeDir);
if (QDir::setCurrent(writeDir)) {
if (fileName.isEmpty()) {
// Generate file name
QDateTime now = QDateTime::currentDateTime();
fullFileName = now.toString("yyyy.MM.dd - hh.mm.ss");
if (!m_nfcTargetAnalyzer->m_tagInfo.tagTypeName.isEmpty()) {
fullFileName.append(" - ");
fullFileName.append(m_nfcTargetAnalyzer->m_tagInfo.tagTypeName);
}
fullFileName.append(".txt");
} else {
fullFileName = fileName;
if (fullFileName.right(4).toLower() != ".txt") {
fullFileName.append(".txt");
}
}
QFile tagFile(fullFileName);
if (tagFile.open(QIODevice::WriteOnly)) {
tagFile.write(message.toByteArray());
tagFile.close();
} else {
qDebug() << "Unable to open file for writing: " << tagFile.fileName();
}
fullFileName = writeDir + fullFileName;
} else {
emit nfcStatusError("Unable to open data directory (" + writeDir + ") - please check the application settings");
qDebug() << "Unable to set current directory to: " << writeDir;
}
}
return fullFileName;
}
/*!
\brief Create the message for writing to the tag and attempt
to write it.
\param writeOneTagOnly automatically switch back to tag reading
mode after writing one tag, or stay in tag writing mode and also
write the same message to further targets.
\return if it was already possible to write to the tag. If
false is returned, the message is cached and will be written
when a writable target is available. Only one message is cached;
if this method is called a second time before the first message
is actually written to a tag, the old message will be discarded
and only the later one written to the tag.
*/
bool NfcInfo::nfcWriteTag(const bool writeOneTagOnly)
{
// Convert the model into a NDEF message
QNdefMessage* message = recordModel()->convertToNdefMessage();
m_cachedNdefContainsAdvMsg = recordModel()->containsAdvMsg();
// Set to writing mode
emit nfcModeChanged(NfcTypes::nfcWriting);
if (m_harmattanPr10) {
m_nfcManager->setTargetAccessModes(QNearFieldManager::NdefWriteTargetAccess);
}
QByteArray rawMessage = message->toByteArray();
emit nfcStatusUpdate("Created message (size: " + QString::number(rawMessage.size()) + " bytes)");
// Write the message (containing either a URL or plain text) to the target.
if (m_cachedNdefMessage) { delete m_cachedNdefMessage; }
m_cachedNdefMessage = message;
m_cachedNdefMessageSize = m_cachedNdefMessage->toByteArray().size();
m_pendingWriteNdef = true;
m_writeOneTagOnly = writeOneTagOnly;
return writeCachedNdefMessage();
}
/*!
\brief Load an NDEF message from a file and attempt
to write it.
The specified file has to contain the raw and complete NDEF
message contents.
\param fileName data file that contains the complete, binary
contents of an NDEF message.
\param writeOneTagOnly automatically switch back to tag reading
mode after writing one tag, or stay in tag writing mode and also
write the same message to further targets.
\return if it was already possible to write to the tag. If
false is returned, the message is cached and will be written
when a writable target is available. Only one message is cached;
if this method is called a second time before the first message
is actually written to a tag, the old message will be discarded
and only the later one written to the tag.
Additionally, false can also be returned if loading or parsing
the file was not successful. In this case, the method will also
emit nfcTagWriteError() with the error message, and does not
switch the class to write mode.
*/
bool NfcInfo::nfcWriteTag(const QString& fileName, const bool writeOneTagOnly)
{
QNdefMessage message = loadNdefFromFile(fileName);
if (message.isEmpty()) {
// Error while loading from the file - don't switch to writing mode.
// Error info has already been emitted by loading method.
return false;
}
// Set to writing mode
emit nfcModeChanged(NfcTypes::nfcWriting);
if (m_harmattanPr10) {
m_nfcManager->setTargetAccessModes(QNearFieldManager::NdefWriteTargetAccess);
}
// Write the message (containing either a URL or plain text) to the target.
if (m_cachedNdefMessage) { delete m_cachedNdefMessage; }
m_cachedNdefMessage = new QNdefMessage(message);
m_cachedNdefMessageSize = m_cachedNdefMessage->toByteArray().size();
m_pendingWriteNdef = true;
m_writeOneTagOnly = writeOneTagOnly;
return writeCachedNdefMessage();
}
/*!
\brief Load tag contents from file name and put contents into
the record model for editing.
\param fileName data file that contains the complete, binary
contents of an NDEF message.
*/
bool NfcInfo::nfcEditTag(const QString& fileName)
{
QNdefMessage message = loadNdefFromFile(fileName);
if (message.isEmpty()) {
// Error while loading from the file - don't switch to writing mode.
// Error info has already been emitted by loading method.
return false;
}
// Parse contents of the message into the record model
m_nfcNdefParser->setParseToModel(true);
m_nfcNdefParser->parseNdefMessage(message);
m_nfcNdefParser->setParseToModel(false);
return true;
}
QString NfcInfo::nfcSaveModelToFile(const QString &fileName)
{
QString savedFileName = storeNdefToFile(fileName, *m_nfcRecordModel->convertToNdefMessage(), false);
if (!savedFileName.isEmpty()) {
emit nfcStatusSuccess("Stored NDEF message to " + savedFileName);
} else {
emit nfcStatusError("Unable to save NDEF message");
}
return savedFileName;
}
QNdefMessage NfcInfo::loadNdefFromFile(const QString& fileName)
{
qDebug() << "Load tag: " << fileName;
if (fileName.isEmpty()) {
emit nfcTagWriteError("No file name specified");
return QNdefMessage();
}
// Load the NDEF message from the specified file name
QFile tagFile(fileName);
if (!tagFile.open(QIODevice::ReadOnly)) {
emit nfcTagWriteError("Unable to open file: " + fileName);
return QNdefMessage();
}
QByteArray rawMessage = tagFile.readAll();
tagFile.close();
if (rawMessage.isEmpty()) {
// Check if reading the file was successful
emit nfcTagWriteError("Unable to load file: " + fileName);
return QNdefMessage();
}
emit nfcStatusUpdate("Loaded message (size: " + QString::number(rawMessage.size()) + " bytes)");
QNdefMessage message = QNdefMessage::fromByteArray(rawMessage);
if (message.isEmpty()) {
// Unable to create an NDEF message from the file
emit nfcTagWriteError("Unable to create NDEF message from file: " + fileName);
return QNdefMessage();
}
return message;
}
/*!
\brief Stop waiting to write a tag, and switch back to reading mode.
*/
void NfcInfo::nfcStopWritingTags()
{
m_pendingWriteNdef = false;
m_writeOneTagOnly = false;
emit nfcModeChanged(NfcTypes::nfcReading);
if (m_harmattanPr10) {
m_nfcManager->setTargetAccessModes(QNearFieldManager::NdefReadTargetAccess);
}
}
/*!
\brief Attempt to write the currently cached message to the tag.
\return true if it was possible to send the request to the tag.
*/
bool NfcInfo::writeCachedNdefMessage()
{
bool success = false;
if (m_pendingWriteNdef && m_cachedNdefMessage)
{
if (!m_unlimitedAdvancedMsgs) {
qDebug() << "Advanced messages written: " << m_nfcStats->advMsgWrittenCount();
}
if (m_cachedNdefContainsAdvMsg &&
!m_unlimitedAdvancedMsgs &&
m_nfcStats->advMsgWrittenCount() > ADV_MSG_WRITE_COUNT) {
// Not allowed to write more advanced tags in trial mode
// Setting success to true will trigger the signal that the
// tag interaction is stopped.
success = true;
// Don't switch to reading mode by default, as the app would still stay
// in the write page and then no longer show any writing issues,
// but instead succeed in reading the tag.
if (!m_writeOneTagOnly) {
// If writing only one tag, deactivate the writing mode again.
m_pendingWriteNdef = false;
emit nfcModeChanged(NfcTypes::nfcReading);
if (m_harmattanPr10) {
m_nfcManager->setTargetAccessModes(QNearFieldManager::NdefReadTargetAccess);
}
}
emit nfcTagWriteExceeded();
} else {
if (m_cachedTarget)
{
// Check target access mode
QNearFieldManager::TargetAccessModes accessModes = m_nfcManager->targetAccessModes();
// Writing access is active - we should be able to write
if (m_cachedTarget->accessMethods().testFlag(QNearFieldTarget::LlcpAccess) &&
m_usePeerToPeer && m_nfcPeerToPeer)
{
// -----------------------------------------------------
// Peer to peer (SNEP)
m_nfcPeerToPeer->sendNdefMessage(m_cachedNdefMessage);
}
else if (accessModes.testFlag(QNearFieldManager::NdefWriteTargetAccess))
{
// -----------------------------------------------------
// NDEF Access
m_currentActivity = NfcNdefWriting;
if (m_appSettings->deleteTagBeforeWriting() && m_cachedRequestType != NfcNdefDeleting) {
// Write an empty message first
m_cachedRequestType = NfcNdefDeleting;
emit nfcStatusUpdate("Writing empty message to the tag");
// TODO: check if we need to add an empty record, or if
// formatting is also done like this.
m_cachedRequestId = m_cachedTarget->writeNdefMessages(QList<QNdefMessage>() << (QNdefMessage()));
} else {
qDebug() << "Writing message: " << m_cachedNdefMessage->toByteArray();
// Either the empty message was already written, or
// configuration is not set to delete the message first.
m_cachedRequestType = NfcNdefWriting;
emit nfcStatusUpdate("Writing message to the tag");
m_cachedRequestId = m_cachedTarget->writeNdefMessages(QList<QNdefMessage>() << (*m_cachedNdefMessage));
}
success = true;
if (!m_writeOneTagOnly && m_cachedRequestType != NfcNdefDeleting) {
// If writing only one tag, deactivate the writing mode again.
m_pendingWriteNdef = false;
emit nfcModeChanged(NfcTypes::nfcReading);
if (m_harmattanPr10) {
m_nfcManager->setTargetAccessModes(QNearFieldManager::NdefReadTargetAccess);
}
}
}
else
{
// -----------------------------------------------------
// Not in right mode / not right target
emit nfcStatusUpdate("Please touch the tag again to write the message");
}
} else {
// Can't write - no cached target available
emit nfcStatusUpdate("Please touch a tag to write the message");
}
}
}
if (!success) {
// Didn't start a request to write a message - finished interacting
// with the tag
stoppedTagInteraction();
}
return success;
}
/*!
\brief This method should be called by the code of this class whenever the
app is starting interaction with a tag.
This also emits the nfcStartingTagInteraction() signal, so that the UI
can for example show a busy animation, informing the user that he should
keep the phone close to the tag until tag interaction is finished.
*/
void NfcInfo::startedTagInteraction() {
if (!m_nfcTagInteractionActive) {
m_nfcTagInteractionActive = true;
emit nfcStartingTagInteraction();
qDebug() << "*** Starting tag interaction...";
}
}
/*!
\brief This method should be called by the code of this class whenever the
app is stopping interaction with a tag.
This also emits the nfcStoppedTagInteraction() signal, so that the UI
can for example stop the busy animation, informing the user that it's now
safe to remove the phone from the tag again.
*/
void NfcInfo::stoppedTagInteraction() {
if (m_nfcTagInteractionActive) {
m_nfcTagInteractionActive = false;
emit nfcStoppedTagInteraction();
qDebug() << "*** Stopped tag interaction...";
}
}
/*!
\brief Apply the new app settings to the NFC manager classes.
*/
void NfcInfo::applySettings()
{
if (m_nfcPeerToPeer) {
m_nfcPeerToPeer->applySettings();
}
}
/*!
\brief Slot for handling when the target was lost (usually when
it gets out of range.
*/
void NfcInfo::targetLost(QNearFieldTarget *target)
{
if (m_nfcPeerToPeer) {
m_nfcPeerToPeer->targetLost(target);
}
m_cachedTarget = NULL;
target->deleteLater();
stoppedTagInteraction();
emit nfcStatusUpdate("Target lost");
}
/*!
\brief Slot for handling an error with a request to the target.
Emits the nfcTagError signal containing a description of the error,
or the nfcTagWriteError signal if the error occured during writing,
including a more detailed analysis of what could have gone wrong
(as the Qt Mobility APIs don't usually report a reason).
*/
void NfcInfo::targetError(QNearFieldTarget::Error error, const QNearFieldTarget::RequestId &id)
{
QString errorText("Error: " + convertTargetErrorToString(error));
qDebug() << errorText;
if (id == m_cachedRequestId && m_cachedRequestType == NfcNdefDeleting) {
// Writing the empty message failed - try to write the full message
// before reporting the error
writeCachedNdefMessage();
errorText.append("\n\nFailed to write empty message - attempting to write full NDEF message.");
emit nfcTagWriteError(errorText);
} else if (id == m_cachedRequestId && m_cachedRequestType == NfcNdefWriting) {
m_cachedRequestType = NfcIdle;
if (!m_pendingWriteNdef) {
m_currentActivity = NfcIdle;
}
errorText.append("\n\nUnable to write message.");
if (m_nfcTargetAnalyzer->m_tagInfo.combinedWriteAccess() == NearFieldTargetInfo::NfcAccessForbidden) {
errorText.append("\nTag is write-protected.");
} else if (m_nfcTargetAnalyzer->m_tagInfo.combinedWriteAccess() == NearFieldTargetInfo::NfcAccessUnknown) {
errorText.append("\nTag write access unknown.");
}
// Compare tag size to message size
const int tagWritableSize = m_nfcTargetAnalyzer->m_tagInfo.tagWritableSize;
// Check if the app was successful in determining the tag size
if (tagWritableSize > 0 && m_cachedNdefMessageSize > 0) {
// Known tag size - we can do a proper check
if (m_cachedNdefMessageSize > tagWritableSize) {
// Message was too large for the target.
errorText.append("\n\nMessage (" + QString::number(m_cachedNdefMessageSize) + " bytes) and control data were probably too large for the available tag size (" + QString::number(tagWritableSize) + " bytes).");
}
} else if (tagWritableSize <= 0 && m_cachedNdefMessageSize > 0 && m_cachedTarget) {
// Don't know the tag size - print a warning for typical tag sizes
// that we have have to guess
// This happens if the tag has issues, if Qt Mobility APIs
// didn't allow reading the size (on Symbian Anna or Harmattan),
// or if this app doesn't support reading the tag size.
int memorySizeWarning;
switch (m_cachedTarget->type()) {
case QNearFieldTarget::NfcTagType2:
memorySizeWarning = GUESS_TYPE2_SIZE;
break;
case QNearFieldTarget::NfcTagType3:
memorySizeWarning = GUESS_TYPE3_SIZE;
break;
case QNearFieldTarget::NfcTagType4:
memorySizeWarning = GUESS_TYPE4_SIZE;
break;
case QNearFieldTarget::MifareTag:
memorySizeWarning = GUESS_MIFARE_SIZE;
break;
case QNearFieldTarget::NfcTagType1:
default:
memorySizeWarning = GUESS_TYPE1_SIZE;
break;
}
if (m_cachedNdefMessageSize > memorySizeWarning) {
errorText.append("\n\nMessage (" + QString::number(m_cachedNdefMessageSize) + " bytes) plus control data might be too large for the " + m_nfcTargetAnalyzer->convertTagTypeToString(m_cachedTarget->type()) + " target?");
}
}
emit nfcTagWriteError(errorText);
stoppedTagInteraction();
} else if (id == m_cachedRequestId && m_cachedRequestType == NfcNdefReading) {
// Error while reading the tag
emit nfcTagError(errorText);
stoppedTagInteraction();
} else {
// Filter errors when not reading or writing
// E.g., during analysis, the Qt Mobility APIs might not support some tag-specific
// access, resulting in InvalidParametersError or UnsupportedError.
// The analysis part will correctly handle those cases, no need to spam the user
// with error messages.
if (m_reportingLevel == AppSettings::FullReporting ||
(error != QNearFieldTarget::InvalidParametersError &&
error != QNearFieldTarget::UnsupportedError)) {
emit nfcTagError(errorText);
}
}
}
/*!
\brief Slot called by Qt Mobility when a request has been completed.
This method emits the nfcStatusUpdate signal to log the event in the
user interface. In case the request was of a kind that contains a
response, information about the response will also be emitted through
another signal of the same type. In case the response is a byte array,
it will be printed as a string of hex characters.
*/
void NfcInfo::requestCompleted(const QNearFieldTarget::RequestId &id)
{
QString message;
bool showAnotherTagWriteMsg = false;
if (id == m_cachedRequestId) {
bool noStatusChange = false;
switch (m_cachedRequestType) {
case NfcIdle: {
message = "Active request completed.";
m_currentActivity = NfcIdle;
break; }
case NfcNdefReading: {
message = "Read request completed.";
m_currentActivity = NfcIdle;
break; }
case NfcNdefDeleting: {
// requestCompleted() is not called on Symbian, only
// ndefMessageWritten().
// On the n9, both requestCompleted() and ndefMessageWritten()
// are called after the empty message has been written.
// -> only start writing the "real" message from one place,
// so that we don't start two simultaneous write requests on the N9.
// Also, don't change the status of the class to say that
// we stopped tag interaction.
// Plus, the cached request type needs to remain at deleting,
// so that the write method knows that it should proceed to the
// second step and write the real message now.
noStatusChange = true;
break; }
case NfcNdefWriting: {
message = "Write request completed.";
if (m_pendingWriteNdef) {
// Writing tags is still pending - means the user can write another
// tag with the same contents.
showAnotherTagWriteMsg = true;
} else {
m_currentActivity = NfcIdle; // Read or write request finished
}
break; }
default: {
message = "Request completed.";
break; }
}
if (!message.isEmpty()) {
qDebug() << message;
if (!(m_cachedRequestType == NfcNdefWriting && m_reportingLevel == AppSettings::OnlyImportantReporting)) {
// Writing success will already be reported by the nfcTagWritten method
// (this method is called as well on MeeGo, resulting in 2x status updates).
// Therefore, for writing, do not print this status mesesage if
// reporting is set to only important.
emit nfcStatusSuccess(message);
}
}
if (!noStatusChange) {
m_cachedRequestType = NfcIdle;
stoppedTagInteraction();
}
} else {
message = "Request completed.";
if (m_reportingLevel == AppSettings::DebugReporting) {
qDebug() << message;
}
}
// This is already done by the nfcTagWritten() method.
// if (showAnotherTagWriteMsg) {
// // Emit the message that the user can write another tag.
// // (after we emitted the message that the previous write request completed).
// emit nfcStatusUpdate("Touch another tag to write again.");
// }
// Request the response in case we're in debug reporting mode
// Usually, if the response is important, it will be handled directly
// by the requesting method.
if (m_reportingLevel == AppSettings::DebugReporting) {
// Print the response of the tag to the debug output
// in case debug reporting is active.
if (m_cachedTarget)
{
QVariant response = m_cachedTarget->requestResponse(id);
if (response.isValid()) {
if (response.type() == QVariant::ByteArray) {
//emit nfcStatusUpdate("Response (" + QString(response.typeName()) + ")");
qDebug() << "Response (" << QString(response.typeName()) << ")";
} else {
//emit nfcStatusUpdate("Response (" + QString(response.typeName()) + "): " + response.toString());
qDebug() << "Response (" << QString(response.typeName()) << "): " << response.toString();
}
if (response.type() == QVariant::ByteArray) {
QByteArray p = response.toByteArray();
QString arrayContents = "";
for (int i = 0; i < p.size(); ++i) {
arrayContents.append(QString("0x") + QString::number(p.at(i), 16) + " ");
}
qDebug() << "Raw contents of response:\n" << arrayContents;
}
}
}
}
}
/*!
\brief Slot called by Qt Mobility when an NDEF message was successfully
written to a tag.
Emits an nfcStatusUpdate signal to log this in the user interface.
On MeeGo, both the requestCompleted() method and this method will be called
when writing a tag.
*/
void NfcInfo::ndefMessageWritten()
{
if (m_cachedRequestType == NfcNdefDeleting) {
// Start writing the full message, now that
// the empty message has been written
emit nfcStatusSuccess("Empty message written.");
// Need to start the second write request with a delay from
// a separate thread; wouldn't work otherwise.
QTimer::singleShot(50, this, SLOT(writeCachedNdefMessage()));
return;
}
// Store the composed message type count to the actual written count
m_nfcStats->commitComposedToWrittenCount();
emit nfcTagWritten();
stoppedTagInteraction();
if (m_pendingWriteNdef) {
// Writing tags is still pending - means the user can write another
// tag with the same contents.
// If the class is only supposed to write to one tag,
// writeCachedNdefMessage() would have changed m_pendingWriteNdef to false.
emit nfcStatusUpdate("Touch another tag to write again.");
} else {
m_currentActivity = NfcIdle;