blob: f23dc2da291ae6531ec403c530c31130e5bef098 [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
Ruchi Kandoi4480f252019-03-04 13:20:24 -080019import android.annotation.NonNull;
Martijn Coenen52246082013-08-30 11:14:46 -070020import android.annotation.SdkConstant;
21import android.annotation.SdkConstant.SdkConstantType;
Martijn Coenen2f6f3a012014-04-25 17:00:21 -070022import android.app.Activity;
Martijn Coenen52246082013-08-30 11:14:46 -070023import android.app.ActivityThread;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.pm.IPackageManager;
27import android.content.pm.PackageManager;
28import android.nfc.INfcCardEmulation;
29import android.nfc.NfcAdapter;
30import android.os.RemoteException;
Martijn Coenen52246082013-08-30 11:14:46 -070031import android.provider.Settings;
Martijn Coenen2f6f3a012014-04-25 17:00:21 -070032import android.provider.Settings.SettingNotFoundException;
Martijn Coenen52246082013-08-30 11:14:46 -070033import android.util.Log;
34
35import java.util.HashMap;
36import java.util.List;
37
Martijn Coenen35bf6282013-10-14 20:39:59 +020038/**
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,
Robert Schaubf64c80a2015-06-29 17:21:20 -070043 * please read the <a href="{@docRoot}guide/topics/connectivity/nfc/hce.html">
Martijn Coenen35bf6282013-10-14 20:39:59 +020044 * 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 */
Martijn Coenen52246082013-08-30 11:14:46 -070050public 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 /**
Martijn Coenen35bf6282013-10-14 20:39:59 +020067 * The category extra for {@link #ACTION_CHANGE_DEFAULT}.
Martijn Coenen52246082013-08-30 11:14:46 -070068 *
69 * @see #ACTION_CHANGE_DEFAULT
70 */
71 public static final String EXTRA_CATEGORY = "category";
72
73 /**
Martijn Coenen35bf6282013-10-14 20:39:59 +020074 * The service {@link ComponentName} object passed in as an
75 * extra for {@link #ACTION_CHANGE_DEFAULT}.
Martijn Coenen52246082013-08-30 11:14:46 -070076 *
77 * @see #ACTION_CHANGE_DEFAULT
78 */
79 public static final String EXTRA_SERVICE_COMPONENT = "component";
80
81 /**
Martijn Coenen35bf6282013-10-14 20:39:59 +020082 * Category used for NFC payment services.
Martijn Coenen52246082013-08-30 11:14:46 -070083 */
84 public static final String CATEGORY_PAYMENT = "payment";
85
86 /**
Martijn Coenen35bf6282013-10-14 20:39:59 +020087 * Category that can be used for all other card emulation
88 * services.
Martijn Coenen52246082013-08-30 11:14:46 -070089 */
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
Martijn Coenen35bf6282013-10-14 20:39:59 +020096 * 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)
Martijn Coenen52246082013-08-30 11:14:46 -0700101 * that the default service has registered in this category,
102 * that service will automatically be bound to to handle
103 * the transaction.
Martijn Coenen52246082013-08-30 11:14:46 -0700104 */
105 public static final int SELECTION_MODE_PREFER_DEFAULT = 0;
106
107 /**
108 * Return value for {@link #getSelectionModeForCategory(String)}.
109 *
Martijn Coenen35bf6282013-10-14 20:39:59 +0200110 * <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
Martijn Coenen52246082013-08-30 11:14:46 -0700113 * 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 *
Martijn Coenen35bf6282013-10-14 20:39:59 +0200120 * <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.
Martijn Coenen52246082013-08-30 11:14:46 -0700125 */
126 public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
127
128 static boolean sIsInitialized = false;
Martijn Coenen35bf6282013-10-14 20:39:59 +0200129 static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>();
Martijn Coenen52246082013-08-30 11:14:46 -0700130 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
Martijn Coenen35bf6282013-10-14 20:39:59 +0200139 /**
140 * Helper to get an instance of this class.
141 *
142 * @param adapter A reference to an NfcAdapter object.
143 * @return
144 */
Martijn Coenen52246082013-08-30 11:14:46 -0700145 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 {
Jeff Sharkey115d2c12016-02-15 17:25:57 -0700159 if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION, 0)) {
Martijn Coenen52246082013-08-30 11:14:46 -0700160 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();
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700173 if (service == null) {
174 Log.e(TAG, "This device does not implement the INfcCardEmulation interface.");
175 throw new UnsupportedOperationException();
176 }
Martijn Coenen52246082013-08-30 11:14:46 -0700177 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)}
Martijn Coenen35bf6282013-10-14 20:39:59 +0200188 * 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.
Martijn Coenen52246082013-08-30 11:14:46 -0700195 *
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.
Martijn Coenen35bf6282013-10-14 20:39:59 +0200199 *
200 * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
Martijn Coenen52246082013-08-30 11:14:46 -0700201 */
202 public boolean isDefaultServiceForCategory(ComponentName service, String category) {
203 try {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700204 return sService.isDefaultServiceForCategory(mContext.getUserId(), service, category);
Martijn Coenen52246082013-08-30 11:14:46 -0700205 } 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 {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700213 return sService.isDefaultServiceForCategory(mContext.getUserId(), service,
Martijn Coenen52246082013-08-30 11:14:46 -0700214 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
Martijn Coenen35bf6282013-10-14 20:39:59 +0200229 * @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.
Martijn Coenen52246082013-08-30 11:14:46 -0700232 */
233 public boolean isDefaultServiceForAid(ComponentName service, String aid) {
234 try {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700235 return sService.isDefaultServiceForAid(mContext.getUserId(), service, aid);
Martijn Coenen52246082013-08-30 11:14:46 -0700236 } 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 {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700244 return sService.isDefaultServiceForAid(mContext.getUserId(), service, aid);
Martijn Coenen52246082013-08-30 11:14:46 -0700245 } catch (RemoteException ee) {
246 Log.e(TAG, "Failed to reach CardEmulationService.");
247 return false;
248 }
249 }
250 }
251
252 /**
Martijn Coenen2f6f3a012014-04-25 17:00:21 -0700253 * 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 /**
Martijn Coenen35bf6282013-10-14 20:39:59 +0200280 * Returns the service selection mode for the passed in category.
Martijn Coenen52246082013-08-30 11:14:46 -0700281 * Valid return values are:
282 * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default
Martijn Coenen35bf6282013-10-14 20:39:59 +0200283 * service for this category, which will be preferred.
Martijn Coenen52246082013-08-30 11:14:46 -0700284 * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked
Martijn Coenen35bf6282013-10-14 20:39:59 +0200285 * every time what service he would like to use in this category.
Martijn Coenen52246082013-08-30 11:14:46 -0700286 * <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}
Martijn Coenen35bf6282013-10-14 20:39:59 +0200289 * @return the selection mode for the passed in category
Martijn Coenen52246082013-08-30 11:14:46 -0700290 */
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 {
Martijn Coenen52246082013-08-30 11:14:46 -0700301 return SELECTION_MODE_ASK_IF_CONFLICT;
302 }
303 }
304
305 /**
Martijn Coenendf48db32014-05-20 13:52:14 -0700306 * Registers a list of AIDs for a specific category for the
307 * specified service.
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700308 *
Martijn Coenendf48db32014-05-20 13:52:14 -0700309 * <p>If a list of AIDs for that category was previously
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700310 * registered for this service (either statically
311 * through the manifest, or dynamically by using this API),
Martijn Coenendf48db32014-05-20 13:52:14 -0700312 * that list of AIDs will be replaced with this one.
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700313 *
314 * <p>Note that you can only register AIDs for a service that
Martijn Coenen2f6f3a012014-04-25 17:00:21 -0700315 * is running under the same UID as the caller of this API. Typically
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700316 * 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
Martijn Coenendf48db32014-05-20 13:52:14 -0700321 * @param category The category of AIDs to be registered
322 * @param aids A list containing the AIDs to be registered
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700323 * @return whether the registration was successful.
324 */
Martijn Coenendf48db32014-05-20 13:52:14 -0700325 public boolean registerAidsForService(ComponentName service, String category,
326 List<String> aids) {
327 AidGroup aidGroup = new AidGroup(aids, category);
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700328 try {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700329 return sService.registerAidGroupForService(mContext.getUserId(), service, aidGroup);
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700330 } 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 {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700338 return sService.registerAidGroupForService(mContext.getUserId(), service,
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700339 aidGroup);
340 } catch (RemoteException ee) {
341 Log.e(TAG, "Failed to reach CardEmulationService.");
342 return false;
343 }
344 }
345 }
346
347 /**
Ruchi Kandoi44bb5792018-10-19 13:08:34 -0700348 * Unsets the off-host Secure Element for the given service.
349 *
350 * <p>Note that this will only remove Secure Element that was dynamically
351 * set using the {@link #setOffHostForService(ComponentName, String)}
352 * and resets it to a value that was statically assigned using manifest.
353 *
354 * <p>Note that you can only unset off-host SE for a service that
355 * is running under the same UID as the caller of this API. Typically
356 * this means you need to call this from the same
357 * package as the service itself, though UIDs can also
358 * be shared between packages using shared UIDs.
359 *
360 * @param service The component name of the service
361 * @return whether the registration was successful.
362 */
Ruchi Kandoi4480f252019-03-04 13:20:24 -0800363 public boolean unsetOffHostForService(@NonNull ComponentName service) {
Ruchi Kandoi44bb5792018-10-19 13:08:34 -0700364 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
365 if (adapter == null) {
366 return false;
367 }
368
369 try {
370 return sService.unsetOffHostForService(mContext.getUserId(), service);
371 } catch (RemoteException e) {
372 // Try one more time
373 recoverService();
374 if (sService == null) {
375 Log.e(TAG, "Failed to recover CardEmulationService.");
376 return false;
377 }
378 try {
379 return sService.unsetOffHostForService(mContext.getUserId(), service);
380 } catch (RemoteException ee) {
381 Log.e(TAG, "Failed to reach CardEmulationService.");
382 return false;
383 }
384 }
385 }
386
387 /**
388 * Sets the off-host Secure Element for the given service.
389 *
390 * <p>If off-host SE was initially set (either statically
391 * through the manifest, or dynamically by using this API),
392 * it will be replaced with this one. All AIDs registered by
393 * this service will be re-routed to this Secure Element if
394 * successful.
395 *
396 * <p>Note that you can only set off-host SE for a service that
397 * is running under the same UID as the caller of this API. Typically
398 * this means you need to call this from the same
399 * package as the service itself, though UIDs can also
400 * be shared between packages using shared UIDs.
401 *
402 * <p>Registeration will be successful only if the Secure Element
403 * exists on the device.
404 *
405 * @param service The component name of the service
406 * @param offHostSecureElement Secure Element to register the AID to
407 * @return whether the registration was successful.
408 */
Ruchi Kandoi4480f252019-03-04 13:20:24 -0800409 public boolean setOffHostForService(@NonNull ComponentName service,
410 @NonNull String offHostSecureElement) {
Ruchi Kandoi44bb5792018-10-19 13:08:34 -0700411 boolean validSecureElement = false;
412
413 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
414 if (adapter == null || offHostSecureElement == null) {
415 return false;
416 }
417
418 List<String> validSE = adapter.getSupportedOffHostSecureElements();
419 if ((offHostSecureElement.startsWith("eSE") && !validSE.contains("eSE"))
420 || (offHostSecureElement.startsWith("SIM") && !validSE.contains("SIM"))) {
421 return false;
422 }
423
424 if (offHostSecureElement.equals("eSE")) {
425 offHostSecureElement = "eSE1";
426 } else if (offHostSecureElement.equals("SIM")) {
427 offHostSecureElement = "SIM1";
428 }
429
430 try {
431 return sService.setOffHostForService(mContext.getUserId(), service,
432 offHostSecureElement);
433 } catch (RemoteException e) {
434 // Try one more time
435 recoverService();
436 if (sService == null) {
437 Log.e(TAG, "Failed to recover CardEmulationService.");
438 return false;
439 }
440 try {
441 return sService.setOffHostForService(mContext.getUserId(), service,
442 offHostSecureElement);
443 } catch (RemoteException ee) {
444 Log.e(TAG, "Failed to reach CardEmulationService.");
445 return false;
446 }
447 }
448 }
449
450 /**
Martijn Coenendf48db32014-05-20 13:52:14 -0700451 * Retrieves the currently registered AIDs for the specified
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700452 * category for a service.
453 *
Martijn Coenendf48db32014-05-20 13:52:14 -0700454 * <p>Note that this will only return AIDs that were dynamically
455 * registered using {@link #registerAidsForService(ComponentName, String, List)}
456 * method. It will *not* return AIDs that were statically registered
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700457 * in the manifest.
458 *
459 * @param service The component name of the service
Martijn Coenendf48db32014-05-20 13:52:14 -0700460 * @param category The category for which the AIDs were registered,
461 * e.g. {@link #CATEGORY_PAYMENT}
462 * @return The list of AIDs registered for this category, or null if it couldn't be found.
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700463 */
Martijn Coenendf48db32014-05-20 13:52:14 -0700464 public List<String> getAidsForService(ComponentName service, String category) {
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700465 try {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700466 AidGroup group = sService.getAidGroupForService(mContext.getUserId(), service,
Martijn Coenendf48db32014-05-20 13:52:14 -0700467 category);
468 return (group != null ? group.getAids() : null);
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700469 } catch (RemoteException e) {
470 recoverService();
471 if (sService == null) {
472 Log.e(TAG, "Failed to recover CardEmulationService.");
473 return null;
474 }
475 try {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700476 AidGroup group = sService.getAidGroupForService(mContext.getUserId(), service,
Martijn Coenendf48db32014-05-20 13:52:14 -0700477 category);
478 return (group != null ? group.getAids() : null);
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700479 } catch (RemoteException ee) {
480 Log.e(TAG, "Failed to recover CardEmulationService.");
481 return null;
482 }
483 }
484 }
485
486 /**
Martijn Coenendf48db32014-05-20 13:52:14 -0700487 * Removes a previously registered list of AIDs for the specified category for the
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700488 * service provided.
489 *
Martijn Coenendf48db32014-05-20 13:52:14 -0700490 * <p>Note that this will only remove AIDs that were dynamically
491 * registered using the {@link #registerAidsForService(ComponentName, String, List)}
492 * method. It will *not* remove AIDs that were statically registered in
493 * the manifest. If dynamically registered AIDs are removed using
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700494 * this method, and a statically registered AID group for the same category
Martijn Coenen2f6f3a012014-04-25 17:00:21 -0700495 * exists in the manifest, the static AID group will become active again.
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700496 *
497 * @param service The component name of the service
Martijn Coenendf48db32014-05-20 13:52:14 -0700498 * @param category The category of the AIDs to be removed, e.g. {@link #CATEGORY_PAYMENT}
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700499 * @return whether the group was successfully removed.
500 */
Martijn Coenendf48db32014-05-20 13:52:14 -0700501 public boolean removeAidsForService(ComponentName service, String category) {
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700502 try {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700503 return sService.removeAidGroupForService(mContext.getUserId(), service, category);
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700504 } catch (RemoteException e) {
505 // Try one more time
506 recoverService();
507 if (sService == null) {
508 Log.e(TAG, "Failed to recover CardEmulationService.");
509 return false;
510 }
511 try {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700512 return sService.removeAidGroupForService(mContext.getUserId(), service, category);
Martijn Coenenaa1492d2014-04-11 12:54:22 -0700513 } catch (RemoteException ee) {
514 Log.e(TAG, "Failed to reach CardEmulationService.");
515 return false;
516 }
517 }
518 }
519
520 /**
Martijn Coenen2f6f3a012014-04-25 17:00:21 -0700521 * Allows a foreground application to specify which card emulation service
522 * should be preferred while a specific Activity is in the foreground.
523 *
524 * <p>The specified Activity must currently be in resumed state. A good
525 * paradigm is to call this method in your {@link Activity#onResume}, and to call
526 * {@link #unsetPreferredService(Activity)} in your {@link Activity#onPause}.
527 *
528 * <p>This method call will fail in two specific scenarios:
529 * <ul>
530 * <li> If the service registers one or more AIDs in the {@link #CATEGORY_PAYMENT}
531 * category, but the user has indicated that foreground apps are not allowed
532 * to override the default payment service.
533 * <li> If the service registers one or more AIDs in the {@link #CATEGORY_OTHER}
534 * category that are also handled by the default payment service, and the
535 * user has indicated that foreground apps are not allowed to override the
536 * default payment service.
537 * </ul>
538 *
539 * <p> Use {@link #categoryAllowsForegroundPreference(String)} to determine
540 * whether foreground apps can override the default payment service.
541 *
542 * <p>Note that this preference is not persisted by the OS, and hence must be
543 * called every time the Activity is resumed.
544 *
545 * @param activity The activity which prefers this service to be invoked
546 * @param service The service to be preferred while this activity is in the foreground
547 * @return whether the registration was successful
548 */
549 public boolean setPreferredService(Activity activity, ComponentName service) {
550 // Verify the activity is in the foreground before calling into NfcService
551 if (activity == null || service == null) {
552 throw new NullPointerException("activity or service or category is null");
553 }
554 if (!activity.isResumed()) {
555 throw new IllegalArgumentException("Activity must be resumed.");
556 }
557 try {
558 return sService.setPreferredService(service);
559 } catch (RemoteException e) {
560 // Try one more time
561 recoverService();
562 if (sService == null) {
563 Log.e(TAG, "Failed to recover CardEmulationService.");
564 return false;
565 }
566 try {
567 return sService.setPreferredService(service);
568 } catch (RemoteException ee) {
569 Log.e(TAG, "Failed to reach CardEmulationService.");
570 return false;
571 }
572 }
573 }
574
575 /**
576 * Unsets the preferred service for the specified Activity.
577 *
578 * <p>Note that the specified Activity must still be in resumed
579 * state at the time of this call. A good place to call this method
580 * is in your {@link Activity#onPause} implementation.
581 *
582 * @param activity The activity which the service was registered for
583 * @return true when successful
584 */
585 public boolean unsetPreferredService(Activity activity) {
586 if (activity == null) {
587 throw new NullPointerException("activity is null");
588 }
589 if (!activity.isResumed()) {
590 throw new IllegalArgumentException("Activity must be resumed.");
591 }
592 try {
593 return sService.unsetPreferredService();
594 } catch (RemoteException e) {
595 // Try one more time
596 recoverService();
597 if (sService == null) {
598 Log.e(TAG, "Failed to recover CardEmulationService.");
599 return false;
600 }
601 try {
602 return sService.unsetPreferredService();
603 } catch (RemoteException ee) {
604 Log.e(TAG, "Failed to reach CardEmulationService.");
605 return false;
606 }
607 }
608 }
609
610 /**
Martijn Coenend92c1682014-07-02 16:30:06 -0700611 * Some devices may allow an application to register all
612 * AIDs that starts with a certain prefix, e.g.
613 * "A000000004*" to register all MasterCard AIDs.
614 *
615 * Use this method to determine whether this device
616 * supports registering AID prefixes.
617 *
618 * @return whether AID prefix registering is supported on this device.
619 */
620 public boolean supportsAidPrefixRegistration() {
Martijn Coenen826a73b2014-08-05 07:51:58 -0700621 try {
622 return sService.supportsAidPrefixRegistration();
623 } catch (RemoteException e) {
624 recoverService();
625 if (sService == null) {
626 Log.e(TAG, "Failed to recover CardEmulationService.");
627 return false;
628 }
629 try {
630 return sService.supportsAidPrefixRegistration();
631 } catch (RemoteException ee) {
632 Log.e(TAG, "Failed to reach CardEmulationService.");
633 return false;
634 }
635 }
Martijn Coenend92c1682014-07-02 16:30:06 -0700636 }
637
638 /**
Martijn Coenen52246082013-08-30 11:14:46 -0700639 * @hide
640 */
641 public boolean setDefaultServiceForCategory(ComponentName service, String category) {
642 try {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700643 return sService.setDefaultServiceForCategory(mContext.getUserId(), service, category);
Martijn Coenen52246082013-08-30 11:14:46 -0700644 } catch (RemoteException e) {
645 // Try one more time
646 recoverService();
647 if (sService == null) {
648 Log.e(TAG, "Failed to recover CardEmulationService.");
649 return false;
650 }
651 try {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700652 return sService.setDefaultServiceForCategory(mContext.getUserId(), service,
Martijn Coenen52246082013-08-30 11:14:46 -0700653 category);
654 } catch (RemoteException ee) {
655 Log.e(TAG, "Failed to reach CardEmulationService.");
656 return false;
657 }
658 }
659 }
660
661 /**
662 * @hide
663 */
664 public boolean setDefaultForNextTap(ComponentName service) {
665 try {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700666 return sService.setDefaultForNextTap(mContext.getUserId(), service);
Martijn Coenen52246082013-08-30 11:14:46 -0700667 } catch (RemoteException e) {
668 // Try one more time
669 recoverService();
670 if (sService == null) {
671 Log.e(TAG, "Failed to recover CardEmulationService.");
672 return false;
673 }
674 try {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700675 return sService.setDefaultForNextTap(mContext.getUserId(), service);
Martijn Coenen52246082013-08-30 11:14:46 -0700676 } catch (RemoteException ee) {
677 Log.e(TAG, "Failed to reach CardEmulationService.");
678 return false;
679 }
680 }
681 }
Martijn Coenen35bf6282013-10-14 20:39:59 +0200682
Martijn Coenen52246082013-08-30 11:14:46 -0700683 /**
684 * @hide
685 */
686 public List<ApduServiceInfo> getServices(String category) {
687 try {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700688 return sService.getServices(mContext.getUserId(), category);
Martijn Coenen52246082013-08-30 11:14:46 -0700689 } catch (RemoteException e) {
690 // Try one more time
691 recoverService();
692 if (sService == null) {
693 Log.e(TAG, "Failed to recover CardEmulationService.");
694 return null;
695 }
696 try {
Jeff Sharkeyad357d12018-02-02 13:25:31 -0700697 return sService.getServices(mContext.getUserId(), category);
Martijn Coenen52246082013-08-30 11:14:46 -0700698 } catch (RemoteException ee) {
699 Log.e(TAG, "Failed to reach CardEmulationService.");
700 return null;
701 }
702 }
703 }
704
Martijn Coenenb5144112014-07-02 12:44:33 -0700705 /**
706 * A valid AID according to ISO/IEC 7816-4:
707 * <ul>
708 * <li>Has >= 5 bytes and <=16 bytes (>=10 hex chars and <= 32 hex chars)
709 * <li>Consist of only hex characters
710 * <li>Additionally, we allow an asterisk at the end, to indicate
711 * a prefix
Love Khannae910e8b2017-05-12 13:53:42 +0530712 * <li>Additinally we allow an (#) at symbol at the end, to indicate
713 * a subset
Martijn Coenenb5144112014-07-02 12:44:33 -0700714 * </ul>
715 *
716 * @hide
717 */
718 public static boolean isValidAid(String aid) {
719 if (aid == null)
720 return false;
721
Love Khannae910e8b2017-05-12 13:53:42 +0530722 // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
723 if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
Martijn Coenenb5144112014-07-02 12:44:33 -0700724 Log.e(TAG, "AID " + aid + " is not a valid AID.");
725 return false;
726 }
727
Love Khannae910e8b2017-05-12 13:53:42 +0530728 // If not a prefix/subset AID, the total length must be even (even # of AID chars)
729 if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
Martijn Coenenb5144112014-07-02 12:44:33 -0700730 Log.e(TAG, "AID " + aid + " is not a valid AID.");
731 return false;
732 }
733
734 // Verify hex characters
Love Khannae910e8b2017-05-12 13:53:42 +0530735 if (!aid.matches("[0-9A-Fa-f]{10,32}\\*?\\#?")) {
Martijn Coenenb5144112014-07-02 12:44:33 -0700736 Log.e(TAG, "AID " + aid + " is not a valid AID.");
737 return false;
738 }
739
740 return true;
741 }
742
Martijn Coenen52246082013-08-30 11:14:46 -0700743 void recoverService() {
744 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
745 sService = adapter.getCardEmulationService();
746 }
Martijn Coenenb5144112014-07-02 12:44:33 -0700747
Martijn Coenen52246082013-08-30 11:14:46 -0700748}