• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.nfc.dhimpl;
18 
19 import android.annotation.Nullable;
20 import android.nfc.FormatException;
21 import android.nfc.NdefMessage;
22 import android.nfc.tech.IsoDep;
23 import android.nfc.tech.MifareClassic;
24 import android.nfc.tech.MifareUltralight;
25 import android.nfc.tech.Ndef;
26 import android.nfc.tech.NfcA;
27 import android.nfc.tech.NfcB;
28 import android.nfc.tech.NfcBarcode;
29 import android.nfc.tech.NfcF;
30 import android.nfc.tech.NfcV;
31 import android.nfc.tech.TagTechnology;
32 import android.os.Bundle;
33 import android.util.Log;
34 
35 import com.android.nfc.DeviceHost;
36 import com.android.nfc.DeviceHost.TagEndpoint;
37 
38 /** Native interface to the NFC tag functions */
39 public class NativeNfcTag implements TagEndpoint {
40     static final boolean DBG = true;
41 
42     static final int STATUS_CODE_TARGET_LOST = 146;
43 
44     private int[] mTechList;
45     private int[] mTechHandles;
46     private int[] mTechLibNfcTypes;
47     private Bundle[] mTechExtras;
48     private byte[][] mTechPollBytes;
49     private byte[][] mTechActBytes;
50     private byte[] mUid;
51     // Based on flag send T2T tag classification request
52     private boolean mClassifyT2T = true;
53 
54     // mConnectedHandle stores the *real* libnfc handle
55     // that we're connected to.
56     private int mConnectedHandle;
57 
58     // mConnectedTechIndex stores to which technology
59     // the upper layer stack is connected. Note that
60     // we may be connected to a libnfchandle without being
61     // connected to a technology - technology changes
62     // may occur runtime, whereas the underlying handle
63     // could stay present. Usually all technologies are on the
64     // same handle, with the exception of multi-protocol
65     // tags.
66     private int mConnectedTechIndex; // Index in mTechHandles
67 
68     private final String TAG = "NativeNfcTag";
69 
70     private boolean mIsPresent; // Whether the tag is known to be still present
71 
72     private PresenceCheckWatchdog mWatchdog;
73 
74     private boolean mIsRemovalDetectionModeReq = false;
75 
76     class PresenceCheckWatchdog extends Thread {
77 
78         private final int watchdogTimeout;
79         private DeviceHost.TagDisconnectedCallback tagDisconnectedCallback;
80 
81         private boolean isPresent = true;
82         private boolean isStopped = false;
83         private boolean isPaused = false;
84         private boolean doCheck = true;
85 
PresenceCheckWatchdog( int presenceCheckDelay, @Nullable DeviceHost.TagDisconnectedCallback callback)86         PresenceCheckWatchdog(
87                 int presenceCheckDelay, @Nullable DeviceHost.TagDisconnectedCallback callback) {
88             watchdogTimeout = presenceCheckDelay;
89             tagDisconnectedCallback = callback;
90         }
91 
getPresenceCheckTimeout()92         public synchronized int getPresenceCheckTimeout() {
93             return watchdogTimeout;
94         }
95 
pause()96         public synchronized void pause() {
97             isPaused = true;
98             doCheck = false;
99             this.notifyAll();
100         }
101 
doResume()102         public synchronized void doResume() {
103             isPaused = false;
104             // We don't want to resume presence checking immediately,
105             // but go through at least one more wait period.
106             doCheck = false;
107             this.notifyAll();
108         }
109 
end(boolean disableCallback)110         public synchronized void end(boolean disableCallback) {
111             isStopped = true;
112             doCheck = false;
113             if (disableCallback) {
114                 tagDisconnectedCallback = null;
115             }
116             this.notifyAll();
117         }
118 
119         @Override
run()120         public void run() {
121             synchronized (this) {
122                 if (DBG) Log.d(TAG, "Starting background presence check");
123                 while (isPresent && !isStopped) {
124                     try {
125                         if (!isPaused) {
126                             doCheck = true;
127                         }
128                         this.wait(watchdogTimeout);
129                         if (doCheck) {
130                             isPresent = doPresenceCheck();
131                         } else {
132                             // 1) We are paused, waiting for unpause
133                             // 2) We just unpaused, do pres check in next iteration
134                             //       (after watchdogTimeout ms sleep)
135                             // 3) We just set the timeout, wait for this timeout
136                             //       to expire once first.
137                             // 4) We just stopped, exit loop anyway
138                         }
139                     } catch (InterruptedException e) {
140                         // Activity detected, loop
141                     }
142                 }
143             }
144 
145             synchronized (NativeNfcTag.this) {
146                 mIsPresent = false;
147             }
148 
149             if (!isRemovalDetectionModeRequested()) {
150                 // Restart the polling loop
151                 Log.d(TAG, "Tag lost, restarting polling loop");
152                 doDisconnect();
153             }
154             if (tagDisconnectedCallback != null) {
155                 tagDisconnectedCallback.onTagDisconnected();
156             }
157             if (DBG) Log.d(TAG, "Stopping background presence check");
158         }
159     }
160 
isRemovalDetectionModeRequested()161     private synchronized boolean isRemovalDetectionModeRequested() {
162         if (mIsRemovalDetectionModeReq) {
163             Log.d(TAG, "isRemovalDetectionModeRequested: true");
164             mIsRemovalDetectionModeReq = false;
165             return true;
166         } else {
167             return false;
168         }
169     }
170 
doConnect(int handle, boolean force)171     private native int doConnect(int handle, boolean force);
172 
connectWithStatus(int technology)173     public synchronized int connectWithStatus(int technology) {
174         if (mWatchdog != null) {
175             mWatchdog.pause();
176         }
177         int status = -1;
178         for (int i = 0; i < mTechList.length; i++) {
179             if (mTechList[i] == technology) {
180                 if ((technology == TagTechnology.NDEF)
181                         || (technology == TagTechnology.NDEF_FORMATABLE)) {
182                     // special case for NDEF, this will cause switch to ISO_DEP frame intf
183                     i = 0;
184                 }
185 
186                 status = doConnect(i, true);
187 
188                 if (status == 0) {
189                     mConnectedHandle = mTechHandles[i];
190                     mConnectedTechIndex = i;
191                 }
192 
193                 break;
194             }
195         }
196         if (mWatchdog != null) {
197             mWatchdog.doResume();
198         }
199         return status;
200     }
201 
202     /**
203      * connectWithIdx() is use by findAndReadNdef()
204      * Update of variables mConnectedTechIndex and mConnectedHandle when
205      * performing reconnect().
206      */
connectWithIdx(int idx)207     public synchronized int connectWithIdx(int idx) {
208         if (DBG) Log.d(TAG, "connectWithIdx: idx: " + idx);
209         if (mWatchdog != null) {
210             mWatchdog.pause();
211         }
212         int status = -1;
213 
214         // Not connected yet
215         // status = doConnect(mTechHandles[i]);
216         status = doConnect(idx, false);
217 
218         if (status == 0) {
219             mConnectedHandle = mTechHandles[idx];
220             mConnectedTechIndex = idx;
221         }
222 
223         if (mWatchdog != null) {
224             mWatchdog.doResume();
225         }
226         return status;
227     }
228 
229     @Override
connect(int technology)230     public synchronized boolean connect(int technology) {
231         return connectWithStatus(technology) == 0;
232     }
233 
234     @Override
isPresenceCheckStopped()235     public boolean isPresenceCheckStopped() {
236         PresenceCheckWatchdog watchdog;
237 
238         synchronized (this) {
239             watchdog = mWatchdog;
240         }
241         if (watchdog == null) {
242            return true;
243         }
244         return false;
245     }
246 
247     @Override
prepareForRemovalDetectionMode()248     public void prepareForRemovalDetectionMode() {
249         synchronized (this) {
250             mIsRemovalDetectionModeReq = true;
251         }
252         doTerminatePresenceCheckThread(true);
253         clearConnectedContext();
254     }
255 
256     @Override
stopPresenceChecking()257     public synchronized void stopPresenceChecking() {
258         mIsPresent = false;
259         if (mWatchdog != null) {
260             mWatchdog.end(true);
261         }
262     }
263 
264     @Override
startPresenceChecking( int presenceCheckDelay, DeviceHost.TagDisconnectedCallback callback)265     public synchronized void startPresenceChecking(
266             int presenceCheckDelay, DeviceHost.TagDisconnectedCallback callback) {
267         // Once we start presence checking, we allow the upper layers
268         // to know the tag is in the field.
269         mIsPresent = true;
270         if (mWatchdog == null) {
271             mWatchdog = new PresenceCheckWatchdog(presenceCheckDelay, callback);
272             mWatchdog.start();
273         }
274     }
275 
276     @Override
isPresent()277     public synchronized boolean isPresent() {
278         // Returns whether the tag is still in the field to the best
279         // of our knowledge.
280         return mIsPresent;
281     }
282 
doDisconnect()283     native boolean doDisconnect();
284 
doTerminatePresenceCheckThread(boolean disableCallback)285     private boolean doTerminatePresenceCheckThread(boolean disableCallback) {
286 
287         PresenceCheckWatchdog watchdog;
288         synchronized (this) {
289             mIsPresent = false;
290             watchdog = mWatchdog;
291         }
292         if (watchdog != null) {
293             // Watchdog has already disconnected or will do it
294             watchdog.end(disableCallback);
295             try {
296                 watchdog.join();
297             } catch (InterruptedException e) {
298                 // Should never happen.
299             }
300             synchronized (this) {
301                 mWatchdog = null;
302             }
303             return true;
304         }
305         return false;
306     }
clearConnectedContext()307     private void clearConnectedContext() {
308         mConnectedTechIndex = -1;
309         mConnectedHandle = -1;
310         mClassifyT2T = true;
311     }
312 
313     @Override
disconnect()314     public boolean disconnect() {
315         boolean result = false;
316         result = doTerminatePresenceCheckThread(false);
317         if (!result) {
318             result = doDisconnect();
319         }
320         clearConnectedContext();
321         return result;
322     }
323 
doReconnect()324     native int doReconnect();
325 
326     @Override
reconnect()327     public synchronized boolean reconnect() {
328         if (DBG) Log.d(TAG, "reconnect");
329         if (mWatchdog != null) {
330             mWatchdog.pause();
331         }
332         int status = doReconnect();
333         if (status == 0x00) {
334             // Reconnection might change the current connected target idx
335             // If connected to frame RF/ISO-DEP it will got back to
336             // ISO-DEP/ISO-DEP
337             // If connected to frame RF/MIFARE it will got back to
338             // MIFARE/MIFARE
339             for (int i = 0; i < mTechLibNfcTypes.length; i++) {
340                 if (mTechLibNfcTypes[mConnectedTechIndex] == mTechLibNfcTypes[i]) {
341                     mConnectedTechIndex = i;
342                     mConnectedHandle = mTechHandles[i];
343                     break;
344                 }
345             }
346         }
347         if (mWatchdog != null) {
348             mWatchdog.doResume();
349         }
350         return (status == 0);
351     }
352 
doTransceive(byte[] data, boolean raw, int[] returnCode)353     private native byte[] doTransceive(byte[] data, boolean raw, int[] returnCode);
354 
355     @Override
transceive(byte[] data, boolean raw, int[] returnCode)356     public synchronized byte[] transceive(byte[] data, boolean raw, int[] returnCode) {
357         if (mWatchdog != null) {
358             mWatchdog.pause();
359         }
360         byte[] result = doTransceive(data, raw, returnCode);
361         if (mWatchdog != null) {
362             mWatchdog.doResume();
363         }
364         return result;
365     }
366 
doCheckNdef(int[] ndefinfo)367     private native int doCheckNdef(int[] ndefinfo);
368 
checkNdefWithStatus(int[] ndefinfo)369     private synchronized int checkNdefWithStatus(int[] ndefinfo) {
370         if (mWatchdog != null) {
371             mWatchdog.pause();
372         }
373         int status = doCheckNdef(ndefinfo);
374         if (mWatchdog != null) {
375             mWatchdog.doResume();
376         }
377         return status;
378     }
379 
380     @Override
checkNdef(int[] ndefinfo)381     public synchronized boolean checkNdef(int[] ndefinfo) {
382         boolean status = false;
383         if (hasTech(TagTechnology.NDEF)) {
384             status = true;
385         } else {
386             status = checkNdefWithStatus(ndefinfo) == 0;
387         }
388         return status;
389     }
390 
doRead()391     private native byte[] doRead();
392 
393     @Override
readNdef()394     public synchronized byte[] readNdef() {
395         if (mWatchdog != null) {
396             mWatchdog.pause();
397         }
398         byte[] result = doRead();
399         if (mWatchdog != null) {
400             mWatchdog.doResume();
401         }
402         return result;
403     }
404 
doWrite(byte[] buf)405     private native boolean doWrite(byte[] buf);
406 
407     @Override
writeNdef(byte[] buf)408     public synchronized boolean writeNdef(byte[] buf) {
409         if (mWatchdog != null) {
410             mWatchdog.pause();
411         }
412         boolean result = doWrite(buf);
413         if (mWatchdog != null) {
414             mWatchdog.doResume();
415         }
416         return result;
417     }
418 
doPresenceCheck()419     native boolean doPresenceCheck();
420 
421     @Override
presenceCheck()422     public synchronized boolean presenceCheck() {
423         if (mWatchdog != null) {
424             mWatchdog.pause();
425         }
426         boolean result = doPresenceCheck();
427         if (mWatchdog != null) {
428             mWatchdog.doResume();
429         }
430         return result;
431     }
432 
doNdefFormat(byte[] key)433     native boolean doNdefFormat(byte[] key);
434 
435     @Override
formatNdef(byte[] key)436     public synchronized boolean formatNdef(byte[] key) {
437         if (mWatchdog != null) {
438             mWatchdog.pause();
439         }
440         boolean result = doNdefFormat(key);
441         if (mWatchdog != null) {
442             mWatchdog.doResume();
443         }
444         return result;
445     }
446 
doMakeReadonly(byte[] key)447     native boolean doMakeReadonly(byte[] key);
448 
449     @Override
makeReadOnly()450     public synchronized boolean makeReadOnly() {
451         if (mWatchdog != null) {
452             mWatchdog.pause();
453         }
454         boolean result;
455         if (hasTech(TagTechnology.MIFARE_CLASSIC)) {
456             result = doMakeReadonly(MifareClassic.KEY_DEFAULT);
457         } else {
458             // No key needed for other technologies
459             result = doMakeReadonly(new byte[] {});
460         }
461         if (mWatchdog != null) {
462             mWatchdog.doResume();
463         }
464         return result;
465     }
466 
doIsIsoDepNdefFormatable(byte[] poll, byte[] act)467     native boolean doIsIsoDepNdefFormatable(byte[] poll, byte[] act);
468 
469     @Override
isNdefFormatable()470     public synchronized boolean isNdefFormatable() {
471         // Let native code decide whether the currently activated tag
472         // is formatable.  Although the name of the JNI function refers
473         // to ISO-DEP, the JNI function checks all tag types.
474         return doIsIsoDepNdefFormatable(mTechPollBytes[0], mTechActBytes[0]);
475     }
476 
477     @Override
getHandle()478     public int getHandle() {
479         // This is just a handle for the clients; it can simply use the first
480         // technology handle we have.
481         if (mTechHandles.length > 0) {
482             return mTechHandles[0];
483         } else {
484             return 0;
485         }
486     }
487 
488     @Override
getUid()489     public byte[] getUid() {
490         return mUid;
491     }
492 
493     @Override
getTechList()494     public int[] getTechList() {
495         return mTechList;
496     }
497 
getConnectedHandle()498     private int getConnectedHandle() {
499         return mConnectedHandle;
500     }
501 
getConnectedLibNfcType()502     private int getConnectedLibNfcType() {
503         if (mConnectedTechIndex != -1 && mConnectedTechIndex < mTechLibNfcTypes.length) {
504             return mTechLibNfcTypes[mConnectedTechIndex];
505         } else {
506             return 0;
507         }
508     }
509 
510     @Override
getConnectedTechnology()511     public int getConnectedTechnology() {
512         if (mConnectedTechIndex != -1 && mConnectedTechIndex < mTechList.length) {
513             return mTechList[mConnectedTechIndex];
514         } else {
515             return 0;
516         }
517     }
518 
doGetNdefType(int libnfctype, int javatype)519     native int doGetNdefType(int libnfctype, int javatype);
520 
getNdefType(int libnfctype, int javatype)521     private int getNdefType(int libnfctype, int javatype) {
522         return doGetNdefType(libnfctype, javatype);
523     }
524 
addTechnology(int tech, int handle, int libnfctype)525     private void addTechnology(int tech, int handle, int libnfctype) {
526         int[] mNewTechList = new int[mTechList.length + 1];
527         System.arraycopy(mTechList, 0, mNewTechList, 0, mTechList.length);
528         mNewTechList[mTechList.length] = tech;
529         mTechList = mNewTechList;
530 
531         int[] mNewHandleList = new int[mTechHandles.length + 1];
532         System.arraycopy(mTechHandles, 0, mNewHandleList, 0, mTechHandles.length);
533         mNewHandleList[mTechHandles.length] = handle;
534         mTechHandles = mNewHandleList;
535 
536         int[] mNewTypeList = new int[mTechLibNfcTypes.length + 1];
537         System.arraycopy(mTechLibNfcTypes, 0, mNewTypeList, 0, mTechLibNfcTypes.length);
538         mNewTypeList[mTechLibNfcTypes.length] = libnfctype;
539         mTechLibNfcTypes = mNewTypeList;
540     }
541 
542     @Override
removeTechnology(int tech)543     public void removeTechnology(int tech) {
544         synchronized (this) {
545             int techIndex = getTechIndex(tech);
546             if (techIndex != -1) {
547                 int[] mNewTechList = new int[mTechList.length - 1];
548                 System.arraycopy(mTechList, 0, mNewTechList, 0, techIndex);
549                 System.arraycopy(
550                         mTechList,
551                         techIndex + 1,
552                         mNewTechList,
553                         techIndex,
554                         mTechList.length - techIndex - 1);
555                 mTechList = mNewTechList;
556 
557                 int[] mNewHandleList = new int[mTechHandles.length - 1];
558                 System.arraycopy(mTechHandles, 0, mNewHandleList, 0, techIndex);
559                 System.arraycopy(
560                         mTechHandles,
561                         techIndex + 1,
562                         mNewTechList,
563                         techIndex,
564                         mTechHandles.length - techIndex - 1);
565                 mTechHandles = mNewHandleList;
566 
567                 int[] mNewTypeList = new int[mTechLibNfcTypes.length - 1];
568                 System.arraycopy(mTechLibNfcTypes, 0, mNewTypeList, 0, techIndex);
569                 System.arraycopy(
570                         mTechLibNfcTypes,
571                         techIndex + 1,
572                         mNewTypeList,
573                         techIndex,
574                         mTechLibNfcTypes.length - techIndex - 1);
575                 mTechLibNfcTypes = mNewTypeList;
576 
577                 // The technology must be removed from the mTechExtras array,
578                 // just like the above arrays.
579                 // Remove the specified element from the array,
580                 // then shift the remaining elements by one.
581                 if (mTechExtras != null) {
582                     Bundle[] mNewTechExtras = new Bundle[mTechExtras.length - 1];
583                     System.arraycopy(mTechExtras, 0, mNewTechExtras, 0, techIndex);
584                     System.arraycopy(
585                             mTechExtras,
586                             techIndex + 1,
587                             mNewTechExtras,
588                             techIndex,
589                             mTechExtras.length - techIndex - 1);
590                     mTechExtras = mNewTechExtras;
591                 }
592             }
593         }
594     }
595 
addNdefFormatableTechnology(int handle, int libnfcType)596     public void addNdefFormatableTechnology(int handle, int libnfcType) {
597         synchronized (this) {
598             addTechnology(TagTechnology.NDEF_FORMATABLE, handle, libnfcType);
599         }
600     }
601 
602     /**
603      * This method exists to "patch in" the ndef technologies, which is done inside Java instead of
604      * the native JNI code. To not create some nasty dependencies on the order on which things are
605      * called (most notably getTechExtras()), it needs some additional checking.
606      */
addNdefTechnology( NdefMessage msg, int handle, int libnfcType, int javaType, int maxLength, int cardState)607     public void addNdefTechnology(
608             NdefMessage msg,
609             int handle,
610             int libnfcType,
611             int javaType,
612             int maxLength,
613             int cardState) {
614         synchronized (this) {
615             addTechnology(TagTechnology.NDEF, handle, libnfcType);
616 
617             Bundle extras = new Bundle();
618             extras.putParcelable(Ndef.EXTRA_NDEF_MSG, msg);
619             extras.putInt(Ndef.EXTRA_NDEF_MAXLENGTH, maxLength);
620             extras.putInt(Ndef.EXTRA_NDEF_CARDSTATE, cardState);
621             extras.putInt(Ndef.EXTRA_NDEF_TYPE, getNdefType(libnfcType, javaType));
622 
623             if (mTechExtras == null) {
624                 // This will build the tech extra's for the first time,
625                 // including a NULL ref for the NDEF tech we generated above.
626                 Bundle[] builtTechExtras = getTechExtras();
627                 builtTechExtras[builtTechExtras.length - 1] = extras;
628             } else {
629                 // Tech extras were built before, patch the NDEF one in
630                 Bundle[] oldTechExtras = getTechExtras();
631                 Bundle[] newTechExtras = new Bundle[oldTechExtras.length + 1];
632                 System.arraycopy(oldTechExtras, 0, newTechExtras, 0, oldTechExtras.length);
633                 newTechExtras[oldTechExtras.length] = extras;
634                 mTechExtras = newTechExtras;
635             }
636         }
637     }
638 
getTechIndex(int tech)639     private int getTechIndex(int tech) {
640         int techIndex = -1;
641         for (int i = 0; i < mTechList.length; i++) {
642             if (mTechList[i] == tech) {
643                 techIndex = i;
644                 break;
645             }
646         }
647         return techIndex;
648     }
649 
hasTech(int tech)650     private boolean hasTech(int tech) {
651         boolean hasTech = false;
652         for (int i = 0; i < mTechList.length; i++) {
653             if (mTechList[i] == tech) {
654                 hasTech = true;
655                 break;
656             }
657         }
658         return hasTech;
659     }
660 
hasTechOnHandle(int tech, int handle)661     private boolean hasTechOnHandle(int tech, int handle) {
662         boolean hasTech = false;
663         for (int i = 0; i < mTechList.length; i++) {
664             if (mTechList[i] == tech && mTechHandles[i] == handle) {
665                 hasTech = true;
666                 break;
667             }
668         }
669         return hasTech;
670     }
671 
isUltralightC()672     private boolean isUltralightC() {
673         /* Make a best-effort attempt at classifying ULTRALIGHT
674          * vs ULTRALIGHT-C (based on NXP's public AN1303).
675          * The memory layout is as follows:
676          *   Page # BYTE1  BYTE2  BYTE3  BYTE4
677          *   2      INT1   INT2   LOCK   LOCK
678          *   3      OTP    OTP    OTP    OTP  (NDEF CC if NDEF-formatted)
679          *   4      DATA   DATA   DATA   DATA (version info if factory-state)
680          *
681          * Read four blocks from page 2, which will get us both
682          * the lock page, the OTP page and the version info.
683          */
684         boolean isUltralightC = false;
685         byte[] readCmd = {0x30, 0x02};
686         int[] retCode = new int[2];
687         byte[] respData = transceive(readCmd, false, retCode);
688         if (respData != null && respData.length == 16) {
689             // Check the lock bits (last 2 bytes in page2)
690             // and the OTP bytes (entire page 3)
691             if (respData[2] == 0
692                     && respData[3] == 0
693                     && respData[4] == 0
694                     && respData[5] == 0
695                     && respData[6] == 0
696                     && respData[7] == 0) {
697                 // Very likely to be a blank card, look at version info
698                 // in page 4.
699                 if ((respData[8] == (byte) 0x02) && respData[9] == (byte) 0x00) {
700                     // This is Ultralight-C
701                     isUltralightC = true;
702                 } else {
703                     // 0xFF 0xFF would indicate Ultralight, but we also use Ultralight
704                     // as a fallback if it's anything else
705                     isUltralightC = false;
706                 }
707             } else {
708                 // See if we can find the NDEF CC in the OTP page and if it's
709                 // smaller than major version two
710                 if (respData[4] == (byte) 0xE1 && ((respData[5] & 0xff) < 0x20)) {
711                     // OK, got NDEF. Technically we'd have to search for the
712                     // NDEF TLV as well. However, this would add too much
713                     // time for discovery and we can make already make a good guess
714                     // with the data we have here. Byte 2 of the OTP page
715                     // indicates the size of the tag - 0x06 is UL, anything
716                     // above indicates UL-C.
717                     if ((respData[6] & 0xff) > 0x06) {
718                         isUltralightC = true;
719                     }
720                 } else {
721                     // Fall back to ultralight
722                     isUltralightC = false;
723                 }
724             }
725         }
726         return isUltralightC;
727     }
728 
729     @Override
getTechExtras()730     public Bundle[] getTechExtras() {
731         synchronized (this) {
732             if (mTechExtras != null) return mTechExtras;
733             mTechExtras = new Bundle[mTechList.length];
734             for (int i = 0; i < mTechList.length; i++) {
735                 Bundle extras = new Bundle();
736                 switch (mTechList[i]) {
737                     case TagTechnology.NFC_A:
738                         if ((mTechActBytes[i] != null) && (mTechActBytes[i].length > 0)) {
739                             extras.putShort(NfcA.EXTRA_SAK,
740                                     (short) (mTechActBytes[i][0] & (short) 0xFF));
741                         } else {
742                             // Unfortunately Jewel doesn't have act bytes,
743                             // ignore this case.
744                         }
745                         extras.putByteArray(NfcA.EXTRA_ATQA, mTechPollBytes[i]);
746                         break;
747 
748                     case TagTechnology.NFC_B:
749 
750                         // What's returned from the PN544 is actually:
751                         // 4 bytes app data
752                         // 3 bytes prot info
753                         byte[] appData = new byte[4];
754                         byte[] protInfo = new byte[3];
755                         if (mTechPollBytes[i].length >= 7) {
756                             System.arraycopy(mTechPollBytes[i], 0, appData, 0, 4);
757                             System.arraycopy(mTechPollBytes[i], 4, protInfo, 0, 3);
758 
759                             extras.putByteArray(NfcB.EXTRA_APPDATA, appData);
760                             extras.putByteArray(NfcB.EXTRA_PROTINFO, protInfo);
761                         }
762                         break;
763 
764                     case TagTechnology.NFC_F:
765                         byte[] pmm = new byte[8];
766                         byte[] sc = new byte[2];
767                         if (mTechPollBytes[i].length >= 8) {
768                             // At least pmm is present
769                             System.arraycopy(mTechPollBytes[i], 0, pmm, 0, 8);
770                             extras.putByteArray(NfcF.EXTRA_PMM, pmm);
771                         }
772                         if (mTechPollBytes[i].length == 10) {
773                             System.arraycopy(mTechPollBytes[i], 8, sc, 0, 2);
774                             extras.putByteArray(NfcF.EXTRA_SC, sc);
775                         }
776                         break;
777 
778                     case TagTechnology.ISO_DEP:
779                         if (hasTech(TagTechnology.NFC_A)) {
780                             extras.putByteArray(IsoDep.EXTRA_HIST_BYTES, mTechActBytes[i]);
781                         } else {
782                             extras.putByteArray(IsoDep.EXTRA_HI_LAYER_RESP, mTechActBytes[i]);
783                         }
784                         break;
785 
786                     case TagTechnology.NFC_V:
787 
788                         // First byte response flags, second byte DSFID
789                         if (mTechPollBytes[i] != null && mTechPollBytes[i].length >= 2) {
790                             extras.putByte(NfcV.EXTRA_RESP_FLAGS, mTechPollBytes[i][0]);
791                             extras.putByte(NfcV.EXTRA_DSFID, mTechPollBytes[i][1]);
792                         }
793                         break;
794 
795                     case TagTechnology.MIFARE_ULTRALIGHT:
796                         if (mClassifyT2T) {
797                             boolean isUlc = isUltralightC();
798                             extras.putBoolean(MifareUltralight.EXTRA_IS_UL_C, isUlc);
799                         }
800                         break;
801 
802                     case TagTechnology.MIFARE_CLASSIC:
803                         if ((mTechActBytes[i] != null) && (mTechActBytes[i].length > 0)) {
804                             extras.putShort(NfcA.EXTRA_SAK,
805                                     (short) (mTechActBytes[i][0] & (short) 0xFF));
806                         }
807                         extras.putByteArray(NfcA.EXTRA_ATQA, mTechPollBytes[i]);
808                         break;
809 
810                     case TagTechnology.NFC_BARCODE:
811 
812                         // hard code this for now, this is the only valid type
813                         extras.putInt(NfcBarcode.EXTRA_BARCODE_TYPE, NfcBarcode.TYPE_KOVIO);
814                         break;
815 
816                     default:
817 
818                         // Leave the entry in the array null
819                         continue;
820                 }
821                 mTechExtras[i] = extras;
822             }
823             return mTechExtras;
824         }
825     }
826 
isMifareDesfireTag()827     private boolean isMifareDesfireTag() {
828         for (int i = 0; i < mTechList.length; i++) {
829             if (mTechList[i] != TagTechnology.NFC_A) {
830                 continue;
831             } else if ((mTechActBytes[i] == null) || (mTechActBytes[i].length == 0)) {
832                 continue;
833             } else if (((mTechActBytes[i][0] & (short) 0xFF) == 0x20)
834                     && (((mTechPollBytes[i][0] & (short) 0xFF) == 0x44)
835                             || (((mTechPollBytes[i][0] & (short) 0xFF) == 0x04)))
836                     && ((mTechPollBytes[i][1] & (short) 0xFF) == 0x03)) {
837                 if (DBG) Log.d(TAG, "isMifareDesfireTag: true, need reconnect()");
838                 return true;
839             }
840         }
841         return false;
842     }
843 
844     @Override
findAndReadNdef()845     public NdefMessage findAndReadNdef() {
846         // Try to find NDEF on any of the technologies.
847         int[] technologies = getTechList();
848         NdefMessage ndefMsg = null;
849         boolean foundFormattable = false;
850         int formattableHandle = 0;
851         int formattableLibNfcType = 0;
852         int status;
853         int currentTargetTech = 0;
854 
855         for (int techIndex = 0; techIndex < technologies.length; techIndex++) {
856             if (currentTargetTech == technologies[techIndex]) {
857                 continue;
858             }
859             currentTargetTech = technologies[techIndex];
860             status = connectWithIdx(techIndex);
861             if (status != 0) {
862                 Log.d(TAG, "findAndReadNdef: Connect Failed, status = " + status);
863                 if (status == STATUS_CODE_TARGET_LOST) {
864                     break;
865                 }
866                 continue; // try next handle
867             }
868             // Check if this type is NDEF formatable
869             if (!foundFormattable) {
870                 if (isNdefFormatable()) {
871                     foundFormattable = true;
872                     formattableHandle = getConnectedHandle();
873                     formattableLibNfcType = getConnectedLibNfcType();
874                     // We'll only add formattable tech if no ndef is
875                     // found - this is because libNFC refuses to format
876                     // an already NDEF formatted tag.
877                     if (isMifareDesfireTag()) {
878                         reconnect();
879                         connectWithIdx(techIndex);
880                     }
881                 }
882             }
883 
884             int[] ndefinfo = new int[2];
885             status = checkNdefWithStatus(ndefinfo);
886             if (status != 0) {
887                 Log.d(TAG, "findAndReadNdef: Check NDEF Failed, status = " + status);
888                 if (status == STATUS_CODE_TARGET_LOST) {
889                     break;
890                 }
891                 continue; // try next handle
892             }
893 
894             // found our NDEF handle
895             boolean generateEmptyNdef = false;
896 
897             int supportedNdefLength = ndefinfo[0];
898             int cardState = ndefinfo[1];
899             byte[] buff = readNdef();
900             if (buff != null && buff.length > 0) {
901                 try {
902                     ndefMsg = new NdefMessage(buff);
903                     addNdefTechnology(
904                             ndefMsg,
905                             getConnectedHandle(),
906                             getConnectedLibNfcType(),
907                             getConnectedTechnology(),
908                             supportedNdefLength,
909                             cardState);
910                 } catch (FormatException e) {
911                     // Create an intent anyway, without NDEF messages
912                     generateEmptyNdef = true;
913                 }
914             } else if (buff != null) {
915                 // Empty buffer, unformatted tags fall into this case
916                 generateEmptyNdef = true;
917             }
918 
919             if (generateEmptyNdef) {
920                 ndefMsg = null;
921                 addNdefTechnology(
922                         null,
923                         getConnectedHandle(),
924                         getConnectedLibNfcType(),
925                         getConnectedTechnology(),
926                         supportedNdefLength,
927                         cardState);
928                 foundFormattable = false;
929             }
930             break;
931         }
932 
933         if (ndefMsg == null && foundFormattable) {
934             // Tag is not NDEF yet, and found a formattable target,
935             // so add formattable tech to tech list.
936             addNdefFormatableTechnology(formattableHandle, formattableLibNfcType);
937         }
938 
939         return ndefMsg;
940     }
941 
942     @Override
findNdef()943     public void findNdef() {
944         int[] technologies = getTechList();
945         int[] handles = mTechHandles;
946         int currHandle = 0;
947         mClassifyT2T = !hasTech(TagTechnology.MIFARE_ULTRALIGHT);
948 
949         for (int techIndex = 0; techIndex < technologies.length; techIndex++) {
950             if (currHandle != handles[techIndex]) {
951                 currHandle = handles[techIndex];
952                 int status = connectWithStatus(technologies[techIndex]);
953                 if (status != 0) {
954                     Log.d(TAG, "findNdef: Connect Failed, status = " + status);
955                     if (status == STATUS_CODE_TARGET_LOST) {
956                         break;
957                     }
958                     continue; // try next handle
959                 }
960 
961                 int[] ndefinfo = new int[2];
962                 status = checkNdefWithStatus(ndefinfo);
963                 if (status != 0) {
964                     Log.d(TAG, "findNdef: Check NDEF Failed, status = " + status);
965                     if (status == STATUS_CODE_TARGET_LOST) {
966                         break;
967                     }
968                     continue; // try next handle
969                 } else {
970                     int supportedNdefLength = ndefinfo[0];
971                     int cardState = ndefinfo[1];
972                     addNdefTechnology(
973                             null,
974                             getConnectedHandle(),
975                             getConnectedLibNfcType(),
976                             getConnectedTechnology(),
977                             supportedNdefLength,
978                             cardState);
979                     break;
980                 }
981             } else {
982                 Log.d(TAG, "findNdef: Duplicate techIndex = " + techIndex);
983             }
984         }
985     }
986 
987     @Override
getNdef()988     public NdefMessage getNdef() {
989         Log.d(TAG, "getNdef: Searching for NfcCharging information");
990         int[] ndefinfo = new int[2];
991         int status;
992         NdefMessage ndefMsg = null;
993         status = checkNdefWithStatus(ndefinfo);
994         if (status != 0) {
995             Log.d(TAG, "getNdef: Check NDEF Failed, status = " + status);
996             return ndefMsg;
997         }
998 
999         byte[] buff = readNdef();
1000         if (buff != null && buff.length > 0) {
1001             try {
1002                 ndefMsg = new NdefMessage(buff);
1003             } catch (FormatException e) {
1004                 // Create an intent anyway, without NDEF messages
1005                 ndefMsg = null;
1006             }
1007         } else if (buff != null) {
1008             // Empty buffer, unformatted tags fall into this case
1009             ndefMsg = null;
1010         }
1011 
1012         return ndefMsg;
1013     }
1014 }
1015