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