/* * 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.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; 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"; /** * 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 mStaticAidGroups; /** * Mapping from category to dynamic AID group */ private final HashMap mDynamicAidGroups; private final Map mAutoTransact; private final Map 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; /** * @hide */ @UnsupportedAppUsage public ApduServiceInfo(ResolveInfo info, boolean onHost, String description, ArrayList staticAidGroups, ArrayList 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 staticAidGroups, ArrayList 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 staticAidGroups, List 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(), new HashMap()); } /** * @hide */ public ApduServiceInfo(ResolveInfo info, boolean onHost, String description, List staticAidGroups, List dynamicAidGroups, boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid, String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled, Map autoTransact, Map autoTransactPatterns) { this.mService = info; this.mDescription = description; this.mStaticAidGroups = new HashMap(); this.mDynamicAidGroups = new HashMap(); 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 tag"); } else if (!onHost && !"offhost-apdu-service".equals(tagName)) { throw new XmlPullParserException( "Meta-data does not start with 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); 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.HostApduService_shouldDefaultToObserveMode, false); if (mOffHostName != null) { if (mOffHostName.equals("eSE")) { mOffHostName = "eSE1"; } else if (mOffHostName.equals("SIM")) { mOffHostName = "SIM1"; } } mStaticOffHostName = mOffHostName; sa.recycle(); } mStaticAidGroups = new HashMap(); mDynamicAidGroups = new HashMap(); mAutoTransact = new HashMap(); mAutoTransactPatterns = new HashMap(); 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 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 autoTranact"); } 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 autoTranact"); } 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 getAids() { final ArrayList aids = new ArrayList(); 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 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 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 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 getPrefixAids() { final ArrayList prefixAids = new ArrayList(); 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 getSubsetAids() { final ArrayList subsetAids = new ArrayList(); 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 getAidGroups() { final ArrayList groups = new ArrayList(); for (Map.Entry entry : mDynamicAidGroups.entrySet()) { groups.add(entry.getValue()); } for (Map.Entry 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 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 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(mStaticAidGroups.values())); } dest.writeInt(mDynamicAidGroups.size()); if (mDynamicAidGroups.size() > 0) { dest.writeTypedList(new ArrayList(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 CREATOR = new Parcelable.Creator() { @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 staticAidGroups = new ArrayList(); int numStaticGroups = source.readInt(); if (numStaticGroups > 0) { source.readTypedList(staticAidGroups, AidGroup.CREATOR); } ArrayList dynamicAidGroups = new ArrayList(); 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 autoTransact = new HashMap(autoTransactSize); source.readMap(autoTransact, getClass().getClassLoader(), String.class, Boolean.class); int autoTransactPatternSize = source.readInt(); HashMap autoTransactPatterns = new HashMap(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); } /** * 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); } 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; } }