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
17package com.android.nfc.dhimpl;
18
19import com.android.nfc.DeviceHost.TagEndpoint;
20
21import android.nfc.FormatException;
22import android.nfc.NdefMessage;
23import android.nfc.tech.IsoDep;
24import android.nfc.tech.MifareClassic;
25import android.nfc.tech.MifareUltralight;
26import android.nfc.tech.Ndef;
27import android.nfc.tech.NfcA;
28import android.nfc.tech.NfcB;
29import android.nfc.tech.NfcF;
30import android.nfc.tech.NfcV;
31import android.nfc.tech.TagTechnology;
32import android.os.Bundle;
33import android.util.Log;
34
35/**
36 * Native interface to the NFC tag functions
37 */
38public class NativeNfcTag implements TagEndpoint {
39    static final boolean DBG = false;
40
41    static final int STATUS_CODE_TARGET_LOST = 146;
42
43    private int[] mTechList;
44    private int[] mTechHandles;
45    private int[] mTechLibNfcTypes;
46    private Bundle[] mTechExtras;
47    private byte[][] mTechPollBytes;
48    private byte[][] mTechActBytes;
49    private byte[] mUid;
50
51    // mConnectedHandle stores the *real* libnfc handle
52    // that we're connected to.
53    private int mConnectedHandle;
54
55    // mConnectedTechIndex stores to which technology
56    // the upper layer stack is connected. Note that
57    // we may be connected to a libnfchandle without being
58    // connected to a technology - technology changes
59    // may occur runtime, whereas the underlying handle
60    // could stay present. Usually all technologies are on the
61    // same handle, with the exception of multi-protocol
62    // tags.
63    private int mConnectedTechIndex; // Index in mTechHandles
64
65    private final String TAG = "NativeNfcTag";
66
67    private boolean mIsPresent; // Whether the tag is known to be still present
68
69    private PresenceCheckWatchdog mWatchdog;
70    class PresenceCheckWatchdog extends Thread {
71
72        private int watchdogTimeout = 125;
73
74        private boolean isPresent = true;
75        private boolean isStopped = false;
76        private boolean isPaused = false;
77        private boolean doCheck = true;
78
79        public synchronized void pause() {
80            isPaused = true;
81            doCheck = false;
82            this.notifyAll();
83        }
84
85        public synchronized void doResume() {
86            isPaused = false;
87            // We don't want to resume presence checking immediately,
88            // but go through at least one more wait period.
89            doCheck = false;
90            this.notifyAll();
91        }
92
93        public synchronized void end() {
94            isStopped = true;
95            doCheck = false;
96            this.notifyAll();
97        }
98
99        public synchronized void setTimeout(int timeout) {
100            watchdogTimeout = timeout;
101            doCheck = false; // Do it only after we have waited "timeout" ms again
102            this.notifyAll();
103        }
104
105        @Override
106        public synchronized void run() {
107            if (DBG) Log.d(TAG, "Starting background presence check");
108            while (isPresent && !isStopped) {
109                try {
110                    if (!isPaused) {
111                        doCheck = true;
112                    }
113                    this.wait(watchdogTimeout);
114                    if (doCheck) {
115                        isPresent = doPresenceCheck();
116                    } else {
117                        // 1) We are paused, waiting for unpause
118                        // 2) We just unpaused, do pres check in next iteration
119                        //       (after watchdogTimeout ms sleep)
120                        // 3) We just set the timeout, wait for this timeout
121                        //       to expire once first.
122                        // 4) We just stopped, exit loop anyway
123                    }
124                } catch (InterruptedException e) {
125                    // Activity detected, loop
126                }
127            }
128            mIsPresent = false;
129            // Restart the polling loop
130
131            Log.d(TAG, "Tag lost, restarting polling loop");
132            doDisconnect();
133            if (DBG) Log.d(TAG, "Stopping background presence check");
134        }
135    }
136
137    private native int doConnect(int handle);
138    public synchronized int connectWithStatus(int technology) {
139        if (technology == TagTechnology.NFC_B) {
140            // Not supported by PN544
141            return -1;
142        }
143        if (mWatchdog != null) {
144            mWatchdog.pause();
145        }
146        int status = -1;
147        for (int i = 0; i < mTechList.length; i++) {
148            if (mTechList[i] == technology) {
149                // Get the handle and connect, if not already connected
150                if (mConnectedHandle != mTechHandles[i]) {
151                    // We're not yet connected to this handle, there are
152                    // a few scenario's here:
153                    // 1) We are not connected to anything yet - allow
154                    // 2) We are connected to a technology which has
155                    //    a different handle (multi-protocol tag); we support
156                    //    switching to that.
157                    if (mConnectedHandle == -1) {
158                        // Not connected yet
159                        status = doConnect(mTechHandles[i]);
160                    } else {
161                        // Connect to a tech with a different handle
162                        status = reconnectWithStatus(mTechHandles[i]);
163                    }
164                    if (status == 0) {
165                        mConnectedHandle = mTechHandles[i];
166                        mConnectedTechIndex = i;
167                    }
168                } else {
169                    // 1) We are connected to a technology which has the same
170                    //    handle; we do not support connecting at a different
171                    //    level (libnfc auto-activates to the max level on
172                    //    any handle).
173                    // 2) We are connecting to the ndef technology - always
174                    //    allowed.
175                    if ((technology == TagTechnology.NDEF) ||
176                            (technology == TagTechnology.NDEF_FORMATABLE)) {
177                        status = 0;
178                    } else {
179                        if ((technology != TagTechnology.ISO_DEP) &&
180                            (hasTechOnHandle(TagTechnology.ISO_DEP, mTechHandles[i]))) {
181                            // Don't allow to connect a -4 tag at a different level
182                            // than IsoDep, as this is not supported by
183                            // libNFC.
184                            status = -1;
185                        } else {
186                            status = 0;
187                        }
188                    }
189                    if (status == 0) {
190                        mConnectedTechIndex = i;
191                        // Handle was already identical
192                    }
193                }
194                break;
195            }
196        }
197        if (mWatchdog != null) {
198            mWatchdog.doResume();
199        }
200        return status;
201    }
202    @Override
203    public synchronized boolean connect(int technology) {
204        return connectWithStatus(technology) == 0;
205    }
206
207    @Override
208    public synchronized void startPresenceChecking() {
209        // Once we start presence checking, we allow the upper layers
210        // to know the tag is in the field.
211        mIsPresent = true;
212        if (mWatchdog == null) {
213            mWatchdog = new PresenceCheckWatchdog();
214            mWatchdog.start();
215        }
216    }
217
218    @Override
219    public synchronized boolean isPresent() {
220        // Returns whether the tag is still in the field to the best
221        // of our knowledge.
222        return mIsPresent;
223    }
224    native boolean doDisconnect();
225    @Override
226    public synchronized boolean disconnect() {
227        boolean result = false;
228
229        mIsPresent = false;
230        if (mWatchdog != null) {
231            // Watchdog has already disconnected or will do it
232            mWatchdog.end();
233            try {
234                mWatchdog.join();
235            } catch (InterruptedException e) {
236                // Should never happen.
237            }
238            mWatchdog = null;
239            result = true;
240        } else {
241            result = doDisconnect();
242        }
243
244        mConnectedTechIndex = -1;
245        mConnectedHandle = -1;
246        return result;
247    }
248
249    native int doReconnect();
250    public synchronized int reconnectWithStatus() {
251        if (mWatchdog != null) {
252            mWatchdog.pause();
253        }
254        int status = doReconnect();
255        if (mWatchdog != null) {
256            mWatchdog.doResume();
257        }
258        return status;
259    }
260    @Override
261    public synchronized boolean reconnect() {
262        return reconnectWithStatus() == 0;
263    }
264
265    native int doHandleReconnect(int handle);
266    public synchronized int reconnectWithStatus(int handle) {
267        if (mWatchdog != null) {
268            mWatchdog.pause();
269        }
270        int status = doHandleReconnect(handle);
271        if (mWatchdog != null) {
272            mWatchdog.doResume();
273        }
274        return status;
275    }
276
277    private native byte[] doTransceive(byte[] data, boolean raw, int[] returnCode);
278    @Override
279    public synchronized byte[] transceive(byte[] data, boolean raw, int[] returnCode) {
280        if (mWatchdog != null) {
281            mWatchdog.pause();
282        }
283        byte[] result = doTransceive(data, raw, returnCode);
284        if (mWatchdog != null) {
285            mWatchdog.doResume();
286        }
287        return result;
288    }
289
290    private native int doCheckNdef(int[] ndefinfo);
291    private synchronized int checkNdefWithStatus(int[] ndefinfo) {
292        if (mWatchdog != null) {
293            mWatchdog.pause();
294        }
295        int status = doCheckNdef(ndefinfo);
296        if (mWatchdog != null) {
297            mWatchdog.doResume();
298        }
299        return status;
300    }
301    @Override
302    public synchronized boolean checkNdef(int[] ndefinfo) {
303        return checkNdefWithStatus(ndefinfo) == 0;
304    }
305
306    private native byte[] doRead();
307    @Override
308    public synchronized byte[] readNdef() {
309        if (mWatchdog != null) {
310            mWatchdog.pause();
311        }
312        byte[] result = doRead();
313        if (mWatchdog != null) {
314            mWatchdog.doResume();
315        }
316        return result;
317    }
318
319    private native boolean doWrite(byte[] buf);
320    @Override
321    public synchronized boolean writeNdef(byte[] buf) {
322        if (mWatchdog != null) {
323            mWatchdog.pause();
324        }
325        boolean result = doWrite(buf);
326        if (mWatchdog != null) {
327            mWatchdog.doResume();
328        }
329        return result;
330    }
331
332    native boolean doPresenceCheck();
333    @Override
334    public synchronized boolean presenceCheck() {
335        if (mWatchdog != null) {
336            mWatchdog.pause();
337        }
338        boolean result = doPresenceCheck();
339        if (mWatchdog != null) {
340            mWatchdog.doResume();
341        }
342        return result;
343    }
344
345    native boolean doNdefFormat(byte[] key);
346    @Override
347    public synchronized boolean formatNdef(byte[] key) {
348        if (mWatchdog != null) {
349            mWatchdog.pause();
350        }
351        boolean result = doNdefFormat(key);
352        if (mWatchdog != null) {
353            mWatchdog.doResume();
354        }
355        return result;
356    }
357
358    native boolean doMakeReadonly(byte[] key);
359    @Override
360    public synchronized boolean makeReadOnly() {
361        if (mWatchdog != null) {
362            mWatchdog.pause();
363        }
364        boolean result;
365        if (hasTech(TagTechnology.MIFARE_CLASSIC)) {
366            result = doMakeReadonly(MifareClassic.KEY_DEFAULT);
367        } else {
368            // No key needed for other technologies
369            result = doMakeReadonly(new byte[] {});
370        }
371        if (mWatchdog != null) {
372            mWatchdog.doResume();
373        }
374        return result;
375    }
376
377    native boolean doIsIsoDepNdefFormatable(byte[] poll, byte[] act);
378    @Override
379    public synchronized boolean isNdefFormatable() {
380        if (hasTech(TagTechnology.MIFARE_CLASSIC) || hasTech(TagTechnology.MIFARE_ULTRALIGHT)) {
381            // These are always formatable
382            return true;
383        }
384        if (hasTech(TagTechnology.NFC_V)) {
385            // Currently libnfc only formats NXP NFC-V tags
386            if (mUid[5] >= 1 && mUid[5] <= 3 && mUid[6] == 0x04) {
387                return true;
388            } else {
389                return false;
390            }
391        }
392        // For ISO-DEP, call native code to determine at lower level if format
393        // is possible. It will need NFC-A poll/activation time bytes for this.
394        if (hasTech(TagTechnology.ISO_DEP)) {
395            int nfcaTechIndex = getTechIndex(TagTechnology.NFC_A);
396            if (nfcaTechIndex != -1) {
397                return doIsIsoDepNdefFormatable(mTechPollBytes[nfcaTechIndex],
398                        mTechActBytes[nfcaTechIndex]);
399            } else {
400                return false;
401            }
402        } else {
403            // Formatting not supported by libNFC
404            return false;
405        }
406    }
407
408    @Override
409    public int getHandle() {
410        // This is just a handle for the clients; it can simply use the first
411        // technology handle we have.
412        if (mTechHandles.length > 0) {
413            return mTechHandles[0];
414        } else {
415            return 0;
416        }
417    }
418
419    @Override
420    public byte[] getUid() {
421        return mUid;
422    }
423
424    @Override
425    public int[] getTechList() {
426        return mTechList;
427    }
428
429    private int getConnectedHandle() {
430        return mConnectedHandle;
431    }
432
433    private int getConnectedLibNfcType() {
434        if (mConnectedTechIndex != -1 && mConnectedTechIndex < mTechLibNfcTypes.length) {
435            return mTechLibNfcTypes[mConnectedTechIndex];
436        } else {
437            return 0;
438        }
439    }
440
441    @Override
442    public int getConnectedTechnology() {
443        if (mConnectedTechIndex != -1 && mConnectedTechIndex < mTechList.length) {
444            return mTechList[mConnectedTechIndex];
445        } else {
446            return 0;
447        }
448    }
449    native int doGetNdefType(int libnfctype, int javatype);
450    private int getNdefType(int libnfctype, int javatype) {
451        return doGetNdefType(libnfctype, javatype);
452    }
453
454    private void addTechnology(int tech, int handle, int libnfctype) {
455            int[] mNewTechList = new int[mTechList.length + 1];
456            System.arraycopy(mTechList, 0, mNewTechList, 0, mTechList.length);
457            mNewTechList[mTechList.length] = tech;
458            mTechList = mNewTechList;
459
460            int[] mNewHandleList = new int[mTechHandles.length + 1];
461            System.arraycopy(mTechHandles, 0, mNewHandleList, 0, mTechHandles.length);
462            mNewHandleList[mTechHandles.length] = handle;
463            mTechHandles = mNewHandleList;
464
465            int[] mNewTypeList = new int[mTechLibNfcTypes.length + 1];
466            System.arraycopy(mTechLibNfcTypes, 0, mNewTypeList, 0, mTechLibNfcTypes.length);
467            mNewTypeList[mTechLibNfcTypes.length] = libnfctype;
468            mTechLibNfcTypes = mNewTypeList;
469    }
470
471    @Override
472    public void removeTechnology(int tech) {
473        synchronized (this) {
474            int techIndex = getTechIndex(tech);
475            if (techIndex != -1) {
476                int[] mNewTechList = new int[mTechList.length - 1];
477                System.arraycopy(mTechList, 0, mNewTechList, 0, techIndex);
478                System.arraycopy(mTechList, techIndex + 1, mNewTechList, techIndex,
479                        mTechList.length - techIndex - 1);
480                mTechList = mNewTechList;
481
482                int[] mNewHandleList = new int[mTechHandles.length - 1];
483                System.arraycopy(mTechHandles, 0, mNewHandleList, 0, techIndex);
484                System.arraycopy(mTechHandles, techIndex + 1, mNewTechList, techIndex,
485                        mTechHandles.length - techIndex - 1);
486                mTechHandles = mNewHandleList;
487
488                int[] mNewTypeList = new int[mTechLibNfcTypes.length - 1];
489                System.arraycopy(mTechLibNfcTypes, 0, mNewTypeList, 0, techIndex);
490                System.arraycopy(mTechLibNfcTypes, techIndex + 1, mNewTypeList, techIndex,
491                        mTechLibNfcTypes.length - techIndex - 1);
492                mTechLibNfcTypes = mNewTypeList;
493            }
494        }
495    }
496
497    public void addNdefFormatableTechnology(int handle, int libnfcType) {
498        synchronized (this) {
499            addTechnology(TagTechnology.NDEF_FORMATABLE, handle, libnfcType);
500        }
501    }
502
503    // This method exists to "patch in" the ndef technologies,
504    // which is done inside Java instead of the native JNI code.
505    // To not create some nasty dependencies on the order on which things
506    // are called (most notably getTechExtras()), it needs some additional
507    // checking.
508    public void addNdefTechnology(NdefMessage msg, int handle, int libnfcType,
509            int javaType, int maxLength, int cardState) {
510        synchronized (this) {
511            addTechnology(TagTechnology.NDEF, handle, libnfcType);
512
513            Bundle extras = new Bundle();
514            extras.putParcelable(Ndef.EXTRA_NDEF_MSG, msg);
515            extras.putInt(Ndef.EXTRA_NDEF_MAXLENGTH, maxLength);
516            extras.putInt(Ndef.EXTRA_NDEF_CARDSTATE, cardState);
517            extras.putInt(Ndef.EXTRA_NDEF_TYPE, getNdefType(libnfcType, javaType));
518
519            if (mTechExtras == null) {
520                // This will build the tech extra's for the first time,
521                // including a NULL ref for the NDEF tech we generated above.
522                Bundle[] builtTechExtras = getTechExtras();
523                builtTechExtras[builtTechExtras.length - 1] = extras;
524            }
525            else {
526                // Tech extras were built before, patch the NDEF one in
527                Bundle[] oldTechExtras = getTechExtras();
528                Bundle[] newTechExtras = new Bundle[oldTechExtras.length + 1];
529                System.arraycopy(oldTechExtras, 0, newTechExtras, 0, oldTechExtras.length);
530                newTechExtras[oldTechExtras.length] = extras;
531                mTechExtras = newTechExtras;
532            }
533
534
535        }
536    }
537
538    private int getTechIndex(int tech) {
539      int techIndex = -1;
540      for (int i = 0; i < mTechList.length; i++) {
541          if (mTechList[i] == tech) {
542              techIndex = i;
543              break;
544          }
545      }
546      return techIndex;
547    }
548
549    private boolean hasTech(int tech) {
550      boolean hasTech = false;
551      for (int i = 0; i < mTechList.length; i++) {
552          if (mTechList[i] == tech) {
553              hasTech = true;
554              break;
555          }
556      }
557      return hasTech;
558    }
559
560    private boolean hasTechOnHandle(int tech, int handle) {
561      boolean hasTech = false;
562      for (int i = 0; i < mTechList.length; i++) {
563          if (mTechList[i] == tech && mTechHandles[i] == handle) {
564              hasTech = true;
565              break;
566          }
567      }
568      return hasTech;
569
570    }
571
572    private boolean isUltralightC() {
573        /* Make a best-effort attempt at classifying ULTRALIGHT
574         * vs ULTRALIGHT-C (based on NXP's public AN1303).
575         * The memory layout is as follows:
576         *   Page # BYTE1  BYTE2  BYTE3  BYTE4
577         *   2      INT1   INT2   LOCK   LOCK
578         *   3      OTP    OTP    OTP    OTP  (NDEF CC if NDEF-formatted)
579         *   4      DATA   DATA   DATA   DATA (version info if factory-state)
580         *
581         * Read four blocks from page 2, which will get us both
582         * the lock page, the OTP page and the version info.
583         */
584        boolean isUltralightC = false;
585        byte[] readCmd = { 0x30, 0x02 };
586        int[] retCode = new int[2];
587        byte[] respData = transceive(readCmd, false, retCode);
588        if (respData != null && respData.length == 16) {
589            // Check the lock bits (last 2 bytes in page2)
590            // and the OTP bytes (entire page 3)
591            if (respData[2] == 0 && respData[3] == 0 && respData[4] == 0 &&
592                respData[5] == 0 && respData[6] == 0 && respData[7] == 0) {
593                // Very likely to be a blank card, look at version info
594                // in page 4.
595                if ((respData[8] == (byte)0x02) && respData[9] == (byte)0x00) {
596                    // This is Ultralight-C
597                    isUltralightC = true;
598                } else {
599                    // 0xFF 0xFF would indicate Ultralight, but we also use Ultralight
600                    // as a fallback if it's anything else
601                    isUltralightC = false;
602                }
603            } else {
604                // See if we can find the NDEF CC in the OTP page and if it's
605                // smaller than major version two
606                if (respData[4] == (byte)0xE1 && ((respData[5] & 0xff) < 0x20)) {
607                    // OK, got NDEF. Technically we'd have to search for the
608                    // NDEF TLV as well. However, this would add too much
609                    // time for discovery and we can make already make a good guess
610                    // with the data we have here. Byte 2 of the OTP page
611                    // indicates the size of the tag - 0x06 is UL, anything
612                    // above indicates UL-C.
613                    if ((respData[6] & 0xff) > 0x06) {
614                        isUltralightC = true;
615                    }
616                } else {
617                    // Fall back to ultralight
618                    isUltralightC = false;
619                }
620            }
621        }
622        return isUltralightC;
623    }
624
625    @Override
626    public Bundle[] getTechExtras() {
627        synchronized (this) {
628            if (mTechExtras != null) return mTechExtras;
629            mTechExtras = new Bundle[mTechList.length];
630            for (int i = 0; i < mTechList.length; i++) {
631                Bundle extras = new Bundle();
632                switch (mTechList[i]) {
633                    case TagTechnology.NFC_A: {
634                        byte[] actBytes = mTechActBytes[i];
635                        if ((actBytes != null) && (actBytes.length > 0)) {
636                            extras.putShort(NfcA.EXTRA_SAK, (short) (actBytes[0] & (short) 0xFF));
637                        } else {
638                            // Unfortunately Jewel doesn't have act bytes,
639                            // ignore this case.
640                        }
641                        extras.putByteArray(NfcA.EXTRA_ATQA, mTechPollBytes[i]);
642                        break;
643                    }
644
645                    case TagTechnology.NFC_B: {
646                        // What's returned from the PN544 is actually:
647                        // 4 bytes app data
648                        // 3 bytes prot info
649                        byte[] appData = new byte[4];
650                        byte[] protInfo = new byte[3];
651                        if (mTechPollBytes[i].length >= 7) {
652                            System.arraycopy(mTechPollBytes[i], 0, appData, 0, 4);
653                            System.arraycopy(mTechPollBytes[i], 4, protInfo, 0, 3);
654
655                            extras.putByteArray(NfcB.EXTRA_APPDATA, appData);
656                            extras.putByteArray(NfcB.EXTRA_PROTINFO, protInfo);
657                        }
658                        break;
659                    }
660
661                    case TagTechnology.NFC_F: {
662                        byte[] pmm = new byte[8];
663                        byte[] sc = new byte[2];
664                        if (mTechPollBytes[i].length >= 8) {
665                            // At least pmm is present
666                            System.arraycopy(mTechPollBytes[i], 0, pmm, 0, 8);
667                            extras.putByteArray(NfcF.EXTRA_PMM, pmm);
668                        }
669                        if (mTechPollBytes[i].length == 10) {
670                            System.arraycopy(mTechPollBytes[i], 8, sc, 0, 2);
671                            extras.putByteArray(NfcF.EXTRA_SC, sc);
672                        }
673                        break;
674                    }
675
676                    case TagTechnology.ISO_DEP: {
677                        if (hasTech(TagTechnology.NFC_A)) {
678                            extras.putByteArray(IsoDep.EXTRA_HIST_BYTES, mTechActBytes[i]);
679                        }
680                        else {
681                            extras.putByteArray(IsoDep.EXTRA_HI_LAYER_RESP, mTechActBytes[i]);
682                        }
683                        break;
684                    }
685
686                    case TagTechnology.NFC_V: {
687                        // First byte response flags, second byte DSFID
688                        if (mTechPollBytes[i] != null && mTechPollBytes[i].length >= 2) {
689                            extras.putByte(NfcV.EXTRA_RESP_FLAGS, mTechPollBytes[i][0]);
690                            extras.putByte(NfcV.EXTRA_DSFID, mTechPollBytes[i][1]);
691                        }
692                        break;
693                    }
694
695                    case TagTechnology.MIFARE_ULTRALIGHT: {
696                        boolean isUlc = isUltralightC();
697                        extras.putBoolean(MifareUltralight.EXTRA_IS_UL_C, isUlc);
698                        break;
699                    }
700
701                    default: {
702                        // Leave the entry in the array null
703                        continue;
704                    }
705                }
706                mTechExtras[i] = extras;
707            }
708            return mTechExtras;
709        }
710    }
711
712    @Override
713    public NdefMessage findAndReadNdef() {
714        // Try to find NDEF on any of the technologies.
715        int[] technologies = getTechList();
716        int[] handles = mTechHandles;
717        NdefMessage ndefMsg = null;
718        boolean foundFormattable = false;
719        int formattableHandle = 0;
720        int formattableLibNfcType = 0;
721        int status;
722
723        for (int techIndex = 0; techIndex < technologies.length; techIndex++) {
724            // have we seen this handle before?
725            for (int i = 0; i < techIndex; i++) {
726                if (handles[i] == handles[techIndex]) {
727                    continue;  // don't check duplicate handles
728                }
729            }
730
731            status = connectWithStatus(technologies[techIndex]);
732            if (status != 0) {
733                Log.d(TAG, "Connect Failed - status = "+ status);
734                if (status == STATUS_CODE_TARGET_LOST) {
735                    break;
736                }
737                continue;  // try next handle
738            }
739            // Check if this type is NDEF formatable
740            if (!foundFormattable) {
741                if (isNdefFormatable()) {
742                    foundFormattable = true;
743                    formattableHandle = getConnectedHandle();
744                    formattableLibNfcType = getConnectedLibNfcType();
745                    // We'll only add formattable tech if no ndef is
746                    // found - this is because libNFC refuses to format
747                    // an already NDEF formatted tag.
748                }
749                reconnect();
750            }
751
752            int[] ndefinfo = new int[2];
753            status = checkNdefWithStatus(ndefinfo);
754            if (status != 0) {
755                Log.d(TAG, "Check NDEF Failed - status = " + status);
756                if (status == STATUS_CODE_TARGET_LOST) {
757                    break;
758                }
759                continue;  // try next handle
760            }
761
762            // found our NDEF handle
763            boolean generateEmptyNdef = false;
764
765            int supportedNdefLength = ndefinfo[0];
766            int cardState = ndefinfo[1];
767            byte[] buff = readNdef();
768            if (buff != null) {
769                try {
770                    ndefMsg = new NdefMessage(buff);
771                    addNdefTechnology(ndefMsg,
772                            getConnectedHandle(),
773                            getConnectedLibNfcType(),
774                            getConnectedTechnology(),
775                            supportedNdefLength, cardState);
776                    reconnect();
777                } catch (FormatException e) {
778                   // Create an intent anyway, without NDEF messages
779                   generateEmptyNdef = true;
780                }
781            } else {
782                generateEmptyNdef = true;
783            }
784
785            if (generateEmptyNdef) {
786                ndefMsg = null;
787                addNdefTechnology(null,
788                        getConnectedHandle(),
789                        getConnectedLibNfcType(),
790                        getConnectedTechnology(),
791                        supportedNdefLength, cardState);
792                foundFormattable = false;
793                reconnect();
794            }
795            break;
796        }
797
798        if (ndefMsg == null && foundFormattable) {
799            // Tag is not NDEF yet, and found a formattable target,
800            // so add formattable tech to tech list.
801            addNdefFormatableTechnology(
802                    formattableHandle,
803                    formattableLibNfcType);
804        }
805
806        return ndefMsg;
807    }
808}
809