1/*
2 * Copyright (C) 2011 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;
18
19import com.android.nfc.echoserver.EchoServer;
20import com.android.nfc.handover.HandoverClient;
21import com.android.nfc.handover.HandoverManager;
22import com.android.nfc.handover.HandoverServer;
23import com.android.nfc.ndefpush.NdefPushClient;
24import com.android.nfc.ndefpush.NdefPushServer;
25import com.android.nfc.snep.SnepClient;
26import com.android.nfc.snep.SnepMessage;
27import com.android.nfc.snep.SnepServer;
28
29import android.app.ActivityManager;
30import android.app.ActivityManager.RunningTaskInfo;
31import android.content.Context;
32import android.content.SharedPreferences;
33import android.content.pm.ApplicationInfo;
34import android.content.pm.PackageManager;
35import android.content.pm.PackageManager.NameNotFoundException;
36import android.net.Uri;
37import android.nfc.INdefPushCallback;
38import android.nfc.NdefMessage;
39import android.nfc.NdefRecord;
40import android.os.AsyncTask;
41import android.os.Bundle;
42import android.os.Handler;
43import android.os.Message;
44import android.os.RemoteException;
45import android.os.SystemClock;
46import android.provider.ContactsContract.Contacts;
47import android.provider.ContactsContract.Profile;
48import android.util.Log;
49
50import java.io.FileDescriptor;
51import java.io.IOException;
52import java.io.PrintWriter;
53import java.nio.charset.Charsets;
54import java.util.Arrays;
55import java.util.List;
56
57/**
58 * Interface to listen for P2P events.
59 * All callbacks are made from the UI thread.
60 */
61interface P2pEventListener {
62    /**
63     * Indicates a P2P device is in range.
64     * <p>onP2pInRange() and onP2pOutOfRange() will always be called
65     * alternately.
66     * <p>All other callbacks will only occur while a P2P device is in range.
67     */
68    public void onP2pInRange();
69
70    /**
71     * Called when a NDEF payload is prepared to send, and confirmation is
72     * required. Call Callback.onP2pSendConfirmed() to make the confirmation.
73     */
74    public void onP2pSendConfirmationRequested();
75
76    /**
77     * Called to indicate a send was successful.
78     */
79    public void onP2pSendComplete();
80
81    /**
82     * Called to indicate the remote device does not support connection handover
83     */
84    public void onP2pHandoverNotSupported();
85
86    /**
87     * Called to indicate a receive was successful.
88     */
89    public void onP2pReceiveComplete(boolean playSound);
90
91    /**
92     * Indicates the P2P device went out of range.
93     */
94    public void onP2pOutOfRange();
95
96    public interface Callback {
97        public void onP2pSendConfirmed();
98    }
99}
100
101/**
102 * Manages sending and receiving NDEF message over LLCP link.
103 * Does simple debouncing of the LLCP link - so that even if the link
104 * drops and returns the user does not know.
105 */
106public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback {
107    static final String TAG = "NfcP2pLinkManager";
108    static final boolean DBG = true;
109
110    /** Include this constant as a meta-data entry in the manifest
111     *  of an application to disable beaming the market/AAR link, like this:
112     *  <pre>{@code
113     *  <application ...>
114     *      <meta-data android:name="android.nfc.disable_beam_default"
115     *          android:value="true" />
116     *  </application>
117     *  }</pre>
118     */
119    static final String DISABLE_BEAM_DEFAULT = "android.nfc.disable_beam_default";
120
121    /** Enables the LLCP EchoServer, which can be used to test the android
122     * LLCP stack against nfcpy.
123     */
124    static final boolean ECHOSERVER_ENABLED = false;
125
126    // TODO dynamically assign SAP values
127    static final int NDEFPUSH_SAP = 0x10;
128    static final int HANDOVER_SAP = 0x14;
129
130    static final int LINK_DEBOUNCE_MS = 750;
131
132    static final int MSG_DEBOUNCE_TIMEOUT = 1;
133    static final int MSG_RECEIVE_COMPLETE = 2;
134    static final int MSG_RECEIVE_HANDOVER = 3;
135    static final int MSG_SEND_COMPLETE = 4;
136    static final int MSG_START_ECHOSERVER = 5;
137    static final int MSG_STOP_ECHOSERVER = 6;
138    static final int MSG_HANDOVER_NOT_SUPPORTED = 7;
139
140    // values for mLinkState
141    static final int LINK_STATE_DOWN = 1;
142    static final int LINK_STATE_UP = 2;
143    static final int LINK_STATE_DEBOUNCE =3;
144
145    // values for mSendState
146    static final int SEND_STATE_NOTHING_TO_SEND = 1;
147    static final int SEND_STATE_NEED_CONFIRMATION = 2;
148    static final int SEND_STATE_SENDING = 3;
149
150    // return values for doSnepProtocol
151    static final int SNEP_SUCCESS = 0;
152    static final int SNEP_FAILURE = 1;
153    static final int SNEP_HANDOVER_UNSUPPORTED = 2;
154
155    static final Uri PROFILE_URI = Profile.CONTENT_VCARD_URI.buildUpon().
156            appendQueryParameter(Contacts.QUERY_PARAMETER_VCARD_NO_PHOTO, "true").
157            build();
158
159    final NdefPushServer mNdefPushServer;
160    final SnepServer mDefaultSnepServer;
161    final HandoverServer mHandoverServer;
162    final EchoServer mEchoServer;
163    final ActivityManager mActivityManager;
164    final PackageManager mPackageManager;
165    final Context mContext;
166    final P2pEventListener mEventListener;
167    final Handler mHandler;
168    final HandoverManager mHandoverManager;
169
170    final int mDefaultMiu;
171    final int mDefaultRwSize;
172
173    // Locked on NdefP2pManager.this
174    int mLinkState;
175    int mSendState;  // valid during LINK_STATE_UP or LINK_STATE_DEBOUNCE
176    boolean mIsSendEnabled;
177    boolean mIsReceiveEnabled;
178    NdefMessage mMessageToSend;  // valid during SEND_STATE_NEED_CONFIRMATION or SEND_STATE_SENDING
179    Uri[] mUrisToSend;  // valid during SEND_STATE_NEED_CONFIRMATION or SEND_STATE_SENDING
180    INdefPushCallback mCallbackNdef;
181    SendTask mSendTask;
182    SharedPreferences mPrefs;
183    boolean mFirstBeam;
184
185    public P2pLinkManager(Context context, HandoverManager handoverManager, int defaultMiu,
186            int defaultRwSize) {
187        mNdefPushServer = new NdefPushServer(NDEFPUSH_SAP, mNppCallback);
188        mDefaultSnepServer = new SnepServer(mDefaultSnepCallback, defaultMiu, defaultRwSize);
189        mHandoverServer = new HandoverServer(HANDOVER_SAP, handoverManager, mHandoverCallback);
190
191        if (ECHOSERVER_ENABLED) {
192            mEchoServer = new EchoServer();
193        } else {
194            mEchoServer = null;
195        }
196        mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
197        mPackageManager = context.getPackageManager();
198        mContext = context;
199        mEventListener = new P2pEventManager(context, this);
200        mHandler = new Handler(this);
201        mLinkState = LINK_STATE_DOWN;
202        mSendState = SEND_STATE_NOTHING_TO_SEND;
203        mIsSendEnabled = false;
204        mIsReceiveEnabled = false;
205        mPrefs = context.getSharedPreferences(NfcService.PREF, Context.MODE_PRIVATE);
206        mFirstBeam = mPrefs.getBoolean(NfcService.PREF_FIRST_BEAM, true);
207        mHandoverManager = handoverManager;
208        mDefaultMiu = defaultMiu;
209        mDefaultRwSize = defaultRwSize;
210     }
211
212    /**
213     * May be called from any thread.
214     * Assumes that NFC is already on if any parameter is true.
215     */
216    public void enableDisable(boolean sendEnable, boolean receiveEnable) {
217        synchronized (this) {
218            if (!mIsReceiveEnabled && receiveEnable) {
219                mDefaultSnepServer.start();
220                mNdefPushServer.start();
221                mHandoverServer.start();
222                if (mEchoServer != null) {
223                    mHandler.sendEmptyMessage(MSG_START_ECHOSERVER);
224                }
225            } else if (mIsReceiveEnabled && !receiveEnable) {
226                mDefaultSnepServer.stop();
227                mNdefPushServer.stop();
228                mHandoverServer.stop();
229                if (mEchoServer != null) {
230                    mHandler.sendEmptyMessage(MSG_STOP_ECHOSERVER);
231                }
232            }
233            mIsSendEnabled = sendEnable;
234            mIsReceiveEnabled = receiveEnable;
235        }
236    }
237
238    /**
239     * Set NDEF callback for sending.
240     * May be called from any thread.
241     * NDEF callbacks may be set at any time (even if NFC is
242     * currently off or P2P send is currently off). They will become
243     * active as soon as P2P send is enabled.
244     */
245    public void setNdefCallback(INdefPushCallback callbackNdef) {
246        synchronized (this) {
247            mCallbackNdef = callbackNdef;
248        }
249    }
250
251    /**
252     * Must be called on UI Thread.
253     */
254    public void onLlcpActivated() {
255        Log.i(TAG, "LLCP activated");
256
257        synchronized (P2pLinkManager.this) {
258            if (mEchoServer != null) {
259                mEchoServer.onLlcpActivated();
260            }
261
262            switch (mLinkState) {
263                case LINK_STATE_DOWN:
264                    mLinkState = LINK_STATE_UP;
265                    mSendState = SEND_STATE_NOTHING_TO_SEND;
266                    if (DBG) Log.d(TAG, "onP2pInRange()");
267                    mEventListener.onP2pInRange();
268
269                    prepareMessageToSend();
270                    if (mMessageToSend != null ||
271                            (mUrisToSend != null && mHandoverManager.isHandoverSupported())) {
272                        mSendState = SEND_STATE_NEED_CONFIRMATION;
273                        if (DBG) Log.d(TAG, "onP2pSendConfirmationRequested()");
274                        mEventListener.onP2pSendConfirmationRequested();
275                    }
276                    break;
277                case LINK_STATE_UP:
278                    if (DBG) Log.d(TAG, "Duplicate onLlcpActivated()");
279                    return;
280                case LINK_STATE_DEBOUNCE:
281                    mLinkState = LINK_STATE_UP;
282                    mHandler.removeMessages(MSG_DEBOUNCE_TIMEOUT);
283
284                    if (mSendState == SEND_STATE_SENDING) {
285                        Log.i(TAG, "Retry send...");
286                        sendNdefMessage();
287                    }
288                    break;
289            }
290        }
291    }
292
293    void prepareMessageToSend() {
294        synchronized (P2pLinkManager.this) {
295            if (!mIsSendEnabled) {
296                mMessageToSend = null;
297                mUrisToSend = null;
298                return;
299            }
300
301            // Try application callback first
302            //TODO: Check that mCallbackNdef refers to the foreground activity
303            if (mCallbackNdef != null) {
304                try {
305                    mMessageToSend = mCallbackNdef.createMessage();
306                    mUrisToSend = mCallbackNdef.getUris();
307                    return;
308                } catch (RemoteException e) {
309                    // Ignore
310                }
311            }
312
313            // fall back to default NDEF for this activity, unless the
314            // application disabled this explicitly in their manifest.
315            List<RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
316            if (tasks.size() > 0) {
317                String pkg = tasks.get(0).baseActivity.getPackageName();
318                if (beamDefaultDisabled(pkg)) {
319                    Log.d(TAG, "Disabling default Beam behavior");
320                    mMessageToSend = null;
321                } else {
322                    mMessageToSend = createDefaultNdef(pkg);
323                }
324            } else {
325                mMessageToSend = null;
326            }
327            if (DBG) Log.d(TAG, "mMessageToSend = " + mMessageToSend);
328            if (DBG) Log.d(TAG, "mUrisToSend = " + mUrisToSend);
329        }
330    }
331
332    boolean beamDefaultDisabled(String pkgName) {
333        try {
334            ApplicationInfo ai = mPackageManager.getApplicationInfo(pkgName,
335                    PackageManager.GET_META_DATA);
336            if (ai == null || ai.metaData == null) {
337                return false;
338            }
339            return ai.metaData.getBoolean(DISABLE_BEAM_DEFAULT);
340        } catch (NameNotFoundException e) {
341            return false;
342        }
343    }
344
345    NdefMessage createDefaultNdef(String pkgName) {
346        NdefRecord appUri = NdefRecord.createUri(Uri.parse(
347                "http://play.google.com/store/apps/details?id=" + pkgName + "&feature=beam"));
348        NdefRecord appRecord = NdefRecord.createApplicationRecord(pkgName);
349        return new NdefMessage(new NdefRecord[] { appUri, appRecord });
350    }
351
352    /**
353     * Must be called on UI Thread.
354     */
355    public void onLlcpDeactivated() {
356        Log.i(TAG, "LLCP deactivated.");
357        synchronized (this) {
358            if (mEchoServer != null) {
359                mEchoServer.onLlcpDeactivated();
360            }
361
362            switch (mLinkState) {
363                case LINK_STATE_DOWN:
364                case LINK_STATE_DEBOUNCE:
365                    Log.i(TAG, "Duplicate onLlcpDectivated()");
366                    break;
367                case LINK_STATE_UP:
368                    // Debounce
369                    mLinkState = LINK_STATE_DEBOUNCE;
370                    mHandler.sendEmptyMessageDelayed(MSG_DEBOUNCE_TIMEOUT, LINK_DEBOUNCE_MS);
371                    cancelSendNdefMessage();
372                    break;
373            }
374         }
375     }
376
377    void onHandoverUnsupported() {
378        mHandler.sendEmptyMessage(MSG_HANDOVER_NOT_SUPPORTED);
379    }
380
381    void onSendComplete(NdefMessage msg, long elapsedRealtime) {
382        if (mFirstBeam) {
383            EventLogTags.writeNfcFirstShare();
384            mPrefs.edit().putBoolean(NfcService.PREF_FIRST_BEAM, false).apply();
385            mFirstBeam = false;
386        }
387        EventLogTags.writeNfcShare(getMessageSize(msg), getMessageTnf(msg), getMessageType(msg),
388                getMessageAarPresent(msg), (int) elapsedRealtime);
389        // Make callbacks on UI thread
390        mHandler.sendEmptyMessage(MSG_SEND_COMPLETE);
391    }
392
393    void sendNdefMessage() {
394        synchronized (this) {
395            cancelSendNdefMessage();
396            mSendTask = new SendTask();
397            mSendTask.execute();
398        }
399    }
400
401    void cancelSendNdefMessage() {
402        synchronized (P2pLinkManager.this) {
403            if (mSendTask != null) {
404                mSendTask.cancel(true);
405            }
406        }
407    }
408
409    final class SendTask extends AsyncTask<Void, Void, Void> {
410        @Override
411        public Void doInBackground(Void... args) {
412            NdefMessage m;
413            Uri[] uris;
414            boolean result;
415
416            synchronized (P2pLinkManager.this) {
417                if (mLinkState != LINK_STATE_UP || mSendState != SEND_STATE_SENDING) {
418                    return null;
419                }
420                m = mMessageToSend;
421                uris = mUrisToSend;
422            }
423
424            long time = SystemClock.elapsedRealtime();
425
426            try {
427                if (DBG) Log.d(TAG, "Sending ndef via SNEP");
428
429                int snepResult = doSnepProtocol(mHandoverManager, m, uris,
430                        mDefaultMiu, mDefaultRwSize);
431
432                switch (snepResult) {
433                    case SNEP_HANDOVER_UNSUPPORTED:
434                        onHandoverUnsupported();
435                        return null;
436                    case SNEP_SUCCESS:
437                        result = true;
438                        break;
439                    case SNEP_FAILURE:
440                        result = false;
441                        break;
442                    default:
443                        result = false;
444                }
445            } catch (IOException e) {
446                Log.i(TAG, "Failed to connect over SNEP, trying NPP");
447
448                if (isCancelled()) {
449                    return null;
450                }
451
452                if (m != null) {
453                    result = new NdefPushClient().push(m);
454                } else {
455                    result = false;
456                }
457            }
458            time = SystemClock.elapsedRealtime() - time;
459
460            if (DBG) Log.d(TAG, "SendTask result=" + result + ", time ms=" + time);
461
462            if (result) {
463                onSendComplete(m, time);
464            }
465            return null;
466        }
467    }
468
469    static int doSnepProtocol(HandoverManager handoverManager,
470            NdefMessage msg, Uri[] uris, int miu, int rwSize) throws IOException {
471        SnepClient snepClient = new SnepClient(miu, rwSize);
472        try {
473            snepClient.connect();
474        } catch (IOException e) {
475            // Throw exception to fall back to NPP.
476            snepClient.close();
477            throw new IOException("SNEP not available.", e);
478        }
479
480        try {
481            if (uris != null) {
482                HandoverClient handoverClient = new HandoverClient();
483
484                NdefMessage response = null;
485                NdefMessage request = handoverManager.createHandoverRequestMessage();
486                if (request != null) {
487                    response = handoverClient.sendHandoverRequest(request);
488
489                    if (response == null) {
490                        // Remote device may not support handover service,
491                        // try the (deprecated) SNEP GET implementation
492                        // for devices running Android 4.1
493                        SnepMessage snepResponse = snepClient.get(request);
494                        response = snepResponse.getNdefMessage();
495                    }
496                } // else, handover not supported
497                if (response != null) {
498                    handoverManager.doHandoverUri(uris, response);
499                } else if (msg != null) {
500                    // For backwards-compatibility to pre-J devices,
501                    // try to push an NDEF message (if any) if the handover GET
502                    // does not work.
503                    snepClient.put(msg);
504                } else {
505                    // We had a failed handover and no alternate message to
506                    // send; indicate remote device doesn't support handover.
507                    return SNEP_HANDOVER_UNSUPPORTED;
508                }
509            } else if (msg != null) {
510                snepClient.put(msg);
511            }
512            return SNEP_SUCCESS;
513        } catch (IOException e) {
514            // SNEP available but had errors, don't fall back to NPP.
515        } finally {
516            snepClient.close();
517        }
518        return SNEP_FAILURE;
519    }
520
521    final HandoverServer.Callback mHandoverCallback = new HandoverServer.Callback() {
522        @Override
523        public void onHandoverRequestReceived() {
524            onReceiveHandover();
525        }
526    };
527
528    final NdefPushServer.Callback mNppCallback = new NdefPushServer.Callback() {
529        @Override
530        public void onMessageReceived(NdefMessage msg) {
531            onReceiveComplete(msg);
532        }
533    };
534
535    final SnepServer.Callback mDefaultSnepCallback = new SnepServer.Callback() {
536        @Override
537        public SnepMessage doPut(NdefMessage msg) {
538            onReceiveComplete(msg);
539            return SnepMessage.getMessage(SnepMessage.RESPONSE_SUCCESS);
540        }
541
542        @Override
543        public SnepMessage doGet(int acceptableLength, NdefMessage msg) {
544            // The NFC Forum Default SNEP server is not allowed to respond to
545            // SNEP GET requests - see SNEP 1.0 TS section 6.1. However,
546            // since Android 4.1 used the NFC Forum default server to
547            // implement connection handover, we will support this
548            // until we can deprecate it.
549            NdefMessage response = mHandoverManager.tryHandoverRequest(msg);
550            if (response != null) {
551                onReceiveHandover();
552                return SnepMessage.getSuccessResponse(response);
553            } else {
554                return SnepMessage.getMessage(SnepMessage.RESPONSE_NOT_IMPLEMENTED);
555            }
556        }
557    };
558
559    void onReceiveHandover() {
560        mHandler.obtainMessage(MSG_RECEIVE_HANDOVER).sendToTarget();
561    }
562
563    void onReceiveComplete(NdefMessage msg) {
564        EventLogTags.writeNfcNdefReceived(getMessageSize(msg), getMessageTnf(msg),
565                getMessageType(msg), getMessageAarPresent(msg));
566        // Make callbacks on UI thread
567        mHandler.obtainMessage(MSG_RECEIVE_COMPLETE, msg).sendToTarget();
568    }
569
570    @Override
571    public boolean handleMessage(Message msg) {
572        switch (msg.what) {
573            case MSG_START_ECHOSERVER:
574                synchronized (this) {
575                    mEchoServer.start();
576                    break;
577                }
578            case MSG_STOP_ECHOSERVER:
579                synchronized (this) {
580                    mEchoServer.stop();
581                    break;
582                }
583            case MSG_DEBOUNCE_TIMEOUT:
584                synchronized (this) {
585                    if (mLinkState != LINK_STATE_DEBOUNCE) {
586                        break;
587                    }
588                    if (mSendState == SEND_STATE_SENDING) {
589                        EventLogTags.writeNfcShareFail(getMessageSize(mMessageToSend),
590                                getMessageTnf(mMessageToSend), getMessageType(mMessageToSend),
591                                getMessageAarPresent(mMessageToSend));
592                    }
593                    if (DBG) Log.d(TAG, "Debounce timeout");
594                    mLinkState = LINK_STATE_DOWN;
595                    mSendState = SEND_STATE_NOTHING_TO_SEND;
596                    mMessageToSend = null;
597                    mUrisToSend = null;
598                    if (DBG) Log.d(TAG, "onP2pOutOfRange()");
599                    mEventListener.onP2pOutOfRange();
600                }
601                break;
602            case MSG_RECEIVE_HANDOVER:
603                // We're going to do a handover request
604                synchronized (this) {
605                    if (mLinkState == LINK_STATE_DOWN) {
606                        break;
607                    }
608                    if (mSendState == SEND_STATE_SENDING) {
609                        cancelSendNdefMessage();
610                    }
611                    mSendState = SEND_STATE_NOTHING_TO_SEND;
612                    if (DBG) Log.d(TAG, "onP2pReceiveComplete()");
613                    mEventListener.onP2pReceiveComplete(false);
614                }
615                break;
616            case MSG_RECEIVE_COMPLETE:
617                NdefMessage m = (NdefMessage) msg.obj;
618                synchronized (this) {
619                    if (mLinkState == LINK_STATE_DOWN) {
620                        break;
621                    }
622                    if (mSendState == SEND_STATE_SENDING) {
623                        cancelSendNdefMessage();
624                    }
625                    mSendState = SEND_STATE_NOTHING_TO_SEND;
626                    if (DBG) Log.d(TAG, "onP2pReceiveComplete()");
627                    mEventListener.onP2pReceiveComplete(true);
628                    NfcService.getInstance().sendMockNdefTag(m);
629                }
630                break;
631            case MSG_HANDOVER_NOT_SUPPORTED:
632                synchronized (P2pLinkManager.this) {
633                    mSendTask = null;
634
635                    if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_SENDING) {
636                        break;
637                    }
638                    mSendState = SEND_STATE_NOTHING_TO_SEND;
639                    if (DBG) Log.d(TAG, "onP2pHandoverNotSupported()");
640                    mEventListener.onP2pHandoverNotSupported();
641                }
642                break;
643            case MSG_SEND_COMPLETE:
644                synchronized (P2pLinkManager.this) {
645                    mSendTask = null;
646
647                    if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_SENDING) {
648                        break;
649                    }
650                    mSendState = SEND_STATE_NOTHING_TO_SEND;
651                    if (DBG) Log.d(TAG, "onP2pSendComplete()");
652                    mEventListener.onP2pSendComplete();
653                    if (mCallbackNdef != null) {
654                        try {
655                            mCallbackNdef.onNdefPushComplete();
656                        } catch (RemoteException e) { }
657                    }
658                }
659                break;
660        }
661        return true;
662    }
663
664    int getMessageSize(NdefMessage msg) {
665        if (msg != null) {
666            return msg.toByteArray().length;
667        } else {
668            return 0;
669        }
670    }
671
672    int getMessageTnf(NdefMessage msg) {
673        if (msg == null) {
674            return NdefRecord.TNF_EMPTY;
675        }
676        NdefRecord records[] = msg.getRecords();
677        if (records == null || records.length == 0) {
678            return NdefRecord.TNF_EMPTY;
679        }
680        return records[0].getTnf();
681    }
682
683    String getMessageType(NdefMessage msg) {
684        if (msg == null) {
685            return "null";
686        }
687        NdefRecord records[] = msg.getRecords();
688        if (records == null || records.length == 0) {
689            return "null";
690        }
691        NdefRecord record = records[0];
692        switch (record.getTnf()) {
693            case NdefRecord.TNF_ABSOLUTE_URI:
694                // The actual URI is in the type field, don't log it
695                return "uri";
696            case NdefRecord.TNF_EXTERNAL_TYPE:
697            case NdefRecord.TNF_MIME_MEDIA:
698            case NdefRecord.TNF_WELL_KNOWN:
699                return new String(record.getType(), Charsets.UTF_8);
700            default:
701                return "unknown";
702        }
703    }
704
705    int getMessageAarPresent(NdefMessage msg) {
706        if (msg == null) {
707            return 0;
708        }
709        NdefRecord records[] = msg.getRecords();
710        if (records == null) {
711            return 0;
712        }
713        for (NdefRecord record : records) {
714            if (record.getTnf() == NdefRecord.TNF_EXTERNAL_TYPE &&
715                    Arrays.equals(NdefRecord.RTD_ANDROID_APP, record.getType())) {
716                return 1;
717            }
718        }
719        return 0;
720    }
721
722    @Override
723    public void onP2pSendConfirmed() {
724        if (DBG) Log.d(TAG, "onP2pSendConfirmed()");
725        synchronized (this) {
726            if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_NEED_CONFIRMATION) {
727                return;
728            }
729            mSendState = SEND_STATE_SENDING;
730            if (mLinkState == LINK_STATE_UP) {
731                sendNdefMessage();
732            }
733        }
734    }
735
736    static String sendStateToString(int state) {
737        switch (state) {
738            case SEND_STATE_NOTHING_TO_SEND:
739                return "SEND_STATE_NOTHING_TO_SEND";
740            case SEND_STATE_NEED_CONFIRMATION:
741                return "SEND_STATE_NEED_CONFIRMATION";
742            case SEND_STATE_SENDING:
743                return "SEND_STATE_SENDING";
744            default:
745                return "<error>";
746        }
747    }
748
749    static String linkStateToString(int state) {
750        switch (state) {
751            case LINK_STATE_DOWN:
752                return "LINK_STATE_DOWN";
753            case LINK_STATE_DEBOUNCE:
754                return "LINK_STATE_DEBOUNCE";
755            case LINK_STATE_UP:
756                return "LINK_STATE_UP";
757            default:
758                return "<error>";
759        }
760    }
761
762    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
763        synchronized (this) {
764            pw.println("mIsSendEnabled=" + mIsSendEnabled);
765            pw.println("mIsReceiveEnabled=" + mIsReceiveEnabled);
766            pw.println("mLinkState=" + linkStateToString(mLinkState));
767            pw.println("mSendState=" + sendStateToString(mSendState));
768
769            pw.println("mCallbackNdef=" + mCallbackNdef);
770            pw.println("mMessageToSend=" + mMessageToSend);
771            pw.println("mUrisToSend=" + mUrisToSend);
772        }
773    }
774}
775