Home | History | Annotate | Download | only in cardemulation
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.nfc.cardemulation;
     18 
     19 import android.annotation.SdkConstant;
     20 import android.annotation.SdkConstant.SdkConstantType;
     21 import android.app.Activity;
     22 import android.app.ActivityThread;
     23 import android.content.ComponentName;
     24 import android.content.Context;
     25 import android.content.pm.IPackageManager;
     26 import android.content.pm.PackageManager;
     27 import android.nfc.INfcCardEmulation;
     28 import android.nfc.NfcAdapter;
     29 import android.os.RemoteException;
     30 import android.os.UserHandle;
     31 import android.provider.Settings;
     32 import android.provider.Settings.SettingNotFoundException;
     33 import android.util.Log;
     34 
     35 import java.util.HashMap;
     36 import java.util.List;
     37 
     38 /**
     39  * This class can be used to query the state of
     40  * NFC card emulation services.
     41  *
     42  * For a general introduction into NFC card emulation,
     43  * please read the <a href="{@docRoot}guide/topics/connectivity/nfc/hce.html">
     44  * NFC card emulation developer guide</a>.</p>
     45  *
     46  * <p class="note">Use of this class requires the
     47  * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present
     48  * on the device.
     49  */
     50 public final class CardEmulation {
     51     static final String TAG = "CardEmulation";
     52 
     53     /**
     54      * Activity action: ask the user to change the default
     55      * card emulation service for a certain category. This will
     56      * show a dialog that asks the user whether he wants to
     57      * replace the current default service with the service
     58      * identified with the ComponentName specified in
     59      * {@link #EXTRA_SERVICE_COMPONENT}, for the category
     60      * specified in {@link #EXTRA_CATEGORY}
     61      */
     62     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     63     public static final String ACTION_CHANGE_DEFAULT =
     64             "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
     65 
     66     /**
     67      * The category extra for {@link #ACTION_CHANGE_DEFAULT}.
     68      *
     69      * @see #ACTION_CHANGE_DEFAULT
     70      */
     71     public static final String EXTRA_CATEGORY = "category";
     72 
     73     /**
     74      * The service {@link ComponentName} object passed in as an
     75      * extra for {@link #ACTION_CHANGE_DEFAULT}.
     76      *
     77      * @see #ACTION_CHANGE_DEFAULT
     78      */
     79     public static final String EXTRA_SERVICE_COMPONENT = "component";
     80 
     81     /**
     82      * Category used for NFC payment services.
     83      */
     84     public static final String CATEGORY_PAYMENT = "payment";
     85 
     86     /**
     87      * Category that can be used for all other card emulation
     88      * services.
     89      */
     90     public static final String CATEGORY_OTHER = "other";
     91 
     92     /**
     93      * Return value for {@link #getSelectionModeForCategory(String)}.
     94      *
     95      * <p>In this mode, the user has set a default service for this
     96      *    category.
     97      *
     98      * <p>When using ISO-DEP card emulation with {@link HostApduService}
     99      *    or {@link OffHostApduService}, if a remote NFC device selects
    100      *    any of the Application IDs (AIDs)
    101      *    that the default service has registered in this category,
    102      *    that service will automatically be bound to to handle
    103      *    the transaction.
    104      */
    105     public static final int SELECTION_MODE_PREFER_DEFAULT = 0;
    106 
    107     /**
    108      * Return value for {@link #getSelectionModeForCategory(String)}.
    109      *
    110      * <p>In this mode, when using ISO-DEP card emulation with {@link HostApduService}
    111      *    or {@link OffHostApduService}, whenever an Application ID (AID) of this category
    112      *    is selected, the user is asked which service he wants to use to handle
    113      *    the transaction, even if there is only one matching service.
    114      */
    115     public static final int SELECTION_MODE_ALWAYS_ASK = 1;
    116 
    117     /**
    118      * Return value for {@link #getSelectionModeForCategory(String)}.
    119      *
    120      * <p>In this mode, when using ISO-DEP card emulation with {@link HostApduService}
    121      *    or {@link OffHostApduService}, the user will only be asked to select a service
    122      *    if the Application ID (AID) selected by the reader has been registered by multiple
    123      *    services. If there is only one service that has registered for the AID,
    124      *    that service will be invoked directly.
    125      */
    126     public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
    127 
    128     static boolean sIsInitialized = false;
    129     static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>();
    130     static INfcCardEmulation sService;
    131 
    132     final Context mContext;
    133 
    134     private CardEmulation(Context context, INfcCardEmulation service) {
    135         mContext = context.getApplicationContext();
    136         sService = service;
    137     }
    138 
    139     /**
    140      * Helper to get an instance of this class.
    141      *
    142      * @param adapter A reference to an NfcAdapter object.
    143      * @return
    144      */
    145     public static synchronized CardEmulation getInstance(NfcAdapter adapter) {
    146         if (adapter == null) throw new NullPointerException("NfcAdapter is null");
    147         Context context = adapter.getContext();
    148         if (context == null) {
    149             Log.e(TAG, "NfcAdapter context is null.");
    150             throw new UnsupportedOperationException();
    151         }
    152         if (!sIsInitialized) {
    153             IPackageManager pm = ActivityThread.getPackageManager();
    154             if (pm == null) {
    155                 Log.e(TAG, "Cannot get PackageManager");
    156                 throw new UnsupportedOperationException();
    157             }
    158             try {
    159                 if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)) {
    160                     Log.e(TAG, "This device does not support card emulation");
    161                     throw new UnsupportedOperationException();
    162                 }
    163             } catch (RemoteException e) {
    164                 Log.e(TAG, "PackageManager query failed.");
    165                 throw new UnsupportedOperationException();
    166             }
    167             sIsInitialized = true;
    168         }
    169         CardEmulation manager = sCardEmus.get(context);
    170         if (manager == null) {
    171             // Get card emu service
    172             INfcCardEmulation service = adapter.getCardEmulationService();
    173             if (service == null) {
    174                 Log.e(TAG, "This device does not implement the INfcCardEmulation interface.");
    175                 throw new UnsupportedOperationException();
    176             }
    177             manager = new CardEmulation(context, service);
    178             sCardEmus.put(context, manager);
    179         }
    180         return manager;
    181     }
    182 
    183     /**
    184      * Allows an application to query whether a service is currently
    185      * the default service to handle a card emulation category.
    186      *
    187      * <p>Note that if {@link #getSelectionModeForCategory(String)}
    188      * returns {@link #SELECTION_MODE_ALWAYS_ASK} or {@link #SELECTION_MODE_ASK_IF_CONFLICT},
    189      * this method will always return false. That is because in these
    190      * selection modes a default can't be set at the category level. For categories where
    191      * the selection mode is {@link #SELECTION_MODE_ALWAYS_ASK} or
    192      * {@link #SELECTION_MODE_ASK_IF_CONFLICT}, use
    193      * {@link #isDefaultServiceForAid(ComponentName, String)} to determine whether a service
    194      * is the default for a specific AID.
    195      *
    196      * @param service The ComponentName of the service
    197      * @param category The category
    198      * @return whether service is currently the default service for the category.
    199      *
    200      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
    201      */
    202     public boolean isDefaultServiceForCategory(ComponentName service, String category) {
    203         try {
    204             return sService.isDefaultServiceForCategory(mContext.getUserId(), service, category);
    205         } catch (RemoteException e) {
    206             // Try one more time
    207             recoverService();
    208             if (sService == null) {
    209                 Log.e(TAG, "Failed to recover CardEmulationService.");
    210                 return false;
    211             }
    212             try {
    213                 return sService.isDefaultServiceForCategory(mContext.getUserId(), service,
    214                         category);
    215             } catch (RemoteException ee) {
    216                 Log.e(TAG, "Failed to recover CardEmulationService.");
    217                 return false;
    218             }
    219         }
    220     }
    221 
    222     /**
    223      *
    224      * Allows an application to query whether a service is currently
    225      * the default handler for a specified ISO7816-4 Application ID.
    226      *
    227      * @param service The ComponentName of the service
    228      * @param aid The ISO7816-4 Application ID
    229      * @return whether the service is the default handler for the specified AID
    230      *
    231      * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
    232      */
    233     public boolean isDefaultServiceForAid(ComponentName service, String aid) {
    234         try {
    235             return sService.isDefaultServiceForAid(mContext.getUserId(), service, aid);
    236         } catch (RemoteException e) {
    237             // Try one more time
    238             recoverService();
    239             if (sService == null) {
    240                 Log.e(TAG, "Failed to recover CardEmulationService.");
    241                 return false;
    242             }
    243             try {
    244                 return sService.isDefaultServiceForAid(mContext.getUserId(), service, aid);
    245             } catch (RemoteException ee) {
    246                 Log.e(TAG, "Failed to reach CardEmulationService.");
    247                 return false;
    248             }
    249         }
    250     }
    251 
    252     /**
    253      * Returns whether the user has allowed AIDs registered in the
    254      * specified category to be handled by a service that is preferred
    255      * by the foreground application, instead of by a pre-configured default.
    256      *
    257      * Foreground applications can set such preferences using the
    258      * {@link #setPreferredService(Activity, ComponentName)} method.
    259      *
    260      * @param category The category, e.g. {@link #CATEGORY_PAYMENT}
    261      * @return whether AIDs in the category can be handled by a service
    262      *         specified by the foreground app.
    263      */
    264     public boolean categoryAllowsForegroundPreference(String category) {
    265         if (CATEGORY_PAYMENT.equals(category)) {
    266             boolean preferForeground = false;
    267             try {
    268                 preferForeground = Settings.Secure.getInt(mContext.getContentResolver(),
    269                         Settings.Secure.NFC_PAYMENT_FOREGROUND) != 0;
    270             } catch (SettingNotFoundException e) {
    271             }
    272             return preferForeground;
    273         } else {
    274             // Allowed for all other categories
    275             return true;
    276         }
    277     }
    278 
    279     /**
    280      * Returns the service selection mode for the passed in category.
    281      * Valid return values are:
    282      * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default
    283      *    service for this category, which will be preferred.
    284      * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked
    285      *    every time what service he would like to use in this category.
    286      * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked
    287      *    to pick a service if there is a conflict.
    288      * @param category The category, for example {@link #CATEGORY_PAYMENT}
    289      * @return the selection mode for the passed in category
    290      */
    291     public int getSelectionModeForCategory(String category) {
    292         if (CATEGORY_PAYMENT.equals(category)) {
    293             String defaultComponent = Settings.Secure.getString(mContext.getContentResolver(),
    294                     Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
    295             if (defaultComponent != null) {
    296                 return SELECTION_MODE_PREFER_DEFAULT;
    297             } else {
    298                 return SELECTION_MODE_ALWAYS_ASK;
    299             }
    300         } else {
    301             return SELECTION_MODE_ASK_IF_CONFLICT;
    302         }
    303     }
    304 
    305     /**
    306      * Registers a list of AIDs for a specific category for the
    307      * specified service.
    308      *
    309      * <p>If a list of AIDs for that category was previously
    310      * registered for this service (either statically
    311      * through the manifest, or dynamically by using this API),
    312      * that list of AIDs will be replaced with this one.
    313      *
    314      * <p>Note that you can only register AIDs for a service that
    315      * is running under the same UID as the caller of this API. Typically
    316      * this means you need to call this from the same
    317      * package as the service itself, though UIDs can also
    318      * be shared between packages using shared UIDs.
    319      *
    320      * @param service The component name of the service
    321      * @param category The category of AIDs to be registered
    322      * @param aids A list containing the AIDs to be registered
    323      * @return whether the registration was successful.
    324      */
    325     public boolean registerAidsForService(ComponentName service, String category,
    326             List<String> aids) {
    327         AidGroup aidGroup = new AidGroup(aids, category);
    328         try {
    329             return sService.registerAidGroupForService(mContext.getUserId(), service, aidGroup);
    330         } catch (RemoteException e) {
    331             // Try one more time
    332             recoverService();
    333             if (sService == null) {
    334                 Log.e(TAG, "Failed to recover CardEmulationService.");
    335                 return false;
    336             }
    337             try {
    338                 return sService.registerAidGroupForService(mContext.getUserId(), service,
    339                         aidGroup);
    340             } catch (RemoteException ee) {
    341                 Log.e(TAG, "Failed to reach CardEmulationService.");
    342                 return false;
    343             }
    344         }
    345     }
    346 
    347     /**
    348      * Retrieves the currently registered AIDs for the specified
    349      * category for a service.
    350      *
    351      * <p>Note that this will only return AIDs that were dynamically
    352      * registered using {@link #registerAidsForService(ComponentName, String, List)}
    353      * method. It will *not* return AIDs that were statically registered
    354      * in the manifest.
    355      *
    356      * @param service The component name of the service
    357      * @param category The category for which the AIDs were registered,
    358      *                 e.g. {@link #CATEGORY_PAYMENT}
    359      * @return The list of AIDs registered for this category, or null if it couldn't be found.
    360      */
    361     public List<String> getAidsForService(ComponentName service, String category) {
    362         try {
    363             AidGroup group =  sService.getAidGroupForService(mContext.getUserId(), service,
    364                     category);
    365             return (group != null ? group.getAids() : null);
    366         } catch (RemoteException e) {
    367             recoverService();
    368             if (sService == null) {
    369                 Log.e(TAG, "Failed to recover CardEmulationService.");
    370                 return null;
    371             }
    372             try {
    373                 AidGroup group = sService.getAidGroupForService(mContext.getUserId(), service,
    374                         category);
    375                 return (group != null ? group.getAids() : null);
    376             } catch (RemoteException ee) {
    377                 Log.e(TAG, "Failed to recover CardEmulationService.");
    378                 return null;
    379             }
    380         }
    381     }
    382 
    383     /**
    384      * Removes a previously registered list of AIDs for the specified category for the
    385      * service provided.
    386      *
    387      * <p>Note that this will only remove AIDs that were dynamically
    388      * registered using the {@link #registerAidsForService(ComponentName, String, List)}
    389      * method. It will *not* remove AIDs that were statically registered in
    390      * the manifest. If dynamically registered AIDs are removed using
    391      * this method, and a statically registered AID group for the same category
    392      * exists in the manifest, the static AID group will become active again.
    393      *
    394      * @param service The component name of the service
    395      * @param category The category of the AIDs to be removed, e.g. {@link #CATEGORY_PAYMENT}
    396      * @return whether the group was successfully removed.
    397      */
    398     public boolean removeAidsForService(ComponentName service, String category) {
    399         try {
    400             return sService.removeAidGroupForService(mContext.getUserId(), service, category);
    401         } catch (RemoteException e) {
    402             // Try one more time
    403             recoverService();
    404             if (sService == null) {
    405                 Log.e(TAG, "Failed to recover CardEmulationService.");
    406                 return false;
    407             }
    408             try {
    409                 return sService.removeAidGroupForService(mContext.getUserId(), service, category);
    410             } catch (RemoteException ee) {
    411                 Log.e(TAG, "Failed to reach CardEmulationService.");
    412                 return false;
    413             }
    414         }
    415     }
    416 
    417     /**
    418      * Allows a foreground application to specify which card emulation service
    419      * should be preferred while a specific Activity is in the foreground.
    420      *
    421      * <p>The specified Activity must currently be in resumed state. A good
    422      * paradigm is to call this method in your {@link Activity#onResume}, and to call
    423      * {@link #unsetPreferredService(Activity)} in your {@link Activity#onPause}.
    424      *
    425      * <p>This method call will fail in two specific scenarios:
    426      * <ul>
    427      * <li> If the service registers one or more AIDs in the {@link #CATEGORY_PAYMENT}
    428      * category, but the user has indicated that foreground apps are not allowed
    429      * to override the default payment service.
    430      * <li> If the service registers one or more AIDs in the {@link #CATEGORY_OTHER}
    431      * category that are also handled by the default payment service, and the
    432      * user has indicated that foreground apps are not allowed to override the
    433      * default payment service.
    434      * </ul>
    435      *
    436      * <p> Use {@link #categoryAllowsForegroundPreference(String)} to determine
    437      * whether foreground apps can override the default payment service.
    438      *
    439      * <p>Note that this preference is not persisted by the OS, and hence must be
    440      * called every time the Activity is resumed.
    441      *
    442      * @param activity The activity which prefers this service to be invoked
    443      * @param service The service to be preferred while this activity is in the foreground
    444      * @return whether the registration was successful
    445      */
    446     public boolean setPreferredService(Activity activity, ComponentName service) {
    447         // Verify the activity is in the foreground before calling into NfcService
    448         if (activity == null || service == null) {
    449             throw new NullPointerException("activity or service or category is null");
    450         }
    451         if (!activity.isResumed()) {
    452             throw new IllegalArgumentException("Activity must be resumed.");
    453         }
    454         try {
    455             return sService.setPreferredService(service);
    456         } catch (RemoteException e) {
    457             // Try one more time
    458             recoverService();
    459             if (sService == null) {
    460                 Log.e(TAG, "Failed to recover CardEmulationService.");
    461                 return false;
    462             }
    463             try {
    464                 return sService.setPreferredService(service);
    465             } catch (RemoteException ee) {
    466                 Log.e(TAG, "Failed to reach CardEmulationService.");
    467                 return false;
    468             }
    469         }
    470     }
    471 
    472     /**
    473      * Unsets the preferred service for the specified Activity.
    474      *
    475      * <p>Note that the specified Activity must still be in resumed
    476      * state at the time of this call. A good place to call this method
    477      * is in your {@link Activity#onPause} implementation.
    478      *
    479      * @param activity The activity which the service was registered for
    480      * @return true when successful
    481      */
    482     public boolean unsetPreferredService(Activity activity) {
    483         if (activity == null) {
    484             throw new NullPointerException("activity is null");
    485         }
    486         if (!activity.isResumed()) {
    487             throw new IllegalArgumentException("Activity must be resumed.");
    488         }
    489         try {
    490             return sService.unsetPreferredService();
    491         } catch (RemoteException e) {
    492             // Try one more time
    493             recoverService();
    494             if (sService == null) {
    495                 Log.e(TAG, "Failed to recover CardEmulationService.");
    496                 return false;
    497             }
    498             try {
    499                 return sService.unsetPreferredService();
    500             } catch (RemoteException ee) {
    501                 Log.e(TAG, "Failed to reach CardEmulationService.");
    502                 return false;
    503             }
    504         }
    505     }
    506 
    507     /**
    508      * Some devices may allow an application to register all
    509      * AIDs that starts with a certain prefix, e.g.
    510      * "A000000004*" to register all MasterCard AIDs.
    511      *
    512      * Use this method to determine whether this device
    513      * supports registering AID prefixes.
    514      *
    515      * @return whether AID prefix registering is supported on this device.
    516      */
    517     public boolean supportsAidPrefixRegistration() {
    518         try {
    519             return sService.supportsAidPrefixRegistration();
    520         } catch (RemoteException e) {
    521             recoverService();
    522             if (sService == null) {
    523                 Log.e(TAG, "Failed to recover CardEmulationService.");
    524                 return false;
    525             }
    526             try {
    527                 return sService.supportsAidPrefixRegistration();
    528             } catch (RemoteException ee) {
    529                 Log.e(TAG, "Failed to reach CardEmulationService.");
    530                 return false;
    531             }
    532         }
    533     }
    534 
    535     /**
    536      * @hide
    537      */
    538     public boolean setDefaultServiceForCategory(ComponentName service, String category) {
    539         try {
    540             return sService.setDefaultServiceForCategory(mContext.getUserId(), service, category);
    541         } catch (RemoteException e) {
    542             // Try one more time
    543             recoverService();
    544             if (sService == null) {
    545                 Log.e(TAG, "Failed to recover CardEmulationService.");
    546                 return false;
    547             }
    548             try {
    549                 return sService.setDefaultServiceForCategory(mContext.getUserId(), service,
    550                         category);
    551             } catch (RemoteException ee) {
    552                 Log.e(TAG, "Failed to reach CardEmulationService.");
    553                 return false;
    554             }
    555         }
    556     }
    557 
    558     /**
    559      * @hide
    560      */
    561     public boolean setDefaultForNextTap(ComponentName service) {
    562         try {
    563             return sService.setDefaultForNextTap(mContext.getUserId(), service);
    564         } catch (RemoteException e) {
    565             // Try one more time
    566             recoverService();
    567             if (sService == null) {
    568                 Log.e(TAG, "Failed to recover CardEmulationService.");
    569                 return false;
    570             }
    571             try {
    572                 return sService.setDefaultForNextTap(mContext.getUserId(), service);
    573             } catch (RemoteException ee) {
    574                 Log.e(TAG, "Failed to reach CardEmulationService.");
    575                 return false;
    576             }
    577         }
    578     }
    579 
    580     /**
    581      * @hide
    582      */
    583     public List<ApduServiceInfo> getServices(String category) {
    584         try {
    585             return sService.getServices(mContext.getUserId(), category);
    586         } catch (RemoteException e) {
    587             // Try one more time
    588             recoverService();
    589             if (sService == null) {
    590                 Log.e(TAG, "Failed to recover CardEmulationService.");
    591                 return null;
    592             }
    593             try {
    594                 return sService.getServices(mContext.getUserId(), category);
    595             } catch (RemoteException ee) {
    596                 Log.e(TAG, "Failed to reach CardEmulationService.");
    597                 return null;
    598             }
    599         }
    600     }
    601 
    602     /**
    603      * A valid AID according to ISO/IEC 7816-4:
    604      * <ul>
    605      * <li>Has >= 5 bytes and <=16 bytes (>=10 hex chars and <= 32 hex chars)
    606      * <li>Consist of only hex characters
    607      * <li>Additionally, we allow an asterisk at the end, to indicate
    608      *     a prefix
    609      * <li>Additinally we allow an (#) at symbol at the end, to indicate
    610      *     a subset
    611      * </ul>
    612      *
    613      * @hide
    614      */
    615     public static boolean isValidAid(String aid) {
    616         if (aid == null)
    617             return false;
    618 
    619         // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
    620         if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
    621             Log.e(TAG, "AID " + aid + " is not a valid AID.");
    622             return false;
    623         }
    624 
    625         // If not a prefix/subset AID, the total length must be even (even # of AID chars)
    626         if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
    627             Log.e(TAG, "AID " + aid + " is not a valid AID.");
    628             return false;
    629         }
    630 
    631         // Verify hex characters
    632         if (!aid.matches("[0-9A-Fa-f]{10,32}\\*?\\#?")) {
    633             Log.e(TAG, "AID " + aid + " is not a valid AID.");
    634             return false;
    635         }
    636 
    637         return true;
    638     }
    639 
    640     void recoverService() {
    641         NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
    642         sService = adapter.getCardEmulationService();
    643     }
    644 
    645 }
    646