• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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