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