NfcFCardEmulation.java revision 115d2c189a46f535778d9dd0923f703ff2f888fe
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 17package android.nfc.cardemulation; 18 19import android.app.Activity; 20import android.app.ActivityThread; 21import android.content.ComponentName; 22import android.content.Context; 23import android.content.pm.IPackageManager; 24import android.content.pm.PackageManager; 25import android.nfc.INfcFCardEmulation; 26import android.nfc.NfcAdapter; 27import android.os.RemoteException; 28import android.os.UserHandle; 29import android.util.Log; 30 31import java.util.HashMap; 32import 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 */ 46public 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 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 */ 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 #removeSystemCodeForService(ComponentName)}, "null" is returned. 112 * 113 * @param service The component name of the service 114 * @return the current System Code 115 */ 116 public String getSystemCodeForService(ComponentName service) { 117 if (service == null) { 118 throw new NullPointerException("service is null"); 119 } 120 try { 121 return sService.getSystemCodeForService(UserHandle.myUserId(), 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(UserHandle.myUserId(), service); 131 } catch (RemoteException ee) { 132 Log.e(TAG, "Failed to reach CardEmulationService."); 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 */ 160 public boolean registerSystemCodeForService(ComponentName service, String systemCode) { 161 if (service == null || systemCode == null) { 162 throw new NullPointerException("service or systemCode is null"); 163 } 164 try { 165 return sService.registerSystemCodeForService(UserHandle.myUserId(), 166 service, systemCode); 167 } catch (RemoteException e) { 168 // Try one more time 169 recoverService(); 170 if (sService == null) { 171 Log.e(TAG, "Failed to recover CardEmulationService."); 172 return false; 173 } 174 try { 175 return sService.registerSystemCodeForService(UserHandle.myUserId(), 176 service, systemCode); 177 } catch (RemoteException ee) { 178 Log.e(TAG, "Failed to reach CardEmulationService."); 179 return false; 180 } 181 } 182 } 183 184 /** 185 * Removes a registered System Code for the specified service. 186 * 187 * @param service The component name of the service 188 * @return whether the System Code was successfully removed. 189 */ 190 public boolean removeSystemCodeForService(ComponentName service) { 191 if (service == null) { 192 throw new NullPointerException("service is null"); 193 } 194 try { 195 return sService.removeSystemCodeForService(UserHandle.myUserId(), service); 196 } catch (RemoteException e) { 197 // Try one more time 198 recoverService(); 199 if (sService == null) { 200 Log.e(TAG, "Failed to recover CardEmulationService."); 201 return false; 202 } 203 try { 204 return sService.removeSystemCodeForService(UserHandle.myUserId(), service); 205 } catch (RemoteException ee) { 206 Log.e(TAG, "Failed to reach CardEmulationService."); 207 return false; 208 } 209 } 210 } 211 212 /** 213 * Retrieves the current NFCID2 for the specified service. 214 * 215 * <p>Before calling {@link #setNfcid2ForService(ComponentName, String)}, 216 * the NFCID2 contained in the Manifest file is returned. If "random" is specified 217 * in the Manifest file, a random number assigned by the system at installation time 218 * is returned. After setting an NFCID2 219 * with {@link #setNfcid2ForService(ComponentName, String)}, this NFCID2 is returned. 220 * 221 * @param service The component name of the service 222 * @return the current NFCID2 223 */ 224 public String getNfcid2ForService(ComponentName service) { 225 if (service == null) { 226 throw new NullPointerException("service is null"); 227 } 228 try { 229 return sService.getNfcid2ForService(UserHandle.myUserId(), service); 230 } catch (RemoteException e) { 231 // Try one more time 232 recoverService(); 233 if (sService == null) { 234 Log.e(TAG, "Failed to recover CardEmulationService."); 235 return null; 236 } 237 try { 238 return sService.getNfcid2ForService(UserHandle.myUserId(), service); 239 } catch (RemoteException ee) { 240 Log.e(TAG, "Failed to reach CardEmulationService."); 241 return null; 242 } 243 } 244 } 245 246 /** 247 * Set a NFCID2 for the specified service. 248 * 249 * <p>The NFCID2 must be in range from "02FE000000000000" to "02FEFFFFFFFFFFFF". 250 * 251 * <p>If a NFCID2 was previously set for this service 252 * (either statically through the manifest, or dynamically by using this API), 253 * it will be replaced. 254 * 255 * <p>Note that you can only set the NFCID2 for a service that 256 * is running under the same UID as the caller of this API. Typically 257 * this means you need to call this from the same 258 * package as the service itself, though UIDs can also 259 * be shared between packages using shared UIDs. 260 * 261 * @param service The component name of the service 262 * @param nfcid2 The NFCID2 to be registered 263 * @return whether the setting was successful. 264 */ 265 public boolean setNfcid2ForService(ComponentName service, String nfcid2) { 266 if (service == null || nfcid2 == null) { 267 throw new NullPointerException("service or nfcid2 is null"); 268 } 269 try { 270 return sService.setNfcid2ForService(UserHandle.myUserId(), 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(UserHandle.myUserId(), 281 service, nfcid2); 282 } catch (RemoteException ee) { 283 Log.e(TAG, "Failed to reach CardEmulationService."); 284 return false; 285 } 286 } 287 } 288 289 /** 290 * Allows a foreground application to specify which card emulation service 291 * should be enabled while a specific Activity is in the foreground. 292 * 293 * <p>The specified HCE-F service is only enabled when the corresponding application is 294 * in the foreground and this method has been called. When the application is moved to 295 * the background, {@link #disableNfcFForegroundService(Activity)} is called, or 296 * NFCID2 or System Code is replaced, the HCE-F service is disabled. 297 * 298 * <p>The specified Activity must currently be in resumed state. A good 299 * paradigm is to call this method in your {@link Activity#onResume}, and to call 300 * {@link #disableNfcFForegroundService(Activity)} in your {@link Activity#onPause}. 301 * 302 * <p>Note that this preference is not persisted by the OS, and hence must be 303 * called every time the Activity is resumed. 304 * 305 * @param activity The activity which prefers this service to be invoked 306 * @param service The service to be preferred while this activity is in the foreground 307 * @return whether the registration was successful 308 */ 309 public boolean enableNfcFForegroundService(Activity activity, ComponentName service) { 310 if (activity == null || service == null) { 311 throw new NullPointerException("activity or service is null"); 312 } 313 // Verify the activity is in the foreground before calling into NfcService 314 if (!activity.isResumed()) { 315 throw new IllegalArgumentException("Activity must be resumed."); 316 } 317 try { 318 return sService.enableNfcFForegroundService(service); 319 } catch (RemoteException e) { 320 // Try one more time 321 recoverService(); 322 if (sService == null) { 323 Log.e(TAG, "Failed to recover CardEmulationService."); 324 return false; 325 } 326 try { 327 return sService.enableNfcFForegroundService(service); 328 } catch (RemoteException ee) { 329 Log.e(TAG, "Failed to reach CardEmulationService."); 330 return false; 331 } 332 } 333 } 334 335 /** 336 * Disables the service for the specified Activity. 337 * 338 * <p>Note that the specified Activity must still be in resumed 339 * state at the time of this call. A good place to call this method 340 * is in your {@link Activity#onPause} implementation. 341 * 342 * @param activity The activity which the service was registered for 343 * @return true when successful 344 */ 345 public boolean disableNfcFForegroundService(Activity activity) { 346 if (activity == null) { 347 throw new NullPointerException("activity is null"); 348 } 349 if (!activity.isResumed()) { 350 throw new IllegalArgumentException("Activity must be resumed."); 351 } 352 try { 353 return sService.disableNfcFForegroundService(); 354 } catch (RemoteException e) { 355 // Try one more time 356 recoverService(); 357 if (sService == null) { 358 Log.e(TAG, "Failed to recover CardEmulationService."); 359 return false; 360 } 361 try { 362 return sService.disableNfcFForegroundService(); 363 } catch (RemoteException ee) { 364 Log.e(TAG, "Failed to reach CardEmulationService."); 365 return false; 366 } 367 } 368 } 369 370 /** 371 * @hide 372 */ 373 public List<NfcFServiceInfo> getNfcFServices() { 374 try { 375 return sService.getNfcFServices(UserHandle.myUserId()); 376 } catch (RemoteException e) { 377 // Try one more time 378 recoverService(); 379 if (sService == null) { 380 Log.e(TAG, "Failed to recover CardEmulationService."); 381 return null; 382 } 383 try { 384 return sService.getNfcFServices(UserHandle.myUserId()); 385 } catch (RemoteException ee) { 386 Log.e(TAG, "Failed to reach CardEmulationService."); 387 return null; 388 } 389 } 390 } 391 392 /** 393 * @hide 394 */ 395 public int getMaxNumOfRegisterableSystemCodes() { 396 try { 397 return sService.getMaxNumOfRegisterableSystemCodes(); 398 } catch (RemoteException e) { 399 // Try one more time 400 recoverService(); 401 if (sService == null) { 402 Log.e(TAG, "Failed to recover CardEmulationService."); 403 return -1; 404 } 405 try { 406 return sService.getMaxNumOfRegisterableSystemCodes(); 407 } catch (RemoteException ee) { 408 Log.e(TAG, "Failed to reach CardEmulationService."); 409 return -1; 410 } 411 } 412 } 413 414 /** 415 * @hide 416 */ 417 public static boolean isValidSystemCode(String systemCode) { 418 if (systemCode == null) { 419 return false; 420 } 421 if (systemCode.length() != 4) { 422 Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); 423 return false; 424 } 425 // check if the value is between "4000" and "4FFF" (excluding "4*FF") 426 if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) { 427 Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); 428 return false; 429 } 430 try { 431 Integer.valueOf(systemCode, 16); 432 } catch (NumberFormatException e) { 433 Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); 434 return false; 435 } 436 return true; 437 } 438 439 /** 440 * @hide 441 */ 442 public static boolean isValidNfcid2(String nfcid2) { 443 if (nfcid2 == null) { 444 return false; 445 } 446 if (nfcid2.length() != 16) { 447 Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); 448 return false; 449 } 450 // check if the the value starts with "02FE" 451 if (!nfcid2.toUpperCase().startsWith("02FE")) { 452 Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); 453 return false; 454 } 455 try { 456 Long.valueOf(nfcid2, 16); 457 } catch (NumberFormatException e) { 458 Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); 459 return false; 460 } 461 return true; 462 } 463 464 void recoverService() { 465 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext); 466 sService = adapter.getNfcFCardEmulationService(); 467 } 468 469} 470 471