diff options
| -rw-r--r-- | Android.mk | 1 | ||||
| -rw-r--r-- | api/current.txt | 23 | ||||
| -rw-r--r-- | api/system-current.txt | 23 | ||||
| -rw-r--r-- | core/java/android/content/pm/PackageManager.java | 8 | ||||
| -rw-r--r-- | core/java/android/nfc/INfcAdapter.aidl | 2 | ||||
| -rw-r--r-- | core/java/android/nfc/INfcFCardEmulation.aidl | 36 | ||||
| -rw-r--r-- | core/java/android/nfc/NfcAdapter.java | 23 | ||||
| -rw-r--r-- | core/java/android/nfc/cardemulation/HostNfcFService.java | 210 | ||||
| -rw-r--r-- | core/java/android/nfc/cardemulation/NfcFCardEmulation.java | 470 | ||||
| -rw-r--r-- | core/java/android/nfc/cardemulation/NfcFServiceInfo.aidl | 19 | ||||
| -rw-r--r-- | core/java/android/nfc/cardemulation/NfcFServiceInfo.java | 304 | ||||
| -rw-r--r-- | core/res/res/values/attrs.xml | 26 |
12 files changed, 1145 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk index 5e405d365f84..c34d38e6cadc 100644 --- a/Android.mk +++ b/Android.mk @@ -202,6 +202,7 @@ LOCAL_SRC_FILES += \ core/java/android/nfc/INfcAdapterExtras.aidl \ core/java/android/nfc/INfcTag.aidl \ core/java/android/nfc/INfcCardEmulation.aidl \ + core/java/android/nfc/INfcFCardEmulation.aidl \ core/java/android/nfc/INfcUnlockHandler.aidl \ core/java/android/os/IBatteryPropertiesListener.aidl \ core/java/android/os/IBatteryPropertiesRegistrar.aidl \ diff --git a/api/current.txt b/api/current.txt index 41a7d5b54982..fa7f49f44321 100644 --- a/api/current.txt +++ b/api/current.txt @@ -9336,6 +9336,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_MIDI = "android.software.midi"; field public static final java.lang.String FEATURE_NFC = "android.hardware.nfc"; field public static final java.lang.String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce"; + field public static final java.lang.String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef"; field public static final java.lang.String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep"; field public static final java.lang.String FEATURE_PRINTING = "android.software.print"; field public static final java.lang.String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape"; @@ -23873,6 +23874,28 @@ package android.nfc.cardemulation { field public static final java.lang.String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service"; } + public abstract class HostNfcFService extends android.app.Service { + ctor public HostNfcFService(); + method public final android.os.IBinder onBind(android.content.Intent); + method public abstract void onDeactivated(int); + method public abstract byte[] processNfcFPacket(byte[], android.os.Bundle); + method public final void sendResponsePacket(byte[]); + field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0 + field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_NFCF_SERVICE"; + field public static final java.lang.String SERVICE_META_DATA = "android.nfc.cardemulation.host_nfcf_service"; + } + + public final class NfcFCardEmulation { + method public boolean disableNfcFForegroundService(android.app.Activity); + method public boolean enableNfcFForegroundService(android.app.Activity, android.content.ComponentName); + method public static synchronized android.nfc.cardemulation.NfcFCardEmulation getInstance(android.nfc.NfcAdapter); + method public java.lang.String getNfcid2ForService(android.content.ComponentName); + method public java.lang.String getSystemCodeForService(android.content.ComponentName); + method public boolean registerSystemCodeForService(android.content.ComponentName, java.lang.String); + method public boolean removeSystemCodeForService(android.content.ComponentName); + method public boolean setNfcid2ForService(android.content.ComponentName, java.lang.String); + } + public abstract class OffHostApduService extends android.app.Service { ctor public OffHostApduService(); method public abstract android.os.IBinder onBind(android.content.Intent); diff --git a/api/system-current.txt b/api/system-current.txt index 7db60749a4a9..a2c904044e68 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -9630,6 +9630,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_MIDI = "android.software.midi"; field public static final java.lang.String FEATURE_NFC = "android.hardware.nfc"; field public static final java.lang.String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce"; + field public static final java.lang.String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef"; field public static final java.lang.String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep"; field public static final java.lang.String FEATURE_PRINTING = "android.software.print"; field public static final java.lang.String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape"; @@ -25818,6 +25819,28 @@ package android.nfc.cardemulation { field public static final java.lang.String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service"; } + public abstract class HostNfcFService extends android.app.Service { + ctor public HostNfcFService(); + method public final android.os.IBinder onBind(android.content.Intent); + method public abstract void onDeactivated(int); + method public abstract byte[] processNfcFPacket(byte[], android.os.Bundle); + method public final void sendResponsePacket(byte[]); + field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0 + field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_NFCF_SERVICE"; + field public static final java.lang.String SERVICE_META_DATA = "android.nfc.cardemulation.host_nfcf_service"; + } + + public final class NfcFCardEmulation { + method public boolean disableNfcFForegroundService(android.app.Activity); + method public boolean enableNfcFForegroundService(android.app.Activity, android.content.ComponentName); + method public static synchronized android.nfc.cardemulation.NfcFCardEmulation getInstance(android.nfc.NfcAdapter); + method public java.lang.String getNfcid2ForService(android.content.ComponentName); + method public java.lang.String getSystemCodeForService(android.content.ComponentName); + method public boolean registerSystemCodeForService(android.content.ComponentName, java.lang.String); + method public boolean removeSystemCodeForService(android.content.ComponentName); + method public boolean setNfcid2ForService(android.content.ComponentName, java.lang.String); + } + public abstract class OffHostApduService extends android.app.Service { ctor public OffHostApduService(); method public abstract android.os.IBinder onBind(android.content.Intent); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 054dafe72750..3029b119f27b 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1295,6 +1295,14 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports host- + * based NFC-F card emulation. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports the OpenGL ES * <a href="http://www.khronos.org/registry/gles/extensions/ANDROID/ANDROID_extension_pack_es31a.txt"> * Android Extension Pack</a>. diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index 961a3f41cd5e..940565f09c03 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -26,6 +26,7 @@ import android.nfc.IAppCallback; import android.nfc.INfcAdapterExtras; import android.nfc.INfcTag; import android.nfc.INfcCardEmulation; +import android.nfc.INfcFCardEmulation; import android.nfc.INfcUnlockHandler; import android.os.Bundle; @@ -36,6 +37,7 @@ interface INfcAdapter { INfcTag getNfcTagInterface(); INfcCardEmulation getNfcCardEmulationInterface(); + INfcFCardEmulation getNfcFCardEmulationInterface(); INfcAdapterExtras getNfcAdapterExtrasInterface(in String pkg); int getState(); diff --git a/core/java/android/nfc/INfcFCardEmulation.aidl b/core/java/android/nfc/INfcFCardEmulation.aidl new file mode 100644 index 000000000000..124bfac4f0d0 --- /dev/null +++ b/core/java/android/nfc/INfcFCardEmulation.aidl @@ -0,0 +1,36 @@ +/* + * 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; + +import android.content.ComponentName; +import android.nfc.cardemulation.NfcFServiceInfo; + +/** + * @hide + */ +interface INfcFCardEmulation +{ + String getSystemCodeForService(int userHandle, in ComponentName service); + boolean registerSystemCodeForService(int userHandle, in ComponentName service, String systemCode); + boolean removeSystemCodeForService(int userHandle, in ComponentName service); + String getNfcid2ForService(int userHandle, in ComponentName service); + boolean setNfcid2ForService(int userHandle, in ComponentName service, String nfcid2); + boolean enableNfcFForegroundService(in ComponentName service); + boolean disableNfcFForegroundService(); + List<NfcFServiceInfo> getNfcFServices(int userHandle); + int getMaxNumOfRegisterableSystemCodes(); +} diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index b492debdd7c8..605fe9d4a958 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -294,6 +294,7 @@ public final class NfcAdapter { static INfcAdapter sService; static INfcTag sTagService; static INfcCardEmulation sCardEmulationService; + static INfcFCardEmulation sNfcFCardEmulationService; /** * The NfcAdapter object for each application context. @@ -452,6 +453,13 @@ public final class NfcAdapter { throw new UnsupportedOperationException(); } + try { + sNfcFCardEmulationService = sService.getNfcFCardEmulationInterface(); + } catch (RemoteException e) { + Log.e(TAG, "could not retrieve NFC-F card emulation service"); + throw new UnsupportedOperationException(); + } + sIsInitialized = true; } if (context == null) { @@ -571,6 +579,15 @@ public final class NfcAdapter { } /** + * Returns the binder interface to the NFC-F card emulation service. + * @hide + */ + public INfcFCardEmulation getNfcFCardEmulationService() { + isEnabled(); + return sNfcFCardEmulationService; + } + + /** * NFC service dead - attempt best effort recovery * @hide */ @@ -601,6 +618,12 @@ public final class NfcAdapter { Log.e(TAG, "could not retrieve NFC card emulation service during service recovery"); } + try { + sNfcFCardEmulationService = service.getNfcFCardEmulationInterface(); + } catch (RemoteException ee) { + Log.e(TAG, "could not retrieve NFC-F card emulation service during service recovery"); + } + return; } diff --git a/core/java/android/nfc/cardemulation/HostNfcFService.java b/core/java/android/nfc/cardemulation/HostNfcFService.java new file mode 100644 index 000000000000..273ddeb965a3 --- /dev/null +++ b/core/java/android/nfc/cardemulation/HostNfcFService.java @@ -0,0 +1,210 @@ +/* + * 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.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. + */ +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); +} diff --git a/core/java/android/nfc/cardemulation/NfcFCardEmulation.java b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java new file mode 100644 index 000000000000..d61ac02eb98e --- /dev/null +++ b/core/java/android/nfc/cardemulation/NfcFCardEmulation.java @@ -0,0 +1,470 @@ +/* + * 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.app.Activity; +import android.app.ActivityThread; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.nfc.INfcFCardEmulation; +import android.nfc.NfcAdapter; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +import java.util.HashMap; +import java.util.List; + +/** + * This class can be used to query the state of + * NFC-F card emulation services. + * + * For a general introduction into NFC card emulation, + * please read the <a href="{@docRoot}guide/topics/connectivity/nfc/hce.html"> + * NFC card emulation developer guide</a>.</p> + * + * <p class="note">Use of this class requires the + * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION_NFCF} + * to be present on the device. + */ +public final class NfcFCardEmulation { + static final String TAG = "NfcFCardEmulation"; + + static boolean sIsInitialized = false; + static HashMap<Context, NfcFCardEmulation> sCardEmus = new HashMap<Context, NfcFCardEmulation>(); + static INfcFCardEmulation sService; + + final Context mContext; + + private NfcFCardEmulation(Context context, INfcFCardEmulation service) { + mContext = context.getApplicationContext(); + sService = service; + } + + /** + * Helper to get an instance of this class. + * + * @param adapter A reference to an NfcAdapter object. + * @return + */ + public static synchronized NfcFCardEmulation getInstance(NfcAdapter adapter) { + if (adapter == null) throw new NullPointerException("NfcAdapter is null"); + Context context = adapter.getContext(); + if (context == null) { + Log.e(TAG, "NfcAdapter context is null."); + throw new UnsupportedOperationException(); + } + if (!sIsInitialized) { + IPackageManager pm = ActivityThread.getPackageManager(); + if (pm == null) { + Log.e(TAG, "Cannot get PackageManager"); + throw new UnsupportedOperationException(); + } + try { + if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)) { + Log.e(TAG, "This device does not support NFC-F card emulation"); + throw new UnsupportedOperationException(); + } + } catch (RemoteException e) { + Log.e(TAG, "PackageManager query failed."); + throw new UnsupportedOperationException(); + } + sIsInitialized = true; + } + NfcFCardEmulation manager = sCardEmus.get(context); + if (manager == null) { + // Get card emu service + INfcFCardEmulation service = adapter.getNfcFCardEmulationService(); + if (service == null) { + Log.e(TAG, "This device does not implement the INfcFCardEmulation interface."); + throw new UnsupportedOperationException(); + } + manager = new NfcFCardEmulation(context, service); + sCardEmus.put(context, manager); + } + return manager; + } + + /** + * Retrieves the current System Code for the specified service. + * + * <p>Before calling {@link #registerSystemCodeForService(ComponentName, String)}, + * the System Code contained in the Manifest file is returned. After calling + * {@link #registerSystemCodeForService(ComponentName, String)}, the System Code + * registered there is returned. After calling + * {@link #removeSystemCodeForService(ComponentName)}, "null" is returned. + * + * @param service The component name of the service + * @return the current System Code + */ + public String getSystemCodeForService(ComponentName service) { + if (service == null) { + throw new NullPointerException("service is null"); + } + try { + return sService.getSystemCodeForService(UserHandle.myUserId(), service); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return null; + } + try { + return sService.getSystemCodeForService(UserHandle.myUserId(), service); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return null; + } + } + } + + /** + * Registers a System Code for the specified service. + * + * <p>The System Code must be in range from "4000" to "4FFF" (excluding "4*FF"). + * + * <p>If a System Code was previously registered for this service + * (either statically through the manifest, or dynamically by using this API), + * it will be replaced with this one. + * + * <p>Even if the same System Code is already registered for another service, + * this method succeeds in registering the System Code. + * + * <p>Note that you can only register a System Code for a service that + * is running under the same UID as the caller of this API. Typically + * this means you need to call this from the same + * package as the service itself, though UIDs can also + * be shared between packages using shared UIDs. + * + * @param service The component name of the service + * @param systemCode The System Code to be registered + * @return whether the registration was successful. + */ + public boolean registerSystemCodeForService(ComponentName service, String systemCode) { + if (service == null || systemCode == null) { + throw new NullPointerException("service or systemCode is null"); + } + try { + return sService.registerSystemCodeForService(UserHandle.myUserId(), + service, systemCode); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return false; + } + try { + return sService.registerSystemCodeForService(UserHandle.myUserId(), + service, systemCode); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return false; + } + } + } + + /** + * Removes a registered System Code for the specified service. + * + * @param service The component name of the service + * @return whether the System Code was successfully removed. + */ + public boolean removeSystemCodeForService(ComponentName service) { + if (service == null) { + throw new NullPointerException("service is null"); + } + try { + return sService.removeSystemCodeForService(UserHandle.myUserId(), service); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return false; + } + try { + return sService.removeSystemCodeForService(UserHandle.myUserId(), service); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return false; + } + } + } + + /** + * Retrieves the current NFCID2 for the specified service. + * + * <p>Before calling {@link #setNfcid2ForService(ComponentName, String)}, + * the NFCID2 contained in the Manifest file is returned. If "random" is specified + * in the Manifest file, a random number assigned by the system at installation time + * is returned. After setting an NFCID2 + * with {@link #setNfcid2ForService(ComponentName, String)}, this NFCID2 is returned. + * + * @param service The component name of the service + * @return the current NFCID2 + */ + public String getNfcid2ForService(ComponentName service) { + if (service == null) { + throw new NullPointerException("service is null"); + } + try { + return sService.getNfcid2ForService(UserHandle.myUserId(), service); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return null; + } + try { + return sService.getNfcid2ForService(UserHandle.myUserId(), service); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return null; + } + } + } + + /** + * Set a NFCID2 for the specified service. + * + * <p>The NFCID2 must be in range from "02FE000000000000" to "02FEFFFFFFFFFFFF". + * + * <p>If a NFCID2 was previously set for this service + * (either statically through the manifest, or dynamically by using this API), + * it will be replaced. + * + * <p>Note that you can only set the NFCID2 for a service that + * is running under the same UID as the caller of this API. Typically + * this means you need to call this from the same + * package as the service itself, though UIDs can also + * be shared between packages using shared UIDs. + * + * @param service The component name of the service + * @param nfcid2 The NFCID2 to be registered + * @return whether the setting was successful. + */ + public boolean setNfcid2ForService(ComponentName service, String nfcid2) { + if (service == null || nfcid2 == null) { + throw new NullPointerException("service or nfcid2 is null"); + } + try { + return sService.setNfcid2ForService(UserHandle.myUserId(), + service, nfcid2); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return false; + } + try { + return sService.setNfcid2ForService(UserHandle.myUserId(), + service, nfcid2); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return false; + } + } + } + + /** + * Allows a foreground application to specify which card emulation service + * should be enabled while a specific Activity is in the foreground. + * + * <p>The specified HCE-F service is only enabled when the corresponding application is + * in the foreground and this method has been called. When the application is moved to + * the background, {@link #disableNfcFForegroundService(Activity)} is called, or + * NFCID2 or System Code is replaced, the HCE-F service is disabled. + * + * <p>The specified Activity must currently be in resumed state. A good + * paradigm is to call this method in your {@link Activity#onResume}, and to call + * {@link #disableNfcFForegroundService(Activity)} in your {@link Activity#onPause}. + * + * <p>Note that this preference is not persisted by the OS, and hence must be + * called every time the Activity is resumed. + * + * @param activity The activity which prefers this service to be invoked + * @param service The service to be preferred while this activity is in the foreground + * @return whether the registration was successful + */ + public boolean enableNfcFForegroundService(Activity activity, ComponentName service) { + if (activity == null || service == null) { + throw new NullPointerException("activity or service is null"); + } + // Verify the activity is in the foreground before calling into NfcService + if (!activity.isResumed()) { + throw new IllegalArgumentException("Activity must be resumed."); + } + try { + return sService.enableNfcFForegroundService(service); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return false; + } + try { + return sService.enableNfcFForegroundService(service); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return false; + } + } + } + + /** + * Disables the service for the specified Activity. + * + * <p>Note that the specified Activity must still be in resumed + * state at the time of this call. A good place to call this method + * is in your {@link Activity#onPause} implementation. + * + * @param activity The activity which the service was registered for + * @return true when successful + */ + public boolean disableNfcFForegroundService(Activity activity) { + if (activity == null) { + throw new NullPointerException("activity is null"); + } + if (!activity.isResumed()) { + throw new IllegalArgumentException("Activity must be resumed."); + } + try { + return sService.disableNfcFForegroundService(); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return false; + } + try { + return sService.disableNfcFForegroundService(); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return false; + } + } + } + + /** + * @hide + */ + public List<NfcFServiceInfo> getNfcFServices() { + try { + return sService.getNfcFServices(UserHandle.myUserId()); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return null; + } + try { + return sService.getNfcFServices(UserHandle.myUserId()); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return null; + } + } + } + + /** + * @hide + */ + public int getMaxNumOfRegisterableSystemCodes() { + try { + return sService.getMaxNumOfRegisterableSystemCodes(); + } catch (RemoteException e) { + // Try one more time + recoverService(); + if (sService == null) { + Log.e(TAG, "Failed to recover CardEmulationService."); + return -1; + } + try { + return sService.getMaxNumOfRegisterableSystemCodes(); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to reach CardEmulationService."); + return -1; + } + } + } + + /** + * @hide + */ + public static boolean isValidSystemCode(String systemCode) { + if (systemCode == null) { + return false; + } + if (systemCode.length() != 4) { + Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); + return false; + } + // check if the value is between "4000" and "4FFF" (excluding "4*FF") + if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) { + Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); + return false; + } + try { + Integer.valueOf(systemCode, 16); + } catch (NumberFormatException e) { + Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); + return false; + } + return true; + } + + /** + * @hide + */ + public static boolean isValidNfcid2(String nfcid2) { + if (nfcid2 == null) { + return false; + } + if (nfcid2.length() != 16) { + Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); + return false; + } + // check if the the value starts with "02FE" + if (!nfcid2.toUpperCase().startsWith("02FE")) { + Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); + return false; + } + try { + Long.valueOf(nfcid2, 16); + } catch (NumberFormatException e) { + Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); + return false; + } + return true; + } + + void recoverService() { + NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext); + sService = adapter.getNfcFCardEmulationService(); + } + +} + diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.aidl b/core/java/android/nfc/cardemulation/NfcFServiceInfo.aidl new file mode 100644 index 000000000000..56b98ebd90fa --- /dev/null +++ b/core/java/android/nfc/cardemulation/NfcFServiceInfo.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +parcelable NfcFServiceInfo; diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java new file mode 100644 index 000000000000..b93eec11c342 --- /dev/null +++ b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java @@ -0,0 +1,304 @@ +/* + * 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.content.ComponentName; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * @hide + */ +public final class NfcFServiceInfo implements Parcelable { + static final String TAG = "NfcFServiceInfo"; + + /** + * The service that implements this + */ + final ResolveInfo mService; + + /** + * Description of the service + */ + final String mDescription; + + /** + * System Code of the service + */ + final String mSystemCode; + + /** + * System Code of the service registered by API + */ + String mDynamicSystemCode; + + /** + * NFCID2 of the service + */ + final String mNfcid2; + + /** + * NFCID2 of the service registered by API + */ + String mDynamicNfcid2; + + /** + * The uid of the package the service belongs to + */ + final int mUid; + + /** + * @hide + */ + public NfcFServiceInfo(ResolveInfo info, String description, + String systemCode, String dynamicSystemCode, String nfcid2, String dynamicNfcid2, + int uid) { + this.mService = info; + this.mDescription = description; + this.mSystemCode = systemCode; + this.mDynamicSystemCode = dynamicSystemCode; + this.mNfcid2 = nfcid2; + this.mDynamicNfcid2 = dynamicNfcid2; + this.mUid = uid; + } + + public NfcFServiceInfo(PackageManager pm, ResolveInfo info) + throws XmlPullParserException, IOException { + ServiceInfo si = info.serviceInfo; + XmlResourceParser parser = null; + try { + parser = si.loadXmlMetaData(pm, HostNfcFService.SERVICE_META_DATA); + if (parser == null) { + throw new XmlPullParserException("No " + HostNfcFService.SERVICE_META_DATA + + " meta-data"); + } + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.START_TAG && + eventType != XmlPullParser.END_DOCUMENT) { + eventType = parser.next(); + } + + String tagName = parser.getName(); + if (!"host-nfcf-service".equals(tagName)) { + throw new XmlPullParserException( + "Meta-data does not start with <host-nfcf-service> tag"); + } + + Resources res = pm.getResourcesForApplication(si.applicationInfo); + AttributeSet attrs = Xml.asAttributeSet(parser); + TypedArray sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.HostNfcFService); + mService = info; + mDescription = sa.getString( + com.android.internal.R.styleable.HostNfcFService_description); + mDynamicSystemCode = null; + mDynamicNfcid2 = null; + sa.recycle(); + + String systemCode = null; + String nfcid2 = null; + final int depth = parser.getDepth(); + + while (((eventType = parser.next()) != XmlPullParser.END_TAG || + parser.getDepth() > depth) && eventType != XmlPullParser.END_DOCUMENT) { + tagName = parser.getName(); + if (eventType == XmlPullParser.START_TAG && + "system-code-filter".equals(tagName) && systemCode == null) { + final TypedArray a = res.obtainAttributes(attrs, + com.android.internal.R.styleable.SystemCodeFilter); + systemCode = a.getString( + com.android.internal.R.styleable.SystemCodeFilter_name).toUpperCase(); + if (!NfcFCardEmulation.isValidSystemCode(systemCode) && + !systemCode.equalsIgnoreCase("NULL")) { + Log.e(TAG, "Invalid System Code: " + systemCode); + systemCode = null; + } + a.recycle(); + } else if (eventType == XmlPullParser.START_TAG && + "nfcid2-filter".equals(tagName) && nfcid2 == null) { + final TypedArray a = res.obtainAttributes(attrs, + com.android.internal.R.styleable.Nfcid2Filter); + nfcid2 = a.getString( + com.android.internal.R.styleable.Nfcid2Filter_name).toUpperCase(); + if (!nfcid2.equalsIgnoreCase("RANDOM") && + !nfcid2.equalsIgnoreCase("NULL") && + !NfcFCardEmulation.isValidNfcid2(nfcid2)) { + Log.e(TAG, "Invalid NFCID2: " + nfcid2); + nfcid2 = null; + } + a.recycle(); + } + } + mSystemCode = (systemCode == null ? "NULL" : systemCode); + mNfcid2 = (nfcid2 == null ? "NULL" : nfcid2); + } catch (NameNotFoundException e) { + throw new XmlPullParserException("Unable to create context for: " + si.packageName); + } finally { + if (parser != null) parser.close(); + } + // Set uid + mUid = si.applicationInfo.uid; + } + + public ComponentName getComponent() { + return new ComponentName(mService.serviceInfo.packageName, + mService.serviceInfo.name); + } + + public String getSystemCode() { + return (mDynamicSystemCode == null ? mSystemCode : mDynamicSystemCode); + } + + public void setOrReplaceDynamicSystemCode(String systemCode) { + mDynamicSystemCode = systemCode; + } + + public String getNfcid2() { + return (mDynamicNfcid2 == null ? mNfcid2 : mDynamicNfcid2); + } + + public void setOrReplaceDynamicNfcid2(String nfcid2) { + mDynamicNfcid2 = nfcid2; + } + + public String getDescription() { + return mDescription; + } + + public int getUid() { + return mUid; + } + + public CharSequence loadLabel(PackageManager pm) { + return mService.loadLabel(pm); + } + + public Drawable loadIcon(PackageManager pm) { + return mService.loadIcon(pm); + } + + @Override + public String toString() { + StringBuilder out = new StringBuilder("NfcFService: "); + out.append(getComponent()); + out.append(", description: " + mDescription); + out.append(", System Code: " + mSystemCode); + if (mDynamicSystemCode != null) { + out.append(", dynamic System Code: " + mDynamicSystemCode); + } + out.append(", NFCID2: " + mNfcid2); + if (mDynamicNfcid2 != null) { + out.append(", dynamic NFCID2: " + mDynamicNfcid2); + } + return out.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof NfcFServiceInfo)) return false; + NfcFServiceInfo thatService = (NfcFServiceInfo) o; + + if (!thatService.getComponent().equals(this.getComponent())) return false; + if (!thatService.mSystemCode.equalsIgnoreCase(this.mSystemCode)) return false; + if (!thatService.mNfcid2.equalsIgnoreCase(this.mNfcid2)) return false; + + return true; + } + + @Override + public int hashCode() { + return getComponent().hashCode(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + mService.writeToParcel(dest, flags); + dest.writeString(mDescription); + dest.writeString(mSystemCode); + dest.writeInt(mDynamicSystemCode != null ? 1 : 0); + if (mDynamicSystemCode != null) { + dest.writeString(mDynamicSystemCode); + } + dest.writeString(mNfcid2); + dest.writeInt(mDynamicNfcid2 != null ? 1 : 0); + if (mDynamicNfcid2 != null) { + dest.writeString(mDynamicNfcid2); + } + dest.writeInt(mUid); + }; + + public static final Parcelable.Creator<NfcFServiceInfo> CREATOR = + new Parcelable.Creator<NfcFServiceInfo>() { + @Override + public NfcFServiceInfo createFromParcel(Parcel source) { + ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source); + String description = source.readString(); + String systemCode = source.readString(); + String dynamicSystemCode = null; + if (source.readInt() != 0) { + dynamicSystemCode = source.readString(); + } + String nfcid2 = source.readString(); + String dynamicNfcid2 = null; + if (source.readInt() != 0) { + dynamicNfcid2 = source.readString(); + } + int uid = source.readInt(); + NfcFServiceInfo service = new NfcFServiceInfo(info, description, + systemCode, dynamicSystemCode, nfcid2, dynamicNfcid2, uid); + return service; + } + + @Override + public NfcFServiceInfo[] newArray(int size) { + return new NfcFServiceInfo[size]; + } + }; + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(" " + getComponent() + + " (Description: " + getDescription() + ")"); + pw.println(" System Code: " + getSystemCode()); + pw.println(" NFCID2: " + getNfcid2()); + } +} + diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 2828d2175047..5a081698937e 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3315,6 +3315,32 @@ i <attr name="name" /> </declare-styleable> + <!-- Use <code>host-nfcf-service</code> as the root tag of the XML resource that + describes an {@link android.nfc.cardemulation.HostNfcFService} service, which + is referenced from its {@link android.nfc.cardemulation.HostNfcFService#SERVICE_META_DATA} + entry. --> + <declare-styleable name="HostNfcFService"> + <!-- Short description of the functionality the service implements. This attribute + is mandatory.--> + <attr name="description" /> + </declare-styleable> + + <!-- Specify one or more <code>system-code-filter</code> elements inside a + <code>host-nfcf-service</code> element to specify a System Code + your service can handle. --> + <declare-styleable name="SystemCodeFilter"> + <!-- The System Code. This attribute is mandatory. --> + <attr name="name" /> + </declare-styleable> + + <!-- Specify one or more <code>nfcid2-filter</code> elements inside a + <code>host-nfcf-service</code> element to specify a NFCID2 + your service can handle. --> + <declare-styleable name="Nfcid2Filter"> + <!-- The NFCID2. This attribute is mandatory. --> + <attr name="name" /> + </declare-styleable> + <declare-styleable name="ActionMenuItemView"> <attr name="minWidth" /> </declare-styleable> |