Merge "NFC: Host-based card emulation APIs."
diff --git a/Android.mk b/Android.mk
index 9adf434..8f62777 100644
--- a/Android.mk
+++ b/Android.mk
@@ -143,6 +143,7 @@
 	core/java/android/nfc/INfcAdapter.aidl \
 	core/java/android/nfc/INfcAdapterExtras.aidl \
 	core/java/android/nfc/INfcTag.aidl \
+	core/java/android/nfc/INfcCardEmulation.aidl \
 	core/java/android/os/IBatteryPropertiesListener.aidl \
 	core/java/android/os/IBatteryPropertiesRegistrar.aidl \
 	core/java/android/os/ICancellationSignal.aidl \
diff --git a/api/current.txt b/api/current.txt
index 32fa009..429caa5a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -23,6 +23,7 @@
     field public static final java.lang.String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
     field public static final java.lang.String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
     field public static final java.lang.String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
+    field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
     field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
     field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
     field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
@@ -14808,6 +14809,18 @@
 
 package android.nfc.cardemulation {
 
+  public final class CardEmulationManager {
+    method public static synchronized android.nfc.cardemulation.CardEmulationManager getInstance(android.nfc.NfcAdapter);
+    method public boolean isDefaultServiceForAid(android.content.ComponentName, java.lang.String);
+    method public boolean isDefaultServiceForCategory(android.content.ComponentName, java.lang.String);
+    method public boolean setDefaultServiceForCategory(android.content.ComponentName, java.lang.String);
+    field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.ACTION_CHANGE_DEFAULT";
+    field public static final java.lang.String CATEGORY_OTHER = "other";
+    field public static final java.lang.String CATEGORY_PAYMENT = "payment";
+    field public static final java.lang.String EXTRA_CATEGORY = "category";
+    field public static final java.lang.String EXTRA_SERVICE_COMPONENT = "component";
+  }
+
   public abstract class HostApduService extends android.app.Service {
     ctor public HostApduService();
     method public final android.os.IBinder onBind(android.content.Intent);
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 39810ba..9c97659 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -24,6 +24,7 @@
 import android.nfc.INdefPushCallback;
 import android.nfc.INfcAdapterExtras;
 import android.nfc.INfcTag;
+import android.nfc.INfcCardEmulation;
 
 /**
  * @hide
@@ -31,6 +32,7 @@
 interface INfcAdapter
 {
     INfcTag getNfcTagInterface();
+    INfcCardEmulation getNfcCardEmulationInterface();
     INfcAdapterExtras getNfcAdapterExtrasInterface(in String pkg);
 
     int getState();
diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl
new file mode 100644
index 0000000..7369c0b
--- /dev/null
+++ b/core/java/android/nfc/INfcCardEmulation.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import android.content.ComponentName;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.os.RemoteCallback;
+
+/**
+ * @hide
+ */
+interface INfcCardEmulation
+{
+    boolean isDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
+    boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid);
+    boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
+    List<ApduServiceInfo> getServices(int userHandle, in String category);
+}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index ca4a7d6..2a4f93c 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -227,6 +227,7 @@
     // recovery
     static INfcAdapter sService;
     static INfcTag sTagService;
+    static INfcCardEmulation sCardEmulationService;
 
     /**
      * The NfcAdapter object for each application context.
@@ -348,6 +349,13 @@
                 throw new UnsupportedOperationException();
             }
 
+            try {
+                sCardEmulationService = sService.getNfcCardEmulationInterface();
+            } catch (RemoteException e) {
+                Log.e(TAG, "could not retrieve card emulation service");
+                throw new UnsupportedOperationException();
+            }
+
             sIsInitialized = true;
         }
         if (context == null) {
@@ -456,6 +464,15 @@
     }
 
     /**
+     * Returns the binder interface to the card emulation service.
+     * @hide
+     */
+    public INfcCardEmulation getCardEmulationService() {
+        isEnabled();
+        return sCardEmulationService;
+    }
+
+    /**
      * NFC service dead - attempt best effort recovery
      * @hide
      */
@@ -477,6 +494,13 @@
             Log.e(TAG, "could not retrieve NFC tag service during service recovery");
             // nothing more can be done now, sService is still stale, we'll hit
             // this recovery path again later
+            return;
+        }
+
+        try {
+            sCardEmulationService = service.getNfcCardEmulationInterface();
+        } catch (RemoteException ee) {
+            Log.e(TAG, "could not retrieve NFC card emulation service during service recovery");
         }
 
         return;
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.aidl b/core/java/android/nfc/cardemulation/ApduServiceInfo.aidl
new file mode 100644
index 0000000..a62fdd6
--- /dev/null
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.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 ApduServiceInfo;
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
new file mode 100644
index 0000000..11fd39a
--- /dev/null
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -0,0 +1,359 @@
+/*
+ * 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;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * @hide
+ */
+public final class ApduServiceInfo implements Parcelable {
+    static final String TAG = "ApduServiceInfo";
+
+    /**
+     * The service that implements this
+     */
+    final ResolveInfo mService;
+
+    /**
+     * Description of the service
+     */
+    final String mDescription;
+
+    /**
+     * Convenience AID list
+     */
+    final ArrayList<String> mAids;
+
+    /**
+     * Whether this is an {@link HostApduService} or {@link OffHostApduService}
+     */
+    final boolean mOnHost;
+
+    /**
+     * All AID groups this service handles
+     */
+    final ArrayList<AidGroup> mAidGroups;
+
+    /**
+     * Convenience hashmap
+     */
+    final HashMap<String, AidGroup> mCategoryToGroup;
+
+    /**
+     * @hide
+     */
+    public ApduServiceInfo(ResolveInfo info, String description,
+            ArrayList<AidGroup> aidGroups) {
+        this.mService = info;
+        this.mDescription = description;
+        this.mAidGroups = aidGroups;
+        this.mAids = new ArrayList<String>();
+        this.mCategoryToGroup = new HashMap<String, AidGroup>();
+        this.mOnHost = false;
+        for (AidGroup aidGroup : aidGroups) {
+            this.mCategoryToGroup.put(aidGroup.category, aidGroup);
+            this.mAids.addAll(aidGroup.aids);
+        }
+    }
+
+    public ApduServiceInfo(PackageManager pm, ResolveInfo info) throws XmlPullParserException,
+            IOException {
+        ServiceInfo si = info.serviceInfo;
+
+        XmlResourceParser parser = null;
+        try {
+            parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA);
+            if (parser == null) {
+                throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
+                        " meta-data");
+            }
+
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) {
+                eventType = parser.next();
+            }
+
+            String tagName = parser.getName();
+            if (!"host-apdu-service".equals(tagName)) {
+                throw new XmlPullParserException(
+                        "Meta-data does not start with <host-apdu-service> tag");
+            }
+
+            Resources res = pm.getResourcesForApplication(si.applicationInfo);
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+            TypedArray sa = res.obtainAttributes(attrs,
+                    com.android.internal.R.styleable.HostApduService);
+            mService = info;
+            mDescription = sa.getString(
+                    com.android.internal.R.styleable.HostApduService_description);
+            mAidGroups = new ArrayList<AidGroup>();
+            mCategoryToGroup = new HashMap<String, AidGroup>();
+            mAids = new ArrayList<String>();
+            mOnHost = true; // TODO
+            final int depth = parser.getDepth();
+            AidGroup currentGroup = null;
+
+            // Parsed values for the current AID group
+            while (((eventType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                    && eventType != XmlPullParser.END_DOCUMENT) {
+                tagName = parser.getName();
+                if (eventType == XmlPullParser.START_TAG && "aid-group".equals(tagName) &&
+                        currentGroup == null) {
+                    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);
+                    if (!CardEmulationManager.CATEGORY_PAYMENT.equals(groupCategory)) {
+                        groupCategory = CardEmulationManager.CATEGORY_OTHER;
+                    }
+                    currentGroup = mCategoryToGroup.get(groupCategory);
+                    if (currentGroup != null) {
+                        if (!CardEmulationManager.CATEGORY_OTHER.equals(groupCategory)) {
+                            Log.e(TAG, "Not allowing multiple aid-groups in the " +
+                                    groupCategory + " category");
+                            currentGroup = null;
+                        }
+                    } else {
+                        currentGroup = new AidGroup(groupCategory, groupDescription);
+                    }
+                } 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);
+                        }
+                    } else {
+                        Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs");
+                    }
+                    currentGroup = null;
+                } else if (eventType == XmlPullParser.START_TAG && "aid-filter".equals(tagName) &&
+                        currentGroup != null) {
+                    final TypedArray a = res.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.AidFilter);
+                    String aid = a.getString(com.android.internal.R.styleable.AidFilter_name);
+                    if (isValidAid(aid) && !currentGroup.aids.contains(aid)) {
+                        currentGroup.aids.add(aid);
+                        mAids.add(aid);
+                    } else {
+                        Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
+                    }
+                }
+            }
+        } catch (NameNotFoundException e) {
+            throw new XmlPullParserException("Unable to create context for: " + si.packageName);
+        } finally {
+            if (parser != null) parser.close();
+        }
+    }
+
+    public ComponentName getComponent() {
+        return new ComponentName(mService.serviceInfo.packageName,
+                mService.serviceInfo.name);
+    }
+
+    public ArrayList<String> getAids() {
+        return mAids;
+    }
+
+    public ArrayList<AidGroup> getAidGroups() {
+        return mAidGroups;
+    }
+
+    public boolean hasCategory(String category) {
+        return mCategoryToGroup.containsKey(category);
+    }
+
+    public CharSequence loadLabel(PackageManager pm) {
+        return mService.loadLabel(pm);
+    }
+
+    public Drawable loadIcon(PackageManager pm) {
+        return mService.loadIcon(pm);
+    }
+
+    static boolean isValidAid(String aid) {
+        if (aid == null)
+            return false;
+
+        int aidLength = aid.length();
+        if (aidLength == 0 || (aidLength % 2) != 0) {
+            Log.e(TAG, "AID " + aid + " is not correctly formatted.");
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder out = new StringBuilder("ApduService: ");
+        out.append(getComponent());
+        out.append(", description: " + mDescription);
+        out.append(", AID Groups: ");
+        for (AidGroup aidGroup : mAidGroups) {
+            out.append(aidGroup.toString());
+        }
+        return out.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ApduServiceInfo)) return false;
+        ApduServiceInfo thatService = (ApduServiceInfo) o;
+
+        return thatService.getComponent().equals(this.getComponent());
+    }
+
+    @Override
+    public int hashCode() {
+        return getComponent().hashCode();
+    }
+
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        mService.writeToParcel(dest, flags);
+        dest.writeString(mDescription);
+        dest.writeInt(mAidGroups.size());
+        if (mAidGroups.size() > 0) {
+            dest.writeTypedList(mAidGroups);
+        }
+    };
+
+    public static final Parcelable.Creator<ApduServiceInfo> CREATOR =
+            new Parcelable.Creator<ApduServiceInfo>() {
+        @Override
+        public ApduServiceInfo createFromParcel(Parcel source) {
+            ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
+            String description = source.readString();
+            ArrayList<AidGroup> aidGroups = new ArrayList<AidGroup>();
+            int numGroups = source.readInt();
+            if (numGroups > 0) {
+                source.readTypedList(aidGroups, AidGroup.CREATOR);
+            }
+            return new ApduServiceInfo(info, description, aidGroups);
+        }
+
+        @Override
+        public ApduServiceInfo[] newArray(int size) {
+            return new ApduServiceInfo[size];
+        }
+    };
+
+    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 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];
+            }
+        };
+    }
+}
diff --git a/core/java/android/nfc/cardemulation/CardEmulationManager.java b/core/java/android/nfc/cardemulation/CardEmulationManager.java
new file mode 100644
index 0000000..abfeaf9
--- /dev/null
+++ b/core/java/android/nfc/cardemulation/CardEmulationManager.java
@@ -0,0 +1,236 @@
+/*
+ * 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;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.ActivityThread;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.nfc.INfcCardEmulation;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+
+public final class CardEmulationManager {
+    static final String TAG = "CardEmulationManager";
+
+    /**
+     * Activity action: ask the user to change the default
+     * card emulation service for a certain category. This will
+     * show a dialog that asks the user whether he wants to
+     * replace the current default service with the service
+     * identified with the ComponentName specified in
+     * {@link #EXTRA_SERVICE_COMPONENT}, for the category
+     * specified in {@link #EXTRA_CATEGORY}
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CHANGE_DEFAULT =
+            "android.nfc.cardemulation.ACTION_CHANGE_DEFAULT";
+
+    /**
+     * The category extra for {@link #ACTION_CHANGE_DEFAULT}
+     *
+     * @see #ACTION_CHANGE_DEFAULT
+     */
+    public static final String EXTRA_CATEGORY = "category";
+
+    /**
+     * The ComponentName of the card emulation service component.
+     *
+     * @see #ACTION_CHANGE_DEFAULT
+     */
+    public static final String EXTRA_SERVICE_COMPONENT = "component";
+
+    /**
+     * The payment category can be used to indicate that an AID
+     * represents a payment application.
+     */
+    public static final String CATEGORY_PAYMENT = "payment";
+
+    /**
+     * If an AID group does not contain a category, or the
+     * specified category is not defined by the platform version
+     * that is parsing the AID group, all AIDs in the group will
+     * automatically be categorized under the {@link #CATEGORY_OTHER}
+     * category.
+     */
+    public static final String CATEGORY_OTHER = "other";
+
+    static boolean sIsInitialized = false;
+    static HashMap<Context, CardEmulationManager> sCardEmuManagers = new HashMap();
+    static INfcCardEmulation sService;
+
+    /**
+     * @hide
+     */
+    public static final String PAYMENT_MODE_AUTO = "auto";
+
+    /**
+     * @hide
+     */
+    public static final String PAYMENT_MODE_MANUAL = "manual";
+
+    final Context mContext;
+
+    private CardEmulationManager(Context context, INfcCardEmulation service) {
+        mContext = context.getApplicationContext();
+        sService = service;
+    }
+
+    public static synchronized CardEmulationManager 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) {
+            IPackageManager pm = ActivityThread.getPackageManager();
+            if (pm == null) {
+                Log.e(TAG, "Cannot get PackageManager");
+                throw new UnsupportedOperationException();
+            }
+            try {
+                if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HCE)) {
+                    Log.e(TAG, "This device does not support card emulation");
+                    throw new UnsupportedOperationException();
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "PackageManager query failed.");
+                throw new UnsupportedOperationException();
+            }
+            sIsInitialized = true;
+        }
+        CardEmulationManager manager = sCardEmuManagers.get(context);
+        if (manager == null) {
+            // Get card emu service
+            INfcCardEmulation service = adapter.getCardEmulationService();
+            manager = new CardEmulationManager(context, service);
+            sCardEmuManagers.put(context, manager);
+        }
+        return manager;
+    }
+
+    /**
+     * Allows an application to query whether a service is currently
+     * the default service to handle a card emulation category.
+     *
+     * @param service The ComponentName of the service
+     * @param category The category
+     * @return whether service is currently the default service for the category.
+     */
+    public boolean isDefaultServiceForCategory(ComponentName service, String category) {
+        try {
+            return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service, category);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            try {
+                return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service,
+                        category);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     *
+     * Allows an application to query whether a service is currently
+     * the default handler for a specified ISO7816-4 Application ID.
+     *
+     * @param service The ComponentName of the service
+     * @param aid The ISO7816-4 Application ID
+     * @return
+     */
+    public boolean isDefaultServiceForAid(ComponentName service, String aid) {
+        try {
+            return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * @return
+     */
+    public boolean setDefaultServiceForCategory(ComponentName service, String category) {
+        try {
+            return sService.setDefaultServiceForCategory(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.setDefaultServiceForCategory(UserHandle.myUserId(), service,
+                        category);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public List<ApduServiceInfo> getServices(String category) {
+        try {
+            return sService.getServices(UserHandle.myUserId(), category);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return null;
+            }
+            try {
+                return sService.getServices(UserHandle.myUserId(), category);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return null;
+            }
+        }
+    }
+
+    void recoverService() {
+        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+        sService = adapter.getCardEmulationService();
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/core/java/android/nfc/cardemulation/HostApduService.java
index 0624dc2..6d091c1 100644
--- a/core/java/android/nfc/cardemulation/HostApduService.java
+++ b/core/java/android/nfc/cardemulation/HostApduService.java
@@ -28,7 +28,8 @@
  *     &lt;meta-data android:name="android.nfc.HostApduService" android:resource="@xml/apduservice.xml"/&gt;
  * &lt;/service&gt;</pre>
  * <p>For more details refer to {@link #SERVICE_META_DATA},
- * <code>&lt;{@link android.R.styleable#HostApduService host-apdu-service}&gt;</code> and
+ * <code>&lt;{@link android.R.styleable#HostApduService host-apdu-service}&gt;</code>,
+ * <code>&lt;{@link android.R.styleable#AidGroup aid-group}&gt;</code> and
  * <code>&lt;{@link android.R.styleable#AidFilter aid-filter}&gt;</code>.
  * <p class="note">The Android platform currently only supports a single
  * logical channel.
diff --git a/core/java/android/nfc/cardemulation/OffHostApduService.java b/core/java/android/nfc/cardemulation/OffHostApduService.java
index 56bb93e..79599db 100644
--- a/core/java/android/nfc/cardemulation/OffHostApduService.java
+++ b/core/java/android/nfc/cardemulation/OffHostApduService.java
@@ -32,7 +32,8 @@
  *     &lt;meta-data android:name="android.nfc.OffHostApduService" android:resource="@xml/apduservice.xml"/&gt;
  * &lt;/service&gt;</pre>
  * <p>For more details refer to {@link #SERVICE_META_DATA},
- * <code>&lt;{@link android.R.styleable#OffHostApduService offhost-apdu-service}&gt;</code> and
+ * <code>&lt;{@link android.R.styleable#OffHostApduService offhost-apdu-service}&gt;</code>,
+ * <code>&lt;{@link android.R.styleable#AidGroup aid-group}&gt;</code> and
  * <code>&lt;{@link android.R.styleable#AidFilter aid-filter}&gt;</code>.
  */
 public abstract class OffHostApduService extends Service {
@@ -64,4 +65,4 @@
      * would need to override this method.
      */
     public abstract IBinder onBind(Intent intent);
-}
\ No newline at end of file
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 138b6e4..c926399 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4196,6 +4196,18 @@
         public static final String SCREENSAVER_DEFAULT_COMPONENT = "screensaver_default_component";
 
         /**
+         * The default NFC payment component
+         * @hide
+         */
+        public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
+
+        /**
+         * Whether to automatically invoke NFC payment app or manually select on tap.
+         * @hide
+         */
+        public static final String NFC_PAYMENT_MODE = "nfc_payment_mode";
+
+        /**
          * Name of a package that the current user has explicitly allowed to see all of that
          * user's notifications.
          *
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 378bce6..74d383f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1864,6 +1864,14 @@
         android:description="@string/permdesc_bindPrintService"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by an {@link android.nfc.cardemulation.HostApduService}
+         or {@link android.nfc.cardemulation.OffHostApduService} to ensure that only
+         the system can bind to it. -->
+    <permission android:name="android.permission.BIND_NFC_SERVICE"
+        android:label="@string/permlab_bindNfcService"
+        android:description="@string/permdesc_bindNfcService"
+        android:protectionLevel="signature" />
+
     <!-- Allows an application to call APIs that give it access to all print jobs
          on the device. Usually an app can access only the print jobts it created.
          This permission is not available to third party applications.
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 67a32fd..f7d0282 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2603,16 +2603,27 @@
         <attr name="description" />
     </declare-styleable>
 
-    <!-- Specify one or more <code>aid-filter</code> elements inside a
+    <!-- Specify one or more <code>aid-group</code> elements inside a
          <code>host-apdu-service</code> or <code>offhost-apdu-service</code>
-         element to list the ISO7816 Application ID (AIDs) your service can handle.-->
+         element to define a group of ISO7816 Application ID (AIDs) that
+         your service can handle.-->
+    <declare-styleable name="AidGroup">
+        <!-- Short description of what the AID group implements. This attribute is mandatory.-->
+        <attr name="description" />
+        <!-- The category attribute will be used by the Android platform to present
+             multiple applications that register AIDs in the same category uniformly.
+             Additionally, when a category is specified, Android will ensure that either
+             all AIDs in this group are routed to this application, or none at all.
+             This attribute is optional.-->
+        <attr name="category" format="string" />
+    </declare-styleable>
+
+    <!-- Specify one or more <code>aid-filter</code> elements inside a
+         <code>aid-group</code> element to specify an ISO7816 Application ID (AID)
+         your service can handle. -->
     <declare-styleable name="AidFilter">
         <!-- The ISO7816 Application ID. This attribute is mandatory. -->
         <attr name="name" />
-        <!-- Short description of what the AID implements. This attribute is mandatory.-->
-        <attr name="description" />
-        <!-- Category. This attribute is optional.-->
-        <attr name="category" format="string" />
     </declare-styleable>
 
     <declare-styleable name="ActionMenuItemView">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8743119..eb9941a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -990,6 +990,13 @@
     <string name="permdesc_accessAllPrintJobs">Allows the holder to access print jobs
         created by another app. Should never be needed for normal apps.</string>
 
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_bindNfcService">bind to NFC service</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_bindNfcService">Allows the holder to bind to applications
+        that are emulating NFC cards. Should never be needed for normal apps.</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_bindTextService">bind to a text service</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->