1 /* 2 * Copyright (C) 2015 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 17 package android.nfc.cardemulation; 18 19 import android.app.Activity; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.pm.PackageManager; 23 import android.nfc.INfcFCardEmulation; 24 import android.nfc.NfcAdapter; 25 import android.os.RemoteException; 26 import android.util.Log; 27 28 import java.util.HashMap; 29 import java.util.List; 30 31 /** 32 * This class can be used to query the state of 33 * NFC-F card emulation services. 34 * 35 * For a general introduction into NFC card emulation, 36 * please read the <a href="{@docRoot}guide/topics/connectivity/nfc/hce.html"> 37 * NFC card emulation developer guide</a>.</p> 38 * 39 * <p class="note">Use of this class requires the 40 * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION_NFCF} 41 * to be present on the device. 42 */ 43 public final class NfcFCardEmulation { 44 static final String TAG = "NfcFCardEmulation"; 45 46 static boolean sIsInitialized = false; 47 static HashMap<Context, NfcFCardEmulation> sCardEmus = new HashMap<Context, NfcFCardEmulation>(); 48 static INfcFCardEmulation sService; 49 50 final Context mContext; 51 NfcFCardEmulation(Context context, INfcFCardEmulation service)52 private NfcFCardEmulation(Context context, INfcFCardEmulation service) { 53 mContext = context.getApplicationContext(); 54 sService = service; 55 } 56 57 /** 58 * Helper to get an instance of this class. 59 * 60 * @param adapter A reference to an NfcAdapter object. 61 * @return 62 */ getInstance(NfcAdapter adapter)63 public static synchronized NfcFCardEmulation getInstance(NfcAdapter adapter) { 64 if (adapter == null) throw new NullPointerException("NfcAdapter is null"); 65 Context context = adapter.getContext(); 66 if (context == null) { 67 Log.e(TAG, "NfcAdapter context is null."); 68 throw new UnsupportedOperationException(); 69 } 70 if (!sIsInitialized) { 71 PackageManager pm = context.getPackageManager(); 72 if (pm == null) { 73 Log.e(TAG, "Cannot get PackageManager"); 74 throw new UnsupportedOperationException(); 75 } 76 if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)) { 77 Log.e(TAG, "This device does not support NFC-F card emulation"); 78 throw new UnsupportedOperationException(); 79 } 80 sIsInitialized = true; 81 } 82 NfcFCardEmulation manager = sCardEmus.get(context); 83 if (manager == null) { 84 // Get card emu service 85 INfcFCardEmulation service = adapter.getNfcFCardEmulationService(); 86 if (service == null) { 87 Log.e(TAG, "This device does not implement the INfcFCardEmulation interface."); 88 throw new UnsupportedOperationException(); 89 } 90 manager = new NfcFCardEmulation(context, service); 91 sCardEmus.put(context, manager); 92 } 93 return manager; 94 } 95 96 /** 97 * Retrieves the current System Code for the specified service. 98 * 99 * <p>Before calling {@link #registerSystemCodeForService(ComponentName, String)}, 100 * the System Code contained in the Manifest file is returned. After calling 101 * {@link #registerSystemCodeForService(ComponentName, String)}, the System Code 102 * registered there is returned. After calling 103 * {@link #unregisterSystemCodeForService(ComponentName)}, "null" is returned. 104 * 105 * @param service The component name of the service 106 * @return the current System Code 107 */ getSystemCodeForService(ComponentName service)108 public String getSystemCodeForService(ComponentName service) throws RuntimeException { 109 if (service == null) { 110 throw new NullPointerException("service is null"); 111 } 112 try { 113 return sService.getSystemCodeForService(mContext.getUser().getIdentifier(), service); 114 } catch (RemoteException e) { 115 // Try one more time 116 recoverService(); 117 if (sService == null) { 118 Log.e(TAG, "Failed to recover CardEmulationService."); 119 return null; 120 } 121 try { 122 return sService.getSystemCodeForService(mContext.getUser().getIdentifier(), 123 service); 124 } catch (RemoteException ee) { 125 Log.e(TAG, "Failed to reach CardEmulationService."); 126 ee.rethrowAsRuntimeException(); 127 return null; 128 } 129 } 130 } 131 132 /** 133 * Registers a System Code for the specified service. 134 * 135 * <p>The System Code must be in range from "4000" to "4FFF" (excluding "4*FF"). 136 * 137 * <p>If a System Code was previously registered for this service 138 * (either statically through the manifest, or dynamically by using this API), 139 * it will be replaced with this one. 140 * 141 * <p>Even if the same System Code is already registered for another service, 142 * this method succeeds in registering the System Code. 143 * 144 * <p>Note that you can only register a System Code for a service that 145 * is running under the same UID as the caller of this API. Typically 146 * this means you need to call this from the same 147 * package as the service itself, though UIDs can also 148 * be shared between packages using shared UIDs. 149 * 150 * @param service The component name of the service 151 * @param systemCode The System Code to be registered 152 * @return whether the registration was successful. 153 */ registerSystemCodeForService(ComponentName service, String systemCode)154 public boolean registerSystemCodeForService(ComponentName service, String systemCode) 155 throws RuntimeException { 156 if (service == null || systemCode == null) { 157 throw new NullPointerException("service or systemCode is null"); 158 } 159 try { 160 return sService.registerSystemCodeForService(mContext.getUser().getIdentifier(), 161 service, systemCode); 162 } catch (RemoteException e) { 163 // Try one more time 164 recoverService(); 165 if (sService == null) { 166 Log.e(TAG, "Failed to recover CardEmulationService."); 167 return false; 168 } 169 try { 170 return sService.registerSystemCodeForService(mContext.getUser().getIdentifier(), 171 service, systemCode); 172 } catch (RemoteException ee) { 173 Log.e(TAG, "Failed to reach CardEmulationService."); 174 ee.rethrowAsRuntimeException(); 175 return false; 176 } 177 } 178 } 179 180 /** 181 * Removes a registered System Code for the specified service. 182 * 183 * @param service The component name of the service 184 * @return whether the System Code was successfully removed. 185 */ unregisterSystemCodeForService(ComponentName service)186 public boolean unregisterSystemCodeForService(ComponentName service) throws RuntimeException { 187 if (service == null) { 188 throw new NullPointerException("service is null"); 189 } 190 try { 191 return sService.removeSystemCodeForService(mContext.getUser().getIdentifier(), service); 192 } catch (RemoteException e) { 193 // Try one more time 194 recoverService(); 195 if (sService == null) { 196 Log.e(TAG, "Failed to recover CardEmulationService."); 197 return false; 198 } 199 try { 200 return sService.removeSystemCodeForService(mContext.getUser().getIdentifier(), 201 service); 202 } catch (RemoteException ee) { 203 Log.e(TAG, "Failed to reach CardEmulationService."); 204 ee.rethrowAsRuntimeException(); 205 return false; 206 } 207 } 208 } 209 210 /** 211 * Retrieves the current NFCID2 for the specified service. 212 * 213 * <p>Before calling {@link #setNfcid2ForService(ComponentName, String)}, 214 * the NFCID2 contained in the Manifest file is returned. If "random" is specified 215 * in the Manifest file, a random number assigned by the system at installation time 216 * is returned. After setting an NFCID2 217 * with {@link #setNfcid2ForService(ComponentName, String)}, this NFCID2 is returned. 218 * 219 * @param service The component name of the service 220 * @return the current NFCID2 221 */ getNfcid2ForService(ComponentName service)222 public String getNfcid2ForService(ComponentName service) throws RuntimeException { 223 if (service == null) { 224 throw new NullPointerException("service is null"); 225 } 226 try { 227 return sService.getNfcid2ForService(mContext.getUser().getIdentifier(), service); 228 } catch (RemoteException e) { 229 // Try one more time 230 recoverService(); 231 if (sService == null) { 232 Log.e(TAG, "Failed to recover CardEmulationService."); 233 return null; 234 } 235 try { 236 return sService.getNfcid2ForService(mContext.getUser().getIdentifier(), service); 237 } catch (RemoteException ee) { 238 Log.e(TAG, "Failed to reach CardEmulationService."); 239 ee.rethrowAsRuntimeException(); 240 return null; 241 } 242 } 243 } 244 245 /** 246 * Set a NFCID2 for the specified service. 247 * 248 * <p>The NFCID2 must be in range from "02FE000000000000" to "02FEFFFFFFFFFFFF". 249 * 250 * <p>If a NFCID2 was previously set for this service 251 * (either statically through the manifest, or dynamically by using this API), 252 * it will be replaced. 253 * 254 * <p>Note that you can only set the NFCID2 for a service that 255 * is running under the same UID as the caller of this API. Typically 256 * this means you need to call this from the same 257 * package as the service itself, though UIDs can also 258 * be shared between packages using shared UIDs. 259 * 260 * @param service The component name of the service 261 * @param nfcid2 The NFCID2 to be registered 262 * @return whether the setting was successful. 263 */ setNfcid2ForService(ComponentName service, String nfcid2)264 public boolean setNfcid2ForService(ComponentName service, String nfcid2) 265 throws RuntimeException { 266 if (service == null || nfcid2 == null) { 267 throw new NullPointerException("service or nfcid2 is null"); 268 } 269 try { 270 return sService.setNfcid2ForService(mContext.getUser().getIdentifier(), 271 service, nfcid2); 272 } catch (RemoteException e) { 273 // Try one more time 274 recoverService(); 275 if (sService == null) { 276 Log.e(TAG, "Failed to recover CardEmulationService."); 277 return false; 278 } 279 try { 280 return sService.setNfcid2ForService(mContext.getUser().getIdentifier(), 281 service, nfcid2); 282 } catch (RemoteException ee) { 283 Log.e(TAG, "Failed to reach CardEmulationService."); 284 ee.rethrowAsRuntimeException(); 285 return false; 286 } 287 } 288 } 289 290 /** 291 * Allows a foreground application to specify which card emulation service 292 * should be enabled while a specific Activity is in the foreground. 293 * 294 * <p>The specified HCE-F service is only enabled when the corresponding application is 295 * in the foreground and this method has been called. When the application is moved to 296 * the background, {@link #disableService(Activity)} is called, or 297 * NFCID2 or System Code is replaced, the HCE-F service is disabled. 298 * 299 * <p>The specified Activity must currently be in resumed state. A good 300 * paradigm is to call this method in your {@link Activity#onResume}, and to call 301 * {@link #disableService(Activity)} in your {@link Activity#onPause}. 302 * 303 * <p>Note that this preference is not persisted by the OS, and hence must be 304 * called every time the Activity is resumed. 305 * 306 * @param activity The activity which prefers this service to be invoked 307 * @param service The service to be preferred while this activity is in the foreground 308 * @return whether the registration was successful 309 */ enableService(Activity activity, ComponentName service)310 public boolean enableService(Activity activity, ComponentName service) throws RuntimeException { 311 if (activity == null || service == null) { 312 throw new NullPointerException("activity or service is null"); 313 } 314 // Verify the activity is in the foreground before calling into NfcService 315 if (!activity.isResumed()) { 316 throw new IllegalArgumentException("Activity must be resumed."); 317 } 318 try { 319 return sService.enableNfcFForegroundService(service); 320 } catch (RemoteException e) { 321 // Try one more time 322 recoverService(); 323 if (sService == null) { 324 Log.e(TAG, "Failed to recover CardEmulationService."); 325 return false; 326 } 327 try { 328 return sService.enableNfcFForegroundService(service); 329 } catch (RemoteException ee) { 330 Log.e(TAG, "Failed to reach CardEmulationService."); 331 ee.rethrowAsRuntimeException(); 332 return false; 333 } 334 } 335 } 336 337 /** 338 * Disables the service for the specified Activity. 339 * 340 * <p>Note that the specified Activity must still be in resumed 341 * state at the time of this call. A good place to call this method 342 * is in your {@link Activity#onPause} implementation. 343 * 344 * @param activity The activity which the service was registered for 345 * @return true when successful 346 */ disableService(Activity activity)347 public boolean disableService(Activity activity) throws RuntimeException { 348 if (activity == null) { 349 throw new NullPointerException("activity is null"); 350 } 351 if (!activity.isResumed()) { 352 throw new IllegalArgumentException("Activity must be resumed."); 353 } 354 try { 355 return sService.disableNfcFForegroundService(); 356 } catch (RemoteException e) { 357 // Try one more time 358 recoverService(); 359 if (sService == null) { 360 Log.e(TAG, "Failed to recover CardEmulationService."); 361 return false; 362 } 363 try { 364 return sService.disableNfcFForegroundService(); 365 } catch (RemoteException ee) { 366 Log.e(TAG, "Failed to reach CardEmulationService."); 367 ee.rethrowAsRuntimeException(); 368 return false; 369 } 370 } 371 } 372 373 /** 374 * @hide 375 */ getNfcFServices()376 public List<NfcFServiceInfo> getNfcFServices() { 377 try { 378 return sService.getNfcFServices(mContext.getUser().getIdentifier()); 379 } catch (RemoteException e) { 380 // Try one more time 381 recoverService(); 382 if (sService == null) { 383 Log.e(TAG, "Failed to recover CardEmulationService."); 384 return null; 385 } 386 try { 387 return sService.getNfcFServices(mContext.getUser().getIdentifier()); 388 } catch (RemoteException ee) { 389 Log.e(TAG, "Failed to reach CardEmulationService."); 390 return null; 391 } 392 } 393 } 394 395 /** 396 * @hide 397 */ getMaxNumOfRegisterableSystemCodes()398 public int getMaxNumOfRegisterableSystemCodes() { 399 try { 400 return sService.getMaxNumOfRegisterableSystemCodes(); 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 -1; 407 } 408 try { 409 return sService.getMaxNumOfRegisterableSystemCodes(); 410 } catch (RemoteException ee) { 411 Log.e(TAG, "Failed to reach CardEmulationService."); 412 return -1; 413 } 414 } 415 } 416 417 /** 418 * @hide 419 */ isValidSystemCode(String systemCode)420 public static boolean isValidSystemCode(String systemCode) { 421 if (systemCode == null) { 422 return false; 423 } 424 if (systemCode.length() != 4) { 425 Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); 426 return false; 427 } 428 // check if the value is between "4000" and "4FFF" (excluding "4*FF") 429 if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) { 430 Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); 431 return false; 432 } 433 try { 434 Integer.parseInt(systemCode, 16); 435 } catch (NumberFormatException e) { 436 Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); 437 return false; 438 } 439 return true; 440 } 441 442 /** 443 * @hide 444 */ isValidNfcid2(String nfcid2)445 public static boolean isValidNfcid2(String nfcid2) { 446 if (nfcid2 == null) { 447 return false; 448 } 449 if (nfcid2.length() != 16) { 450 Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); 451 return false; 452 } 453 // check if the the value starts with "02FE" 454 if (!nfcid2.toUpperCase().startsWith("02FE")) { 455 Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); 456 return false; 457 } 458 try { 459 Long.parseLong(nfcid2, 16); 460 } catch (NumberFormatException e) { 461 Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); 462 return false; 463 } 464 return true; 465 } 466 recoverService()467 void recoverService() { 468 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext); 469 sService = adapter.getNfcFCardEmulationService(); 470 } 471 472 } 473 474