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