Dynamic AID registration APIs for HCE.

Adds a set of APIs that allows applications
to dynamically register and unregister AID groups
for HCE and Secure Element based services.

Change-Id: I08e9423dff405955cb725c87423c953a7dbe5c72
This commit is contained in:
Martijn Coenen
2014-04-11 12:54:22 -07:00
parent 6f6e64a7a3
commit aa1492d1d8
6 changed files with 434 additions and 109 deletions

View File

@@ -16813,11 +16813,24 @@ package android.nfc {
package android.nfc.cardemulation {
public final class AidGroup implements android.os.Parcelable {
ctor public AidGroup(java.util.ArrayList<java.lang.String>, java.lang.String);
method public int describeContents();
method public java.util.ArrayList<java.lang.String> getAids();
method public java.lang.String getCategory();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
field public static final int MAX_NUM_AIDS = 256; // 0x100
}
public final class CardEmulation {
method public android.nfc.cardemulation.AidGroup getAidGroupForService(android.content.ComponentName, java.lang.String);
method public static synchronized android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
method public int getSelectionModeForCategory(java.lang.String);
method public boolean isDefaultServiceForAid(android.content.ComponentName, java.lang.String);
method public boolean isDefaultServiceForCategory(android.content.ComponentName, java.lang.String);
method public boolean registerAidGroupForService(android.content.ComponentName, android.nfc.cardemulation.AidGroup);
method public boolean removeAidGroupForService(android.content.ComponentName, java.lang.String);
field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
field public static final java.lang.String CATEGORY_OTHER = "other";
field public static final java.lang.String CATEGORY_PAYMENT = "payment";

View File

@@ -17,6 +17,7 @@
package android.nfc;
import android.content.ComponentName;
import android.nfc.cardemulation.AidGroup;
import android.nfc.cardemulation.ApduServiceInfo;
import android.os.RemoteCallback;
@@ -29,5 +30,8 @@ interface INfcCardEmulation
boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid);
boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
boolean setDefaultForNextTap(int userHandle, in ComponentName service);
boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category);
boolean removeAidGroupForService(int userHandle, in ComponentName service, String category);
List<ApduServiceInfo> getServices(int userHandle, in String category);
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2013 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;
parcelable AidGroup;

View File

@@ -0,0 +1,165 @@
package android.nfc.cardemulation;
import java.io.IOException;
import java.util.ArrayList;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
/**
* The AidGroup class represents a group of ISO/IEC 7816-4
* Application Identifiers (AIDs) for a specific application
* category, along with a description resource describing
* the group.
*/
public final class AidGroup implements Parcelable {
/**
* The maximum number of AIDs that can be present in any one group.
*/
public static final int MAX_NUM_AIDS = 256;
static final String TAG = "AidGroup";
final ArrayList<String> aids;
final String category;
final String description;
/**
* Creates a new AidGroup object.
*
* @param aids The list of AIDs present in the group
* @param category The category of this group
*/
public AidGroup(ArrayList<String> aids, String category) {
if (aids == null || aids.size() == 0) {
throw new IllegalArgumentException("No AIDS in AID group.");
}
if (aids.size() > MAX_NUM_AIDS) {
throw new IllegalArgumentException("Too many AIDs in AID group.");
}
if (!isValidCategory(category)) {
throw new IllegalArgumentException("Category specified is not valid.");
}
this.aids = aids;
this.category = category;
this.description = null;
}
AidGroup(String category, String description) {
this.aids = new ArrayList<String>();
this.category = category;
this.description = description;
}
/**
* @return the category of this AID group
*/
public String getCategory() {
return category;
}
/**
* @return the list of AIDs in this group
*/
public ArrayList<String> getAids() {
return aids;
}
@Override
public String toString() {
StringBuilder out = new StringBuilder("Category: " + category +
", AIDs:");
for (String aid : aids) {
out.append(aid);
out.append(", ");
}
return out.toString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(category);
dest.writeInt(aids.size());
if (aids.size() > 0) {
dest.writeStringList(aids);
}
}
public static final Parcelable.Creator<AidGroup> CREATOR =
new Parcelable.Creator<AidGroup>() {
@Override
public AidGroup createFromParcel(Parcel source) {
String category = source.readString();
int listSize = source.readInt();
ArrayList<String> aidList = new ArrayList<String>();
if (listSize > 0) {
source.readStringList(aidList);
}
return new AidGroup(aidList, category);
}
@Override
public AidGroup[] newArray(int size) {
return new AidGroup[size];
}
};
/**
* @hide
* Note: description is not serialized, since it's not localized
* and resource identifiers don't make sense to persist.
*/
static public AidGroup createFromXml(XmlPullParser parser) throws XmlPullParserException, IOException {
String category = parser.getAttributeValue(null, "category");
ArrayList<String> aids = new ArrayList<String>();
int eventType = parser.getEventType();
int minDepth = parser.getDepth();
while (eventType != XmlPullParser.END_DOCUMENT && parser.getDepth() >= minDepth) {
if (eventType == XmlPullParser.START_TAG) {
String tagName = parser.getName();
if (tagName.equals("aid")) {
String aid = parser.getAttributeValue(null, "value");
if (aid != null) {
aids.add(aid);
}
} else {
Log.d(TAG, "Ignorning unexpected tag: " + tagName);
}
}
eventType = parser.next();
}
if (category != null && aids.size() > 0) {
return new AidGroup(aids, category);
} else {
return null;
}
}
/**
* @hide
*/
public void writeAsXml(XmlSerializer out) throws IOException {
out.attribute(null, "category", category);
for (String aid : aids) {
out.startTag(null, "aid");
out.attribute(null, "value", aid);
out.endTag(null, "aid");
}
}
boolean isValidCategory(String category) {
return CardEmulation.CATEGORY_PAYMENT.equals(category) ||
CardEmulation.CATEGORY_OTHER.equals(category);
}
}

View File

@@ -35,9 +35,12 @@ import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* @hide
@@ -55,25 +58,20 @@ public final class ApduServiceInfo implements Parcelable {
*/
final String mDescription;
/**
* Convenience AID list
*/
final ArrayList<String> mAids;
/**
* Whether this service represents AIDs running on the host CPU
*/
final boolean mOnHost;
/**
* All AID groups this service handles
* Mapping from category to static AID group
*/
final ArrayList<AidGroup> mAidGroups;
final HashMap<String, AidGroup> mStaticAidGroups;
/**
* Convenience hashmap
* Mapping from category to dynamic AID group
*/
final HashMap<String, AidGroup> mCategoryToGroup;
final HashMap<String, AidGroup> mDynamicAidGroups;
/**
* Whether this service should only be started when the device is unlocked.
@@ -85,27 +83,34 @@ public final class ApduServiceInfo implements Parcelable {
*/
final int mBannerResourceId;
/**
* The uid of the package the service belongs to
*/
final int mUid;
/**
* @hide
*/
public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
ArrayList<AidGroup> aidGroups, boolean requiresUnlock, int bannerResource) {
ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
boolean requiresUnlock, int bannerResource, int uid) {
this.mService = info;
this.mDescription = description;
this.mAidGroups = aidGroups;
this.mAids = new ArrayList<String>();
this.mCategoryToGroup = new HashMap<String, AidGroup>();
this.mStaticAidGroups = new HashMap<String, AidGroup>();
this.mDynamicAidGroups = new HashMap<String, AidGroup>();
this.mOnHost = onHost;
this.mRequiresDeviceUnlock = requiresUnlock;
for (AidGroup aidGroup : aidGroups) {
this.mCategoryToGroup.put(aidGroup.category, aidGroup);
this.mAids.addAll(aidGroup.aids);
for (AidGroup aidGroup : staticAidGroups) {
this.mStaticAidGroups.put(aidGroup.category, aidGroup);
}
for (AidGroup aidGroup : dynamicAidGroups) {
this.mDynamicAidGroups.put(aidGroup.category, aidGroup);
}
this.mBannerResourceId = bannerResource;
this.mUid = uid;
}
public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost)
throws XmlPullParserException, IOException {
public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost) throws
XmlPullParserException, IOException {
ServiceInfo si = info.serviceInfo;
XmlResourceParser parser = null;
try {
@@ -163,10 +168,10 @@ public final class ApduServiceInfo implements Parcelable {
sa.recycle();
}
mAidGroups = new ArrayList<AidGroup>();
mCategoryToGroup = new HashMap<String, AidGroup>();
mAids = new ArrayList<String>();
mStaticAidGroups = new HashMap<String, AidGroup>();
mDynamicAidGroups = new HashMap<String, AidGroup>();
mOnHost = onHost;
final int depth = parser.getDepth();
AidGroup currentGroup = null;
@@ -179,14 +184,14 @@ public final class ApduServiceInfo implements Parcelable {
final TypedArray groupAttrs = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AidGroup);
// Get category of AID group
String groupDescription = groupAttrs.getString(
com.android.internal.R.styleable.AidGroup_description);
String groupCategory = groupAttrs.getString(
com.android.internal.R.styleable.AidGroup_category);
String groupDescription = groupAttrs.getString(
com.android.internal.R.styleable.AidGroup_description);
if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) {
groupCategory = CardEmulation.CATEGORY_OTHER;
}
currentGroup = mCategoryToGroup.get(groupCategory);
currentGroup = mStaticAidGroups.get(groupCategory);
if (currentGroup != null) {
if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
Log.e(TAG, "Not allowing multiple aid-groups in the " +
@@ -200,9 +205,8 @@ public final class ApduServiceInfo implements Parcelable {
} else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) &&
currentGroup != null) {
if (currentGroup.aids.size() > 0) {
if (!mCategoryToGroup.containsKey(currentGroup.category)) {
mAidGroups.add(currentGroup);
mCategoryToGroup.put(currentGroup.category, currentGroup);
if (!mStaticAidGroups.containsKey(currentGroup.category)) {
mStaticAidGroups.put(currentGroup.category, currentGroup);
}
} else {
Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs");
@@ -216,7 +220,6 @@ public final class ApduServiceInfo implements Parcelable {
toUpperCase();
if (isValidAid(aid) && !currentGroup.aids.contains(aid)) {
currentGroup.aids.add(aid);
mAids.add(aid);
} else {
Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
}
@@ -228,6 +231,8 @@ public final class ApduServiceInfo implements Parcelable {
} finally {
if (parser != null) parser.close();
}
// Set uid
mUid = si.applicationInfo.uid;
}
public ComponentName getComponent() {
@@ -235,16 +240,58 @@ public final class ApduServiceInfo implements Parcelable {
mService.serviceInfo.name);
}
/**
* Returns a consolidated list of AIDs from the AID groups
* registered by this service. Note that if a service has both
* a static (manifest-based) AID group for a category and a dynamic
* AID group, only the dynamically registered AIDs will be returned
* for that category.
* @return List of AIDs registered by the service
*/
public ArrayList<String> getAids() {
return mAids;
final ArrayList<String> aids = new ArrayList<String>();
for (AidGroup group : getAidGroups()) {
aids.addAll(group.aids);
}
return aids;
}
/**
* Returns the registered AID group for this category.
*/
public AidGroup getDynamicAidGroupForCategory(String category) {
return mDynamicAidGroups.get(category);
}
public boolean removeDynamicAidGroupForCategory(String category) {
return (mDynamicAidGroups.remove(category) != null);
}
/**
* Returns a consolidated list of AID groups
* registered by this service. Note that if a service has both
* a static (manifest-based) AID group for a category and a dynamic
* AID group, only the dynamically registered AID group will be returned
* for that category.
* @return List of AIDs registered by the service
*/
public ArrayList<AidGroup> getAidGroups() {
return mAidGroups;
final ArrayList<AidGroup> groups = new ArrayList<AidGroup>();
for (Map.Entry<String, AidGroup> entry : mDynamicAidGroups.entrySet()) {
groups.add(entry.getValue());
}
for (Map.Entry<String, AidGroup> entry : mStaticAidGroups.entrySet()) {
if (!mDynamicAidGroups.containsKey(entry.getKey())) {
// Consolidate AID groups - don't return static ones
// if a dynamic group exists for the category.
groups.add(entry.getValue());
}
}
return groups;
}
public boolean hasCategory(String category) {
return mCategoryToGroup.containsKey(category);
return (mStaticAidGroups.containsKey(category) || mDynamicAidGroups.containsKey(category));
}
public boolean isOnHost() {
@@ -259,6 +306,14 @@ public final class ApduServiceInfo implements Parcelable {
return mDescription;
}
public int getUid() {
return mUid;
}
public void setOrReplaceDynamicAidGroup(AidGroup aidGroup) {
mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
}
public CharSequence loadLabel(PackageManager pm) {
return mService.loadLabel(pm);
}
@@ -304,8 +359,12 @@ public final class ApduServiceInfo implements Parcelable {
StringBuilder out = new StringBuilder("ApduService: ");
out.append(getComponent());
out.append(", description: " + mDescription);
out.append(", AID Groups: ");
for (AidGroup aidGroup : mAidGroups) {
out.append(", Static AID Groups: ");
for (AidGroup aidGroup : mStaticAidGroups.values()) {
out.append(aidGroup.toString());
}
out.append(", Dynamic AID Groups: ");
for (AidGroup aidGroup : mDynamicAidGroups.values()) {
out.append(aidGroup.toString());
}
return out.toString();
@@ -336,12 +395,17 @@ public final class ApduServiceInfo implements Parcelable {
mService.writeToParcel(dest, flags);
dest.writeString(mDescription);
dest.writeInt(mOnHost ? 1 : 0);
dest.writeInt(mAidGroups.size());
if (mAidGroups.size() > 0) {
dest.writeTypedList(mAidGroups);
dest.writeInt(mStaticAidGroups.size());
if (mStaticAidGroups.size() > 0) {
dest.writeTypedList(new ArrayList<AidGroup>(mStaticAidGroups.values()));
}
dest.writeInt(mDynamicAidGroups.size());
if (mDynamicAidGroups.size() > 0) {
dest.writeTypedList(new ArrayList<AidGroup>(mDynamicAidGroups.values()));
}
dest.writeInt(mRequiresDeviceUnlock ? 1 : 0);
dest.writeInt(mBannerResourceId);
dest.writeInt(mUid);
};
public static final Parcelable.Creator<ApduServiceInfo> CREATOR =
@@ -351,14 +415,21 @@ public final class ApduServiceInfo implements Parcelable {
ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
String description = source.readString();
boolean onHost = (source.readInt() != 0) ? true : false;
ArrayList<AidGroup> aidGroups = new ArrayList<AidGroup>();
int numGroups = source.readInt();
if (numGroups > 0) {
source.readTypedList(aidGroups, AidGroup.CREATOR);
ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>();
int numStaticGroups = source.readInt();
if (numStaticGroups > 0) {
source.readTypedList(staticAidGroups, AidGroup.CREATOR);
}
ArrayList<AidGroup> dynamicAidGroups = new ArrayList<AidGroup>();
int numDynamicGroups = source.readInt();
if (numDynamicGroups > 0) {
source.readTypedList(dynamicAidGroups, AidGroup.CREATOR);
}
boolean requiresUnlock = (source.readInt() != 0) ? true : false;
int bannerResource = source.readInt();
return new ApduServiceInfo(info, onHost, description, aidGroups, requiresUnlock, bannerResource);
int uid = source.readInt();
return new ApduServiceInfo(info, onHost, description, staticAidGroups,
dynamicAidGroups, requiresUnlock, bannerResource, uid);
}
@Override
@@ -367,76 +438,22 @@ public final class ApduServiceInfo implements Parcelable {
}
};
public static class AidGroup implements Parcelable {
final ArrayList<String> aids;
final String category;
final String description;
AidGroup(ArrayList<String> aids, String category, String description) {
this.aids = aids;
this.category = category;
this.description = description;
}
AidGroup(String category, String description) {
this.aids = new ArrayList<String>();
this.category = category;
this.description = description;
}
public String getCategory() {
return category;
}
public ArrayList<String> getAids() {
return aids;
}
@Override
public String toString() {
StringBuilder out = new StringBuilder("Category: " + category +
", description: " + description + ", AIDs:");
for (String aid : aids) {
out.append(aid);
out.append(", ");
}
return out.toString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(category);
dest.writeString(description);
dest.writeInt(aids.size());
if (aids.size() > 0) {
dest.writeStringList(aids);
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(" " + getComponent() +
" (Description: " + getDescription() + ")");
pw.println(" Static AID groups:");
for (AidGroup group : mStaticAidGroups.values()) {
pw.println(" Category: " + group.category);
for (String aid : group.aids) {
pw.println(" AID: " + aid);
}
}
public static final Parcelable.Creator<ApduServiceInfo.AidGroup> CREATOR =
new Parcelable.Creator<ApduServiceInfo.AidGroup>() {
@Override
public AidGroup createFromParcel(Parcel source) {
String category = source.readString();
String description = source.readString();
int listSize = source.readInt();
ArrayList<String> aidList = new ArrayList<String>();
if (listSize > 0) {
source.readStringList(aidList);
}
return new AidGroup(aidList, category, description);
pw.println(" Dynamic AID groups:");
for (AidGroup group : mDynamicAidGroups.values()) {
pw.println(" Category: " + group.category);
for (String aid : group.aids) {
pw.println(" AID: " + aid);
}
@Override
public AidGroup[] newArray(int size) {
return new AidGroup[size];
}
};
}
}
}

View File

@@ -168,6 +168,10 @@ public final class CardEmulation {
if (manager == null) {
// Get card emu service
INfcCardEmulation service = adapter.getCardEmulationService();
if (service == null) {
Log.e(TAG, "This device does not implement the INfcCardEmulation interface.");
throw new UnsupportedOperationException();
}
manager = new CardEmulation(context, service);
sCardEmus.put(context, manager);
}
@@ -270,6 +274,109 @@ public final class CardEmulation {
}
}
/**
* Registers a group of AIDs for the specified service.
*
* <p>If an AID group for that category was previously
* registered for this service (either statically
* through the manifest, or dynamically by using this API),
* that AID group will be replaced with this one.
*
* <p>Note that you can only register AIDs for a service that
* is running under the same UID as you are. 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 aidGroup The group of AIDs to be registered
* @return whether the registration was successful.
*/
public boolean registerAidGroupForService(ComponentName service, AidGroup aidGroup) {
try {
return sService.registerAidGroupForService(UserHandle.myUserId(), service, aidGroup);
} catch (RemoteException e) {
// Try one more time
recoverService();
if (sService == null) {
Log.e(TAG, "Failed to recover CardEmulationService.");
return false;
}
try {
return sService.registerAidGroupForService(UserHandle.myUserId(), service,
aidGroup);
} catch (RemoteException ee) {
Log.e(TAG, "Failed to reach CardEmulationService.");
return false;
}
}
}
/**
* Retrieves the currently registered AID group for the specified
* category for a service.
*
* <p>Note that this will only return AID groups that were dynamically
* registered using {@link #registerAidGroupForService(ComponentName, AidGroup)}
* method. It will *not* return AID groups that were statically registered
* in the manifest.
*
* @param service The component name of the service
* @param category The category of the AID group to be returned, e.g. {@link #CATEGORY_PAYMENT}
* @return The AID group, or null if it couldn't be found
*/
public AidGroup getAidGroupForService(ComponentName service, String category) {
try {
return sService.getAidGroupForService(UserHandle.myUserId(), service, category);
} catch (RemoteException e) {
recoverService();
if (sService == null) {
Log.e(TAG, "Failed to recover CardEmulationService.");
return null;
}
try {
return sService.getAidGroupForService(UserHandle.myUserId(), service, category);
} catch (RemoteException ee) {
Log.e(TAG, "Failed to recover CardEmulationService.");
return null;
}
}
}
/**
* Removes a registered AID group for the specified category for the
* service provided.
*
* <p>Note that this will only remove AID groups that were dynamically
* registered using the {@link #registerAidGroupForService(ComponentName, AidGroup)}
* method. It will *not* remove AID groups that were statically registered in
* the manifest. If a dynamically registered AID group is removed using
* this method, and a statically registered AID group for the same category
* exists in the manifest, that AID group will become active again.
*
* @param service The component name of the service
* @param category The category of the AID group to be removed, e.g. {@link #CATEGORY_PAYMENT}
* @return whether the group was successfully removed.
*/
public boolean removeAidGroupForService(ComponentName service, String category) {
try {
return sService.removeAidGroupForService(UserHandle.myUserId(), service, category);
} catch (RemoteException e) {
// Try one more time
recoverService();
if (sService == null) {
Log.e(TAG, "Failed to recover CardEmulationService.");
return false;
}
try {
return sService.removeAidGroupForService(UserHandle.myUserId(), service, category);
} catch (RemoteException ee) {
Log.e(TAG, "Failed to reach CardEmulationService.");
return false;
}
}
}
/**
* @hide
*/