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