| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.nfc.cardemulation; |
| |
| import android.annotation.SdkConstant; |
| import android.annotation.SdkConstant.SdkConstantType; |
| import android.app.Service; |
| import android.content.ComponentName; |
| import android.content.Intent; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.os.Messenger; |
| import android.os.RemoteException; |
| import android.util.Log; |
| |
| /** |
| * <p>HostNfcFService is a convenience {@link Service} class that can be |
| * extended to emulate an NFC-F card inside an Android service component. |
| * |
| * <h3>NFC Protocols</h3> |
| * <p>Cards emulated by this class are based on the NFC-Forum NFC-F |
| * protocol (based on the JIS-X 6319-4 specification.)</p> |
| * |
| * <h3>System Code and NFCID2 registration</h3> |
| * <p>A {@link HostNfcFService HostNfcFService service} can register |
| * exactly one System Code and one NFCID2. For details about the use of |
| * System Code and NFCID2, see the NFC Forum Digital specification.</p> |
| * <p>To statically register a System Code and NFCID2 with the service, a {@link #SERVICE_META_DATA} |
| * entry must be included in the declaration of the service. |
| * |
| * <p>All {@link HostNfcFService HostNfcFService} declarations in the manifest must require the |
| * {@link android.Manifest.permission#BIND_NFC_SERVICE} permission |
| * in their <service> tag, to ensure that only the platform can bind to your service.</p> |
| * |
| * <p>An example of a HostNfcFService manifest declaration is shown below: |
| * |
| * <pre> <service android:name=".MyHostNfcFService" android:exported="true" android:permission="android.permission.BIND_NFC_SERVICE"> |
| * <intent-filter> |
| * <action android:name="android.nfc.cardemulation.action.HOST_NFCF_SERVICE"/> |
| * </intent-filter> |
| * <meta-data android:name="android.nfc.cardemulation.host_nfcf_service" android:resource="@xml/nfcfservice"/> |
| * </service></pre> |
| * |
| * This meta-data tag points to an nfcfservice.xml file. |
| * An example of this file with a System Code and NFCID2 declaration is shown below: |
| * <pre> |
| * <host-nfcf-service xmlns:android="http://schemas.android.com/apk/res/android" |
| * android:description="@string/servicedesc"> |
| * <system-code-filter android:name="4000"/> |
| * <nfcid2-filter android:name="02FE000000000000"/> |
| <t3tPmm-filter android:name="FFFFFFFFFFFFFFFF"/> |
| * </host-nfcf-service> |
| * </pre> |
| * |
| * <p>The {@link android.R.styleable#HostNfcFService <host-nfcf-service>} is required |
| * to contain a |
| * {@link android.R.styleable#HostApduService_description <android:description>} |
| * attribute that contains a user-friendly description of the service that may be shown in UI. |
| * <p>The {@link android.R.styleable#HostNfcFService <host-nfcf-service>} must |
| * contain: |
| * <ul> |
| * <li>Exactly one {@link android.R.styleable#SystemCodeFilter <system-code-filter>} tag.</li> |
| * <li>Exactly one {@link android.R.styleable#Nfcid2Filter <nfcid2-filter>} tag.</li> |
| * <li>Zero or one {@link android.R.styleable#T3tPmmFilter <t3tPmm-filter>} tag.</li> |
| * </ul> |
| * </p> |
| * |
| * <p>Alternatively, the System Code and NFCID2 can be dynamically registererd for a service |
| * by using the {@link NfcFCardEmulation#registerSystemCodeForService(ComponentName, String)} and |
| * {@link NfcFCardEmulation#setNfcid2ForService(ComponentName, String)} methods. |
| * </p> |
| * |
| * <h3>Service selection</h3> |
| * <p>When a remote NFC devices wants to communicate with your service, it |
| * sends a SENSF_REQ command to the NFC controller, requesting a System Code. |
| * If a {@link NfcFCardEmulation NfcFCardEmulation service} has registered |
| * this system code and has been enabled by the foreground application, the |
| * NFC controller will respond with the NFCID2 that is registered for this service. |
| * The reader can then continue data exchange with this service by using the NFCID2.</p> |
| * |
| * <h3>Data exchange</h3> |
| * <p>After service selection, all frames addressed to the NFCID2 of this service will |
| * be sent through {@link #processNfcFPacket(byte[], Bundle)}, until the NFC link is |
| * broken.<p> |
| * |
| * <p>When the NFC link is broken, {@link #onDeactivated(int)} will be called.</p> |
| */ |
| public abstract class HostNfcFService extends Service { |
| /** |
| * The {@link Intent} action that must be declared as handled by the service. |
| */ |
| @SdkConstant(SdkConstantType.SERVICE_ACTION) |
| public static final String SERVICE_INTERFACE = |
| "android.nfc.cardemulation.action.HOST_NFCF_SERVICE"; |
| |
| /** |
| * The name of the meta-data element that contains |
| * more information about this service. |
| */ |
| public static final String SERVICE_META_DATA = |
| "android.nfc.cardemulation.host_nfcf_service"; |
| |
| /** |
| * Reason for {@link #onDeactivated(int)}. |
| * Indicates deactivation was due to the NFC link |
| * being lost. |
| */ |
| public static final int DEACTIVATION_LINK_LOSS = 0; |
| |
| static final String TAG = "NfcFService"; |
| |
| /** |
| * MSG_COMMAND_PACKET is sent by NfcService when |
| * a NFC-F command packet has been received. |
| * |
| * @hide |
| */ |
| public static final int MSG_COMMAND_PACKET = 0; |
| |
| /** |
| * MSG_RESPONSE_PACKET is sent to NfcService to send |
| * a response packet back to the remote device. |
| * |
| * @hide |
| */ |
| public static final int MSG_RESPONSE_PACKET = 1; |
| |
| /** |
| * MSG_DEACTIVATED is sent by NfcService when |
| * the current session is finished; because |
| * the NFC link was deactivated. |
| * |
| * @hide |
| */ |
| public static final int MSG_DEACTIVATED = 2; |
| |
| /** |
| * @hide |
| */ |
| public static final String KEY_DATA = "data"; |
| |
| /** |
| * @hide |
| */ |
| public static final String KEY_MESSENGER = "messenger"; |
| |
| /** |
| * Messenger interface to NfcService for sending responses. |
| * Only accessed on main thread by the message handler. |
| * |
| * @hide |
| */ |
| Messenger mNfcService = null; |
| |
| final Messenger mMessenger = new Messenger(new MsgHandler()); |
| |
| final class MsgHandler extends Handler { |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_COMMAND_PACKET: |
| Bundle dataBundle = msg.getData(); |
| if (dataBundle == null) { |
| return; |
| } |
| if (mNfcService == null) mNfcService = msg.replyTo; |
| |
| byte[] packet = dataBundle.getByteArray(KEY_DATA); |
| if (packet != null) { |
| byte[] responsePacket = processNfcFPacket(packet, null); |
| if (responsePacket != null) { |
| if (mNfcService == null) { |
| Log.e(TAG, "Response not sent; service was deactivated."); |
| return; |
| } |
| Message responseMsg = Message.obtain(null, MSG_RESPONSE_PACKET); |
| Bundle responseBundle = new Bundle(); |
| responseBundle.putByteArray(KEY_DATA, responsePacket); |
| responseMsg.setData(responseBundle); |
| responseMsg.replyTo = mMessenger; |
| try { |
| mNfcService.send(responseMsg); |
| } catch (RemoteException e) { |
| Log.e("TAG", "Response not sent; RemoteException calling into " + |
| "NfcService."); |
| } |
| } |
| } else { |
| Log.e(TAG, "Received MSG_COMMAND_PACKET without data."); |
| } |
| break; |
| case MSG_RESPONSE_PACKET: |
| if (mNfcService == null) { |
| Log.e(TAG, "Response not sent; service was deactivated."); |
| return; |
| } |
| try { |
| msg.replyTo = mMessenger; |
| mNfcService.send(msg); |
| } catch (RemoteException e) { |
| Log.e(TAG, "RemoteException calling into NfcService."); |
| } |
| break; |
| case MSG_DEACTIVATED: |
| // Make sure we won't call into NfcService again |
| mNfcService = null; |
| onDeactivated(msg.arg1); |
| break; |
| default: |
| super.handleMessage(msg); |
| } |
| } |
| } |
| |
| @Override |
| public final IBinder onBind(Intent intent) { |
| return mMessenger.getBinder(); |
| } |
| |
| /** |
| * Sends a response packet back to the remote device. |
| * |
| * <p>Note: this method may be called from any thread and will not block. |
| * @param responsePacket A byte-array containing the response packet. |
| */ |
| public final void sendResponsePacket(byte[] responsePacket) { |
| Message responseMsg = Message.obtain(null, MSG_RESPONSE_PACKET); |
| Bundle dataBundle = new Bundle(); |
| dataBundle.putByteArray(KEY_DATA, responsePacket); |
| responseMsg.setData(dataBundle); |
| try { |
| mMessenger.send(responseMsg); |
| } catch (RemoteException e) { |
| Log.e("TAG", "Local messenger has died."); |
| } |
| } |
| |
| /** |
| * <p>This method will be called when a NFC-F packet has been received |
| * from a remote device. A response packet can be provided directly |
| * by returning a byte-array in this method. Note that in general |
| * response packets must be sent as quickly as possible, given the fact |
| * that the user is likely holding his device over an NFC reader |
| * when this method is called. |
| * |
| * <p class="note">This method is running on the main thread of your application. |
| * If you cannot return a response packet immediately, return null |
| * and use the {@link #sendResponsePacket(byte[])} method later. |
| * |
| * @param commandPacket The NFC-F packet that was received from the remote device |
| * @param extras A bundle containing extra data. May be null. |
| * @return a byte-array containing the response packet, or null if no |
| * response packet can be sent at this point. |
| */ |
| public abstract byte[] processNfcFPacket(byte[] commandPacket, Bundle extras); |
| |
| /** |
| * This method will be called in following possible scenarios: |
| * <li>The NFC link has been lost |
| * @param reason {@link #DEACTIVATION_LINK_LOSS} |
| */ |
| public abstract void onDeactivated(int reason); |
| } |