diff options
author | 2024-12-13 17:19:03 -0800 | |
---|---|---|
committer | 2024-12-13 21:06:29 -0800 | |
commit | 24106379a58f1bc2d06a7770a88bcdcc42b3edf2 (patch) | |
tree | 8c227b0fc4bb71b61205e959596c138484bd890a /nfc-non-updatable | |
parent | 2daef7f6cf3ab06149a0fd6aa63b06554c9c4e60 (diff) |
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
Diffstat (limited to 'nfc-non-updatable')
10 files changed, 2389 insertions, 0 deletions
diff --git a/nfc-non-updatable/Android.bp b/nfc-non-updatable/Android.bp new file mode 100644 index 000000000000..ff987bb84b17 --- /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 000000000000..f46dccd97974 --- /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 000000000000..ee287aba709f --- /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 000000000000..5582f1154cad --- /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 000000000000..56d6fa559677 --- /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 000000000000..ae3e333051d7 --- /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 000000000000..a62fdd6a6c5c --- /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 000000000000..7f64dbea0be3 --- /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 000000000000..56b98ebd90fa --- /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 000000000000..33bc16978721 --- /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; + } +} |