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