blob: 41f039cfc7cad3166099a4b3e8b889cac3ec4e23 [file] [log] [blame]
Martijn Coenen52246082013-08-30 11:14:46 -07001/*
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
Martijn Coenen35bf6282013-10-14 20:39:59 +020036/**
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 */
Martijn Coenen52246082013-08-30 11:14:46 -070048public 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 /**
Martijn Coenen35bf6282013-10-14 20:39:59 +020065 * The category extra for {@link #ACTION_CHANGE_DEFAULT}.
Martijn Coenen52246082013-08-30 11:14:46 -070066 *
67 * @see #ACTION_CHANGE_DEFAULT
68 */
69 public static final String EXTRA_CATEGORY = "category";
70
71 /**
Martijn Coenen35bf6282013-10-14 20:39:59 +020072 * The service {@link ComponentName} object passed in as an
73 * extra for {@link #ACTION_CHANGE_DEFAULT}.
Martijn Coenen52246082013-08-30 11:14:46 -070074 *
75 * @see #ACTION_CHANGE_DEFAULT
76 */
77 public static final String EXTRA_SERVICE_COMPONENT = "component";
78
79 /**
Martijn Coenen35bf6282013-10-14 20:39:59 +020080 * Category used for NFC payment services.
Martijn Coenen52246082013-08-30 11:14:46 -070081 */
82 public static final String CATEGORY_PAYMENT = "payment";
83
84 /**
Martijn Coenen35bf6282013-10-14 20:39:59 +020085 * Category that can be used for all other card emulation
86 * services.
Martijn Coenen52246082013-08-30 11:14:46 -070087 */
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
Martijn Coenen35bf6282013-10-14 20:39:59 +020094 * 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)
Martijn Coenen52246082013-08-30 11:14:46 -070099 * that the default service has registered in this category,
100 * that service will automatically be bound to to handle
101 * the transaction.
Martijn Coenen52246082013-08-30 11:14:46 -0700102 */
103 public static final int SELECTION_MODE_PREFER_DEFAULT = 0;
104
105 /**
106 * Return value for {@link #getSelectionModeForCategory(String)}.
107 *
Martijn Coenen35bf6282013-10-14 20:39:59 +0200108 * <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
Martijn Coenen52246082013-08-30 11:14:46 -0700111 * 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 *
Martijn Coenen35bf6282013-10-14 20:39:59 +0200118 * <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.
Martijn Coenen52246082013-08-30 11:14:46 -0700123 */
124 public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
125
126 static boolean sIsInitialized = false;
Martijn Coenen35bf6282013-10-14 20:39:59 +0200127 static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>();
Martijn Coenen52246082013-08-30 11:14:46 -0700128 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
Martijn Coenen35bf6282013-10-14 20:39:59 +0200137 /**
138 * Helper to get an instance of this class.
139 *
140 * @param adapter A reference to an NfcAdapter object.
141 * @return
142 */
Martijn Coenen52246082013-08-30 11:14:46 -0700143 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();
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700171 if (service == null) {
172 Log.e(TAG, "This device does not implement the INfcCardEmulation interface.");
173 throw new UnsupportedOperationException();
174 }
Martijn Coenen52246082013-08-30 11:14:46 -0700175 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)}
Martijn Coenen35bf6282013-10-14 20:39:59 +0200186 * 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.
Martijn Coenen52246082013-08-30 11:14:46 -0700193 *
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.
Martijn Coenen35bf6282013-10-14 20:39:59 +0200197 *
198 * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
Martijn Coenen52246082013-08-30 11:14:46 -0700199 */
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
Martijn Coenen35bf6282013-10-14 20:39:59 +0200227 * @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.
Martijn Coenen52246082013-08-30 11:14:46 -0700230 */
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 /**
Martijn Coenen35bf6282013-10-14 20:39:59 +0200251 * Returns the service selection mode for the passed in category.
Martijn Coenen52246082013-08-30 11:14:46 -0700252 * Valid return values are:
253 * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default
Martijn Coenen35bf6282013-10-14 20:39:59 +0200254 * service for this category, which will be preferred.
Martijn Coenen52246082013-08-30 11:14:46 -0700255 * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked
Martijn Coenen35bf6282013-10-14 20:39:59 +0200256 * every time what service he would like to use in this category.
Martijn Coenen52246082013-08-30 11:14:46 -0700257 * <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}
Martijn Coenen35bf6282013-10-14 20:39:59 +0200260 * @return the selection mode for the passed in category
Martijn Coenen52246082013-08-30 11:14:46 -0700261 */
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 /**
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700278 * 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 /**
Martijn Coenen52246082013-08-30 11:14:46 -0700381 * @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 }
Martijn Coenen35bf6282013-10-14 20:39:59 +0200424
Martijn Coenen52246082013-08-30 11:14:46 -0700425 /**
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}