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.ndefpush.NdefPushClient;
     20 import com.android.nfc.ndefpush.NdefPushServer;
     21 import com.android.nfc.snep.SnepClient;
     22 import com.android.nfc.snep.SnepMessage;
     23 import com.android.nfc.snep.SnepServer;
     24 
     25 import android.app.ActivityManager;
     26 import android.app.ActivityManager.RunningTaskInfo;
     27 import android.content.Context;
     28 import android.content.SharedPreferences;
     29 import android.content.pm.ApplicationInfo;
     30 import android.content.pm.PackageManager;
     31 import android.content.pm.PackageManager.NameNotFoundException;
     32 import android.net.Uri;
     33 import android.nfc.INdefPushCallback;
     34 import android.nfc.NdefMessage;
     35 import android.nfc.NdefRecord;
     36 import android.os.AsyncTask;
     37 import android.os.Handler;
     38 import android.os.Message;
     39 import android.os.RemoteException;
     40 import android.os.SystemClock;
     41 import android.provider.ContactsContract.Contacts;
     42 import android.provider.ContactsContract.Profile;
     43 import android.util.Log;
     44 
     45 import java.io.FileDescriptor;
     46 import java.io.IOException;
     47 import java.io.PrintWriter;
     48 import java.nio.charset.Charsets;
     49 import java.util.Arrays;
     50 import java.util.List;
     51 
     52 /**
     53  * Interface to listen for P2P events.
     54  * All callbacks are made from the UI thread.
     55  */
     56 interface P2pEventListener {
     57     /**
     58      * Indicates a P2P device is in range.
     59      * <p>onP2pInRange() and onP2pOutOfRange() will always be called
     60      * alternately.
     61      * <p>All other callbacks will only occur while a P2P device is in range.
     62      */
     63     public void onP2pInRange();
     64 
     65     /**
     66      * Called when a NDEF payload is prepared to send, and confirmation is
     67      * required. Call Callback.onP2pSendConfirmed() to make the confirmation.
     68      */
     69     public void onP2pSendConfirmationRequested();
     70 
     71     /**
     72      * Called to indicate a send was successful.
     73      */
     74     public void onP2pSendComplete();
     75 
     76     /**
     77      * Called to indicate a receive was successful.
     78      */
     79     public void onP2pReceiveComplete();
     80 
     81     /**
     82      * Indicates the P2P device went out of range.
     83      */
     84     public void onP2pOutOfRange();
     85 
     86     public interface Callback {
     87         public void onP2pSendConfirmed();
     88     }
     89 }
     90 
     91 /**
     92  * Manages sending and receiving NDEF message over LLCP link.
     93  * Does simple debouncing of the LLCP link - so that even if the link
     94  * drops and returns the user does not know.
     95  */
     96 public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback {
     97     static final String TAG = "NfcP2pLinkManager";
     98     static final boolean DBG = true;
     99 
    100     // TODO dynamically assign SAP values
    101     static final int NDEFPUSH_SAP = 0x10;
    102 
    103     static final int LINK_DEBOUNCE_MS = 750;
    104 
    105     static final int MSG_DEBOUNCE_TIMEOUT = 1;
    106     static final int MSG_RECEIVE_COMPLETE = 2;
    107     static final int MSG_SEND_COMPLETE = 3;
    108 
    109     // values for mLinkState
    110     static final int LINK_STATE_DOWN = 1;
    111     static final int LINK_STATE_UP = 2;
    112     static final int LINK_STATE_DEBOUNCE =3;
    113 
    114     // values for mSendState
    115     static final int SEND_STATE_NOTHING_TO_SEND = 1;
    116     static final int SEND_STATE_NEED_CONFIRMATION = 2;
    117     static final int SEND_STATE_SENDING = 3;
    118 
    119 
    120     static final Uri PROFILE_URI = Profile.CONTENT_VCARD_URI.buildUpon().
    121             appendQueryParameter(Contacts.QUERY_PARAMETER_VCARD_NO_PHOTO, "true").
    122             build();
    123 
    124     final NdefPushServer mNdefPushServer;
    125     final SnepServer mDefaultSnepServer;
    126     final ActivityManager mActivityManager;
    127     final PackageManager mPackageManager;
    128     final Context mContext;
    129     final P2pEventListener mEventListener;
    130     final Handler mHandler;
    131 
    132     // Locked on NdefP2pManager.this
    133     int mLinkState;
    134     int mSendState;  // valid during LINK_STATE_UP or LINK_STATE_DEBOUNCE
    135     boolean mIsSendEnabled;
    136     boolean mIsReceiveEnabled;
    137     NdefMessage mMessageToSend;  // valid during SEND_STATE_NEED_CONFIRMATION or SEND_STATE_SENDING
    138     NdefMessage mStaticNdef;
    139     INdefPushCallback mCallbackNdef;
    140     SendTask mSendTask;
    141     SharedPreferences mPrefs;
    142     boolean mFirstBeam;
    143 
    144     public P2pLinkManager(Context context) {
    145         mNdefPushServer = new NdefPushServer(NDEFPUSH_SAP, mNppCallback);
    146         mDefaultSnepServer = new SnepServer(mDefaultSnepCallback);
    147         mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    148         mPackageManager = context.getPackageManager();
    149         mContext = context;
    150         mEventListener = new P2pEventManager(context, this);
    151         mHandler = new Handler(this);
    152         mLinkState = LINK_STATE_DOWN;
    153         mSendState = SEND_STATE_NOTHING_TO_SEND;
    154         mIsSendEnabled = false;
    155         mIsReceiveEnabled = false;
    156         mPrefs = context.getSharedPreferences(NfcService.PREF, Context.MODE_PRIVATE);
    157         mFirstBeam = mPrefs.getBoolean(NfcService.PREF_FIRST_BEAM, true);
    158      }
    159 
    160     /**
    161      * May be called from any thread.
    162      * Assumes that NFC is already on if any parameter is true.
    163      */
    164     public void enableDisable(boolean sendEnable, boolean receiveEnable) {
    165         synchronized (this) {
    166             if (!mIsReceiveEnabled && receiveEnable) {
    167                 mDefaultSnepServer.start();
    168                 mNdefPushServer.start();
    169             } else if (mIsReceiveEnabled && !receiveEnable) {
    170                 mDefaultSnepServer.stop();
    171                 mNdefPushServer.stop();
    172             }
    173             mIsSendEnabled = sendEnable;
    174             mIsReceiveEnabled = receiveEnable;
    175         }
    176     }
    177 
    178     /**
    179      * Set NDEF message or callback for sending.
    180      * May be called from any thread.
    181      * NDEF messages or callbacks may be set at any time (even if NFC is
    182      * currently off or P2P send is currently off). They will become
    183      * active as soon as P2P send is enabled.
    184      */
    185     public void setNdefToSend(NdefMessage staticNdef, INdefPushCallback callbackNdef) {
    186         synchronized (this) {
    187             mStaticNdef = staticNdef;
    188             mCallbackNdef = callbackNdef;
    189         }
    190     }
    191 
    192     /**
    193      * Must be called on UI Thread.
    194      */
    195     public void onLlcpActivated() {
    196         Log.i(TAG, "LLCP activated");
    197 
    198         synchronized (P2pLinkManager.this) {
    199             switch (mLinkState) {
    200                 case LINK_STATE_DOWN:
    201                     mLinkState = LINK_STATE_UP;
    202                     mSendState = SEND_STATE_NOTHING_TO_SEND;
    203                     if (DBG) Log.d(TAG, "onP2pInRange()");
    204                     mEventListener.onP2pInRange();
    205 
    206                     prepareMessageToSend();
    207                     if (mMessageToSend != null) {
    208                         mSendState = SEND_STATE_NEED_CONFIRMATION;
    209                         if (DBG) Log.d(TAG, "onP2pSendConfirmationRequested()");
    210                         mEventListener.onP2pSendConfirmationRequested();
    211                     }
    212                     break;
    213                 case LINK_STATE_UP:
    214                     if (DBG) Log.d(TAG, "Duplicate onLlcpActivated()");
    215                     return;
    216                 case LINK_STATE_DEBOUNCE:
    217                     mLinkState = LINK_STATE_UP;
    218                     mHandler.removeMessages(MSG_DEBOUNCE_TIMEOUT);
    219 
    220                     if (mSendState == SEND_STATE_SENDING) {
    221                         Log.i(TAG, "Retry send...");
    222                         sendNdefMessage();
    223                     }
    224                     break;
    225             }
    226         }
    227     }
    228 
    229     void prepareMessageToSend() {
    230         synchronized (P2pLinkManager.this) {
    231             if (!mIsSendEnabled) {
    232                 mMessageToSend = null;
    233                 return;
    234             }
    235 
    236             NdefMessage messageToSend = mStaticNdef;
    237             INdefPushCallback callback = mCallbackNdef;
    238 
    239             if (callback != null) {
    240                 try {
    241                     messageToSend = callback.createMessage();
    242                 } catch (RemoteException e) {
    243                     // Ignore
    244                 }
    245             }
    246 
    247             if (messageToSend == null) {
    248                 messageToSend = createDefaultNdef();
    249             }
    250             mMessageToSend = messageToSend;
    251         }
    252     }
    253 
    254     NdefMessage createDefaultNdef() {
    255         List<RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
    256         if (tasks.size() > 0) {
    257             String pkg = tasks.get(0).baseActivity.getPackageName();
    258             try {
    259                 ApplicationInfo appInfo = mPackageManager.getApplicationInfo(pkg, 0);
    260                 if (0 == (appInfo.flags & ApplicationInfo.FLAG_SYSTEM)) {
    261                     NdefRecord appUri = NdefRecord.createUri(
    262                             Uri.parse("http://market.android.com/search?q=pname:" + pkg));
    263                     NdefRecord appRecord = NdefRecord.createApplicationRecord(pkg);
    264                     return new NdefMessage(new NdefRecord[] { appUri, appRecord });
    265                 }
    266             } catch (NameNotFoundException e) {
    267                 Log.e(TAG, "Bad package returned from ActivityManager: " + pkg);
    268             }
    269         } else {
    270             Log.d(TAG, "no foreground activity");
    271         }
    272         return null;
    273     }
    274 
    275     /**
    276      * Must be called on UI Thread.
    277      */
    278     public void onLlcpDeactivated() {
    279         Log.i(TAG, "LLCP deactivated.");
    280         synchronized (this) {
    281             switch (mLinkState) {
    282                 case LINK_STATE_DOWN:
    283                 case LINK_STATE_DEBOUNCE:
    284                     Log.i(TAG, "Duplicate onLlcpDectivated()");
    285                     break;
    286                 case LINK_STATE_UP:
    287                     // Debounce
    288                     mLinkState = LINK_STATE_DEBOUNCE;
    289                     mHandler.sendEmptyMessageDelayed(MSG_DEBOUNCE_TIMEOUT, LINK_DEBOUNCE_MS);
    290                     cancelSendNdefMessage();
    291                     break;
    292             }
    293          }
    294      }
    295 
    296     void onSendComplete(NdefMessage msg, long elapsedRealtime) {
    297         if (mFirstBeam) {
    298             EventLogTags.writeNfcFirstShare();
    299             mPrefs.edit().putBoolean(NfcService.PREF_FIRST_BEAM, false).apply();
    300             mFirstBeam = false;
    301         }
    302         EventLogTags.writeNfcShare(getMessageSize(msg), getMessageTnf(msg), getMessageType(msg),
    303                 getMessageAarPresent(msg), (int) elapsedRealtime);
    304         // Make callbacks on UI thread
    305         mHandler.sendEmptyMessage(MSG_SEND_COMPLETE);
    306     }
    307 
    308     void sendNdefMessage() {
    309         synchronized (this) {
    310             cancelSendNdefMessage();
    311             mSendTask = new SendTask();
    312             mSendTask.execute();
    313         }
    314     }
    315 
    316     void cancelSendNdefMessage() {
    317         synchronized (P2pLinkManager.this) {
    318             if (mSendTask != null) {
    319                 mSendTask.cancel(true);
    320             }
    321         }
    322     }
    323 
    324     final class SendTask extends AsyncTask<Void, Void, Void> {
    325         @Override
    326         public Void doInBackground(Void... args) {
    327             NdefMessage m;
    328             boolean result;
    329 
    330             synchronized (P2pLinkManager.this) {
    331                 if (mLinkState != LINK_STATE_UP || mSendState != SEND_STATE_SENDING) {
    332                     return null;
    333                 }
    334                 m = mMessageToSend;
    335             }
    336 
    337             long time = SystemClock.elapsedRealtime();
    338             try {
    339                 if (DBG) Log.d(TAG, "Sending ndef via SNEP");
    340                 result = doSnepProtocol(m);
    341             } catch (IOException e) {
    342                 Log.i(TAG, "Failed to connect over SNEP, trying NPP");
    343 
    344                 if (isCancelled()) {
    345                     return null;
    346                 }
    347 
    348                 result = new NdefPushClient().push(m);
    349             }
    350             time = SystemClock.elapsedRealtime() - time;
    351 
    352             if (DBG) Log.d(TAG, "SendTask result=" + result + ", time ms=" + time);
    353 
    354             if (result) {
    355                 onSendComplete(m, time);
    356             }
    357             return null;
    358         }
    359     }
    360 
    361     static boolean doSnepProtocol(NdefMessage msg) throws IOException {
    362         SnepClient snepClient = new SnepClient();
    363         try {
    364             snepClient.connect();
    365         } catch (IOException e) {
    366             // Throw exception to fall back to NPP.
    367             snepClient.close();
    368             throw new IOException("SNEP not available.", e);
    369         }
    370 
    371         try {
    372             snepClient.put(msg);
    373             return true;
    374         } catch (IOException e) {
    375             // SNEP available but had errors, don't fall back to NPP.
    376         } finally {
    377             snepClient.close();
    378         }
    379         return false;
    380     }
    381 
    382     final NdefPushServer.Callback mNppCallback = new NdefPushServer.Callback() {
    383         @Override
    384         public void onMessageReceived(NdefMessage msg) {
    385             onReceiveComplete(msg);
    386         }
    387     };
    388 
    389     final SnepServer.Callback mDefaultSnepCallback = new SnepServer.Callback() {
    390         @Override
    391         public SnepMessage doPut(NdefMessage msg) {
    392             onReceiveComplete(msg);
    393             return SnepMessage.getMessage(SnepMessage.RESPONSE_SUCCESS);
    394         }
    395 
    396         @Override
    397         public SnepMessage doGet(int acceptableLength, NdefMessage msg) {
    398             if (DBG) Log.d(TAG, "GET not supported.");
    399             return SnepMessage.getMessage(SnepMessage.RESPONSE_NOT_IMPLEMENTED);
    400         }
    401     };
    402 
    403     void onReceiveComplete(NdefMessage msg) {
    404         EventLogTags.writeNfcNdefReceived(getMessageSize(msg), getMessageTnf(msg),
    405                 getMessageType(msg), getMessageAarPresent(msg));
    406         // Make callbacks on UI thread
    407         mHandler.obtainMessage(MSG_RECEIVE_COMPLETE, msg).sendToTarget();
    408     }
    409 
    410     @Override
    411     public boolean handleMessage(Message msg) {
    412         switch (msg.what) {
    413             case MSG_DEBOUNCE_TIMEOUT:
    414                 synchronized (this) {
    415                     if (mLinkState != LINK_STATE_DEBOUNCE) {
    416                         break;
    417                     }
    418                     if (mSendState == SEND_STATE_SENDING) {
    419                         EventLogTags.writeNfcShareFail(getMessageSize(mMessageToSend),
    420                                 getMessageTnf(mMessageToSend), getMessageType(mMessageToSend),
    421                                 getMessageAarPresent(mMessageToSend));
    422                     }
    423                     if (DBG) Log.d(TAG, "Debounce timeout");
    424                     mLinkState = LINK_STATE_DOWN;
    425                     mSendState = SEND_STATE_NOTHING_TO_SEND;
    426                     mMessageToSend = null;
    427                     if (DBG) Log.d(TAG, "onP2pOutOfRange()");
    428                     mEventListener.onP2pOutOfRange();
    429                 }
    430                 break;
    431             case MSG_RECEIVE_COMPLETE:
    432                 NdefMessage m = (NdefMessage) msg.obj;
    433                 synchronized (this) {
    434                     if (mLinkState == LINK_STATE_DOWN) {
    435                         break;
    436                     }
    437                     if (mSendState == SEND_STATE_SENDING) {
    438                         cancelSendNdefMessage();
    439                     }
    440                     mSendState = SEND_STATE_NOTHING_TO_SEND;
    441                     if (DBG) Log.d(TAG, "onP2pReceiveComplete()");
    442                     mEventListener.onP2pReceiveComplete();
    443                     NfcService.getInstance().sendMockNdefTag(m);
    444                 }
    445                 break;
    446             case MSG_SEND_COMPLETE:
    447                 synchronized (P2pLinkManager.this) {
    448                     mSendTask = null;
    449 
    450                     if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_SENDING) {
    451                         break;
    452                     }
    453                     mSendState = SEND_STATE_NOTHING_TO_SEND;
    454                     if (DBG) Log.d(TAG, "onP2pSendComplete()");
    455                     mEventListener.onP2pSendComplete();
    456                     if (mCallbackNdef != null) {
    457                         try {
    458                             mCallbackNdef.onNdefPushComplete();
    459                         } catch (RemoteException e) { }
    460                     }
    461                     mSendTask = null;
    462                 }
    463                 break;
    464         }
    465         return true;
    466     }
    467 
    468     int getMessageSize(NdefMessage msg) {
    469         if (msg != null) {
    470             return msg.toByteArray().length;
    471         } else {
    472             return 0;
    473         }
    474     }
    475 
    476     int getMessageTnf(NdefMessage msg) {
    477         if (msg == null) {
    478             return NdefRecord.TNF_EMPTY;
    479         }
    480         NdefRecord records[] = msg.getRecords();
    481         if (records == null || records.length == 0) {
    482             return NdefRecord.TNF_EMPTY;
    483         }
    484         return records[0].getTnf();
    485     }
    486 
    487     String getMessageType(NdefMessage msg) {
    488         if (msg == null) {
    489             return "null";
    490         }
    491         NdefRecord records[] = msg.getRecords();
    492         if (records == null || records.length == 0) {
    493             return "null";
    494         }
    495         NdefRecord record = records[0];
    496         switch (record.getTnf()) {
    497             case NdefRecord.TNF_ABSOLUTE_URI:
    498                 // The actual URI is in the type field, don't log it
    499                 return "uri";
    500             case NdefRecord.TNF_EXTERNAL_TYPE:
    501             case NdefRecord.TNF_MIME_MEDIA:
    502             case NdefRecord.TNF_WELL_KNOWN:
    503                 return new String(record.getType(), Charsets.UTF_8);
    504             default:
    505                 return "unknown";
    506         }
    507     }
    508 
    509     int getMessageAarPresent(NdefMessage msg) {
    510         if (msg == null) {
    511             return 0;
    512         }
    513         NdefRecord records[] = msg.getRecords();
    514         if (records == null) {
    515             return 0;
    516         }
    517         for (NdefRecord record : records) {
    518             if (record.getTnf() == NdefRecord.TNF_EXTERNAL_TYPE &&
    519                     Arrays.equals(NdefRecord.RTD_ANDROID_APP, record.getType())) {
    520                 return 1;
    521             }
    522         }
    523         return 0;
    524     }
    525 
    526     @Override
    527     public void onP2pSendConfirmed() {
    528         if (DBG) Log.d(TAG, "onP2pSendConfirmed()");
    529         synchronized (this) {
    530             if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_NEED_CONFIRMATION) {
    531                 return;
    532             }
    533             mSendState = SEND_STATE_SENDING;
    534             if (mLinkState == LINK_STATE_UP) {
    535                 sendNdefMessage();
    536             }
    537         }
    538     }
    539 
    540     static String sendStateToString(int state) {
    541         switch (state) {
    542             case SEND_STATE_NOTHING_TO_SEND:
    543                 return "SEND_STATE_NOTHING_TO_SEND";
    544             case SEND_STATE_NEED_CONFIRMATION:
    545                 return "SEND_STATE_NEED_CONFIRMATION";
    546             case SEND_STATE_SENDING:
    547                 return "SEND_STATE_SENDING";
    548             default:
    549                 return "<error>";
    550         }
    551     }
    552 
    553     static String linkStateToString(int state) {
    554         switch (state) {
    555             case LINK_STATE_DOWN:
    556                 return "LINK_STATE_DOWN";
    557             case LINK_STATE_DEBOUNCE:
    558                 return "LINK_STATE_DEBOUNCE";
    559             case LINK_STATE_UP:
    560                 return "LINK_STATE_UP";
    561             default:
    562                 return "<error>";
    563         }
    564     }
    565 
    566     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    567         synchronized (this) {
    568             pw.println("mIsSendEnabled=" + mIsSendEnabled);
    569             pw.println("mIsReceiveEnabled=" + mIsReceiveEnabled);
    570             pw.println("mLinkState=" + linkStateToString(mLinkState));
    571             pw.println("mSendState=" + sendStateToString(mSendState));
    572 
    573             pw.println("mStaticNdef=" + mStaticNdef);
    574             pw.println("mCallbackNdef=" + mCallbackNdef);
    575             pw.println("mMessageToSend=" + mMessageToSend);
    576         }
    577     }
    578 }
    579