Revert "Revert "nfc(framework): Split non-updatable portion to a..."

Revert submission 3414164-revert-3411958-framework-nfc-code-split-TOOKKZAIEM

Reason for revert: Reland with some missing changes from aosp/main merged to avoid automerger conflicts.

Reverted changes: /q/submissionid:3414164-revert-3411958-framework-nfc-code-split-TOOKKZAIEM

Bug: 367426693 
Change-Id: I620692a29e21d4a1d1083521894a4731648bbc3f
Test: Compiles
diff --git a/nfc-non-updatable/Android.bp b/nfc-non-updatable/Android.bp
new file mode 100644
index 0000000..ff987bb
--- /dev/null
+++ b/nfc-non-updatable/Android.bp
@@ -0,0 +1,23 @@
+package {
+    default_team: "trendy_team_fwk_nfc",
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+filegroup {
+    name: "framework-nfc-non-updatable-sources",
+    path: "java",
+    srcs: [
+        "java/android/nfc/NfcServiceManager.java",
+        "java/android/nfc/cardemulation/ApduServiceInfo.aidl",
+        "java/android/nfc/cardemulation/ApduServiceInfo.java",
+        "java/android/nfc/cardemulation/NfcFServiceInfo.aidl",
+        "java/android/nfc/cardemulation/NfcFServiceInfo.java",
+        "java/android/nfc/cardemulation/AidGroup.aidl",
+        "java/android/nfc/cardemulation/AidGroup.java",
+    ],
+}
diff --git a/nfc-non-updatable/OWNERS b/nfc-non-updatable/OWNERS
new file mode 100644
index 0000000..f46dccd
--- /dev/null
+++ b/nfc-non-updatable/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+include platform/packages/apps/Nfc:/OWNERS
\ No newline at end of file
diff --git a/nfc-non-updatable/flags/flags.aconfig b/nfc-non-updatable/flags/flags.aconfig
new file mode 100644
index 0000000..ee287ab
--- /dev/null
+++ b/nfc-non-updatable/flags/flags.aconfig
@@ -0,0 +1,191 @@
+package: "android.nfc"
+container: "system"
+
+flag {
+    name: "nfc_event_listener"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enable NFC Event listener APIs"
+    bug: "356447790"
+}
+
+flag {
+    name: "enable_nfc_mainline"
+    is_exported: true
+    namespace: "nfc"
+    description: "Flag for NFC mainline changes"
+    bug: "292140387"
+}
+
+flag {
+    name: "enable_nfc_reader_option"
+    is_exported: true
+    namespace: "nfc"
+    description: "Flag for NFC reader option API changes"
+    bug: "291187960"
+}
+
+flag {
+    name: "enable_nfc_user_restriction"
+    is_exported: true
+    namespace: "nfc"
+    description: "Flag for NFC user restriction"
+    bug: "291187960"
+}
+
+flag {
+    name: "nfc_observe_mode"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enable NFC Observe Mode"
+    bug: "294217286"
+}
+
+flag {
+    name: "nfc_read_polling_loop"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enable NFC Polling Loop Notifications"
+    bug: "294217286"
+}
+
+flag {
+    name: "nfc_observe_mode_st_shim"
+    namespace: "nfc"
+    description: "Enable NFC Observe Mode ST shim"
+    bug: "294217286"
+}
+
+flag {
+    name: "nfc_read_polling_loop_st_shim"
+    namespace: "nfc"
+    description: "Enable NFC Polling Loop Notifications ST shim"
+    bug: "294217286"
+}
+
+flag {
+    name: "enable_tag_detection_broadcasts"
+    namespace: "nfc"
+    description: "Enable sending broadcasts to Wallet role holder when a tag enters/leaves the field."
+    bug: "306203494"
+}
+
+flag {
+    name: "enable_nfc_charging"
+    is_exported: true
+    namespace: "nfc"
+    description: "Flag for NFC charging changes"
+    bug: "292143899"
+}
+
+flag {
+    name: "enable_nfc_set_discovery_tech"
+    is_exported: true
+    namespace: "nfc"
+    description: "Flag for NFC set discovery tech API"
+    bug: "300351519"
+}
+
+flag {
+    name: "nfc_vendor_cmd"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enable NFC vendor command support"
+    bug: "289879306"
+}
+
+flag {
+    name: "nfc_oem_extension"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enable NFC OEM extension support"
+    bug: "331206243"
+}
+
+flag {
+    name: "nfc_state_change"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enable nfc state change API"
+    bug: "319934052"
+}
+
+flag {
+    name: "nfc_set_default_disc_tech"
+    is_exported: true
+    namespace: "nfc"
+    description: "Flag for NFC set default disc tech API"
+    bug: "321311407"
+}
+
+flag {
+    name: "nfc_persist_log"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enable NFC persistent log support"
+    bug: "321310044"
+}
+
+flag {
+    name: "nfc_action_manage_services_settings"
+    is_exported: true
+    namespace: "nfc"
+    description: "Add Settings.ACTION_MANAGE_OTHER_NFC_SERVICES_SETTINGS"
+    bug: "358129872"
+}
+
+flag {
+    name: "nfc_override_recover_routing_table"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enable override and recover routing table"
+    bug: "329043523"
+}
+
+flag {
+    name: "nfc_watchdog"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enable watchdog for the NFC system process"
+    bug: "362937338"
+}
+
+flag {
+    name: "enable_card_emulation_euicc"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enable EUICC card emulation"
+    bug: "321314635"
+}
+
+flag {
+    name: "nfc_state_change_security_log_event_enabled"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enabling security log for nfc state change"
+    bug: "319934052"
+}
+
+flag {
+    name: "nfc_associated_role_services"
+    is_exported: true
+    namespace: "nfc"
+    description: "Share wallet role routing priority with associated services"
+    bug: "366243361"
+}
+
+flag {
+    name: "nfc_set_service_enabled_for_category_other"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enable set service enabled for category other"
+    bug: "338157113"
+}
+
+flag {
+    name: "nfc_check_tag_intent_preference"
+    is_exported: true
+    namespace: "nfc"
+    description: "App can check its tag intent preference status"
+    bug: "335916336"
+}
diff --git a/nfc-non-updatable/java/android/nfc/NfcServiceManager.java b/nfc-non-updatable/java/android/nfc/NfcServiceManager.java
new file mode 100644
index 0000000..5582f11
--- /dev/null
+++ b/nfc-non-updatable/java/android/nfc/NfcServiceManager.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+
+/**********************************************************************
+ * This file is not a part of the NFC mainline modure                 *
+ * *******************************************************************/
+
+package android.nfc;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.ServiceManager;
+
+/**
+ * Provides a way to register and obtain the system service binder objects managed by the nfc
+ * service.
+ *
+ * @hide
+ */
+@SystemApi(client = Client.MODULE_LIBRARIES)
+public class NfcServiceManager {
+
+    /**
+     * @hide
+     */
+    public NfcServiceManager() {
+    }
+
+    /**
+     * A class that exposes the methods to register and obtain each system service.
+     */
+    public static final class ServiceRegisterer {
+        private final String mServiceName;
+
+        /**
+         * @hide
+         */
+        public ServiceRegisterer(String serviceName) {
+            mServiceName = serviceName;
+        }
+
+        /**
+         * Register a system server binding object for a service.
+         */
+        public void register(@NonNull IBinder service) {
+            ServiceManager.addService(mServiceName, service);
+        }
+
+        /**
+         * Get the system server binding object for a service.
+         *
+         * <p>This blocks until the service instance is ready,
+         * or a timeout happens, in which case it returns null.
+         */
+        @Nullable
+        public IBinder get() {
+            return ServiceManager.getService(mServiceName);
+        }
+
+        /**
+         * Get the system server binding object for a service.
+         *
+         * <p>This blocks until the service instance is ready,
+         * or a timeout happens, in which case it throws {@link ServiceNotFoundException}.
+         */
+        @NonNull
+        public IBinder getOrThrow() throws ServiceNotFoundException {
+            try {
+                return ServiceManager.getServiceOrThrow(mServiceName);
+            } catch (ServiceManager.ServiceNotFoundException e) {
+                throw new ServiceNotFoundException(mServiceName);
+            }
+        }
+
+        /**
+         * Get the system server binding object for a service. If the specified service is
+         * not available, it returns null.
+         */
+        @Nullable
+        public IBinder tryGet() {
+            return ServiceManager.checkService(mServiceName);
+        }
+    }
+
+    /**
+     * See {@link ServiceRegisterer#getOrThrow}.
+     *
+     */
+    public static class ServiceNotFoundException extends ServiceManager.ServiceNotFoundException {
+        /**
+         * Constructor.
+         *
+         * @param name the name of the binder service that cannot be found.
+         *
+         */
+        public ServiceNotFoundException(@NonNull String name) {
+            super(name);
+        }
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the "nfc" service.
+     */
+    @NonNull
+    public ServiceRegisterer getNfcManagerServiceRegisterer() {
+        return new ServiceRegisterer(Context.NFC_SERVICE);
+    }
+}
diff --git a/nfc-non-updatable/java/android/nfc/cardemulation/AidGroup.aidl b/nfc-non-updatable/java/android/nfc/cardemulation/AidGroup.aidl
new file mode 100644
index 0000000..56d6fa5
--- /dev/null
+++ b/nfc-non-updatable/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/nfc-non-updatable/java/android/nfc/cardemulation/AidGroup.java b/nfc-non-updatable/java/android/nfc/cardemulation/AidGroup.java
new file mode 100644
index 0000000..ae3e333
--- /dev/null
+++ b/nfc-non-updatable/java/android/nfc/cardemulation/AidGroup.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.cardemulation;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.nfc.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+/**********************************************************************
+ * This file is not a part of the NFC mainline module                 *
+ * *******************************************************************/
+
+/**
+ * The AidGroup class represents a group of Application Identifiers (AIDs).
+ *
+ * <p>The format of AIDs is defined in the ISO/IEC 7816-4 specification. This class
+ * requires the AIDs to be input as a hexadecimal string, with an even amount of
+ * hexadecimal characters, e.g. "F014811481".
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+public final class AidGroup implements Parcelable {
+    /**
+     * The maximum number of AIDs that can be present in any one group.
+     */
+    private static final int MAX_NUM_AIDS = 256;
+
+    private static final String TAG = "AidGroup";
+
+
+    private final List<String> mAids;
+    private final String mCategory;
+    @SuppressWarnings("unused") // Unused as of now, but part of the XML input.
+    private final String mDescription;
+
+    /**
+     * Creates a new AidGroup object.
+     *
+     * @param aids list of AIDs present in the group
+     * @param category category of this group, e.g. {@link CardEmulation#CATEGORY_PAYMENT}
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public AidGroup(@NonNull List<String> aids, @Nullable 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.");
+        }
+        for (String aid : aids) {
+            if (!isValidAid(aid)) {
+                throw new IllegalArgumentException("AID " + aid + " is not a valid AID.");
+            }
+        }
+        if (isValidCategory(category)) {
+            this.mCategory = category;
+        } else {
+            this.mCategory = CardEmulation.CATEGORY_OTHER;
+        }
+        this.mAids = new ArrayList<String>(aids.size());
+        for (String aid : aids) {
+            this.mAids.add(aid.toUpperCase(Locale.US));
+        }
+        this.mDescription = null;
+    }
+
+    /**
+     * Creates a new AidGroup object.
+     *
+     * @param category category of this group, e.g. {@link CardEmulation#CATEGORY_PAYMENT}
+     * @param description description of this group
+     */
+    AidGroup(@NonNull String category, @NonNull String description) {
+        this.mAids = new ArrayList<String>();
+        this.mCategory = category;
+        this.mDescription = description;
+    }
+
+    /**
+     * Returns the category of this group.
+     * @return the category of this AID group
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public String getCategory() {
+        return mCategory;
+    }
+
+    /**
+     * Returns the list of AIDs in this group.
+     *
+     * @return the list of AIDs in this group
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public List<String> getAids() {
+        return mAids;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder out = new StringBuilder("Category: " + mCategory
+                + ", AIDs:");
+        for (String aid : mAids) {
+            out.append(aid);
+            out.append(", ");
+        }
+        return out.toString();
+    }
+
+    /**
+     * Dump debugging info as AidGroupProto.
+     *
+     * If the output belongs to a sub message, the caller is responsible for wrapping this function
+     * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}.
+     *
+     * @param proto the ProtoOutputStream to write to
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void dump(@NonNull ProtoOutputStream proto) {
+        proto.write(AidGroupProto.CATEGORY, mCategory);
+        for (String aid : mAids) {
+            proto.write(AidGroupProto.AIDS, aid);
+        }
+    }
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mCategory);
+        dest.writeInt(mAids.size());
+        if (mAids.size() > 0) {
+            dest.writeStringList(mAids);
+        }
+    }
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public static final @NonNull Parcelable.Creator<AidGroup> CREATOR =
+            new Parcelable.Creator<AidGroup>() {
+
+        @Override
+        public AidGroup createFromParcel(Parcel source) {
+            String category = source.readString8();
+            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];
+        }
+    };
+
+    /**
+     * Create an instance of AID group from XML file.
+     *
+     * @param parser input xml parser stream
+     * @throws XmlPullParserException If an error occurs parsing the element.
+     * @throws IOException If an error occurs reading the element.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @Nullable
+    public static AidGroup createFromXml(@NonNull XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        String category = null;
+        ArrayList<String> aids = new ArrayList<String>();
+        AidGroup group = null;
+        boolean inGroup = false;
+
+        int eventType = parser.getEventType();
+        int minDepth = parser.getDepth();
+        while (eventType != XmlPullParser.END_DOCUMENT && parser.getDepth() >= minDepth) {
+            String tagName = parser.getName();
+            if (eventType == XmlPullParser.START_TAG) {
+                if (tagName.equals("aid")) {
+                    if (inGroup) {
+                        String aid = parser.getAttributeValue(null, "value");
+                        if (aid != null) {
+                            aids.add(aid.toUpperCase());
+                        }
+                    } else {
+                        Log.d(TAG, "Ignoring <aid> tag while not in group");
+                    }
+                } else if (tagName.equals("aid-group")) {
+                    category = parser.getAttributeValue(null, "category");
+                    if (category == null) {
+                        Log.e(TAG, "<aid-group> tag without valid category");
+                        return null;
+                    }
+                    inGroup = true;
+                } else {
+                    Log.d(TAG, "Ignoring unexpected tag: " + tagName);
+                }
+            } else if (eventType == XmlPullParser.END_TAG) {
+                if (tagName.equals("aid-group") && inGroup && aids.size() > 0) {
+                    group = new AidGroup(aids, category);
+                    break;
+                }
+            }
+            eventType = parser.next();
+        }
+        return group;
+    }
+
+    /**
+     * Serialize instance of AID group to XML file.
+     * @param out XML serializer stream
+     * @throws IOException If an error occurs reading the element.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void writeAsXml(@NonNull XmlSerializer out) throws IOException {
+        out.startTag(null, "aid-group");
+        out.attribute(null, "category", mCategory);
+        for (String aid : mAids) {
+            out.startTag(null, "aid");
+            out.attribute(null, "value", aid);
+            out.endTag(null, "aid");
+        }
+        out.endTag(null, "aid-group");
+    }
+
+    private static boolean isValidCategory(String category) {
+        return CardEmulation.CATEGORY_PAYMENT.equals(category) ||
+                CardEmulation.CATEGORY_OTHER.equals(category);
+    }
+
+    private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+    /**
+     * Copied over from {@link CardEmulation#isValidAid(String)}
+     * @hide
+     */
+    private static boolean isValidAid(String aid) {
+        if (aid == null)
+            return false;
+
+        // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+        if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+        if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        // Verify hex characters
+        if (!AID_PATTERN.matcher(aid).matches()) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.aidl b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.aidl
new file mode 100644
index 0000000..a62fdd6
--- /dev/null
+++ b/nfc-non-updatable/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/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
new file mode 100644
index 0000000..7f64dbe
--- /dev/null
+++ b/nfc-non-updatable/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -0,0 +1,1195 @@
+/*
+ * 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.
+ */
+
+/**********************************************************************
+ * This file is not a part of the NFC mainline module                 *
+ * *******************************************************************/
+
+package android.nfc.cardemulation;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.nfc.Flags;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.regex.Pattern;
+
+/**
+ * Class holding APDU service info.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+public final class ApduServiceInfo implements Parcelable {
+    private static final String TAG = "ApduServiceInfo";
+
+    /**
+     * Component level {@link android.content.pm.PackageManager.Property PackageManager
+     * .Property} for a system application to change its icon and label
+     * on the default applications page. This property should be added to an
+     * {@link HostApduService} declaration in the manifest.
+     *
+     * <p>For example:
+     * <pre>
+     * &lt;service
+     *     android:apduServiceBanner="@drawable/product_logo"
+     *     android:label="@string/service_label"&gt
+     *      &lt;property
+     *          android:name="android.content.PROPERTY_WALLET_ICON_AND_LABEL_HOLDER"
+     *          android:value="true"/&gt;
+     * &lt/service&gt;
+     * </pre>
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(android.permission.flags.Flags.FLAG_WALLET_ROLE_ICON_PROPERTY_ENABLED)
+    public static final String PROPERTY_WALLET_PREFERRED_BANNER_AND_LABEL =
+            "android.nfc.cardemulation.PROPERTY_WALLET_PREFERRED_BANNER_AND_LABEL";
+
+    /**
+     * The service that implements this
+     */
+    private final ResolveInfo mService;
+
+    /**
+     * Description of the service
+     */
+    private final String mDescription;
+
+    /**
+     * Whether this service represents AIDs running on the host CPU
+     */
+    private final boolean mOnHost;
+
+    /**
+     * Offhost reader name.
+     * eg: SIM, eSE etc
+     */
+    private String mOffHostName;
+
+    /**
+     * Offhost reader name from manifest file.
+     * Used for resetOffHostSecureElement()
+     */
+    private final String mStaticOffHostName;
+
+    /**
+     * Mapping from category to static AID group
+     */
+    private final HashMap<String, AidGroup> mStaticAidGroups;
+
+    /**
+     * Mapping from category to dynamic AID group
+     */
+    private final HashMap<String, AidGroup> mDynamicAidGroups;
+
+
+    private final Map<String, Boolean> mAutoTransact;
+
+    private final Map<Pattern, Boolean> mAutoTransactPatterns;
+
+    /**
+     * Whether this service should only be started when the device is unlocked.
+     */
+    private final boolean mRequiresDeviceUnlock;
+
+    /**
+     * Whether this service should only be started when the device is screen on.
+     */
+    private final boolean mRequiresDeviceScreenOn;
+
+    /**
+     * The id of the service banner specified in XML.
+     */
+    private final int mBannerResourceId;
+
+    /**
+     * The uid of the package the service belongs to
+     */
+    private final int mUid;
+
+    /**
+     * Settings Activity for this service
+     */
+    private final String mSettingsActivityName;
+
+    /**
+     * State of the service for CATEGORY_OTHER selection
+     */
+    private boolean mCategoryOtherServiceEnabled;
+
+    /**
+     * Whether the NFC stack should default to Observe Mode when this preferred service.
+     */
+    private boolean mShouldDefaultToObserveMode;
+
+    /**
+     * Whether or not this service wants to share the same routing priority as the
+     * Wallet role owner.
+     */
+    private boolean mWantsRoleHolderPriority;
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
+            ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
+            boolean requiresUnlock, int bannerResource, int uid,
+            String settingsActivityName, String offHost, String staticOffHost) {
+        this(info, onHost, description, staticAidGroups, dynamicAidGroups,
+                requiresUnlock, bannerResource, uid, settingsActivityName,
+                offHost, staticOffHost, false);
+    }
+
+    /**
+     * @hide
+     */
+    public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
+            ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
+            boolean requiresUnlock, int bannerResource, int uid,
+            String settingsActivityName, String offHost, String staticOffHost,
+            boolean isEnabled) {
+        this(info, onHost, description, staticAidGroups, dynamicAidGroups,
+                requiresUnlock, onHost ? true : false, bannerResource, uid,
+                settingsActivityName, offHost, staticOffHost, isEnabled);
+    }
+
+    /**
+     * @hide
+     */
+    public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
+            List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
+            boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid,
+            String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled) {
+        this(info, onHost, description, staticAidGroups, dynamicAidGroups,
+                requiresUnlock, requiresScreenOn, bannerResource, uid,
+                settingsActivityName, offHost, staticOffHost, isEnabled,
+                new HashMap<String, Boolean>(), new TreeMap<>(
+                        Comparator.comparing(Pattern::toString)));
+    }
+
+    /**
+     * @hide
+     */
+    public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
+            List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
+            boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid,
+            String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled,
+            Map<String, Boolean> autoTransact, Map<Pattern, Boolean> autoTransactPatterns) {
+        this.mService = info;
+        this.mDescription = description;
+        this.mStaticAidGroups = new HashMap<String, AidGroup>();
+        this.mDynamicAidGroups = new HashMap<String, AidGroup>();
+        this.mAutoTransact = autoTransact;
+        this.mAutoTransactPatterns = autoTransactPatterns;
+        this.mOffHostName = offHost;
+        this.mStaticOffHostName = staticOffHost;
+        this.mOnHost = onHost;
+        this.mRequiresDeviceUnlock = requiresUnlock;
+        this.mRequiresDeviceScreenOn = requiresScreenOn;
+        for (AidGroup aidGroup : staticAidGroups) {
+            this.mStaticAidGroups.put(aidGroup.getCategory(), aidGroup);
+        }
+        for (AidGroup aidGroup : dynamicAidGroups) {
+            this.mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
+        }
+        this.mBannerResourceId = bannerResource;
+        this.mUid = uid;
+        this.mSettingsActivityName = settingsActivityName;
+        this.mCategoryOtherServiceEnabled = isEnabled;
+    }
+
+    /**
+     * Creates a new ApduServiceInfo object.
+     *
+     * @param pm packageManager instance
+     * @param info app component info
+     * @param onHost whether service is on host or not (secure element)
+     * @throws XmlPullParserException If an error occurs parsing the element.
+     * @throws IOException If an error occurs reading the element.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public ApduServiceInfo(@NonNull PackageManager pm, @NonNull ResolveInfo info, boolean onHost)
+            throws XmlPullParserException, IOException {
+        ServiceInfo si = info.serviceInfo;
+        XmlResourceParser parser = null;
+        try {
+            if (onHost) {
+                parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA);
+                if (parser == null) {
+                    throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
+                            " meta-data");
+                }
+            } else {
+                parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA);
+                if (parser == null) {
+                    throw new XmlPullParserException("No " + OffHostApduService.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 (onHost && !"host-apdu-service".equals(tagName)) {
+                throw new XmlPullParserException(
+                        "Meta-data does not start with <host-apdu-service> tag");
+            } else if (!onHost && !"offhost-apdu-service".equals(tagName)) {
+                throw new XmlPullParserException(
+                        "Meta-data does not start with <offhost-apdu-service> tag");
+            }
+
+            Resources res = pm.getResourcesForApplication(si.applicationInfo);
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+            if (onHost) {
+                TypedArray sa = res.obtainAttributes(attrs,
+                        com.android.internal.R.styleable.HostApduService);
+                mService = info;
+                mDescription = sa.getString(
+                        com.android.internal.R.styleable.HostApduService_description);
+                mRequiresDeviceUnlock = sa.getBoolean(
+                        com.android.internal.R.styleable.HostApduService_requireDeviceUnlock,
+                        false);
+                mRequiresDeviceScreenOn = sa.getBoolean(
+                        com.android.internal.R.styleable.HostApduService_requireDeviceScreenOn,
+                        true);
+                mBannerResourceId = sa.getResourceId(
+                        com.android.internal.R.styleable.HostApduService_apduServiceBanner, -1);
+                mSettingsActivityName = sa.getString(
+                        com.android.internal.R.styleable.HostApduService_settingsActivity);
+                mOffHostName = null;
+                mStaticOffHostName = mOffHostName;
+                mShouldDefaultToObserveMode = sa.getBoolean(
+                        R.styleable.HostApduService_shouldDefaultToObserveMode,
+                        false);
+                if (Flags.nfcAssociatedRoleServices()) {
+                    mWantsRoleHolderPriority = sa.getBoolean(
+                            R.styleable.HostApduService_wantsRoleHolderPriority,
+                            false
+                    );
+                }
+                sa.recycle();
+            } else {
+                TypedArray sa = res.obtainAttributes(attrs,
+                        com.android.internal.R.styleable.OffHostApduService);
+                mService = info;
+                mDescription = sa.getString(
+                        com.android.internal.R.styleable.OffHostApduService_description);
+                mRequiresDeviceUnlock = sa.getBoolean(
+                        com.android.internal.R.styleable.OffHostApduService_requireDeviceUnlock,
+                        false);
+                mRequiresDeviceScreenOn = sa.getBoolean(
+                        com.android.internal.R.styleable.OffHostApduService_requireDeviceScreenOn,
+                        false);
+                mBannerResourceId = sa.getResourceId(
+                        com.android.internal.R.styleable.OffHostApduService_apduServiceBanner, -1);
+                mSettingsActivityName = sa.getString(
+                        com.android.internal.R.styleable.HostApduService_settingsActivity);
+                mOffHostName = sa.getString(
+                        com.android.internal.R.styleable.OffHostApduService_secureElementName);
+                mShouldDefaultToObserveMode = sa.getBoolean(
+                        R.styleable.OffHostApduService_shouldDefaultToObserveMode,
+                        false);
+                if (mOffHostName != null) {
+                    if (mOffHostName.equals("eSE")) {
+                        mOffHostName = "eSE1";
+                    } else if (mOffHostName.equals("SIM")) {
+                        mOffHostName = "SIM1";
+                    }
+                }
+                mStaticOffHostName = mOffHostName;
+                if (Flags.nfcAssociatedRoleServices()) {
+                    mWantsRoleHolderPriority = sa.getBoolean(
+                            R.styleable.OffHostApduService_wantsRoleHolderPriority,
+                            false
+                    );
+                }
+                sa.recycle();
+            }
+
+            mStaticAidGroups = new HashMap<String, AidGroup>();
+            mDynamicAidGroups = new HashMap<String, AidGroup>();
+            mAutoTransact = new HashMap<String, Boolean>();
+            mAutoTransactPatterns = new TreeMap<Pattern, Boolean>(
+                    Comparator.comparing(Pattern::toString));
+            mOnHost = onHost;
+
+            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 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 = mStaticAidGroups.get(groupCategory);
+                    if (currentGroup != null) {
+                        if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
+                            Log.e(TAG, "Not allowing multiple aid-groups in the " +
+                                    groupCategory + " category");
+                            currentGroup = null;
+                        }
+                    } else {
+                        currentGroup = new AidGroup(groupCategory, groupDescription);
+                    }
+                    groupAttrs.recycle();
+                } else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) &&
+                        currentGroup != null) {
+                    if (currentGroup.getAids().size() > 0) {
+                        if (!mStaticAidGroups.containsKey(currentGroup.getCategory())) {
+                            mStaticAidGroups.put(currentGroup.getCategory(), 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).
+                            toUpperCase();
+                    if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+                        currentGroup.getAids().add(aid);
+                    } else {
+                        Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
+                    }
+                    a.recycle();
+                } else if (eventType == XmlPullParser.START_TAG &&
+                        "aid-prefix-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).
+                            toUpperCase();
+                    // Add wildcard char to indicate prefix
+                    aid = aid.concat("*");
+                    if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+                        currentGroup.getAids().add(aid);
+                    } else {
+                        Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
+                    }
+                    a.recycle();
+                } else if (eventType == XmlPullParser.START_TAG &&
+                        tagName.equals("aid-suffix-filter") && 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).
+                            toUpperCase();
+                    // Add wildcard char to indicate suffix
+                    aid = aid.concat("#");
+                    if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+                        currentGroup.getAids().add(aid);
+                    } else {
+                        Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
+                    }
+                    a.recycle();
+                } else if (eventType == XmlPullParser.START_TAG
+                        && "polling-loop-filter".equals(tagName) && currentGroup == null) {
+                    final TypedArray a = res.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.PollingLoopFilter);
+                    String plf =
+                            a.getString(com.android.internal.R.styleable.PollingLoopFilter_name)
+                            .toUpperCase(Locale.ROOT);
+                    boolean autoTransact = a.getBoolean(
+                            com.android.internal.R.styleable.PollingLoopFilter_autoTransact,
+                            false);
+                    if (!mOnHost && !autoTransact) {
+                        Log.e(TAG, "Ignoring polling-loop-filter " + plf
+                                + " for offhost service that isn't autoTransact");
+                    } else {
+                        mAutoTransact.put(plf, autoTransact);
+                    }
+                    a.recycle();
+                } else if (eventType == XmlPullParser.START_TAG
+                        && "polling-loop-pattern-filter".equals(tagName) && currentGroup == null) {
+                    final TypedArray a = res.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.PollingLoopPatternFilter);
+                    String plf = a.getString(
+                            com.android.internal.R.styleable.PollingLoopPatternFilter_name)
+                                    .toUpperCase(Locale.ROOT);
+                    boolean autoTransact = a.getBoolean(
+                            com.android.internal.R.styleable.PollingLoopFilter_autoTransact,
+                            false);
+                    if (!mOnHost && !autoTransact) {
+                        Log.e(TAG, "Ignoring polling-loop-filter " + plf
+                                + " for offhost service that isn't autoTransact");
+                    } else {
+                        mAutoTransactPatterns.put(Pattern.compile(plf), autoTransact);
+                    }
+                    a.recycle();
+                }
+            }
+        } catch (NameNotFoundException e) {
+            throw new XmlPullParserException("Unable to create context for: " + si.packageName);
+        } finally {
+            if (parser != null) parser.close();
+        }
+        // Set uid
+        mUid = si.applicationInfo.uid;
+
+        mCategoryOtherServiceEnabled = true;    // support other category
+
+    }
+
+    /**
+     * Returns the app component corresponding to this APDU service.
+     *
+     * @return app component for this service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public ComponentName getComponent() {
+        return new ComponentName(mService.serviceInfo.packageName,
+                mService.serviceInfo.name);
+    }
+
+    /**
+     * Returns the offhost secure element name (if the service is offhost).
+     *
+     * @return offhost secure element name for offhost services
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @Nullable
+    public String getOffHostSecureElement() {
+        return mOffHostName;
+    }
+
+    /**
+     * 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
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public List<String> getAids() {
+        final ArrayList<String> aids = new ArrayList<String>();
+        for (AidGroup group : getAidGroups()) {
+            aids.addAll(group.getAids());
+        }
+        return aids;
+    }
+
+    /**
+     * Returns the current polling loop filters for this service.
+     * @return List of polling loop filters.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    @NonNull
+    public List<String> getPollingLoopFilters() {
+        return new ArrayList<>(mAutoTransact.keySet());
+    }
+
+    /**
+     * Returns whether this service would like to automatically transact for a given plf.
+     *
+     * @param plf the polling loop filter to query.
+     * @return {@code true} indicating to auto transact, {@code false} indicating to not.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public boolean getShouldAutoTransact(@NonNull String plf) {
+        if (mAutoTransact.getOrDefault(plf.toUpperCase(Locale.ROOT), false)) {
+            return true;
+        }
+        List<Pattern> patternMatches = mAutoTransactPatterns.keySet().stream()
+                .filter(p -> p.matcher(plf).matches()).toList();
+        if (patternMatches == null || patternMatches.size() == 0) {
+            return false;
+        }
+        for (Pattern patternMatch : patternMatches) {
+            if (mAutoTransactPatterns.get(patternMatch)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the current polling loop pattern filters for this service.
+     * @return List of polling loop pattern filters.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    @NonNull
+    public List<Pattern> getPollingLoopPatternFilters() {
+        return new ArrayList<>(mAutoTransactPatterns.keySet());
+    }
+
+    /**
+     * Returns a consolidated list of AIDs with prefixes 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 prefix AIDs registered by the service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public List<String> getPrefixAids() {
+        final ArrayList<String> prefixAids = new ArrayList<String>();
+        for (AidGroup group : getAidGroups()) {
+            for (String aid : group.getAids()) {
+                if (aid.endsWith("*")) {
+                    prefixAids.add(aid);
+                }
+            }
+        }
+        return prefixAids;
+    }
+
+    /**
+     * Returns a consolidated list of AIDs with subsets 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 prefix AIDs registered by the service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public List<String> getSubsetAids() {
+        final ArrayList<String> subsetAids = new ArrayList<String>();
+        for (AidGroup group : getAidGroups()) {
+            for (String aid : group.getAids()) {
+                if (aid.endsWith("#")) {
+                    subsetAids.add(aid);
+                }
+            }
+        }
+        return subsetAids;
+    }
+
+    /**
+     * Returns the registered AID group for this category.
+     *
+     * @param category category name
+     * @return {@link AidGroup} instance for the provided category
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public AidGroup getDynamicAidGroupForCategory(@NonNull String category) {
+        return mDynamicAidGroups.get(category);
+    }
+
+    /**
+     * Removes the registered AID group for this category.
+     *
+     * @param category category name
+     * @return {@code true} if an AID group existed
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public boolean removeDynamicAidGroupForCategory(@NonNull 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
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public List<AidGroup> getAidGroups() {
+        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;
+    }
+
+    /**
+     * Returns the category to which this service has attributed the AID that is passed in,
+     * or null if we don't know this AID.
+     * @param aid AID to lookup for
+     * @return category name corresponding to this AID
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public String getCategoryForAid(@NonNull String aid) {
+        List<AidGroup> groups = getAidGroups();
+        for (AidGroup group : groups) {
+            if (group.getAids().contains(aid.toUpperCase())) {
+                return group.getCategory();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns whether there is any AID group for this category.
+     * @param category category name
+     * @return {@code true} if an AID group exists
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public boolean hasCategory(@NonNull String category) {
+        return (mStaticAidGroups.containsKey(category) || mDynamicAidGroups.containsKey(category));
+    }
+
+    /**
+     * Returns whether the service is on host or not.
+     * @return true if the service is on host (not secure element)
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public boolean isOnHost() {
+        return mOnHost;
+    }
+
+    /**
+     * Returns whether the service requires device unlock.
+     * @return whether the service requires device unlock
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public boolean requiresUnlock() {
+        return mRequiresDeviceUnlock;
+    }
+
+    /**
+     * Returns whether this service should only be started when the device is screen on.
+     * @return whether the service requires screen on
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public boolean requiresScreenOn() {
+        return mRequiresDeviceScreenOn;
+    }
+
+    /**
+     * Returns whether the NFC stack should default to observe mode when this service is preferred.
+     * @return whether the NFC stack should default to observe mode when this service is preferred
+     */
+    @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+    public boolean shouldDefaultToObserveMode() {
+        return mShouldDefaultToObserveMode;
+    }
+
+    /**
+     * Sets whether the NFC stack should default to observe mode when this service is preferred.
+     * @param shouldDefaultToObserveMode whether the NFC stack should default to observe mode when
+     *                                  this service is preferred
+     */
+    @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
+    public void setShouldDefaultToObserveMode(boolean shouldDefaultToObserveMode) {
+        mShouldDefaultToObserveMode = shouldDefaultToObserveMode;
+    }
+
+    /**
+     * Returns whether or not this service wants to share the Wallet role holder priority
+     * with other packages/services with the same signature.
+     *
+     * @return whether or not this service wants to share priority
+     */
+    @FlaggedApi(Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES)
+    public boolean wantsRoleHolderPriority() {
+        return mWantsRoleHolderPriority;
+    }
+
+    /**
+     * Returns description of service.
+     * @return user readable description of service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public String getDescription() {
+        return mDescription;
+    }
+
+    /**
+     * Returns uid of service.
+     * @return uid of the service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public int getUid() {
+        return mUid;
+    }
+
+    /**
+     * Add or replace an AID group to this service.
+     * @param aidGroup instance of aid group to set or replace
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void setDynamicAidGroup(@NonNull AidGroup aidGroup) {
+        mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
+    }
+
+    /**
+     * Add a Polling Loop Filter. Custom NFC polling frames that match this filter will be
+     * delivered to {@link HostApduService#processPollingFrames(List)}. Adding a key with this
+     * multiple times will cause the value to be overwritten each time.
+     * @param pollingLoopFilter the polling loop filter to add, must be a valid hexadecimal string
+     * @param autoTransact when true, disable observe mode when this filter matches, when false,
+     *                     matching does not change the observe mode state
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public void addPollingLoopFilter(@NonNull String pollingLoopFilter,
+            boolean autoTransact) {
+        if (!mOnHost && !autoTransact) {
+            return;
+        }
+        mAutoTransact.put(pollingLoopFilter, autoTransact);
+    }
+
+    /**
+     * Remove a Polling Loop Filter. Custom NFC polling frames that match this filter will no
+     * longer be delivered to {@link HostApduService#processPollingFrames(List)}.
+     * @param pollingLoopFilter this polling loop filter to add.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public void removePollingLoopFilter(@NonNull String pollingLoopFilter) {
+        mAutoTransact.remove(pollingLoopFilter.toUpperCase(Locale.ROOT));
+    }
+
+    /**
+     * Add a Polling Loop Pattern Filter. Custom NFC polling frames that match this filter will be
+     * delivered to {@link HostApduService#processPollingFrames(List)}. Adding a key with this
+     * multiple times will cause the value to be overwritten each time.
+     * @param pollingLoopPatternFilter the polling loop pattern filter to add, must be a valid
+     *                                regex to match a hexadecimal string
+     * @param autoTransact when true, disable observe mode when this filter matches, when false,
+     *                     matching does not change the observe mode state
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public void addPollingLoopPatternFilter(@NonNull String pollingLoopPatternFilter,
+            boolean autoTransact) {
+        if (!mOnHost && !autoTransact) {
+            return;
+        }
+        mAutoTransactPatterns.put(Pattern.compile(pollingLoopPatternFilter), autoTransact);
+    }
+
+    /**
+     * Remove a Polling Loop Pattern Filter. Custom NFC polling frames that match this filter will
+     * no longer be delivered to {@link HostApduService#processPollingFrames(List)}.
+     * @param pollingLoopPatternFilter this polling loop filter to add.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+    public void removePollingLoopPatternFilter(@NonNull String pollingLoopPatternFilter) {
+        mAutoTransactPatterns.remove(
+                Pattern.compile(pollingLoopPatternFilter.toUpperCase(Locale.ROOT)));
+    }
+
+    /**
+     * Sets the off host Secure Element.
+     * @param  offHost  Secure Element to set. Only accept strings with prefix SIM or prefix eSE.
+     *                  Ref: GSMA TS.26 - NFC Handset Requirements
+     *                  TS26_NFC_REQ_069: For UICC, Secure Element Name SHALL be SIM[smartcard slot]
+     *                                    (e.g. SIM/SIM1, SIM2… SIMn).
+     *                  TS26_NFC_REQ_070: For embedded SE, Secure Element Name SHALL be eSE[number]
+     *                                    (e.g. eSE/eSE1, eSE2, etc.).
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void setOffHostSecureElement(@NonNull String offHost) {
+        mOffHostName = offHost;
+    }
+
+    /**
+     * Resets the off host Secure Element to statically defined
+     * by the service in the manifest file.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void resetOffHostSecureElement() {
+        mOffHostName = mStaticOffHostName;
+    }
+
+    /**
+     * Load label for this service.
+     * @param pm packagemanager instance
+     * @return label name corresponding to service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public CharSequence loadLabel(@NonNull PackageManager pm) {
+        return mService.loadLabel(pm);
+    }
+
+    /**
+     * Load application label for this service.
+     * @param pm packagemanager instance
+     * @return app label name corresponding to service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public CharSequence loadAppLabel(@NonNull PackageManager pm) {
+        try {
+            return pm.getApplicationLabel(pm.getApplicationInfo(
+                    mService.resolvePackageName, PackageManager.GET_META_DATA));
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Load application icon for this service.
+     * @param pm packagemanager instance
+     * @return app icon corresponding to service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public Drawable loadIcon(@NonNull PackageManager pm) {
+        return mService.loadIcon(pm);
+    }
+
+    /**
+     * Load application banner for this service.
+     * @param pm packagemanager instance
+     * @return app banner corresponding to service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public Drawable loadBanner(@NonNull PackageManager pm) {
+        Resources res;
+        try {
+            res = pm.getResourcesForApplication(mService.serviceInfo.packageName);
+            Drawable banner = res.getDrawable(mBannerResourceId);
+            return banner;
+        } catch (NotFoundException e) {
+            Log.e(TAG, "Could not load banner.");
+            return null;
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Could not load banner.");
+            return null;
+        }
+    }
+
+    /**
+     * Load activity name for this service.
+     * @return activity name for this service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public String getSettingsActivityName() { return mSettingsActivityName; }
+
+    @Override
+    public String toString() {
+        StringBuilder out = new StringBuilder("ApduService: ");
+        out.append(getComponent());
+        out.append(", UID: " + mUid);
+        out.append(", description: " + mDescription);
+        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();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (!(o instanceof ApduServiceInfo)) return false;
+        ApduServiceInfo thatService = (ApduServiceInfo) o;
+
+        return thatService.getComponent().equals(this.getComponent())
+                && thatService.getUid() == this.getUid();
+    }
+
+    @Override
+    public int hashCode() {
+        return getComponent().hashCode();
+    }
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mService.writeToParcel(dest, flags);
+        dest.writeString(mDescription);
+        dest.writeInt(mOnHost ? 1 : 0);
+        dest.writeString(mOffHostName);
+        dest.writeString(mStaticOffHostName);
+        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(mRequiresDeviceScreenOn ? 1 : 0);
+        dest.writeInt(mBannerResourceId);
+        dest.writeInt(mUid);
+        dest.writeString(mSettingsActivityName);
+
+        dest.writeInt(mCategoryOtherServiceEnabled ? 1 : 0);
+        dest.writeInt(mAutoTransact.size());
+        dest.writeMap(mAutoTransact);
+        dest.writeInt(mAutoTransactPatterns.size());
+        dest.writeMap(mAutoTransactPatterns);
+    };
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public static final @NonNull Parcelable.Creator<ApduServiceInfo> CREATOR =
+            new Parcelable.Creator<ApduServiceInfo>() {
+                @Override
+                public ApduServiceInfo createFromParcel(Parcel source) {
+                    ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
+                    String description = source.readString();
+                    boolean onHost = source.readInt() != 0;
+                    String offHostName = source.readString();
+                    String staticOffHostName = source.readString();
+                    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;
+                    boolean requiresScreenOn = source.readInt() != 0;
+                    int bannerResource = source.readInt();
+                    int uid = source.readInt();
+                    String settingsActivityName = source.readString();
+                    boolean isEnabled = source.readInt() != 0;
+                    int autoTransactSize = source.readInt();
+                    HashMap<String, Boolean> autoTransact =
+                            new HashMap<String, Boolean>(autoTransactSize);
+                    source.readMap(autoTransact, getClass().getClassLoader(),
+                            String.class, Boolean.class);
+                    int autoTransactPatternSize = source.readInt();
+                    HashMap<Pattern, Boolean> autoTransactPatterns =
+                            new HashMap<Pattern, Boolean>(autoTransactSize);
+                    source.readMap(autoTransactPatterns, getClass().getClassLoader(),
+                            Pattern.class, Boolean.class);
+                    return new ApduServiceInfo(info, onHost, description, staticAidGroups,
+                            dynamicAidGroups, requiresUnlock, requiresScreenOn, bannerResource, uid,
+                            settingsActivityName, offHostName, staticOffHostName,
+                            isEnabled, autoTransact, autoTransactPatterns);
+                }
+
+                @Override
+                public ApduServiceInfo[] newArray(int size) {
+                    return new ApduServiceInfo[size];
+                }
+            };
+
+    /**
+     * Dump contents for debugging.
+     * @param fd parcelfiledescriptor instance
+     * @param pw printwriter instance
+     * @param args args for dumping
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void dump(@NonNull ParcelFileDescriptor fd, @NonNull PrintWriter pw,
+                     @NonNull String[] args) {
+        pw.println("    " + getComponent()
+                + " (Description: " + getDescription() + ")"
+                + " (UID: " + getUid() + ")");
+        if (mOnHost) {
+            pw.println("    On Host Service");
+        } else {
+            pw.println("    Off-host Service");
+            pw.println("        " + "Current off-host SE:" + mOffHostName
+                    + " static off-host SE:" + mStaticOffHostName);
+        }
+        pw.println("    Static AID groups:");
+        for (AidGroup group : mStaticAidGroups.values()) {
+            pw.println("        Category: " + group.getCategory()
+                    + "(enabled: " + mCategoryOtherServiceEnabled + ")");
+            for (String aid : group.getAids()) {
+                pw.println("            AID: " + aid);
+            }
+        }
+        pw.println("    Dynamic AID groups:");
+        for (AidGroup group : mDynamicAidGroups.values()) {
+            pw.println("        Category: " + group.getCategory()
+                    + "(enabled: " + mCategoryOtherServiceEnabled + ")");
+            for (String aid : group.getAids()) {
+                pw.println("            AID: " + aid);
+            }
+        }
+        pw.println("    Settings Activity: " + mSettingsActivityName);
+        pw.println("    Requires Device Unlock: " + mRequiresDeviceUnlock);
+        pw.println("    Requires Device ScreenOn: " + mRequiresDeviceScreenOn);
+        pw.println("    Should Default to Observe Mode: " + mShouldDefaultToObserveMode);
+        pw.println("    Auto-Transact Mapping: " + mAutoTransact);
+        pw.println("    Auto-Transact Patterns: " + mAutoTransactPatterns);
+    }
+
+
+    /**
+     * Enable or disable this CATEGORY_OTHER service.
+     *
+     * @param enabled true to indicate if user has enabled this service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void setCategoryOtherServiceEnabled(boolean enabled) {
+        mCategoryOtherServiceEnabled = enabled;
+    }
+
+
+    /**
+     * Returns whether this CATEGORY_OTHER service is enabled or not.
+     *
+     * @return true to indicate if user has enabled this service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public boolean isCategoryOtherServiceEnabled() {
+        return mCategoryOtherServiceEnabled;
+    }
+
+    /**
+     * Dump debugging info as ApduServiceInfoProto.
+     *
+     * If the output belongs to a sub message, the caller is responsible for wrapping this function
+     * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}.
+     * See proto definition in frameworks/base/core/proto/android/nfc/apdu_service_info.proto
+     *
+     * @param proto the ProtoOutputStream to write to
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void dumpDebug(@NonNull ProtoOutputStream proto) {
+        getComponent().dumpDebug(proto, ApduServiceInfoProto.COMPONENT_NAME);
+        proto.write(ApduServiceInfoProto.DESCRIPTION, getDescription());
+        proto.write(ApduServiceInfoProto.ON_HOST, mOnHost);
+        if (!mOnHost) {
+            proto.write(ApduServiceInfoProto.OFF_HOST_NAME, mOffHostName);
+            proto.write(ApduServiceInfoProto.STATIC_OFF_HOST_NAME, mStaticOffHostName);
+        }
+        for (AidGroup group : mStaticAidGroups.values()) {
+            long token = proto.start(ApduServiceInfoProto.STATIC_AID_GROUPS);
+            group.dump(proto);
+            proto.end(token);
+        }
+        for (AidGroup group : mDynamicAidGroups.values()) {
+            long token = proto.start(ApduServiceInfoProto.STATIC_AID_GROUPS);
+            group.dump(proto);
+            proto.end(token);
+        }
+        proto.write(ApduServiceInfoProto.SETTINGS_ACTIVITY_NAME, mSettingsActivityName);
+        proto.write(ApduServiceInfoProto.SHOULD_DEFAULT_TO_OBSERVE_MODE,
+                mShouldDefaultToObserveMode);
+        {
+            long token = proto.start(ApduServiceInfoProto.AUTO_TRANSACT_MAPPING);
+            for (Map.Entry<String, Boolean> entry : mAutoTransact.entrySet()) {
+                proto.write(ApduServiceInfoProto.AutoTransactMapping.AID, entry.getKey());
+                proto.write(ApduServiceInfoProto.AutoTransactMapping.SHOULD_AUTO_TRANSACT,
+                        entry.getValue());
+            }
+            proto.end(token);
+        }
+        {
+            long token = proto.start(ApduServiceInfoProto.AUTO_TRANSACT_PATTERNS);
+            for (Map.Entry<Pattern, Boolean> entry : mAutoTransactPatterns.entrySet()) {
+                proto.write(ApduServiceInfoProto.AutoTransactPattern.REGEXP_PATTERN,
+                        entry.getKey().pattern());
+                proto.write(ApduServiceInfoProto.AutoTransactPattern.SHOULD_AUTO_TRANSACT,
+                        entry.getValue());
+            }
+            proto.end(token);
+        }
+    }
+
+    private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+    /**
+     * Copied over from {@link CardEmulation#isValidAid(String)}
+     * @hide
+     */
+    private static boolean isValidAid(String aid) {
+        if (aid == null)
+            return false;
+
+        // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+        if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+        if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        // Verify hex characters
+        if (!AID_PATTERN.matcher(aid).matches()) {
+            Log.e(TAG, "AID " + aid + " is not a valid AID.");
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/nfc-non-updatable/java/android/nfc/cardemulation/NfcFServiceInfo.aidl b/nfc-non-updatable/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
new file mode 100644
index 0000000..56b98eb
--- /dev/null
+++ b/nfc-non-updatable/java/android/nfc/cardemulation/NfcFServiceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.cardemulation;
+
+parcelable NfcFServiceInfo;
diff --git a/nfc-non-updatable/java/android/nfc/cardemulation/NfcFServiceInfo.java b/nfc-non-updatable/java/android/nfc/cardemulation/NfcFServiceInfo.java
new file mode 100644
index 0000000..33bc169
--- /dev/null
+++ b/nfc-non-updatable/java/android/nfc/cardemulation/NfcFServiceInfo.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**********************************************************************
+ * This file is not a part of the NFC mainline module                 *
+ * *******************************************************************/
+
+package android.nfc.cardemulation;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.nfc.Flags;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import android.util.proto.ProtoOutputStream;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Class to hold NfcF service info.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+public final class NfcFServiceInfo implements Parcelable {
+    static final String TAG = "NfcFServiceInfo";
+
+    private static final String DEFAULT_T3T_PMM = "FFFFFFFFFFFFFFFF";
+
+    /**
+     * The service that implements this
+     */
+    private final ResolveInfo mService;
+
+    /**
+     * Description of the service
+     */
+    private final String mDescription;
+
+    /**
+     * System Code of the service
+     */
+    private final String mSystemCode;
+
+    /**
+     * System Code of the service registered by API
+     */
+    private String mDynamicSystemCode;
+
+    /**
+     * NFCID2 of the service
+     */
+    private final String mNfcid2;
+
+    /**
+     * NFCID2 of the service registered by API
+     */
+    private String mDynamicNfcid2;
+
+    /**
+     * The uid of the package the service belongs to
+     */
+    private final int mUid;
+
+    /**
+     * LF_T3T_PMM of the service
+     */
+    private final String mT3tPmm;
+
+    /**
+     * @hide
+     */
+    public NfcFServiceInfo(ResolveInfo info, String description,
+            String systemCode, String dynamicSystemCode, String nfcid2, String dynamicNfcid2,
+            int uid, String t3tPmm) {
+        this.mService = info;
+        this.mDescription = description;
+        this.mSystemCode = systemCode;
+        this.mDynamicSystemCode = dynamicSystemCode;
+        this.mNfcid2 = nfcid2;
+        this.mDynamicNfcid2 = dynamicNfcid2;
+        this.mUid = uid;
+        this.mT3tPmm = t3tPmm;
+    }
+
+    /**
+     * Creates a new NfcFServiceInfo object.
+     *
+     * @param pm packageManager instance
+     * @param info app component info
+     * @throws XmlPullParserException If an error occurs parsing the element.
+     * @throws IOException If an error occurs reading the element.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public NfcFServiceInfo(@NonNull PackageManager pm, @NonNull ResolveInfo info)
+            throws XmlPullParserException, IOException {
+        ServiceInfo si = info.serviceInfo;
+        XmlResourceParser parser = null;
+        try {
+            parser = si.loadXmlMetaData(pm, HostNfcFService.SERVICE_META_DATA);
+            if (parser == null) {
+                throw new XmlPullParserException("No " + HostNfcFService.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-nfcf-service".equals(tagName)) {
+                throw new XmlPullParserException(
+                        "Meta-data does not start with <host-nfcf-service> tag");
+            }
+
+            Resources res = pm.getResourcesForApplication(si.applicationInfo);
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+            TypedArray sa = res.obtainAttributes(attrs,
+                    com.android.internal.R.styleable.HostNfcFService);
+            mService = info;
+            mDescription = sa.getString(
+                    com.android.internal.R.styleable.HostNfcFService_description);
+            mDynamicSystemCode = null;
+            mDynamicNfcid2 = null;
+            sa.recycle();
+
+            String systemCode = null;
+            String nfcid2 = null;
+            String t3tPmm = null;
+            final int depth = parser.getDepth();
+
+            while (((eventType = parser.next()) != XmlPullParser.END_TAG ||
+                    parser.getDepth() > depth) && eventType != XmlPullParser.END_DOCUMENT) {
+                tagName = parser.getName();
+                if (eventType == XmlPullParser.START_TAG &&
+                        "system-code-filter".equals(tagName) && systemCode == null) {
+                    final TypedArray a = res.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.SystemCodeFilter);
+                    systemCode = a.getString(
+                            com.android.internal.R.styleable.SystemCodeFilter_name).toUpperCase();
+                    if (!isValidSystemCode(systemCode) &&
+                            !systemCode.equalsIgnoreCase("NULL")) {
+                        Log.e(TAG, "Invalid System Code: " + systemCode);
+                        systemCode = null;
+                    }
+                    a.recycle();
+                } else if (eventType == XmlPullParser.START_TAG &&
+                        "nfcid2-filter".equals(tagName) && nfcid2 == null) {
+                    final TypedArray a = res.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.Nfcid2Filter);
+                    nfcid2 = a.getString(
+                            com.android.internal.R.styleable.Nfcid2Filter_name).toUpperCase();
+                    if (!nfcid2.equalsIgnoreCase("RANDOM") &&
+                            !nfcid2.equalsIgnoreCase("NULL") &&
+                            !isValidNfcid2(nfcid2)) {
+                        Log.e(TAG, "Invalid NFCID2: " + nfcid2);
+                        nfcid2 = null;
+                    }
+                    a.recycle();
+                } else if (eventType == XmlPullParser.START_TAG && tagName.equals("t3tPmm-filter")
+                        && t3tPmm == null) {
+                    final TypedArray a = res.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.T3tPmmFilter);
+                    t3tPmm = a.getString(
+                            com.android.internal.R.styleable.T3tPmmFilter_name).toUpperCase();
+                    a.recycle();
+                }
+            }
+            mSystemCode = (systemCode == null ? "NULL" : systemCode);
+            mNfcid2 = (nfcid2 == null ? "NULL" : nfcid2);
+            mT3tPmm = (t3tPmm == null ? DEFAULT_T3T_PMM : t3tPmm);
+        } catch (NameNotFoundException e) {
+            throw new XmlPullParserException("Unable to create context for: " + si.packageName);
+        } finally {
+            if (parser != null) parser.close();
+        }
+        // Set uid
+        mUid = si.applicationInfo.uid;
+    }
+
+    /**
+     * Returns the app component corresponding to this NFCF service.
+     *
+     * @return app component for this service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public ComponentName getComponent() {
+        return new ComponentName(mService.serviceInfo.packageName,
+                mService.serviceInfo.name);
+    }
+
+    /**
+     * Returns the system code corresponding to this service.
+     *
+     * @return system code for this service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public String getSystemCode() {
+        return (mDynamicSystemCode == null ? mSystemCode : mDynamicSystemCode);
+    }
+
+    /**
+     * Add or replace a system code to this service.
+     * @param systemCode system code to set or replace
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void setDynamicSystemCode(@NonNull String systemCode) {
+        mDynamicSystemCode = systemCode;
+    }
+
+    /**
+     * Returns NFC ID2.
+     *
+     * @return nfc id2 to return
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public String getNfcid2() {
+        return (mDynamicNfcid2 == null ? mNfcid2 : mDynamicNfcid2);
+    }
+
+    /**
+     * Set or replace NFC ID2
+     *
+     * @param nfcid2 NFC ID2 string
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void setDynamicNfcid2(@NonNull String nfcid2) {
+        mDynamicNfcid2 = nfcid2;
+    }
+
+    /**
+     * Returns description of service.
+     * @return user readable description of service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public String getDescription() {
+        return mDescription;
+    }
+
+    /**
+     * Returns uid of service.
+     * @return uid of the service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public int getUid() {
+        return mUid;
+    }
+
+    /**
+     * Returns LF_T3T_PMM of the service
+     * @return returns LF_T3T_PMM of the service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public String getT3tPmm() {
+        return mT3tPmm;
+    }
+
+    /**
+     * Load application label for this service.
+     * @param pm packagemanager instance
+     * @return label name corresponding to service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public CharSequence loadLabel(@NonNull PackageManager pm) {
+        return mService.loadLabel(pm);
+    }
+
+    /**
+     * Load application icon for this service.
+     * @param pm packagemanager instance
+     * @return app icon corresponding to service
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @NonNull
+    public Drawable loadIcon(@NonNull PackageManager pm) {
+        return mService.loadIcon(pm);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder out = new StringBuilder("NfcFService: ");
+        out.append(getComponent());
+        out.append(", UID: " + mUid);
+        out.append(", description: " + mDescription);
+        out.append(", System Code: " + mSystemCode);
+        if (mDynamicSystemCode != null) {
+            out.append(", dynamic System Code: " + mDynamicSystemCode);
+        }
+        out.append(", NFCID2: " + mNfcid2);
+        if (mDynamicNfcid2 != null) {
+            out.append(", dynamic NFCID2: " + mDynamicNfcid2);
+        }
+        out.append(", T3T PMM:" + mT3tPmm);
+        return out.toString();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (!(o instanceof NfcFServiceInfo)) return false;
+        NfcFServiceInfo thatService = (NfcFServiceInfo) o;
+
+        if (!thatService.getComponent().equals(this.getComponent())) return false;
+        if (thatService.getUid() != this.getUid()) return false;
+        if (!thatService.mSystemCode.equalsIgnoreCase(this.mSystemCode)) return false;
+        if (!thatService.mNfcid2.equalsIgnoreCase(this.mNfcid2)) return false;
+        if (!thatService.mT3tPmm.equalsIgnoreCase(this.mT3tPmm)) return false;
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return getComponent().hashCode();
+    }
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mService.writeToParcel(dest, flags);
+        dest.writeString(mDescription);
+        dest.writeString(mSystemCode);
+        dest.writeInt(mDynamicSystemCode != null ? 1 : 0);
+        if (mDynamicSystemCode != null) {
+            dest.writeString(mDynamicSystemCode);
+        }
+        dest.writeString(mNfcid2);
+        dest.writeInt(mDynamicNfcid2 != null ? 1 : 0);
+        if (mDynamicNfcid2 != null) {
+            dest.writeString(mDynamicNfcid2);
+        }
+        dest.writeInt(mUid);
+        dest.writeString(mT3tPmm);
+    };
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public static final @NonNull Parcelable.Creator<NfcFServiceInfo> CREATOR =
+            new Parcelable.Creator<NfcFServiceInfo>() {
+        @Override
+        public NfcFServiceInfo createFromParcel(Parcel source) {
+            ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
+            String description = source.readString();
+            String systemCode = source.readString();
+            String dynamicSystemCode = null;
+            if (source.readInt() != 0) {
+                dynamicSystemCode = source.readString();
+            }
+            String nfcid2 = source.readString();
+            String dynamicNfcid2 = null;
+            if (source.readInt() != 0) {
+                dynamicNfcid2 = source.readString();
+            }
+            int uid = source.readInt();
+            String t3tPmm = source.readString();
+            NfcFServiceInfo service = new NfcFServiceInfo(info, description,
+                    systemCode, dynamicSystemCode, nfcid2, dynamicNfcid2, uid, t3tPmm);
+            return service;
+        }
+
+        @Override
+        public NfcFServiceInfo[] newArray(int size) {
+            return new NfcFServiceInfo[size];
+        }
+    };
+
+    /**
+     * Dump contents of the service for debugging.
+     * @param fd parcelfiledescriptor instance
+     * @param pw printwriter instance
+     * @param args args for dumping
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void dump(@NonNull ParcelFileDescriptor fd, @NonNull PrintWriter pw,
+                     @NonNull String[] args) {
+        pw.println("    " + getComponent()
+                + " (Description: " + getDescription() + ")"
+                + " (UID: " + getUid() + ")");
+        pw.println("    System Code: " + getSystemCode());
+        pw.println("    NFCID2: " + getNfcid2());
+        pw.println("    T3tPmm: " + getT3tPmm());
+    }
+
+    /**
+     * Dump debugging info as NfcFServiceInfoProto.
+     *
+     * If the output belongs to a sub message, the caller is responsible for wrapping this function
+     * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}.
+     *
+     * @param proto the ProtoOutputStream to write to
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+    public void dumpDebug(@NonNull ProtoOutputStream proto) {
+        getComponent().dumpDebug(proto, NfcFServiceInfoProto.COMPONENT_NAME);
+        proto.write(NfcFServiceInfoProto.DESCRIPTION, getDescription());
+        proto.write(NfcFServiceInfoProto.SYSTEM_CODE, getSystemCode());
+        proto.write(NfcFServiceInfoProto.NFCID2, getNfcid2());
+        proto.write(NfcFServiceInfoProto.T3T_PMM, getT3tPmm());
+    }
+
+    /**
+     * Copied over from {@link NfcFCardEmulation#isValidSystemCode(String)}
+     * @hide
+     */
+    private static boolean isValidSystemCode(String systemCode) {
+        if (systemCode == null) {
+            return false;
+        }
+        if (systemCode.length() != 4) {
+            Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+            return false;
+        }
+        // check if the value is between "4000" and "4FFF" (excluding "4*FF")
+        if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) {
+            Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+            return false;
+        }
+        try {
+            Integer.parseInt(systemCode, 16);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Copied over from {@link NfcFCardEmulation#isValidNfcid2(String)}
+     * @hide
+     */
+    private static boolean isValidNfcid2(String nfcid2) {
+        if (nfcid2 == null) {
+            return false;
+        }
+        if (nfcid2.length() != 16) {
+            Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+            return false;
+        }
+        // check if the the value starts with "02FE"
+        if (!nfcid2.toUpperCase().startsWith("02FE")) {
+            Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+            return false;
+        }
+        try {
+            Long.parseLong(nfcid2, 16);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+            return false;
+        }
+        return true;
+    }
+}