Home | History | Annotate | Download | only in cardemulation
      1 /*
      2  * Copyright (C) 2015 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 android.nfc.cardemulation;
     18 
     19 import android.annotation.SdkConstant;
     20 import android.annotation.SdkConstant.SdkConstantType;
     21 import android.app.Service;
     22 import android.content.ComponentName;
     23 import android.content.Intent;
     24 import android.os.Bundle;
     25 import android.os.Handler;
     26 import android.os.IBinder;
     27 import android.os.Message;
     28 import android.os.Messenger;
     29 import android.os.RemoteException;
     30 import android.util.Log;
     31 
     32 /**
     33  * <p>HostNfcFService is a convenience {@link Service} class that can be
     34  * extended to emulate an NFC-F card inside an Android service component.
     35  *
     36  * <h3>NFC Protocols</h3>
     37  * <p>Cards emulated by this class are based on the NFC-Forum NFC-F
     38  * protocol (based on the JIS-X 6319-4 specification.)</p>
     39  *
     40  * <h3>System Code and NFCID2 registration</h3>
     41  * <p>A {@link HostNfcFService HostNfcFService service} can register
     42  * exactly one System Code and one NFCID2. For details about the use of
     43  * System Code and NFCID2, see the NFC Forum Digital specification.</p>
     44  * <p>To statically register a System Code and NFCID2 with the service, a {@link #SERVICE_META_DATA}
     45  * entry must be included in the declaration of the service.
     46  *
     47  * <p>All {@link HostNfcFService HostNfcFService} declarations in the manifest must require the
     48  * {@link android.Manifest.permission#BIND_NFC_SERVICE} permission
     49  * in their &lt;service&gt; tag, to ensure that only the platform can bind to your service.</p>
     50  *
     51  * <p>An example of a HostNfcFService manifest declaration is shown below:
     52  *
     53  * <pre> &lt;service android:name=".MyHostNfcFService" android:exported="true" android:permission="android.permission.BIND_NFC_SERVICE"&gt;
     54  *     &lt;intent-filter&gt;
     55  *         &lt;action android:name="android.nfc.cardemulation.action.HOST_NFCF_SERVICE"/&gt;
     56  *     &lt;/intent-filter&gt;
     57  *     &lt;meta-data android:name="android.nfc.cardemulation.host_nfcf_service" android:resource="@xml/nfcfservice"/&gt;
     58  * &lt;/service&gt;</pre>
     59  *
     60  * This meta-data tag points to an nfcfservice.xml file.
     61  * An example of this file with a System Code and NFCID2 declaration is shown below:
     62  * <pre>
     63  * &lt;host-nfcf-service xmlns:android="http://schemas.android.com/apk/res/android"
     64  *           android:description="@string/servicedesc"&gt;
     65  *       &lt;system-code-filter android:name="4000"/&gt;
     66  *       &lt;nfcid2-filter android:name="02FE000000000000"/&gt;
     67          &lt;t3tPmm-filter android:name="FFFFFFFFFFFFFFFF"/&gt;
     68  * &lt;/host-nfcf-service&gt;
     69  * </pre>
     70  *
     71  * <p>The {@link android.R.styleable#HostNfcFService &lt;host-nfcf-service&gt;} is required
     72  * to contain a
     73  * {@link android.R.styleable#HostApduService_description &lt;android:description&gt;}
     74  * attribute that contains a user-friendly description of the service that may be shown in UI.
     75  * <p>The {@link android.R.styleable#HostNfcFService &lt;host-nfcf-service&gt;} must
     76  * contain:
     77  * <ul>
     78  * <li>Exactly one {@link android.R.styleable#SystemCodeFilter &lt;system-code-filter&gt;} tag.</li>
     79  * <li>Exactly one {@link android.R.styleable#Nfcid2Filter &lt;nfcid2-filter&gt;} tag.</li>
     80  * <li>Zero or one {@link android.R.styleable#T3tPmmFilter &lt;t3tPmm-filter&gt;} tag.</li>
     81  * </ul>
     82  * </p>
     83  *
     84  * <p>Alternatively, the System Code and NFCID2 can be dynamically registererd for a service
     85  * by using the {@link NfcFCardEmulation#registerSystemCodeForService(ComponentName, String)} and
     86  * {@link NfcFCardEmulation#setNfcid2ForService(ComponentName, String)} methods.
     87  * </p>
     88  *
     89  * <h3>Service selection</h3>
     90  * <p>When a remote NFC devices wants to communicate with your service, it
     91  * sends a SENSF_REQ command to the NFC controller, requesting a System Code.
     92  * If a {@link NfcFCardEmulation NfcFCardEmulation service} has registered
     93  * this system code and has been enabled by the foreground application, the
     94  * NFC controller will respond with the NFCID2 that is registered for this service.
     95  * The reader can then continue data exchange with this service by using the NFCID2.</p>
     96  *
     97  * <h3>Data exchange</h3>
     98  * <p>After service selection, all frames addressed to the NFCID2 of this service will
     99  * be sent through {@link #processNfcFPacket(byte[], Bundle)}, until the NFC link is
    100  * broken.<p>
    101  *
    102  * <p>When the NFC link is broken, {@link #onDeactivated(int)} will be called.</p>
    103  */
    104 public abstract class HostNfcFService extends Service {
    105     /**
    106      * The {@link Intent} action that must be declared as handled by the service.
    107      */
    108     @SdkConstant(SdkConstantType.SERVICE_ACTION)
    109     public static final String SERVICE_INTERFACE =
    110             "android.nfc.cardemulation.action.HOST_NFCF_SERVICE";
    111 
    112     /**
    113      * The name of the meta-data element that contains
    114      * more information about this service.
    115      */
    116     public static final String SERVICE_META_DATA =
    117             "android.nfc.cardemulation.host_nfcf_service";
    118 
    119     /**
    120      * Reason for {@link #onDeactivated(int)}.
    121      * Indicates deactivation was due to the NFC link
    122      * being lost.
    123      */
    124     public static final int DEACTIVATION_LINK_LOSS = 0;
    125 
    126     static final String TAG = "NfcFService";
    127 
    128     /**
    129      * MSG_COMMAND_PACKET is sent by NfcService when
    130      * a NFC-F command packet has been received.
    131      *
    132      * @hide
    133      */
    134     public static final int MSG_COMMAND_PACKET = 0;
    135 
    136     /**
    137      * MSG_RESPONSE_PACKET is sent to NfcService to send
    138      * a response packet back to the remote device.
    139      *
    140      * @hide
    141      */
    142     public static final int MSG_RESPONSE_PACKET = 1;
    143 
    144     /**
    145      * MSG_DEACTIVATED is sent by NfcService when
    146      * the current session is finished; because
    147      * the NFC link was deactivated.
    148      *
    149      * @hide
    150      */
    151     public static final int MSG_DEACTIVATED = 2;
    152 
    153    /**
    154      * @hide
    155      */
    156     public static final String KEY_DATA = "data";
    157 
    158     /**
    159      * @hide
    160      */
    161     public static final String KEY_MESSENGER = "messenger";
    162 
    163     /**
    164      * Messenger interface to NfcService for sending responses.
    165      * Only accessed on main thread by the message handler.
    166      *
    167      * @hide
    168      */
    169     Messenger mNfcService = null;
    170 
    171     final Messenger mMessenger = new Messenger(new MsgHandler());
    172 
    173     final class MsgHandler extends Handler {
    174         @Override
    175         public void handleMessage(Message msg) {
    176             switch (msg.what) {
    177             case MSG_COMMAND_PACKET:
    178                 Bundle dataBundle = msg.getData();
    179                 if (dataBundle == null) {
    180                     return;
    181                 }
    182                 if (mNfcService == null) mNfcService = msg.replyTo;
    183 
    184                 byte[] packet = dataBundle.getByteArray(KEY_DATA);
    185                 if (packet != null) {
    186                     byte[] responsePacket = processNfcFPacket(packet, null);
    187                     if (responsePacket != null) {
    188                         if (mNfcService == null) {
    189                             Log.e(TAG, "Response not sent; service was deactivated.");
    190                             return;
    191                         }
    192                         Message responseMsg = Message.obtain(null, MSG_RESPONSE_PACKET);
    193                         Bundle responseBundle = new Bundle();
    194                         responseBundle.putByteArray(KEY_DATA, responsePacket);
    195                         responseMsg.setData(responseBundle);
    196                         responseMsg.replyTo = mMessenger;
    197                         try {
    198                             mNfcService.send(responseMsg);
    199                         } catch (RemoteException e) {
    200                             Log.e("TAG", "Response not sent; RemoteException calling into " +
    201                                     "NfcService.");
    202                         }
    203                     }
    204                 } else {
    205                     Log.e(TAG, "Received MSG_COMMAND_PACKET without data.");
    206                 }
    207                 break;
    208             case MSG_RESPONSE_PACKET:
    209                 if (mNfcService == null) {
    210                     Log.e(TAG, "Response not sent; service was deactivated.");
    211                     return;
    212                 }
    213                 try {
    214                     msg.replyTo = mMessenger;
    215                     mNfcService.send(msg);
    216                 } catch (RemoteException e) {
    217                     Log.e(TAG, "RemoteException calling into NfcService.");
    218                 }
    219                 break;
    220             case MSG_DEACTIVATED:
    221                 // Make sure we won't call into NfcService again
    222                 mNfcService = null;
    223                 onDeactivated(msg.arg1);
    224                 break;
    225             default:
    226                 super.handleMessage(msg);
    227             }
    228         }
    229     }
    230 
    231     @Override
    232     public final IBinder onBind(Intent intent) {
    233         return mMessenger.getBinder();
    234     }
    235 
    236     /**
    237      * Sends a response packet back to the remote device.
    238      *
    239      * <p>Note: this method may be called from any thread and will not block.
    240      * @param responsePacket A byte-array containing the response packet.
    241      */
    242     public final void sendResponsePacket(byte[] responsePacket) {
    243         Message responseMsg = Message.obtain(null, MSG_RESPONSE_PACKET);
    244         Bundle dataBundle = new Bundle();
    245         dataBundle.putByteArray(KEY_DATA, responsePacket);
    246         responseMsg.setData(dataBundle);
    247         try {
    248             mMessenger.send(responseMsg);
    249         } catch (RemoteException e) {
    250             Log.e("TAG", "Local messenger has died.");
    251         }
    252     }
    253 
    254     /**
    255      * <p>This method will be called when a NFC-F packet has been received
    256      * from a remote device. A response packet can be provided directly
    257      * by returning a byte-array in this method. Note that in general
    258      * response packets must be sent as quickly as possible, given the fact
    259      * that the user is likely holding his device over an NFC reader
    260      * when this method is called.
    261      *
    262      * <p class="note">This method is running on the main thread of your application.
    263      * If you cannot return a response packet immediately, return null
    264      * and use the {@link #sendResponsePacket(byte[])} method later.
    265      *
    266      * @param commandPacket The NFC-F packet that was received from the remote device
    267      * @param extras A bundle containing extra data. May be null.
    268      * @return a byte-array containing the response packet, or null if no
    269      *         response packet can be sent at this point.
    270      */
    271     public abstract byte[] processNfcFPacket(byte[] commandPacket, Bundle extras);
    272 
    273     /**
    274      * This method will be called in following possible scenarios:
    275      * <li>The NFC link has been lost
    276      * @param reason {@link #DEACTIVATION_LINK_LOSS}
    277      */
    278     public abstract void onDeactivated(int reason);
    279 }
    280