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