| /* |
| * 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.content.ComponentName; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.nfc.INfcFCardEmulation; |
| import android.nfc.NfcAdapter; |
| import android.os.RemoteException; |
| 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) { |
| PackageManager pm = context.getPackageManager(); |
| if (pm == null) { |
| Log.e(TAG, "Cannot get PackageManager"); |
| throw new UnsupportedOperationException(); |
| } |
| 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(); |
| } |
| 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 #unregisterSystemCodeForService(ComponentName)}, "null" is returned. |
| * |
| * @param service The component name of the service |
| * @return the current System Code |
| */ |
| public String getSystemCodeForService(ComponentName service) throws RuntimeException { |
| if (service == null) { |
| throw new NullPointerException("service is null"); |
| } |
| try { |
| return sService.getSystemCodeForService(mContext.getUser().getIdentifier(), 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(mContext.getUser().getIdentifier(), |
| service); |
| } catch (RemoteException ee) { |
| Log.e(TAG, "Failed to reach CardEmulationService."); |
| ee.rethrowAsRuntimeException(); |
| 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) |
| throws RuntimeException { |
| if (service == null || systemCode == null) { |
| throw new NullPointerException("service or systemCode is null"); |
| } |
| try { |
| return sService.registerSystemCodeForService(mContext.getUser().getIdentifier(), |
| 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(mContext.getUser().getIdentifier(), |
| service, systemCode); |
| } catch (RemoteException ee) { |
| Log.e(TAG, "Failed to reach CardEmulationService."); |
| ee.rethrowAsRuntimeException(); |
| 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 unregisterSystemCodeForService(ComponentName service) throws RuntimeException { |
| if (service == null) { |
| throw new NullPointerException("service is null"); |
| } |
| try { |
| return sService.removeSystemCodeForService(mContext.getUser().getIdentifier(), 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(mContext.getUser().getIdentifier(), |
| service); |
| } catch (RemoteException ee) { |
| Log.e(TAG, "Failed to reach CardEmulationService."); |
| ee.rethrowAsRuntimeException(); |
| 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) throws RuntimeException { |
| if (service == null) { |
| throw new NullPointerException("service is null"); |
| } |
| try { |
| return sService.getNfcid2ForService(mContext.getUser().getIdentifier(), 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(mContext.getUser().getIdentifier(), service); |
| } catch (RemoteException ee) { |
| Log.e(TAG, "Failed to reach CardEmulationService."); |
| ee.rethrowAsRuntimeException(); |
| 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) |
| throws RuntimeException { |
| if (service == null || nfcid2 == null) { |
| throw new NullPointerException("service or nfcid2 is null"); |
| } |
| try { |
| return sService.setNfcid2ForService(mContext.getUser().getIdentifier(), |
| 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(mContext.getUser().getIdentifier(), |
| service, nfcid2); |
| } catch (RemoteException ee) { |
| Log.e(TAG, "Failed to reach CardEmulationService."); |
| ee.rethrowAsRuntimeException(); |
| 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 #disableService(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 #disableService(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 enableService(Activity activity, ComponentName service) throws RuntimeException { |
| 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."); |
| ee.rethrowAsRuntimeException(); |
| 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 disableService(Activity activity) throws RuntimeException { |
| 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."); |
| ee.rethrowAsRuntimeException(); |
| return false; |
| } |
| } |
| } |
| |
| /** |
| * @hide |
| */ |
| public List<NfcFServiceInfo> getNfcFServices() { |
| try { |
| return sService.getNfcFServices(mContext.getUser().getIdentifier()); |
| } catch (RemoteException e) { |
| // Try one more time |
| recoverService(); |
| if (sService == null) { |
| Log.e(TAG, "Failed to recover CardEmulationService."); |
| return null; |
| } |
| try { |
| return sService.getNfcFServices(mContext.getUser().getIdentifier()); |
| } 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.parseInt(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.parseLong(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(); |
| } |
| |
| } |
| |