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
17package android.nfc.cardemulation;
18
19import android.annotation.SdkConstant;
20import android.annotation.SdkConstant.SdkConstantType;
21import android.app.Activity;
22import android.app.ActivityThread;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.pm.IPackageManager;
26import android.content.pm.PackageManager;
27import android.nfc.INfcCardEmulation;
28import android.nfc.NfcAdapter;
29import android.os.RemoteException;
30import android.os.UserHandle;
31import android.provider.Settings;
32import android.provider.Settings.SettingNotFoundException;
33import android.util.Log;
34
35import java.util.HashMap;
36import 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/nfc/ce.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 */
50public 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)) {
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(UserHandle.myUserId(), 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(UserHandle.myUserId(), 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(UserHandle.myUserId(), 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(UserHandle.myUserId(), 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(UserHandle.myUserId(), 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(UserHandle.myUserId(), 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(UserHandle.myUserId(), 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(UserHandle.myUserId(), 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(UserHandle.myUserId(), 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(UserHandle.myUserId(), 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(UserHandle.myUserId(), 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(UserHandle.myUserId(), 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(UserHandle.myUserId(), 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(UserHandle.myUserId(), 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(UserHandle.myUserId(), 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(UserHandle.myUserId(), 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     * </ul>
610     *
611     * @hide
612     */
613    public static boolean isValidAid(String aid) {
614        if (aid == null)
615            return false;
616
617        // If a prefix AID, the total length must be odd (even # of AID chars + '*')
618        if (aid.endsWith("*") && ((aid.length() % 2) == 0)) {
619            Log.e(TAG, "AID " + aid + " is not a valid AID.");
620            return false;
621        }
622
623        // If not a prefix AID, the total length must be even (even # of AID chars)
624        if (!aid.endsWith("*") && ((aid.length() % 2) != 0)) {
625            Log.e(TAG, "AID " + aid + " is not a valid AID.");
626            return false;
627        }
628
629        // Verify hex characters
630        if (!aid.matches("[0-9A-Fa-f]{10,32}\\*?")) {
631            Log.e(TAG, "AID " + aid + " is not a valid AID.");
632            return false;
633        }
634
635        return true;
636    }
637
638    void recoverService() {
639        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
640        sService = adapter.getCardEmulationService();
641    }
642
643}
644