• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.nfc;
18 
19 import com.android.nfc.echoserver.EchoServer;
20 import com.android.nfc.handover.HandoverClient;
21 import com.android.nfc.handover.HandoverManager;
22 import com.android.nfc.handover.HandoverServer;
23 import com.android.nfc.ndefpush.NdefPushClient;
24 import com.android.nfc.ndefpush.NdefPushServer;
25 import com.android.nfc.snep.SnepClient;
26 import com.android.nfc.snep.SnepMessage;
27 import com.android.nfc.snep.SnepServer;
28 
29 import android.app.ActivityManager;
30 import android.app.ActivityManager.RunningTaskInfo;
31 import android.content.Context;
32 import android.content.SharedPreferences;
33 import android.content.pm.ApplicationInfo;
34 import android.content.pm.PackageManager;
35 import android.content.pm.PackageManager.NameNotFoundException;
36 import android.net.Uri;
37 import android.nfc.INdefPushCallback;
38 import android.nfc.NdefMessage;
39 import android.nfc.NdefRecord;
40 import android.os.AsyncTask;
41 import android.os.Bundle;
42 import android.os.Handler;
43 import android.os.Message;
44 import android.os.RemoteException;
45 import android.os.SystemClock;
46 import android.provider.ContactsContract.Contacts;
47 import android.provider.ContactsContract.Profile;
48 import android.util.Log;
49 
50 import java.io.FileDescriptor;
51 import java.io.IOException;
52 import java.io.PrintWriter;
53 import java.nio.charset.Charsets;
54 import java.util.Arrays;
55 import java.util.List;
56 
57 /**
58  * Interface to listen for P2P events.
59  * All callbacks are made from the UI thread.
60  */
61 interface 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      */
onP2pInRange()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      */
onP2pSendConfirmationRequested()74     public void onP2pSendConfirmationRequested();
75 
76     /**
77      * Called to indicate a send was successful.
78      */
onP2pSendComplete()79     public void onP2pSendComplete();
80 
81     /**
82      * Called to indicate the remote device does not support connection handover
83      */
onP2pHandoverNotSupported()84     public void onP2pHandoverNotSupported();
85 
86     /**
87      * Called to indicate a receive was successful.
88      */
onP2pReceiveComplete(boolean playSound)89     public void onP2pReceiveComplete(boolean playSound);
90 
91     /**
92      * Indicates the P2P device went out of range.
93      */
onP2pOutOfRange()94     public void onP2pOutOfRange();
95 
96     public interface Callback {
onP2pSendConfirmed()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  */
106 public 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 
P2pLinkManager(Context context, HandoverManager handoverManager, int defaultMiu, int defaultRwSize)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      */
enableDisable(boolean sendEnable, boolean receiveEnable)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      */
setNdefCallback(INdefPushCallback callbackNdef)245     public void setNdefCallback(INdefPushCallback callbackNdef) {
246         synchronized (this) {
247             mCallbackNdef = callbackNdef;
248         }
249     }
250 
251     /**
252      * Must be called on UI Thread.
253      */
onLlcpActivated()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 
prepareMessageToSend()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 
beamDefaultDisabled(String pkgName)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 
createDefaultNdef(String pkgName)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      */
onLlcpDeactivated()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 
onHandoverUnsupported()377     void onHandoverUnsupported() {
378         mHandler.sendEmptyMessage(MSG_HANDOVER_NOT_SUPPORTED);
379     }
380 
onSendComplete(NdefMessage msg, long elapsedRealtime)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 
sendNdefMessage()393     void sendNdefMessage() {
394         synchronized (this) {
395             cancelSendNdefMessage();
396             mSendTask = new SendTask();
397             mSendTask.execute();
398         }
399     }
400 
cancelSendNdefMessage()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
doInBackground(Void... args)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 
doSnepProtocol(HandoverManager handoverManager, NdefMessage msg, Uri[] uris, int miu, int rwSize)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 
onReceiveHandover()559     void onReceiveHandover() {
560         mHandler.obtainMessage(MSG_RECEIVE_HANDOVER).sendToTarget();
561     }
562 
onReceiveComplete(NdefMessage msg)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
handleMessage(Message msg)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 
getMessageSize(NdefMessage msg)664     int getMessageSize(NdefMessage msg) {
665         if (msg != null) {
666             return msg.toByteArray().length;
667         } else {
668             return 0;
669         }
670     }
671 
getMessageTnf(NdefMessage msg)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 
getMessageType(NdefMessage msg)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 
getMessageAarPresent(NdefMessage msg)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
onP2pSendConfirmed()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 
sendStateToString(int state)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 
linkStateToString(int state)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 
dump(FileDescriptor fd, PrintWriter pw, String[] args)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