CardEmulation.java revision aa1492d1d8c5f80e074faacb83905bd07487975d
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.ActivityThread;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.pm.IPackageManager;
25import android.content.pm.PackageManager;
26import android.nfc.INfcCardEmulation;
27import android.nfc.NfcAdapter;
28import android.os.RemoteException;
29import android.os.UserHandle;
30import android.provider.Settings;
31import android.util.Log;
32
33import java.util.HashMap;
34import java.util.List;
35
36/**
37 * This class can be used to query the state of
38 * NFC card emulation services.
39 *
40 * For a general introduction into NFC card emulation,
41 * please read the <a href="{@docRoot}guide/topics/nfc/ce.html">
42 * NFC card emulation developer guide</a>.</p>
43 *
44 * <p class="note">Use of this class requires the
45 * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present
46 * on the device.
47 */
48public final class CardEmulation {
49    static final String TAG = "CardEmulation";
50
51    /**
52     * Activity action: ask the user to change the default
53     * card emulation service for a certain category. This will
54     * show a dialog that asks the user whether he wants to
55     * replace the current default service with the service
56     * identified with the ComponentName specified in
57     * {@link #EXTRA_SERVICE_COMPONENT}, for the category
58     * specified in {@link #EXTRA_CATEGORY}
59     */
60    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
61    public static final String ACTION_CHANGE_DEFAULT =
62            "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
63
64    /**
65     * The category extra for {@link #ACTION_CHANGE_DEFAULT}.
66     *
67     * @see #ACTION_CHANGE_DEFAULT
68     */
69    public static final String EXTRA_CATEGORY = "category";
70
71    /**
72     * The service {@link ComponentName} object passed in as an
73     * extra for {@link #ACTION_CHANGE_DEFAULT}.
74     *
75     * @see #ACTION_CHANGE_DEFAULT
76     */
77    public static final String EXTRA_SERVICE_COMPONENT = "component";
78
79    /**
80     * Category used for NFC payment services.
81     */
82    public static final String CATEGORY_PAYMENT = "payment";
83
84    /**
85     * Category that can be used for all other card emulation
86     * services.
87     */
88    public static final String CATEGORY_OTHER = "other";
89
90    /**
91     * Return value for {@link #getSelectionModeForCategory(String)}.
92     *
93     * <p>In this mode, the user has set a default service for this
94     *    category.
95     *
96     * <p>When using ISO-DEP card emulation with {@link HostApduService}
97     *    or {@link OffHostApduService}, if a remote NFC device selects
98     *    any of the Application IDs (AIDs)
99     *    that the default service has registered in this category,
100     *    that service will automatically be bound to to handle
101     *    the transaction.
102     */
103    public static final int SELECTION_MODE_PREFER_DEFAULT = 0;
104
105    /**
106     * Return value for {@link #getSelectionModeForCategory(String)}.
107     *
108     * <p>In this mode, when using ISO-DEP card emulation with {@link HostApduService}
109     *    or {@link OffHostApduService}, whenever an Application ID (AID) of this category
110     *    is selected, the user is asked which service he wants to use to handle
111     *    the transaction, even if there is only one matching service.
112     */
113    public static final int SELECTION_MODE_ALWAYS_ASK = 1;
114
115    /**
116     * Return value for {@link #getSelectionModeForCategory(String)}.
117     *
118     * <p>In this mode, when using ISO-DEP card emulation with {@link HostApduService}
119     *    or {@link OffHostApduService}, the user will only be asked to select a service
120     *    if the Application ID (AID) selected by the reader has been registered by multiple
121     *    services. If there is only one service that has registered for the AID,
122     *    that service will be invoked directly.
123     */
124    public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
125
126    static boolean sIsInitialized = false;
127    static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>();
128    static INfcCardEmulation sService;
129
130    final Context mContext;
131
132    private CardEmulation(Context context, INfcCardEmulation service) {
133        mContext = context.getApplicationContext();
134        sService = service;
135    }
136
137    /**
138     * Helper to get an instance of this class.
139     *
140     * @param adapter A reference to an NfcAdapter object.
141     * @return
142     */
143    public static synchronized CardEmulation getInstance(NfcAdapter adapter) {
144        if (adapter == null) throw new NullPointerException("NfcAdapter is null");
145        Context context = adapter.getContext();
146        if (context == null) {
147            Log.e(TAG, "NfcAdapter context is null.");
148            throw new UnsupportedOperationException();
149        }
150        if (!sIsInitialized) {
151            IPackageManager pm = ActivityThread.getPackageManager();
152            if (pm == null) {
153                Log.e(TAG, "Cannot get PackageManager");
154                throw new UnsupportedOperationException();
155            }
156            try {
157                if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
158                    Log.e(TAG, "This device does not support card emulation");
159                    throw new UnsupportedOperationException();
160                }
161            } catch (RemoteException e) {
162                Log.e(TAG, "PackageManager query failed.");
163                throw new UnsupportedOperationException();
164            }
165            sIsInitialized = true;
166        }
167        CardEmulation manager = sCardEmus.get(context);
168        if (manager == null) {
169            // Get card emu service
170            INfcCardEmulation service = adapter.getCardEmulationService();
171            if (service == null) {
172                Log.e(TAG, "This device does not implement the INfcCardEmulation interface.");
173                throw new UnsupportedOperationException();
174            }
175            manager = new CardEmulation(context, service);
176            sCardEmus.put(context, manager);
177        }
178        return manager;
179    }
180
181    /**
182     * Allows an application to query whether a service is currently
183     * the default service to handle a card emulation category.
184     *
185     * <p>Note that if {@link #getSelectionModeForCategory(String)}
186     * returns {@link #SELECTION_MODE_ALWAYS_ASK} or {@link #SELECTION_MODE_ASK_IF_CONFLICT},
187     * this method will always return false. That is because in these
188     * selection modes a default can't be set at the category level. For categories where
189     * the selection mode is {@link #SELECTION_MODE_ALWAYS_ASK} or
190     * {@link #SELECTION_MODE_ASK_IF_CONFLICT}, use
191     * {@link #isDefaultServiceForAid(ComponentName, String)} to determine whether a service
192     * is the default for a specific AID.
193     *
194     * @param service The ComponentName of the service
195     * @param category The category
196     * @return whether service is currently the default service for the category.
197     *
198     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
199     */
200    public boolean isDefaultServiceForCategory(ComponentName service, String category) {
201        try {
202            return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service, category);
203        } catch (RemoteException e) {
204            // Try one more time
205            recoverService();
206            if (sService == null) {
207                Log.e(TAG, "Failed to recover CardEmulationService.");
208                return false;
209            }
210            try {
211                return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service,
212                        category);
213            } catch (RemoteException ee) {
214                Log.e(TAG, "Failed to recover CardEmulationService.");
215                return false;
216            }
217        }
218    }
219
220    /**
221     *
222     * Allows an application to query whether a service is currently
223     * the default handler for a specified ISO7816-4 Application ID.
224     *
225     * @param service The ComponentName of the service
226     * @param aid The ISO7816-4 Application ID
227     * @return whether the service is the default handler for the specified AID
228     *
229     * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
230     */
231    public boolean isDefaultServiceForAid(ComponentName service, String aid) {
232        try {
233            return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid);
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 false;
240            }
241            try {
242                return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid);
243            } catch (RemoteException ee) {
244                Log.e(TAG, "Failed to reach CardEmulationService.");
245                return false;
246            }
247        }
248    }
249
250    /**
251     * Returns the service selection mode for the passed in category.
252     * Valid return values are:
253     * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default
254     *    service for this category, which will be preferred.
255     * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked
256     *    every time what service he would like to use in this category.
257     * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked
258     *    to pick a service if there is a conflict.
259     * @param category The category, for example {@link #CATEGORY_PAYMENT}
260     * @return the selection mode for the passed in category
261     */
262    public int getSelectionModeForCategory(String category) {
263        if (CATEGORY_PAYMENT.equals(category)) {
264            String defaultComponent = Settings.Secure.getString(mContext.getContentResolver(),
265                    Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
266            if (defaultComponent != null) {
267                return SELECTION_MODE_PREFER_DEFAULT;
268            } else {
269                return SELECTION_MODE_ALWAYS_ASK;
270            }
271        } else {
272            // All other categories are in "only ask if conflict" mode
273            return SELECTION_MODE_ASK_IF_CONFLICT;
274        }
275    }
276
277    /**
278     * Registers a group of AIDs for the specified service.
279     *
280     * <p>If an AID group for that category was previously
281     * registered for this service (either statically
282     * through the manifest, or dynamically by using this API),
283     * that AID group will be replaced with this one.
284     *
285     * <p>Note that you can only register AIDs for a service that
286     * is running under the same UID as you are. Typically
287     * this means you need to call this from the same
288     * package as the service itself, though UIDs can also
289     * be shared between packages using shared UIDs.
290     *
291     * @param service The component name of the service
292     * @param aidGroup The group of AIDs to be registered
293     * @return whether the registration was successful.
294     */
295    public boolean registerAidGroupForService(ComponentName service, AidGroup aidGroup) {
296        try {
297            return sService.registerAidGroupForService(UserHandle.myUserId(), service, aidGroup);
298        } catch (RemoteException e) {
299            // Try one more time
300            recoverService();
301            if (sService == null) {
302                Log.e(TAG, "Failed to recover CardEmulationService.");
303                return false;
304            }
305            try {
306                return sService.registerAidGroupForService(UserHandle.myUserId(), service,
307                        aidGroup);
308            } catch (RemoteException ee) {
309                Log.e(TAG, "Failed to reach CardEmulationService.");
310                return false;
311            }
312        }
313    }
314
315    /**
316     * Retrieves the currently registered AID group for the specified
317     * category for a service.
318     *
319     * <p>Note that this will only return AID groups that were dynamically
320     * registered using {@link #registerAidGroupForService(ComponentName, AidGroup)}
321     * method. It will *not* return AID groups that were statically registered
322     * in the manifest.
323     *
324     * @param service The component name of the service
325     * @param category The category of the AID group to be returned, e.g. {@link #CATEGORY_PAYMENT}
326     * @return The AID group, or null if it couldn't be found
327     */
328    public AidGroup getAidGroupForService(ComponentName service, String category) {
329        try {
330            return sService.getAidGroupForService(UserHandle.myUserId(), service, category);
331        } catch (RemoteException e) {
332            recoverService();
333            if (sService == null) {
334                Log.e(TAG, "Failed to recover CardEmulationService.");
335                return null;
336            }
337            try {
338                return sService.getAidGroupForService(UserHandle.myUserId(), service, category);
339            } catch (RemoteException ee) {
340                Log.e(TAG, "Failed to recover CardEmulationService.");
341                return null;
342            }
343        }
344    }
345
346    /**
347     * Removes a registered AID group for the specified category for the
348     * service provided.
349     *
350     * <p>Note that this will only remove AID groups that were dynamically
351     * registered using the {@link #registerAidGroupForService(ComponentName, AidGroup)}
352     * method. It will *not* remove AID groups that were statically registered in
353     * the manifest. If a dynamically registered AID group is removed using
354     * this method, and a statically registered AID group for the same category
355     * exists in the manifest, that AID group will become active again.
356     *
357     * @param service The component name of the service
358     * @param category The category of the AID group to be removed, e.g. {@link #CATEGORY_PAYMENT}
359     * @return whether the group was successfully removed.
360     */
361    public boolean removeAidGroupForService(ComponentName service, String category) {
362        try {
363            return sService.removeAidGroupForService(UserHandle.myUserId(), service, category);
364        } catch (RemoteException e) {
365            // Try one more time
366            recoverService();
367            if (sService == null) {
368                Log.e(TAG, "Failed to recover CardEmulationService.");
369                return false;
370            }
371            try {
372                return sService.removeAidGroupForService(UserHandle.myUserId(), service, category);
373            } catch (RemoteException ee) {
374                Log.e(TAG, "Failed to reach CardEmulationService.");
375                return false;
376            }
377        }
378    }
379
380    /**
381     * @hide
382     */
383    public boolean setDefaultServiceForCategory(ComponentName service, String category) {
384        try {
385            return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service, category);
386        } catch (RemoteException e) {
387            // Try one more time
388            recoverService();
389            if (sService == null) {
390                Log.e(TAG, "Failed to recover CardEmulationService.");
391                return false;
392            }
393            try {
394                return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service,
395                        category);
396            } catch (RemoteException ee) {
397                Log.e(TAG, "Failed to reach CardEmulationService.");
398                return false;
399            }
400        }
401    }
402
403    /**
404     * @hide
405     */
406    public boolean setDefaultForNextTap(ComponentName service) {
407        try {
408            return sService.setDefaultForNextTap(UserHandle.myUserId(), service);
409        } catch (RemoteException e) {
410            // Try one more time
411            recoverService();
412            if (sService == null) {
413                Log.e(TAG, "Failed to recover CardEmulationService.");
414                return false;
415            }
416            try {
417                return sService.setDefaultForNextTap(UserHandle.myUserId(), service);
418            } catch (RemoteException ee) {
419                Log.e(TAG, "Failed to reach CardEmulationService.");
420                return false;
421            }
422        }
423    }
424
425    /**
426     * @hide
427     */
428    public List<ApduServiceInfo> getServices(String category) {
429        try {
430            return sService.getServices(UserHandle.myUserId(), category);
431        } catch (RemoteException e) {
432            // Try one more time
433            recoverService();
434            if (sService == null) {
435                Log.e(TAG, "Failed to recover CardEmulationService.");
436                return null;
437            }
438            try {
439                return sService.getServices(UserHandle.myUserId(), category);
440            } catch (RemoteException ee) {
441                Log.e(TAG, "Failed to reach CardEmulationService.");
442                return null;
443            }
444        }
445    }
446
447    void recoverService() {
448        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
449        sService = adapter.getCardEmulationService();
450    }
451}
452