Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aa1492d1 authored by Martijn Coenen's avatar Martijn Coenen
Browse files

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
parent 6f6e64a7
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -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";
+4 −0
Original line number Diff line number Diff line
@@ -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);
}
+19 −0
Original line number Diff line number Diff line
/*
 * 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;
+165 −0
Original line number Diff line number Diff line
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);
    }
}
+125 −108
Original line number Diff line number Diff line
@@ -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;
    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);
            }

        AidGroup(String category, String description) {
            this.aids = new ArrayList<String>();
            this.category = category;
            this.description = description;
        }

        public String getCategory() {
            return category;
        pw.println("    Dynamic AID groups:");
        for (AidGroup group : mDynamicAidGroups.values()) {
            pw.println("        Category: " + group.category);
            for (String aid : group.aids) {
                pw.println("            AID: " + aid);
            }

        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 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);
            }

            @Override
            public AidGroup[] newArray(int size) {
                return new AidGroup[size];
            }
        };
    }
}
Loading