474 lines
18 KiB
Java
474 lines
18 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2015 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package android.nfc.cardemulation;
|
||
|
|
||
|
import android.app.Activity;
|
||
|
import android.content.ComponentName;
|
||
|
import android.content.Context;
|
||
|
import android.content.pm.PackageManager;
|
||
|
import android.nfc.INfcFCardEmulation;
|
||
|
import android.nfc.NfcAdapter;
|
||
|
import android.os.RemoteException;
|
||
|
import android.util.Log;
|
||
|
|
||
|
import java.util.HashMap;
|
||
|
import java.util.List;
|
||
|
|
||
|
/**
|
||
|
* This class can be used to query the state of
|
||
|
* NFC-F card emulation services.
|
||
|
*
|
||
|
* For a general introduction into NFC card emulation,
|
||
|
* please read the <a href="{@docRoot}guide/topics/connectivity/nfc/hce.html">
|
||
|
* NFC card emulation developer guide</a>.</p>
|
||
|
*
|
||
|
* <p class="note">Use of this class requires the
|
||
|
* {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION_NFCF}
|
||
|
* to be present on the device.
|
||
|
*/
|
||
|
public final class NfcFCardEmulation {
|
||
|
static final String TAG = "NfcFCardEmulation";
|
||
|
|
||
|
static boolean sIsInitialized = false;
|
||
|
static HashMap<Context, NfcFCardEmulation> sCardEmus = new HashMap<Context, NfcFCardEmulation>();
|
||
|
static INfcFCardEmulation sService;
|
||
|
|
||
|
final Context mContext;
|
||
|
|
||
|
private NfcFCardEmulation(Context context, INfcFCardEmulation service) {
|
||
|
mContext = context.getApplicationContext();
|
||
|
sService = service;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Helper to get an instance of this class.
|
||
|
*
|
||
|
* @param adapter A reference to an NfcAdapter object.
|
||
|
* @return
|
||
|
*/
|
||
|
public static synchronized NfcFCardEmulation getInstance(NfcAdapter adapter) {
|
||
|
if (adapter == null) throw new NullPointerException("NfcAdapter is null");
|
||
|
Context context = adapter.getContext();
|
||
|
if (context == null) {
|
||
|
Log.e(TAG, "NfcAdapter context is null.");
|
||
|
throw new UnsupportedOperationException();
|
||
|
}
|
||
|
if (!sIsInitialized) {
|
||
|
PackageManager pm = context.getPackageManager();
|
||
|
if (pm == null) {
|
||
|
Log.e(TAG, "Cannot get PackageManager");
|
||
|
throw new UnsupportedOperationException();
|
||
|
}
|
||
|
if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)) {
|
||
|
Log.e(TAG, "This device does not support NFC-F card emulation");
|
||
|
throw new UnsupportedOperationException();
|
||
|
}
|
||
|
sIsInitialized = true;
|
||
|
}
|
||
|
NfcFCardEmulation manager = sCardEmus.get(context);
|
||
|
if (manager == null) {
|
||
|
// Get card emu service
|
||
|
INfcFCardEmulation service = adapter.getNfcFCardEmulationService();
|
||
|
if (service == null) {
|
||
|
Log.e(TAG, "This device does not implement the INfcFCardEmulation interface.");
|
||
|
throw new UnsupportedOperationException();
|
||
|
}
|
||
|
manager = new NfcFCardEmulation(context, service);
|
||
|
sCardEmus.put(context, manager);
|
||
|
}
|
||
|
return manager;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Retrieves the current System Code for the specified service.
|
||
|
*
|
||
|
* <p>Before calling {@link #registerSystemCodeForService(ComponentName, String)},
|
||
|
* the System Code contained in the Manifest file is returned. After calling
|
||
|
* {@link #registerSystemCodeForService(ComponentName, String)}, the System Code
|
||
|
* registered there is returned. After calling
|
||
|
* {@link #unregisterSystemCodeForService(ComponentName)}, "null" is returned.
|
||
|
*
|
||
|
* @param service The component name of the service
|
||
|
* @return the current System Code
|
||
|
*/
|
||
|
public String getSystemCodeForService(ComponentName service) throws RuntimeException {
|
||
|
if (service == null) {
|
||
|
throw new NullPointerException("service is null");
|
||
|
}
|
||
|
try {
|
||
|
return sService.getSystemCodeForService(mContext.getUser().getIdentifier(), service);
|
||
|
} catch (RemoteException e) {
|
||
|
// Try one more time
|
||
|
recoverService();
|
||
|
if (sService == null) {
|
||
|
Log.e(TAG, "Failed to recover CardEmulationService.");
|
||
|
return null;
|
||
|
}
|
||
|
try {
|
||
|
return sService.getSystemCodeForService(mContext.getUser().getIdentifier(),
|
||
|
service);
|
||
|
} catch (RemoteException ee) {
|
||
|
Log.e(TAG, "Failed to reach CardEmulationService.");
|
||
|
ee.rethrowAsRuntimeException();
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Registers a System Code for the specified service.
|
||
|
*
|
||
|
* <p>The System Code must be in range from "4000" to "4FFF" (excluding "4*FF").
|
||
|
*
|
||
|
* <p>If a System Code was previously registered for this service
|
||
|
* (either statically through the manifest, or dynamically by using this API),
|
||
|
* it will be replaced with this one.
|
||
|
*
|
||
|
* <p>Even if the same System Code is already registered for another service,
|
||
|
* this method succeeds in registering the System Code.
|
||
|
*
|
||
|
* <p>Note that you can only register a System Code for a service that
|
||
|
* is running under the same UID as the caller of this API. Typically
|
||
|
* this means you need to call this from the same
|
||
|
* package as the service itself, though UIDs can also
|
||
|
* be shared between packages using shared UIDs.
|
||
|
*
|
||
|
* @param service The component name of the service
|
||
|
* @param systemCode The System Code to be registered
|
||
|
* @return whether the registration was successful.
|
||
|
*/
|
||
|
public boolean registerSystemCodeForService(ComponentName service, String systemCode)
|
||
|
throws RuntimeException {
|
||
|
if (service == null || systemCode == null) {
|
||
|
throw new NullPointerException("service or systemCode is null");
|
||
|
}
|
||
|
try {
|
||
|
return sService.registerSystemCodeForService(mContext.getUser().getIdentifier(),
|
||
|
service, systemCode);
|
||
|
} catch (RemoteException e) {
|
||
|
// Try one more time
|
||
|
recoverService();
|
||
|
if (sService == null) {
|
||
|
Log.e(TAG, "Failed to recover CardEmulationService.");
|
||
|
return false;
|
||
|
}
|
||
|
try {
|
||
|
return sService.registerSystemCodeForService(mContext.getUser().getIdentifier(),
|
||
|
service, systemCode);
|
||
|
} catch (RemoteException ee) {
|
||
|
Log.e(TAG, "Failed to reach CardEmulationService.");
|
||
|
ee.rethrowAsRuntimeException();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Removes a registered System Code for the specified service.
|
||
|
*
|
||
|
* @param service The component name of the service
|
||
|
* @return whether the System Code was successfully removed.
|
||
|
*/
|
||
|
public boolean unregisterSystemCodeForService(ComponentName service) throws RuntimeException {
|
||
|
if (service == null) {
|
||
|
throw new NullPointerException("service is null");
|
||
|
}
|
||
|
try {
|
||
|
return sService.removeSystemCodeForService(mContext.getUser().getIdentifier(), service);
|
||
|
} catch (RemoteException e) {
|
||
|
// Try one more time
|
||
|
recoverService();
|
||
|
if (sService == null) {
|
||
|
Log.e(TAG, "Failed to recover CardEmulationService.");
|
||
|
return false;
|
||
|
}
|
||
|
try {
|
||
|
return sService.removeSystemCodeForService(mContext.getUser().getIdentifier(),
|
||
|
service);
|
||
|
} catch (RemoteException ee) {
|
||
|
Log.e(TAG, "Failed to reach CardEmulationService.");
|
||
|
ee.rethrowAsRuntimeException();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Retrieves the current NFCID2 for the specified service.
|
||
|
*
|
||
|
* <p>Before calling {@link #setNfcid2ForService(ComponentName, String)},
|
||
|
* the NFCID2 contained in the Manifest file is returned. If "random" is specified
|
||
|
* in the Manifest file, a random number assigned by the system at installation time
|
||
|
* is returned. After setting an NFCID2
|
||
|
* with {@link #setNfcid2ForService(ComponentName, String)}, this NFCID2 is returned.
|
||
|
*
|
||
|
* @param service The component name of the service
|
||
|
* @return the current NFCID2
|
||
|
*/
|
||
|
public String getNfcid2ForService(ComponentName service) throws RuntimeException {
|
||
|
if (service == null) {
|
||
|
throw new NullPointerException("service is null");
|
||
|
}
|
||
|
try {
|
||
|
return sService.getNfcid2ForService(mContext.getUser().getIdentifier(), service);
|
||
|
} catch (RemoteException e) {
|
||
|
// Try one more time
|
||
|
recoverService();
|
||
|
if (sService == null) {
|
||
|
Log.e(TAG, "Failed to recover CardEmulationService.");
|
||
|
return null;
|
||
|
}
|
||
|
try {
|
||
|
return sService.getNfcid2ForService(mContext.getUser().getIdentifier(), service);
|
||
|
} catch (RemoteException ee) {
|
||
|
Log.e(TAG, "Failed to reach CardEmulationService.");
|
||
|
ee.rethrowAsRuntimeException();
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set a NFCID2 for the specified service.
|
||
|
*
|
||
|
* <p>The NFCID2 must be in range from "02FE000000000000" to "02FEFFFFFFFFFFFF".
|
||
|
*
|
||
|
* <p>If a NFCID2 was previously set for this service
|
||
|
* (either statically through the manifest, or dynamically by using this API),
|
||
|
* it will be replaced.
|
||
|
*
|
||
|
* <p>Note that you can only set the NFCID2 for a service that
|
||
|
* is running under the same UID as the caller of this API. Typically
|
||
|
* this means you need to call this from the same
|
||
|
* package as the service itself, though UIDs can also
|
||
|
* be shared between packages using shared UIDs.
|
||
|
*
|
||
|
* @param service The component name of the service
|
||
|
* @param nfcid2 The NFCID2 to be registered
|
||
|
* @return whether the setting was successful.
|
||
|
*/
|
||
|
public boolean setNfcid2ForService(ComponentName service, String nfcid2)
|
||
|
throws RuntimeException {
|
||
|
if (service == null || nfcid2 == null) {
|
||
|
throw new NullPointerException("service or nfcid2 is null");
|
||
|
}
|
||
|
try {
|
||
|
return sService.setNfcid2ForService(mContext.getUser().getIdentifier(),
|
||
|
service, nfcid2);
|
||
|
} catch (RemoteException e) {
|
||
|
// Try one more time
|
||
|
recoverService();
|
||
|
if (sService == null) {
|
||
|
Log.e(TAG, "Failed to recover CardEmulationService.");
|
||
|
return false;
|
||
|
}
|
||
|
try {
|
||
|
return sService.setNfcid2ForService(mContext.getUser().getIdentifier(),
|
||
|
service, nfcid2);
|
||
|
} catch (RemoteException ee) {
|
||
|
Log.e(TAG, "Failed to reach CardEmulationService.");
|
||
|
ee.rethrowAsRuntimeException();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Allows a foreground application to specify which card emulation service
|
||
|
* should be enabled while a specific Activity is in the foreground.
|
||
|
*
|
||
|
* <p>The specified HCE-F service is only enabled when the corresponding application is
|
||
|
* in the foreground and this method has been called. When the application is moved to
|
||
|
* the background, {@link #disableService(Activity)} is called, or
|
||
|
* NFCID2 or System Code is replaced, the HCE-F service is disabled.
|
||
|
*
|
||
|
* <p>The specified Activity must currently be in resumed state. A good
|
||
|
* paradigm is to call this method in your {@link Activity#onResume}, and to call
|
||
|
* {@link #disableService(Activity)} in your {@link Activity#onPause}.
|
||
|
*
|
||
|
* <p>Note that this preference is not persisted by the OS, and hence must be
|
||
|
* called every time the Activity is resumed.
|
||
|
*
|
||
|
* @param activity The activity which prefers this service to be invoked
|
||
|
* @param service The service to be preferred while this activity is in the foreground
|
||
|
* @return whether the registration was successful
|
||
|
*/
|
||
|
public boolean enableService(Activity activity, ComponentName service) throws RuntimeException {
|
||
|
if (activity == null || service == null) {
|
||
|
throw new NullPointerException("activity or service is null");
|
||
|
}
|
||
|
// Verify the activity is in the foreground before calling into NfcService
|
||
|
if (!activity.isResumed()) {
|
||
|
throw new IllegalArgumentException("Activity must be resumed.");
|
||
|
}
|
||
|
try {
|
||
|
return sService.enableNfcFForegroundService(service);
|
||
|
} catch (RemoteException e) {
|
||
|
// Try one more time
|
||
|
recoverService();
|
||
|
if (sService == null) {
|
||
|
Log.e(TAG, "Failed to recover CardEmulationService.");
|
||
|
return false;
|
||
|
}
|
||
|
try {
|
||
|
return sService.enableNfcFForegroundService(service);
|
||
|
} catch (RemoteException ee) {
|
||
|
Log.e(TAG, "Failed to reach CardEmulationService.");
|
||
|
ee.rethrowAsRuntimeException();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Disables the service for the specified Activity.
|
||
|
*
|
||
|
* <p>Note that the specified Activity must still be in resumed
|
||
|
* state at the time of this call. A good place to call this method
|
||
|
* is in your {@link Activity#onPause} implementation.
|
||
|
*
|
||
|
* @param activity The activity which the service was registered for
|
||
|
* @return true when successful
|
||
|
*/
|
||
|
public boolean disableService(Activity activity) throws RuntimeException {
|
||
|
if (activity == null) {
|
||
|
throw new NullPointerException("activity is null");
|
||
|
}
|
||
|
if (!activity.isResumed()) {
|
||
|
throw new IllegalArgumentException("Activity must be resumed.");
|
||
|
}
|
||
|
try {
|
||
|
return sService.disableNfcFForegroundService();
|
||
|
} catch (RemoteException e) {
|
||
|
// Try one more time
|
||
|
recoverService();
|
||
|
if (sService == null) {
|
||
|
Log.e(TAG, "Failed to recover CardEmulationService.");
|
||
|
return false;
|
||
|
}
|
||
|
try {
|
||
|
return sService.disableNfcFForegroundService();
|
||
|
} catch (RemoteException ee) {
|
||
|
Log.e(TAG, "Failed to reach CardEmulationService.");
|
||
|
ee.rethrowAsRuntimeException();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public List<NfcFServiceInfo> getNfcFServices() {
|
||
|
try {
|
||
|
return sService.getNfcFServices(mContext.getUser().getIdentifier());
|
||
|
} catch (RemoteException e) {
|
||
|
// Try one more time
|
||
|
recoverService();
|
||
|
if (sService == null) {
|
||
|
Log.e(TAG, "Failed to recover CardEmulationService.");
|
||
|
return null;
|
||
|
}
|
||
|
try {
|
||
|
return sService.getNfcFServices(mContext.getUser().getIdentifier());
|
||
|
} catch (RemoteException ee) {
|
||
|
Log.e(TAG, "Failed to reach CardEmulationService.");
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public int getMaxNumOfRegisterableSystemCodes() {
|
||
|
try {
|
||
|
return sService.getMaxNumOfRegisterableSystemCodes();
|
||
|
} catch (RemoteException e) {
|
||
|
// Try one more time
|
||
|
recoverService();
|
||
|
if (sService == null) {
|
||
|
Log.e(TAG, "Failed to recover CardEmulationService.");
|
||
|
return -1;
|
||
|
}
|
||
|
try {
|
||
|
return sService.getMaxNumOfRegisterableSystemCodes();
|
||
|
} catch (RemoteException ee) {
|
||
|
Log.e(TAG, "Failed to reach CardEmulationService.");
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public static boolean isValidSystemCode(String systemCode) {
|
||
|
if (systemCode == null) {
|
||
|
return false;
|
||
|
}
|
||
|
if (systemCode.length() != 4) {
|
||
|
Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
|
||
|
return false;
|
||
|
}
|
||
|
// check if the value is between "4000" and "4FFF" (excluding "4*FF")
|
||
|
if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) {
|
||
|
Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
|
||
|
return false;
|
||
|
}
|
||
|
try {
|
||
|
Integer.parseInt(systemCode, 16);
|
||
|
} catch (NumberFormatException e) {
|
||
|
Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public static boolean isValidNfcid2(String nfcid2) {
|
||
|
if (nfcid2 == null) {
|
||
|
return false;
|
||
|
}
|
||
|
if (nfcid2.length() != 16) {
|
||
|
Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
|
||
|
return false;
|
||
|
}
|
||
|
// check if the the value starts with "02FE"
|
||
|
if (!nfcid2.toUpperCase().startsWith("02FE")) {
|
||
|
Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
|
||
|
return false;
|
||
|
}
|
||
|
try {
|
||
|
Long.parseLong(nfcid2, 16);
|
||
|
} catch (NumberFormatException e) {
|
||
|
Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void recoverService() {
|
||
|
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
|
||
|
sService = adapter.getNfcFCardEmulationService();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|