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