blob: f2b5ebc15a43223d86005dd375d52970702ee1be [file] [log] [blame]
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +09001/*
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 {
Jeff Sharkey115d2c12016-02-15 17:25:57 -070080 if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF, 0)) {
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +090081 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
Martijn Coenen007e0292016-04-26 09:47:56 +0200111 * {@link #unregisterSystemCodeForService(ComponentName)}, "null" is returned.
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900112 *
113 * @param service The component name of the service
114 * @return the current System Code
115 */
Martijn Coenen007e0292016-04-26 09:47:56 +0200116 public String getSystemCodeForService(ComponentName service) throws RuntimeException {
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900117 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.");
Martijn Coenen007e0292016-04-26 09:47:56 +0200133 ee.rethrowAsRuntimeException();
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900134 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 */
Martijn Coenen007e0292016-04-26 09:47:56 +0200161 public boolean registerSystemCodeForService(ComponentName service, String systemCode)
162 throws RuntimeException {
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900163 if (service == null || systemCode == null) {
164 throw new NullPointerException("service or systemCode is null");
165 }
166 try {
167 return sService.registerSystemCodeForService(UserHandle.myUserId(),
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(UserHandle.myUserId(),
178 service, systemCode);
179 } catch (RemoteException ee) {
180 Log.e(TAG, "Failed to reach CardEmulationService.");
Martijn Coenen007e0292016-04-26 09:47:56 +0200181 ee.rethrowAsRuntimeException();
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900182 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 */
Martijn Coenen007e0292016-04-26 09:47:56 +0200193 public boolean unregisterSystemCodeForService(ComponentName service) throws RuntimeException {
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900194 if (service == null) {
195 throw new NullPointerException("service is null");
196 }
197 try {
198 return sService.removeSystemCodeForService(UserHandle.myUserId(), 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(UserHandle.myUserId(), service);
208 } catch (RemoteException ee) {
209 Log.e(TAG, "Failed to reach CardEmulationService.");
Martijn Coenen007e0292016-04-26 09:47:56 +0200210 ee.rethrowAsRuntimeException();
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900211 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 */
Martijn Coenen007e0292016-04-26 09:47:56 +0200228 public String getNfcid2ForService(ComponentName service) throws RuntimeException {
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900229 if (service == null) {
230 throw new NullPointerException("service is null");
231 }
232 try {
233 return sService.getNfcid2ForService(UserHandle.myUserId(), 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(UserHandle.myUserId(), service);
243 } catch (RemoteException ee) {
244 Log.e(TAG, "Failed to reach CardEmulationService.");
Martijn Coenen007e0292016-04-26 09:47:56 +0200245 ee.rethrowAsRuntimeException();
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900246 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 */
Martijn Coenen007e0292016-04-26 09:47:56 +0200270 public boolean setNfcid2ForService(ComponentName service, String nfcid2)
271 throws RuntimeException {
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900272 if (service == null || nfcid2 == null) {
273 throw new NullPointerException("service or nfcid2 is null");
274 }
275 try {
276 return sService.setNfcid2ForService(UserHandle.myUserId(),
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(UserHandle.myUserId(),
287 service, nfcid2);
288 } catch (RemoteException ee) {
289 Log.e(TAG, "Failed to reach CardEmulationService.");
Martijn Coenen007e0292016-04-26 09:47:56 +0200290 ee.rethrowAsRuntimeException();
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900291 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
Martijn Coenen007e0292016-04-26 09:47:56 +0200302 * the background, {@link #disableService(Activity)} is called, or
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900303 * 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
Martijn Coenen007e0292016-04-26 09:47:56 +0200307 * {@link #disableService(Activity)} in your {@link Activity#onPause}.
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900308 *
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 */
Martijn Coenen007e0292016-04-26 09:47:56 +0200316 public boolean enableService(Activity activity, ComponentName service) throws RuntimeException {
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900317 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.");
Martijn Coenen007e0292016-04-26 09:47:56 +0200337 ee.rethrowAsRuntimeException();
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900338 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 */
Martijn Coenen007e0292016-04-26 09:47:56 +0200353 public boolean disableService(Activity activity) throws RuntimeException {
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900354 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.");
Martijn Coenen007e0292016-04-26 09:47:56 +0200373 ee.rethrowAsRuntimeException();
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900374 return false;
375 }
376 }
377 }
378
379 /**
380 * @hide
381 */
382 public List<NfcFServiceInfo> getNfcFServices() {
383 try {
384 return sService.getNfcFServices(UserHandle.myUserId());
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(UserHandle.myUserId());
394 } catch (RemoteException ee) {
395 Log.e(TAG, "Failed to reach CardEmulationService.");
396 return null;
397 }
398 }
399 }
400
401 /**
402 * @hide
403 */
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 */
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 {
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100440 Integer.parseInt(systemCode, 16);
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900441 } 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 */
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 {
Tobias Thierer28532d02016-04-21 14:52:10 +0100465 Long.parseLong(nfcid2, 16);
Yoshinobu Itoc52adfe2016-01-22 18:14:18 +0900466 } catch (NumberFormatException e) {
467 Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
468 return false;
469 }
470 return true;
471 }
472
473 void recoverService() {
474 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
475 sService = adapter.getNfcFCardEmulationService();
476 }
477
478}
479