diff --git a/api/current.txt b/api/current.txt index 5c962a393e17b..08eee23b29b04 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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); + method public int describeContents(); + method public java.util.ArrayList 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"; diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl index b8a5ba7f226de..ae9796b23ac3e 100644 --- a/core/java/android/nfc/INfcCardEmulation.aidl +++ b/core/java/android/nfc/INfcCardEmulation.aidl @@ -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 getServices(int userHandle, in String category); } diff --git a/core/java/android/nfc/cardemulation/AidGroup.aidl b/core/java/android/nfc/cardemulation/AidGroup.aidl new file mode 100644 index 0000000000000..56d6fa5596775 --- /dev/null +++ b/core/java/android/nfc/cardemulation/AidGroup.aidl @@ -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; diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java new file mode 100644 index 0000000000000..2820f4037e784 --- /dev/null +++ b/core/java/android/nfc/cardemulation/AidGroup.java @@ -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 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 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(); + 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 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 CREATOR = + new Parcelable.Creator() { + + @Override + public AidGroup createFromParcel(Parcel source) { + String category = source.readString(); + int listSize = source.readInt(); + ArrayList aidList = new ArrayList(); + 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 aids = new ArrayList(); + 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); + } +} diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java index d7ef4bc4e97f8..94f35ed5a87ff 100644 --- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -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 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 mAidGroups; + final HashMap mStaticAidGroups; /** - * Convenience hashmap + * Mapping from category to dynamic AID group */ - final HashMap mCategoryToGroup; + final HashMap 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 aidGroups, boolean requiresUnlock, int bannerResource) { + ArrayList staticAidGroups, ArrayList dynamicAidGroups, + boolean requiresUnlock, int bannerResource, int uid) { this.mService = info; this.mDescription = description; - this.mAidGroups = aidGroups; - this.mAids = new ArrayList(); - this.mCategoryToGroup = new HashMap(); + this.mStaticAidGroups = new HashMap(); + this.mDynamicAidGroups = new HashMap(); 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(); - mCategoryToGroup = new HashMap(); - mAids = new ArrayList(); + mStaticAidGroups = new HashMap(); + mDynamicAidGroups = new HashMap(); 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 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 getAids() { - return mAids; + final ArrayList aids = new ArrayList(); + 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 getAidGroups() { - return mAidGroups; + final ArrayList groups = new ArrayList(); + for (Map.Entry entry : mDynamicAidGroups.entrySet()) { + groups.add(entry.getValue()); + } + for (Map.Entry 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(mStaticAidGroups.values())); + } + dest.writeInt(mDynamicAidGroups.size()); + if (mDynamicAidGroups.size() > 0) { + dest.writeTypedList(new ArrayList(mDynamicAidGroups.values())); } dest.writeInt(mRequiresDeviceUnlock ? 1 : 0); dest.writeInt(mBannerResourceId); + dest.writeInt(mUid); }; public static final Parcelable.Creator 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 aidGroups = new ArrayList(); - int numGroups = source.readInt(); - if (numGroups > 0) { - source.readTypedList(aidGroups, AidGroup.CREATOR); + ArrayList staticAidGroups = new ArrayList(); + int numStaticGroups = source.readInt(); + if (numStaticGroups > 0) { + source.readTypedList(staticAidGroups, AidGroup.CREATOR); + } + ArrayList dynamicAidGroups = new ArrayList(); + 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 aids; - final String category; - final String description; - - AidGroup(ArrayList aids, String category, String description) { - this.aids = aids; - this.category = category; - this.description = description; - } - - AidGroup(String category, String description) { - this.aids = new ArrayList(); - this.category = category; - this.description = description; - } - - public String getCategory() { - return category; - } - - public ArrayList 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 CREATOR = - new Parcelable.Creator() { - - @Override - public AidGroup createFromParcel(Parcel source) { - String category = source.readString(); - String description = source.readString(); - int listSize = source.readInt(); - ArrayList aidList = new ArrayList(); - 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]; - } - }; + } } } diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java index 58d9616c72c4b..41f039cfc7cad 100644 --- a/core/java/android/nfc/cardemulation/CardEmulation.java +++ b/core/java/android/nfc/cardemulation/CardEmulation.java @@ -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. + * + *

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. + * + *

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. + * + *

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. + * + *

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 */