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>
+ * <service
+ * android:apduServiceBanner="@drawable/product_logo"
+ * android:label="@string/service_label">
+ * <property
+ * android:name="android.content.PROPERTY_WALLET_ICON_AND_LABEL_HOLDER"
+ * android:value="true"/>
+ * </service>
+ * </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;
+ }
+}