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.app.Activity;
     20 import android.app.ActivityThread;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.pm.IPackageManager;
     24 import android.content.pm.PackageManager;
     25 import android.nfc.INfcFCardEmulation;
     26 import android.nfc.NfcAdapter;
     27 import android.os.RemoteException;
     28 import android.os.UserHandle;
     29 import android.util.Log;
     30 
     31 import java.util.HashMap;
     32 import java.util.List;
     33 
     34 /**
     35  * This class can be used to query the state of
     36  * NFC-F card emulation services.
     37  *
     38  * For a general introduction into NFC card emulation,
     39  * please read the <a href="{@docRoot}guide/topics/connectivity/nfc/hce.html">
     40  * NFC card emulation developer guide</a>.</p>
     41  *
     42  * <p class="note">Use of this class requires the
     43  * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION_NFCF}
     44  * to be present on the device.
     45  */
     46 public final class NfcFCardEmulation {
     47     static final String TAG = "NfcFCardEmulation";
     48 
     49     static boolean sIsInitialized = false;
     50     static HashMap<Context, NfcFCardEmulation> sCardEmus = new HashMap<Context, NfcFCardEmulation>();
     51     static INfcFCardEmulation sService;
     52 
     53     final Context mContext;
     54 
     55     private NfcFCardEmulation(Context context, INfcFCardEmulation service) {
     56         mContext = context.getApplicationContext();
     57         sService = service;
     58     }
     59 
     60     /**
     61      * Helper to get an instance of this class.
     62      *
     63      * @param adapter A reference to an NfcAdapter object.
     64      * @return
     65      */
     66     public static synchronized NfcFCardEmulation getInstance(NfcAdapter adapter) {
     67         if (adapter == null) throw new NullPointerException("NfcAdapter is null");
     68         Context context = adapter.getContext();
     69         if (context == null) {
     70             Log.e(TAG, "NfcAdapter context is null.");
     71             throw new UnsupportedOperationException();
     72         }
     73         if (!sIsInitialized) {
     74             IPackageManager pm = ActivityThread.getPackageManager();
     75             if (pm == null) {
     76                 Log.e(TAG, "Cannot get PackageManager");
     77                 throw new UnsupportedOperationException();
     78             }
     79             try {
     80                 if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0)) {
     81                     Log.e(TAG, "This device does not support NFC-F card emulation");
     82                     throw new UnsupportedOperationException();
     83                 }
     84             } catch (RemoteException e) {
     85                 Log.e(TAG, "PackageManager query failed.");
     86                 throw new UnsupportedOperationException();
     87             }
     88             sIsInitialized = true;
     89         }
     90         NfcFCardEmulation manager = sCardEmus.get(context);
     91         if (manager == null) {
     92             // Get card emu service
     93             INfcFCardEmulation service = adapter.getNfcFCardEmulationService();
     94             if (service == null) {
     95                 Log.e(TAG, "This device does not implement the INfcFCardEmulation interface.");
     96                 throw new UnsupportedOperationException();
     97             }
     98             manager = new NfcFCardEmulation(context, service);
     99             sCardEmus.put(context, manager);
    100         }
    101         return manager;
    102     }
    103 
    104     /**
    105      * Retrieves the current System Code for the specified service.
    106      *
    107      * <p>Before calling {@link #registerSystemCodeForService(ComponentName, String)},
    108      * the System Code contained in the Manifest file is returned. After calling
    109      * {@link #registerSystemCodeForService(ComponentName, String)}, the System Code
    110      * registered there is returned. After calling
    111      * {@link #unregisterSystemCodeForService(ComponentName)}, "null" is returned.
    112      *
    113      * @param service The component name of the service
    114      * @return the current System Code
    115      */
    116     public String getSystemCodeForService(ComponentName service) throws RuntimeException {
    117         if (service == null) {
    118             throw new NullPointerException("service is null");
    119         }
    120         try {
    121             return sService.getSystemCodeForService(mContext.getUserId(), service);
    122         } catch (RemoteException e) {
    123             // Try one more time
    124             recoverService();
    125             if (sService == null) {
    126                 Log.e(TAG, "Failed to recover CardEmulationService.");
    127                 return null;
    128             }
    129             try {
    130                 return sService.getSystemCodeForService(mContext.getUserId(), service);
    131             } catch (RemoteException ee) {
    132                 Log.e(TAG, "Failed to reach CardEmulationService.");
    133                 ee.rethrowAsRuntimeException();
    134                 return null;
    135             }
    136         }
    137     }
    138 
    139     /**
    140      * Registers a System Code for the specified service.
    141      *
    142      * <p>The System Code must be in range from "4000" to "4FFF" (excluding "4*FF").
    143      *
    144      * <p>If a System Code was previously registered for this service
    145      * (either statically through the manifest, or dynamically by using this API),
    146      * it will be replaced with this one.
    147      *
    148      * <p>Even if the same System Code is already registered for another service,
    149      * this method succeeds in registering the System Code.
    150      *
    151      * <p>Note that you can only register a System Code for a service that
    152      * is running under the same UID as the caller of this API. Typically
    153      * this means you need to call this from the same
    154      * package as the service itself, though UIDs can also
    155      * be shared between packages using shared UIDs.
    156      *
    157      * @param service The component name of the service
    158      * @param systemCode The System Code to be registered
    159      * @return whether the registration was successful.
    160      */
    161     public boolean registerSystemCodeForService(ComponentName service, String systemCode)
    162             throws RuntimeException {
    163         if (service == null || systemCode == null) {
    164             throw new NullPointerException("service or systemCode is null");
    165         }
    166         try {
    167             return sService.registerSystemCodeForService(mContext.getUserId(),
    168                     service, systemCode);
    169         } catch (RemoteException e) {
    170             // Try one more time
    171             recoverService();
    172             if (sService == null) {
    173                 Log.e(TAG, "Failed to recover CardEmulationService.");
    174                 return false;
    175             }
    176             try {
    177                 return sService.registerSystemCodeForService(mContext.getUserId(),
    178                         service, systemCode);
    179             } catch (RemoteException ee) {
    180                 Log.e(TAG, "Failed to reach CardEmulationService.");
    181                 ee.rethrowAsRuntimeException();
    182                 return false;
    183             }
    184         }
    185     }
    186 
    187     /**
    188      * Removes a registered System Code for the specified service.
    189      *
    190      * @param service The component name of the service
    191      * @return whether the System Code was successfully removed.
    192      */
    193     public boolean unregisterSystemCodeForService(ComponentName service) throws RuntimeException {
    194         if (service == null) {
    195             throw new NullPointerException("service is null");
    196         }
    197         try {
    198             return sService.removeSystemCodeForService(mContext.getUserId(), service);
    199         } catch (RemoteException e) {
    200             // Try one more time
    201             recoverService();
    202             if (sService == null) {
    203                 Log.e(TAG, "Failed to recover CardEmulationService.");
    204                 return false;
    205             }
    206             try {
    207                 return sService.removeSystemCodeForService(mContext.getUserId(), service);
    208             } catch (RemoteException ee) {
    209                 Log.e(TAG, "Failed to reach CardEmulationService.");
    210                 ee.rethrowAsRuntimeException();
    211                 return false;
    212             }
    213         }
    214     }
    215 
    216     /**
    217      * Retrieves the current NFCID2 for the specified service.
    218      *
    219      * <p>Before calling {@link #setNfcid2ForService(ComponentName, String)},
    220      * the NFCID2 contained in the Manifest file is returned. If "random" is specified
    221      * in the Manifest file, a random number assigned by the system at installation time
    222      * is returned. After setting an NFCID2
    223      * with {@link #setNfcid2ForService(ComponentName, String)}, this NFCID2 is returned.
    224      *
    225      * @param service The component name of the service
    226      * @return the current NFCID2
    227      */
    228     public String getNfcid2ForService(ComponentName service) throws RuntimeException {
    229         if (service == null) {
    230             throw new NullPointerException("service is null");
    231         }
    232         try {
    233             return sService.getNfcid2ForService(mContext.getUserId(), service);
    234         } catch (RemoteException e) {
    235             // Try one more time
    236             recoverService();
    237             if (sService == null) {
    238                 Log.e(TAG, "Failed to recover CardEmulationService.");
    239                 return null;
    240             }
    241             try {
    242                 return sService.getNfcid2ForService(mContext.getUserId(), service);
    243             } catch (RemoteException ee) {
    244                 Log.e(TAG, "Failed to reach CardEmulationService.");
    245                 ee.rethrowAsRuntimeException();
    246                 return null;
    247             }
    248         }
    249     }
    250 
    251     /**
    252      * Set a NFCID2 for the specified service.
    253      *
    254      * <p>The NFCID2 must be in range from "02FE000000000000" to "02FEFFFFFFFFFFFF".
    255      *
    256      * <p>If a NFCID2 was previously set for this service
    257      * (either statically through the manifest, or dynamically by using this API),
    258      * it will be replaced.
    259      *
    260      * <p>Note that you can only set the NFCID2 for a service that
    261      * is running under the same UID as the caller of this API. Typically
    262      * this means you need to call this from the same
    263      * package as the service itself, though UIDs can also
    264      * be shared between packages using shared UIDs.
    265      *
    266      * @param service The component name of the service
    267      * @param nfcid2 The NFCID2 to be registered
    268      * @return whether the setting was successful.
    269      */
    270     public boolean setNfcid2ForService(ComponentName service, String nfcid2)
    271             throws RuntimeException {
    272         if (service == null || nfcid2 == null) {
    273             throw new NullPointerException("service or nfcid2 is null");
    274         }
    275         try {
    276             return sService.setNfcid2ForService(mContext.getUserId(),
    277                     service, nfcid2);
    278         } catch (RemoteException e) {
    279             // Try one more time
    280             recoverService();
    281             if (sService == null) {
    282                 Log.e(TAG, "Failed to recover CardEmulationService.");
    283                 return false;
    284             }
    285             try {
    286                 return sService.setNfcid2ForService(mContext.getUserId(),
    287                         service, nfcid2);
    288             } catch (RemoteException ee) {
    289                 Log.e(TAG, "Failed to reach CardEmulationService.");
    290                 ee.rethrowAsRuntimeException();
    291                 return false;
    292             }
    293         }
    294     }
    295 
    296     /**
    297      * Allows a foreground application to specify which card emulation service
    298      * should be enabled while a specific Activity is in the foreground.
    299      *
    300      * <p>The specified HCE-F service is only enabled when the corresponding application is
    301      * in the foreground and this method has been called. When the application is moved to
    302      * the background, {@link #disableService(Activity)} is called, or
    303      * NFCID2 or System Code is replaced, the HCE-F service is disabled.
    304      *
    305      * <p>The specified Activity must currently be in resumed state. A good
    306      * paradigm is to call this method in your {@link Activity#onResume}, and to call
    307      * {@link #disableService(Activity)} in your {@link Activity#onPause}.
    308      *
    309      * <p>Note that this preference is not persisted by the OS, and hence must be
    310      * called every time the Activity is resumed.
    311      *
    312      * @param activity The activity which prefers this service to be invoked
    313      * @param service The service to be preferred while this activity is in the foreground
    314      * @return whether the registration was successful
    315      */
    316     public boolean enableService(Activity activity, ComponentName service) throws RuntimeException {
    317         if (activity == null || service == null) {
    318             throw new NullPointerException("activity or service is null");
    319         }
    320         // Verify the activity is in the foreground before calling into NfcService
    321         if (!activity.isResumed()) {
    322             throw new IllegalArgumentException("Activity must be resumed.");
    323         }
    324         try {
    325             return sService.enableNfcFForegroundService(service);
    326         } catch (RemoteException e) {
    327             // Try one more time
    328             recoverService();
    329             if (sService == null) {
    330                 Log.e(TAG, "Failed to recover CardEmulationService.");
    331                 return false;
    332             }
    333             try {
    334                 return sService.enableNfcFForegroundService(service);
    335             } catch (RemoteException ee) {
    336                 Log.e(TAG, "Failed to reach CardEmulationService.");
    337                 ee.rethrowAsRuntimeException();
    338                 return false;
    339             }
    340         }
    341     }
    342 
    343     /**
    344      * Disables the service for the specified Activity.
    345      *
    346      * <p>Note that the specified Activity must still be in resumed
    347      * state at the time of this call. A good place to call this method
    348      * is in your {@link Activity#onPause} implementation.
    349      *
    350      * @param activity The activity which the service was registered for
    351      * @return true when successful
    352      */
    353     public boolean disableService(Activity activity) throws RuntimeException {
    354         if (activity == null) {
    355             throw new NullPointerException("activity is null");
    356         }
    357         if (!activity.isResumed()) {
    358             throw new IllegalArgumentException("Activity must be resumed.");
    359         }
    360         try {
    361             return sService.disableNfcFForegroundService();
    362         } catch (RemoteException e) {
    363             // Try one more time
    364             recoverService();
    365             if (sService == null) {
    366                 Log.e(TAG, "Failed to recover CardEmulationService.");
    367                 return false;
    368             }
    369             try {
    370                 return sService.disableNfcFForegroundService();
    371             } catch (RemoteException ee) {
    372                 Log.e(TAG, "Failed to reach CardEmulationService.");
    373                 ee.rethrowAsRuntimeException();
    374                 return false;
    375             }
    376         }
    377     }
    378 
    379     /**
    380      * @hide
    381      */
    382     public List<NfcFServiceInfo> getNfcFServices() {
    383         try {
    384             return sService.getNfcFServices(mContext.getUserId());
    385         } catch (RemoteException e) {
    386             // Try one more time
    387             recoverService();
    388             if (sService == null) {
    389                 Log.e(TAG, "Failed to recover CardEmulationService.");
    390                 return null;
    391             }
    392             try {
    393                 return sService.getNfcFServices(mContext.getUserId());
    394             } catch (RemoteException ee) {
    395                 Log.e(TAG, "Failed to reach CardEmulationService.");
    396                 return null;
    397             }
    398         }
    399     }
    400 
    401     /**
    402      * @hide
    403      */
    404     public int getMaxNumOfRegisterableSystemCodes() {
    405         try {
    406             return sService.getMaxNumOfRegisterableSystemCodes();
    407         } catch (RemoteException e) {
    408             // Try one more time
    409             recoverService();
    410             if (sService == null) {
    411                 Log.e(TAG, "Failed to recover CardEmulationService.");
    412                 return -1;
    413             }
    414             try {
    415                 return sService.getMaxNumOfRegisterableSystemCodes();
    416             } catch (RemoteException ee) {
    417                 Log.e(TAG, "Failed to reach CardEmulationService.");
    418                 return -1;
    419             }
    420         }
    421     }
    422 
    423     /**
    424      * @hide
    425      */
    426     public static boolean isValidSystemCode(String systemCode) {
    427         if (systemCode == null) {
    428             return false;
    429         }
    430         if (systemCode.length() != 4) {
    431             Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
    432             return false;
    433         }
    434         // check if the value is between "4000" and "4FFF" (excluding "4*FF")
    435         if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) {
    436             Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
    437             return false;
    438         }
    439         try {
    440             Integer.parseInt(systemCode, 16);
    441         } catch (NumberFormatException e) {
    442             Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
    443             return false;
    444         }
    445         return true;
    446     }
    447 
    448     /**
    449      * @hide
    450      */
    451     public static boolean isValidNfcid2(String nfcid2) {
    452         if (nfcid2 == null) {
    453             return false;
    454         }
    455         if (nfcid2.length() != 16) {
    456             Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
    457             return false;
    458         }
    459         // check if the the value starts with "02FE"
    460         if (!nfcid2.toUpperCase().startsWith("02FE")) {
    461             Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
    462             return false;
    463         }
    464         try {
    465             Long.parseLong(nfcid2, 16);
    466         } catch (NumberFormatException e) {
    467             Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
    468             return false;
    469         }
    470         return true;
    471     }
    472 
    473     void recoverService() {
    474         NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
    475         sService = adapter.getNfcFCardEmulationService();
    476     }
    477 
    478 }
    479 
    480