• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /**********************************************************************
18  * This file is not a part of the NFC mainline module                 *
19  * *******************************************************************/
20 
21 package android.nfc.cardemulation;
22 
23 import android.annotation.FlaggedApi;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.SystemApi;
27 import android.compat.annotation.UnsupportedAppUsage;
28 import android.content.ComponentName;
29 import android.content.pm.PackageManager;
30 import android.content.pm.PackageManager.NameNotFoundException;
31 import android.content.pm.ResolveInfo;
32 import android.content.pm.ServiceInfo;
33 import android.content.res.Resources;
34 import android.content.res.Resources.NotFoundException;
35 import android.content.res.TypedArray;
36 import android.content.res.XmlResourceParser;
37 import android.graphics.drawable.Drawable;
38 import android.nfc.Flags;
39 import android.os.Parcel;
40 import android.os.ParcelFileDescriptor;
41 import android.os.Parcelable;
42 import android.util.AttributeSet;
43 import android.util.Log;
44 import android.util.Xml;
45 import android.util.proto.ProtoOutputStream;
46 
47 import com.android.internal.R;
48 
49 import org.xmlpull.v1.XmlPullParser;
50 import org.xmlpull.v1.XmlPullParserException;
51 
52 import java.io.IOException;
53 import java.io.PrintWriter;
54 import java.util.ArrayList;
55 import java.util.Comparator;
56 import java.util.HashMap;
57 import java.util.List;
58 import java.util.Locale;
59 import java.util.Map;
60 import java.util.TreeMap;
61 import java.util.regex.Pattern;
62 
63 /**
64  * Class holding APDU service info.
65  *
66  * @hide
67  */
68 @SystemApi
69 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
70 public final class ApduServiceInfo implements Parcelable {
71     private static final String TAG = "ApduServiceInfo";
72 
73     private static final Pattern PLPF_PATTERN =
74             Pattern.compile("[0-9A-Fa-f]{2,}[0-9A-Fa-f,\\?,\\*\\.]*");
75     private static final Pattern PLF_PATTERN =
76             Pattern.compile("[0-9A-Fa-f]{2,}");
77 
78     /**
79      * Component level {@link android.content.pm.PackageManager.Property PackageManager
80      * .Property} for a system application to change its icon and label
81      * on the default applications page. This property should be added to an
82      * {@link HostApduService} declaration in the manifest.
83      *
84      * <p>For example:
85      * <pre>
86      * &lt;service
87      *     android:apduServiceBanner="@drawable/product_logo"
88      *     android:label="@string/service_label"&gt
89      *      &lt;property
90      *          android:name="android.content.PROPERTY_WALLET_ICON_AND_LABEL_HOLDER"
91      *          android:value="true"/&gt;
92      * &lt/service&gt;
93      * </pre>
94      * @hide
95      */
96     @SystemApi
97     @FlaggedApi(android.permission.flags.Flags.FLAG_WALLET_ROLE_ICON_PROPERTY_ENABLED)
98     public static final String PROPERTY_WALLET_PREFERRED_BANNER_AND_LABEL =
99             "android.nfc.cardemulation.PROPERTY_WALLET_PREFERRED_BANNER_AND_LABEL";
100 
101     /**
102      * The service that implements this
103      */
104     private final ResolveInfo mService;
105 
106     /**
107      * Description of the service
108      */
109     private final String mDescription;
110 
111     /**
112      * Whether this service represents AIDs running on the host CPU
113      */
114     private final boolean mOnHost;
115 
116     /**
117      * Offhost reader name.
118      * eg: SIM, eSE etc
119      */
120     private String mOffHostName;
121 
122     /**
123      * Offhost reader name from manifest file.
124      * Used for resetOffHostSecureElement()
125      */
126     private final String mStaticOffHostName;
127 
128     /**
129      * Mapping from category to static AID group
130      */
131     private final HashMap<String, AidGroup> mStaticAidGroups;
132 
133     /**
134      * Mapping from category to dynamic AID group
135      */
136     private final HashMap<String, AidGroup> mDynamicAidGroups;
137 
138 
139     private final Map<String, Boolean> mAutoTransact;
140 
141     private final Map<Pattern, Boolean> mAutoTransactPatterns;
142 
143     /**
144      * Whether this service should only be started when the device is unlocked.
145      */
146     private final boolean mRequiresDeviceUnlock;
147 
148     /**
149      * Whether this service should only be started when the device is screen on.
150      */
151     private final boolean mRequiresDeviceScreenOn;
152 
153     /**
154      * The id of the service banner specified in XML.
155      */
156     private final int mBannerResourceId;
157 
158     /**
159      * The uid of the package the service belongs to
160      */
161     private final int mUid;
162 
163     /**
164      * Settings Activity for this service
165      */
166     private final String mSettingsActivityName;
167 
168     /**
169      * State of the service for CATEGORY_OTHER selection
170      */
171     private boolean mCategoryOtherServiceEnabled;
172 
173     /**
174      * Whether the NFC stack should default to Observe Mode when this preferred service.
175      */
176     private boolean mShouldDefaultToObserveMode;
177 
178     /**
179      * Whether or not this service wants to share the same routing priority as the
180      * Wallet role owner.
181      */
182     private boolean mWantsRoleHolderPriority;
183 
184     /**
185      * Constructor of {@link ApduServiceInfo}.
186      * @param info App component info
187      * @param onHost whether service is on host or not (secure element)
188      * @param description The description of service
189      * @param staticAidGroups static AID groups
190      * @param dynamicAidGroups dynamic AID groups
191      * @param requiresUnlock whether this service should only be started
192      *                       when the device is unlocked
193      * @param bannerResource The id of the service banner specified in XML
194      * @param uid The uid of the package the service belongs to
195      * @param settingsActivityName Settings Activity for this service
196      * @param offHost Off-host reader name
197      * @param staticOffHost Off-host reader name from manifest file
198      *
199      * @hide
200      */
201     @UnsupportedAppUsage
202     @SystemApi
203     @FlaggedApi(Flags.FLAG_NFC_APDU_SERVICE_INFO_CONSTRUCTOR)
ApduServiceInfo(@onNull ResolveInfo info, boolean onHost, @NonNull String description, @NonNull List<AidGroup> staticAidGroups, @NonNull List<AidGroup> dynamicAidGroups, boolean requiresUnlock, int bannerResource, int uid, @NonNull String settingsActivityName, @NonNull String offHost, @NonNull String staticOffHost)204     public ApduServiceInfo(@NonNull ResolveInfo info, boolean onHost, @NonNull String description,
205             @NonNull List<AidGroup> staticAidGroups, @NonNull List<AidGroup> dynamicAidGroups,
206             boolean requiresUnlock, int bannerResource, int uid,
207             @NonNull String settingsActivityName, @NonNull String offHost,
208             @NonNull String staticOffHost) {
209         this(info, onHost, description, staticAidGroups, dynamicAidGroups,
210                 requiresUnlock, bannerResource, uid, settingsActivityName,
211                 offHost, staticOffHost, false);
212     }
213 
214     /**
215      * @hide
216      */
ApduServiceInfo(ResolveInfo info, boolean onHost, String description, List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups, boolean requiresUnlock, int bannerResource, int uid, String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled)217     public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
218             List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
219             boolean requiresUnlock, int bannerResource, int uid,
220             String settingsActivityName, String offHost, String staticOffHost,
221             boolean isEnabled) {
222         this(info, onHost, description, staticAidGroups, dynamicAidGroups,
223                 requiresUnlock, onHost ? true : false, bannerResource, uid,
224                 settingsActivityName, offHost, staticOffHost, isEnabled);
225     }
226 
227     /**
228      * @hide
229      */
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)230     public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
231             List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
232             boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid,
233             String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled) {
234         this(info, onHost, description, staticAidGroups, dynamicAidGroups,
235                 requiresUnlock, requiresScreenOn, bannerResource, uid,
236                 settingsActivityName, offHost, staticOffHost, isEnabled,
237                 new HashMap<String, Boolean>(), new TreeMap<>(
238                         Comparator.comparing(Pattern::toString)));
239     }
240 
241     /**
242      * @hide
243      */
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)244     public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
245             List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups,
246             boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid,
247             String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled,
248             Map<String, Boolean> autoTransact, Map<Pattern, Boolean> autoTransactPatterns) {
249         this.mService = info;
250         this.mDescription = description;
251         this.mStaticAidGroups = new HashMap<String, AidGroup>();
252         this.mDynamicAidGroups = new HashMap<String, AidGroup>();
253         this.mAutoTransact = autoTransact;
254         this.mAutoTransactPatterns = autoTransactPatterns;
255         this.mOffHostName = offHost;
256         this.mStaticOffHostName = staticOffHost;
257         this.mOnHost = onHost;
258         this.mRequiresDeviceUnlock = requiresUnlock;
259         this.mRequiresDeviceScreenOn = requiresScreenOn;
260         for (AidGroup aidGroup : staticAidGroups) {
261             this.mStaticAidGroups.put(aidGroup.getCategory(), aidGroup);
262         }
263         for (AidGroup aidGroup : dynamicAidGroups) {
264             this.mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
265         }
266         this.mBannerResourceId = bannerResource;
267         this.mUid = uid;
268         this.mSettingsActivityName = settingsActivityName;
269         this.mCategoryOtherServiceEnabled = isEnabled;
270     }
271 
272     /**
273      * Creates a new ApduServiceInfo object.
274      *
275      * @param pm packageManager instance
276      * @param info app component info
277      * @param onHost whether service is on host or not (secure element)
278      * @throws XmlPullParserException If an error occurs parsing the element.
279      * @throws IOException If an error occurs reading the element.
280      */
281     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
ApduServiceInfo(@onNull PackageManager pm, @NonNull ResolveInfo info, boolean onHost)282     public ApduServiceInfo(@NonNull PackageManager pm, @NonNull ResolveInfo info, boolean onHost)
283             throws XmlPullParserException, IOException {
284         ServiceInfo si = info.serviceInfo;
285         XmlResourceParser parser = null;
286         try {
287             if (onHost) {
288                 parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA);
289                 if (parser == null) {
290                     throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
291                             " meta-data");
292                 }
293             } else {
294                 parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA);
295                 if (parser == null) {
296                     throw new XmlPullParserException("No " + OffHostApduService.SERVICE_META_DATA +
297                             " meta-data");
298                 }
299             }
300 
301             int eventType = parser.getEventType();
302             while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) {
303                 eventType = parser.next();
304             }
305 
306             String tagName = parser.getName();
307             if (onHost && !"host-apdu-service".equals(tagName)) {
308                 throw new XmlPullParserException(
309                         "Meta-data does not start with <host-apdu-service> tag");
310             } else if (!onHost && !"offhost-apdu-service".equals(tagName)) {
311                 throw new XmlPullParserException(
312                         "Meta-data does not start with <offhost-apdu-service> tag");
313             }
314 
315             Resources res = pm.getResourcesForApplication(si.applicationInfo);
316             AttributeSet attrs = Xml.asAttributeSet(parser);
317             if (onHost) {
318                 TypedArray sa = res.obtainAttributes(attrs,
319                         com.android.internal.R.styleable.HostApduService);
320                 mService = info;
321                 mDescription = sa.getString(
322                         com.android.internal.R.styleable.HostApduService_description);
323                 mRequiresDeviceUnlock = sa.getBoolean(
324                         com.android.internal.R.styleable.HostApduService_requireDeviceUnlock,
325                         false);
326                 mRequiresDeviceScreenOn = sa.getBoolean(
327                         com.android.internal.R.styleable.HostApduService_requireDeviceScreenOn,
328                         true);
329                 mBannerResourceId = sa.getResourceId(
330                         com.android.internal.R.styleable.HostApduService_apduServiceBanner, -1);
331                 mSettingsActivityName = sa.getString(
332                         com.android.internal.R.styleable.HostApduService_settingsActivity);
333                 mOffHostName = null;
334                 mStaticOffHostName = mOffHostName;
335                 mShouldDefaultToObserveMode = sa.getBoolean(
336                         R.styleable.HostApduService_shouldDefaultToObserveMode,
337                         false);
338                 if (Flags.nfcAssociatedRoleServices()) {
339                     mWantsRoleHolderPriority = sa.getBoolean(
340                             R.styleable.HostApduService_wantsRoleHolderPriority,
341                             false
342                     );
343                 }
344                 sa.recycle();
345             } else {
346                 TypedArray sa = res.obtainAttributes(attrs,
347                         com.android.internal.R.styleable.OffHostApduService);
348                 mService = info;
349                 mDescription = sa.getString(
350                         com.android.internal.R.styleable.OffHostApduService_description);
351                 mRequiresDeviceUnlock = sa.getBoolean(
352                         com.android.internal.R.styleable.OffHostApduService_requireDeviceUnlock,
353                         false);
354                 mRequiresDeviceScreenOn = sa.getBoolean(
355                         com.android.internal.R.styleable.OffHostApduService_requireDeviceScreenOn,
356                         false);
357                 mBannerResourceId = sa.getResourceId(
358                         com.android.internal.R.styleable.OffHostApduService_apduServiceBanner, -1);
359                 mSettingsActivityName = sa.getString(
360                         com.android.internal.R.styleable.HostApduService_settingsActivity);
361                 mOffHostName = sa.getString(
362                         com.android.internal.R.styleable.OffHostApduService_secureElementName);
363                 mShouldDefaultToObserveMode = sa.getBoolean(
364                         R.styleable.OffHostApduService_shouldDefaultToObserveMode,
365                         false);
366                 if (mOffHostName != null) {
367                     if (mOffHostName.equals("eSE")) {
368                         mOffHostName = "eSE1";
369                     } else if (mOffHostName.equals("SIM")) {
370                         mOffHostName = "SIM1";
371                     }
372                 }
373                 mStaticOffHostName = mOffHostName;
374                 if (Flags.nfcAssociatedRoleServices()) {
375                     mWantsRoleHolderPriority = sa.getBoolean(
376                             R.styleable.OffHostApduService_wantsRoleHolderPriority,
377                             false
378                     );
379                 }
380                 sa.recycle();
381             }
382 
383             mStaticAidGroups = new HashMap<String, AidGroup>();
384             mDynamicAidGroups = new HashMap<String, AidGroup>();
385             mAutoTransact = new HashMap<String, Boolean>();
386             mAutoTransactPatterns = new TreeMap<Pattern, Boolean>(
387                     Comparator.comparing(Pattern::toString));
388             mOnHost = onHost;
389 
390             final int depth = parser.getDepth();
391             AidGroup currentGroup = null;
392 
393             // Parsed values for the current AID group
394             while (((eventType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
395                     && eventType != XmlPullParser.END_DOCUMENT) {
396                 tagName = parser.getName();
397                 if (eventType == XmlPullParser.START_TAG && "aid-group".equals(tagName) &&
398                         currentGroup == null) {
399                     final TypedArray groupAttrs = res.obtainAttributes(attrs,
400                             com.android.internal.R.styleable.AidGroup);
401                     // Get category of AID group
402                     String groupCategory = groupAttrs.getString(
403                             com.android.internal.R.styleable.AidGroup_category);
404                     String groupDescription = groupAttrs.getString(
405                             com.android.internal.R.styleable.AidGroup_description);
406                     if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) {
407                         groupCategory = CardEmulation.CATEGORY_OTHER;
408                     }
409                     currentGroup = mStaticAidGroups.get(groupCategory);
410                     if (currentGroup != null) {
411                         if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
412                             Log.e(TAG, "Not allowing multiple aid-groups in the " +
413                                     groupCategory + " category");
414                             currentGroup = null;
415                         }
416                     } else {
417                         currentGroup = new AidGroup(groupCategory, groupDescription);
418                     }
419                     groupAttrs.recycle();
420                 } else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) &&
421                         currentGroup != null) {
422                     if (currentGroup.getAids().size() > 0) {
423                         if (!mStaticAidGroups.containsKey(currentGroup.getCategory())) {
424                             mStaticAidGroups.put(currentGroup.getCategory(), currentGroup);
425                         }
426                     } else {
427                         Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs");
428                     }
429                     currentGroup = null;
430                 } else if (eventType == XmlPullParser.START_TAG && "aid-filter".equals(tagName) &&
431                         currentGroup != null) {
432                     final TypedArray a = res.obtainAttributes(attrs,
433                             com.android.internal.R.styleable.AidFilter);
434                     String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
435                             toUpperCase();
436                     if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
437                         currentGroup.getAids().add(aid);
438                     } else {
439                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
440                     }
441                     a.recycle();
442                 } else if (eventType == XmlPullParser.START_TAG &&
443                         "aid-prefix-filter".equals(tagName) && currentGroup != null) {
444                     final TypedArray a = res.obtainAttributes(attrs,
445                             com.android.internal.R.styleable.AidFilter);
446                     String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
447                             toUpperCase();
448                     // Add wildcard char to indicate prefix
449                     aid = aid.concat("*");
450                     if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
451                         currentGroup.getAids().add(aid);
452                     } else {
453                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
454                     }
455                     a.recycle();
456                 } else if (eventType == XmlPullParser.START_TAG &&
457                         tagName.equals("aid-suffix-filter") && currentGroup != null) {
458                     final TypedArray a = res.obtainAttributes(attrs,
459                             com.android.internal.R.styleable.AidFilter);
460                     String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
461                             toUpperCase();
462                     // Add wildcard char to indicate suffix
463                     aid = aid.concat("#");
464                     if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
465                         currentGroup.getAids().add(aid);
466                     } else {
467                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
468                     }
469                     a.recycle();
470                 } else if (eventType == XmlPullParser.START_TAG
471                         && "polling-loop-filter".equals(tagName) && currentGroup == null) {
472                     final TypedArray a = res.obtainAttributes(attrs,
473                             com.android.internal.R.styleable.PollingLoopFilter);
474                     String plf =
475                             a.getString(com.android.internal.R.styleable.PollingLoopFilter_name)
476                             .toUpperCase(Locale.ROOT);
477                     boolean autoTransact = a.getBoolean(
478                             com.android.internal.R.styleable.PollingLoopFilter_autoTransact,
479                             false);
480                     boolean isValidFilter = PLF_PATTERN.matcher(plf).matches()
481                             && plf.length() % 2 == 0;
482                     if (!isValidFilter) {
483                         Log.e(TAG, "Ignoring polling-loop-filter " + plf
484                                 + " it is not a valid filter");
485                     } else if (!mOnHost && !autoTransact) {
486                         Log.e(TAG, "Ignoring polling-loop-filter " + plf
487                                 + " for offhost service that isn't autoTransact");
488                     } else {
489                         mAutoTransact.put(plf, autoTransact);
490                     }
491                     a.recycle();
492                 } else if (eventType == XmlPullParser.START_TAG
493                         && "polling-loop-pattern-filter".equals(tagName) && currentGroup == null) {
494                     final TypedArray a = res.obtainAttributes(attrs,
495                             com.android.internal.R.styleable.PollingLoopPatternFilter);
496                     String plf = a.getString(
497                             com.android.internal.R.styleable.PollingLoopPatternFilter_name)
498                                     .toUpperCase(Locale.ROOT);
499                     boolean autoTransact = a.getBoolean(
500                             com.android.internal.R.styleable.PollingLoopFilter_autoTransact,
501                             false);
502                     boolean isValidFilter = PLPF_PATTERN.matcher(plf).matches();
503                     if (!isValidFilter) {
504                         Log.e(TAG, "Ignoring polling-loop-pattern-filter " + plf
505                                 + " it is not a valid pattern filter");
506                     } else if (!mOnHost && !autoTransact) {
507                         Log.e(TAG, "Ignoring polling-loop-pattern-filter " + plf
508                                 + " for offhost service that isn't autoTransact");
509                     } else {
510                         mAutoTransactPatterns.put(Pattern.compile(plf), autoTransact);
511                     }
512                     a.recycle();
513                 }
514             }
515         } catch (NameNotFoundException e) {
516             throw new XmlPullParserException("Unable to create context for: " + si.packageName);
517         } finally {
518             if (parser != null) parser.close();
519         }
520         // Set uid
521         mUid = si.applicationInfo.uid;
522 
523         mCategoryOtherServiceEnabled = true;    // support other category
524 
525     }
526 
527     /**
528      * Returns the app component corresponding to this APDU service.
529      *
530      * @return app component for this service
531      */
532     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
533     @NonNull
getComponent()534     public ComponentName getComponent() {
535         return new ComponentName(mService.serviceInfo.packageName,
536                 mService.serviceInfo.name);
537     }
538 
539     /**
540      * Returns the offhost secure element name (if the service is offhost).
541      *
542      * @return offhost secure element name for offhost services
543      */
544     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
545     @Nullable
getOffHostSecureElement()546     public String getOffHostSecureElement() {
547         return mOffHostName;
548     }
549 
550     /**
551      * Returns a consolidated list of AIDs from the AID groups
552      * registered by this service. Note that if a service has both
553      * a static (manifest-based) AID group for a category and a dynamic
554      * AID group, only the dynamically registered AIDs will be returned
555      * for that category.
556      * @return List of AIDs registered by the service
557      */
558     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
559     @NonNull
getAids()560     public List<String> getAids() {
561         final ArrayList<String> aids = new ArrayList<String>();
562         for (AidGroup group : getAidGroups()) {
563             aids.addAll(group.getAids());
564         }
565         return aids;
566     }
567 
568     /**
569      * Returns the current polling loop filters for this service.
570      * @return List of polling loop filters.
571      */
572     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
573     @NonNull
getPollingLoopFilters()574     public List<String> getPollingLoopFilters() {
575         return new ArrayList<>(mAutoTransact.keySet());
576     }
577 
578     /**
579      * Returns whether this service would like to automatically transact for a given plf.
580      *
581      * @param plf the polling loop filter to query.
582      * @return {@code true} indicating to auto transact, {@code false} indicating to not.
583      */
584     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
getShouldAutoTransact(@onNull String plf)585     public boolean getShouldAutoTransact(@NonNull String plf) {
586         if (mAutoTransact.getOrDefault(plf.toUpperCase(Locale.ROOT), false)) {
587             return true;
588         }
589         boolean isPattern = plf.contains("?") || plf.contains("*");
590         List<Pattern> patternMatches = mAutoTransactPatterns.keySet().stream().filter(
591             p -> isPattern ? p.toString().equals(plf) : p.matcher(plf).matches()).toList();
592 
593         if (patternMatches == null || patternMatches.size() == 0) {
594             return false;
595         }
596         for (Pattern patternMatch : patternMatches) {
597             if (mAutoTransactPatterns.get(patternMatch)) {
598                 return true;
599             }
600         }
601         return false;
602     }
603 
604     /**
605      * Returns the current polling loop pattern filters for this service.
606      * @return List of polling loop pattern filters.
607      */
608     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
609     @NonNull
getPollingLoopPatternFilters()610     public List<Pattern> getPollingLoopPatternFilters() {
611         return new ArrayList<>(mAutoTransactPatterns.keySet());
612     }
613 
614     /**
615      * Returns a consolidated list of AIDs with prefixes from the AID groups
616      * registered by this service. Note that if a service has both
617      * a static (manifest-based) AID group for a category and a dynamic
618      * AID group, only the dynamically registered AIDs will be returned
619      * for that category.
620      * @return List of prefix AIDs registered by the service
621      */
622     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
623     @NonNull
getPrefixAids()624     public List<String> getPrefixAids() {
625         final ArrayList<String> prefixAids = new ArrayList<String>();
626         for (AidGroup group : getAidGroups()) {
627             for (String aid : group.getAids()) {
628                 if (aid.endsWith("*")) {
629                     prefixAids.add(aid);
630                 }
631             }
632         }
633         return prefixAids;
634     }
635 
636     /**
637      * Returns a consolidated list of AIDs with subsets from the AID groups
638      * registered by this service. Note that if a service has both
639      * a static (manifest-based) AID group for a category and a dynamic
640      * AID group, only the dynamically registered AIDs will be returned
641      * for that category.
642      * @return List of prefix AIDs registered by the service
643      */
644     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
645     @NonNull
getSubsetAids()646     public List<String> getSubsetAids() {
647         final ArrayList<String> subsetAids = new ArrayList<String>();
648         for (AidGroup group : getAidGroups()) {
649             for (String aid : group.getAids()) {
650                 if (aid.endsWith("#")) {
651                     subsetAids.add(aid);
652                 }
653             }
654         }
655         return subsetAids;
656     }
657 
658     /**
659      * Returns the registered AID group for this category.
660      *
661      * @param category category name
662      * @return {@link AidGroup} instance for the provided category
663      */
664     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
665     @NonNull
getDynamicAidGroupForCategory(@onNull String category)666     public AidGroup getDynamicAidGroupForCategory(@NonNull String category) {
667         return mDynamicAidGroups.get(category);
668     }
669 
670     /**
671      * Removes the registered AID group for this category.
672      *
673      * @param category category name
674      * @return {@code true} if an AID group existed
675      */
676     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
677     @NonNull
removeDynamicAidGroupForCategory(@onNull String category)678     public boolean removeDynamicAidGroupForCategory(@NonNull String category) {
679         return (mDynamicAidGroups.remove(category) != null);
680     }
681 
682     /**
683      * Returns a consolidated list of AID groups
684      * registered by this service. Note that if a service has both
685      * a static (manifest-based) AID group for a category and a dynamic
686      * AID group, only the dynamically registered AID group will be returned
687      * for that category.
688      * @return List of AIDs registered by the service
689      */
690     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
691     @NonNull
getAidGroups()692     public List<AidGroup> getAidGroups() {
693         final ArrayList<AidGroup> groups = new ArrayList<AidGroup>();
694         for (Map.Entry<String, AidGroup> entry : mDynamicAidGroups.entrySet()) {
695             groups.add(entry.getValue());
696         }
697         for (Map.Entry<String, AidGroup> entry : mStaticAidGroups.entrySet()) {
698             if (!mDynamicAidGroups.containsKey(entry.getKey())) {
699                 // Consolidate AID groups - don't return static ones
700                 // if a dynamic group exists for the category.
701                 groups.add(entry.getValue());
702             }
703         }
704         return groups;
705     }
706 
707     /**
708      * Returns the category to which this service has attributed the AID that is passed in,
709      * or null if we don't know this AID.
710      * @param aid AID to lookup for
711      * @return category name corresponding to this AID
712      */
713     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
714     @NonNull
getCategoryForAid(@onNull String aid)715     public String getCategoryForAid(@NonNull String aid) {
716         List<AidGroup> groups = getAidGroups();
717         for (AidGroup group : groups) {
718             if (group.getAids().contains(aid.toUpperCase())) {
719                 return group.getCategory();
720             }
721         }
722         return null;
723     }
724 
725     /**
726      * Returns whether there is any AID group for this category.
727      * @param category category name
728      * @return {@code true} if an AID group exists
729      */
730     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
hasCategory(@onNull String category)731     public boolean hasCategory(@NonNull String category) {
732         return (mStaticAidGroups.containsKey(category) || mDynamicAidGroups.containsKey(category));
733     }
734 
735     /**
736      * Returns whether the service is on host or not.
737      * @return true if the service is on host (not secure element)
738      */
739     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
isOnHost()740     public boolean isOnHost() {
741         return mOnHost;
742     }
743 
744     /**
745      * Returns whether the service requires device unlock.
746      * @return whether the service requires device unlock
747      */
748     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
requiresUnlock()749     public boolean requiresUnlock() {
750         return mRequiresDeviceUnlock;
751     }
752 
753     /**
754      * Returns whether this service should only be started when the device is screen on.
755      * @return whether the service requires screen on
756      */
757     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
requiresScreenOn()758     public boolean requiresScreenOn() {
759         return mRequiresDeviceScreenOn;
760     }
761 
762     /**
763      * Returns whether the NFC stack should default to observe mode when this service is preferred.
764      * @return whether the NFC stack should default to observe mode when this service is preferred
765      */
766     @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
shouldDefaultToObserveMode()767     public boolean shouldDefaultToObserveMode() {
768         return mShouldDefaultToObserveMode;
769     }
770 
771     /**
772      * Sets whether the NFC stack should default to observe mode when this service is preferred.
773      * @param shouldDefaultToObserveMode whether the NFC stack should default to observe mode when
774      *                                  this service is preferred
775      */
776     @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
setShouldDefaultToObserveMode(boolean shouldDefaultToObserveMode)777     public void setShouldDefaultToObserveMode(boolean shouldDefaultToObserveMode) {
778         mShouldDefaultToObserveMode = shouldDefaultToObserveMode;
779     }
780 
781     /**
782      * Returns whether or not this service wants to share the Wallet role holder priority
783      * with other packages/services with the same signature.
784      *
785      * @return whether or not this service wants to share priority
786      */
787     @FlaggedApi(Flags.FLAG_NFC_ASSOCIATED_ROLE_SERVICES)
wantsRoleHolderPriority()788     public boolean wantsRoleHolderPriority() {
789         return mWantsRoleHolderPriority;
790     }
791 
792     /**
793      * Returns description of service.
794      * @return user readable description of service
795      */
796     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
797     @NonNull
getDescription()798     public String getDescription() {
799         return mDescription;
800     }
801 
802     /**
803      * Returns uid of service.
804      * @return uid of the service
805      */
806     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
getUid()807     public int getUid() {
808         return mUid;
809     }
810 
811     /**
812      * Add or replace an AID group to this service.
813      * @param aidGroup instance of aid group to set or replace
814      */
815     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
setDynamicAidGroup(@onNull AidGroup aidGroup)816     public void setDynamicAidGroup(@NonNull AidGroup aidGroup) {
817         mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
818     }
819 
820     /**
821      * Add a Polling Loop Filter. Custom NFC polling frames that match this filter will be
822      * delivered to {@link HostApduService#processPollingFrames(List)}. Adding a key with this
823      * multiple times will cause the value to be overwritten each time.
824      * @param pollingLoopFilter the polling loop filter to add, must be a valid hexadecimal string
825      * @param autoTransact when true, disable observe mode when this filter matches, when false,
826      *                     matching does not change the observe mode state
827      */
828     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
addPollingLoopFilter(@onNull String pollingLoopFilter, boolean autoTransact)829     public void addPollingLoopFilter(@NonNull String pollingLoopFilter,
830             boolean autoTransact) {
831         if (!PLF_PATTERN.matcher(pollingLoopFilter).matches()
832                 || pollingLoopFilter.length() % 2 != 0) {
833             throw new IllegalArgumentException(
834                     "Polling loop filter must contain an even number of characters 0-9 or A-F"
835             );
836         }
837         if (!mOnHost && !autoTransact) {
838             return;
839         }
840         mAutoTransact.put(pollingLoopFilter.toUpperCase(Locale.ROOT), autoTransact);
841     }
842 
843     /**
844      * Remove a Polling Loop Filter. Custom NFC polling frames that match this filter will no
845      * longer be delivered to {@link HostApduService#processPollingFrames(List)}.
846      * @param pollingLoopFilter this polling loop filter to add.
847      */
848     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
removePollingLoopFilter(@onNull String pollingLoopFilter)849     public void removePollingLoopFilter(@NonNull String pollingLoopFilter) {
850         mAutoTransact.remove(pollingLoopFilter.toUpperCase(Locale.ROOT));
851     }
852 
853     /**
854      * Add a Polling Loop Pattern Filter. Custom NFC polling frames that match this filter will be
855      * delivered to {@link HostApduService#processPollingFrames(List)}. Adding a key with this
856      * multiple times will cause the value to be overwritten each time.
857      * @param pollingLoopPatternFilter the polling loop pattern filter to add, must be a valid
858      *                                regex to match a hexadecimal string
859      * @param autoTransact when true, disable observe mode when this filter matches, when false,
860      *                     matching does not change the observe mode state
861      */
862     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
addPollingLoopPatternFilter(@onNull String pollingLoopPatternFilter, boolean autoTransact)863     public void addPollingLoopPatternFilter(@NonNull String pollingLoopPatternFilter,
864             boolean autoTransact) {
865         if (!PLPF_PATTERN.matcher(pollingLoopPatternFilter).matches()) {
866             throw new IllegalArgumentException(
867                     "Polling loop pattern filter is invalid"
868             );
869         }
870         if (!mOnHost && !autoTransact) {
871             return;
872         }
873         mAutoTransactPatterns.put(Pattern.compile(
874                 pollingLoopPatternFilter.toUpperCase(Locale.ROOT)), autoTransact);
875     }
876 
877     /**
878      * Remove a Polling Loop Pattern Filter. Custom NFC polling frames that match this filter will
879      * no longer be delivered to {@link HostApduService#processPollingFrames(List)}.
880      * @param pollingLoopPatternFilter this polling loop filter to add.
881      */
882     @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
removePollingLoopPatternFilter(@onNull String pollingLoopPatternFilter)883     public void removePollingLoopPatternFilter(@NonNull String pollingLoopPatternFilter) {
884         mAutoTransactPatterns.remove(
885                 Pattern.compile(pollingLoopPatternFilter.toUpperCase(Locale.ROOT)));
886     }
887 
888     /**
889      * Sets the off host Secure Element.
890      * @param  offHost  Secure Element to set. Only accept strings with prefix SIM or prefix eSE.
891      *                  Ref: GSMA TS.26 - NFC Handset Requirements
892      *                  TS26_NFC_REQ_069: For UICC, Secure Element Name SHALL be SIM[smartcard slot]
893      *                                    (e.g. SIM/SIM1, SIM2… SIMn).
894      *                  TS26_NFC_REQ_070: For embedded SE, Secure Element Name SHALL be eSE[number]
895      *                                    (e.g. eSE/eSE1, eSE2, etc.).
896      */
897     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
setOffHostSecureElement(@onNull String offHost)898     public void setOffHostSecureElement(@NonNull String offHost) {
899         mOffHostName = offHost;
900     }
901 
902     /**
903      * Resets the off host Secure Element to statically defined
904      * by the service in the manifest file.
905      */
906     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
resetOffHostSecureElement()907     public void resetOffHostSecureElement() {
908         mOffHostName = mStaticOffHostName;
909     }
910 
911     /**
912      * Load label for this service.
913      * @param pm packagemanager instance
914      * @return label name corresponding to service
915      */
916     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
917     @NonNull
loadLabel(@onNull PackageManager pm)918     public CharSequence loadLabel(@NonNull PackageManager pm) {
919         return mService.loadLabel(pm);
920     }
921 
922     /**
923      * Load application label for this service.
924      * @param pm packagemanager instance
925      * @return app label name corresponding to service
926      */
927     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
928     @NonNull
loadAppLabel(@onNull PackageManager pm)929     public CharSequence loadAppLabel(@NonNull PackageManager pm) {
930         try {
931             return pm.getApplicationLabel(pm.getApplicationInfo(
932                     mService.resolvePackageName, PackageManager.GET_META_DATA));
933         } catch (PackageManager.NameNotFoundException e) {
934             return null;
935         }
936     }
937 
938     /**
939      * Load application icon for this service.
940      * @param pm packagemanager instance
941      * @return app icon corresponding to service
942      */
943     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
944     @NonNull
loadIcon(@onNull PackageManager pm)945     public Drawable loadIcon(@NonNull PackageManager pm) {
946         return mService.loadIcon(pm);
947     }
948 
949     /**
950      * Load application banner for this service.
951      * @param pm packagemanager instance
952      * @return app banner corresponding to service
953      */
954     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
955     @NonNull
loadBanner(@onNull PackageManager pm)956     public Drawable loadBanner(@NonNull PackageManager pm) {
957         Resources res;
958         try {
959             res = pm.getResourcesForApplication(mService.serviceInfo.packageName);
960             Drawable banner = res.getDrawable(mBannerResourceId);
961             return banner;
962         } catch (NotFoundException e) {
963             Log.e(TAG, "Could not load banner.");
964             return null;
965         } catch (NameNotFoundException e) {
966             Log.e(TAG, "Could not load banner.");
967             return null;
968         }
969     }
970 
971     /**
972      * Load activity name for this service.
973      * @return activity name for this service
974      */
975     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
976     @NonNull
getSettingsActivityName()977     public String getSettingsActivityName() { return mSettingsActivityName; }
978 
979     @Override
toString()980     public String toString() {
981         StringBuilder out = new StringBuilder("ApduService: ");
982         out.append(getComponent());
983         out.append(", UID: " + mUid);
984         out.append(", description: " + mDescription);
985         out.append(", Static AID Groups: ");
986         for (AidGroup aidGroup : mStaticAidGroups.values()) {
987             out.append(aidGroup.toString());
988         }
989         out.append(", Dynamic AID Groups: ");
990         for (AidGroup aidGroup : mDynamicAidGroups.values()) {
991             out.append(aidGroup.toString());
992         }
993         return out.toString();
994     }
995 
996     @Override
equals(@ullable Object o)997     public boolean equals(@Nullable Object o) {
998         if (this == o) return true;
999         if (!(o instanceof ApduServiceInfo)) return false;
1000         ApduServiceInfo thatService = (ApduServiceInfo) o;
1001 
1002         return thatService.getComponent().equals(this.getComponent())
1003                 && thatService.getUid() == this.getUid();
1004     }
1005 
1006     @Override
hashCode()1007     public int hashCode() {
1008         return getComponent().hashCode();
1009     }
1010 
1011     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
1012     @Override
describeContents()1013     public int describeContents() {
1014         return 0;
1015     }
1016 
1017     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
1018     @Override
writeToParcel(@onNull Parcel dest, int flags)1019     public void writeToParcel(@NonNull Parcel dest, int flags) {
1020         mService.writeToParcel(dest, flags);
1021         dest.writeString(mDescription);
1022         dest.writeInt(mOnHost ? 1 : 0);
1023         dest.writeString(mOffHostName);
1024         dest.writeString(mStaticOffHostName);
1025         dest.writeInt(mStaticAidGroups.size());
1026         if (mStaticAidGroups.size() > 0) {
1027             dest.writeTypedList(new ArrayList<AidGroup>(mStaticAidGroups.values()));
1028         }
1029         dest.writeInt(mDynamicAidGroups.size());
1030         if (mDynamicAidGroups.size() > 0) {
1031             dest.writeTypedList(new ArrayList<AidGroup>(mDynamicAidGroups.values()));
1032         }
1033         dest.writeInt(mRequiresDeviceUnlock ? 1 : 0);
1034         dest.writeInt(mRequiresDeviceScreenOn ? 1 : 0);
1035         dest.writeInt(mBannerResourceId);
1036         dest.writeInt(mUid);
1037         dest.writeString(mSettingsActivityName);
1038 
1039         dest.writeInt(mCategoryOtherServiceEnabled ? 1 : 0);
1040         dest.writeInt(mAutoTransact.size());
1041         dest.writeMap(mAutoTransact);
1042         dest.writeInt(mAutoTransactPatterns.size());
1043         dest.writeMap(mAutoTransactPatterns);
1044     };
1045 
1046     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
1047     public static final @NonNull Parcelable.Creator<ApduServiceInfo> CREATOR =
1048             new Parcelable.Creator<ApduServiceInfo>() {
1049                 @Override
1050                 public ApduServiceInfo createFromParcel(Parcel source) {
1051                     ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
1052                     String description = source.readString();
1053                     boolean onHost = source.readInt() != 0;
1054                     String offHostName = source.readString();
1055                     String staticOffHostName = source.readString();
1056                     ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>();
1057                     int numStaticGroups = source.readInt();
1058                     if (numStaticGroups > 0) {
1059                         source.readTypedList(staticAidGroups, AidGroup.CREATOR);
1060                     }
1061                     ArrayList<AidGroup> dynamicAidGroups = new ArrayList<AidGroup>();
1062                     int numDynamicGroups = source.readInt();
1063                     if (numDynamicGroups > 0) {
1064                         source.readTypedList(dynamicAidGroups, AidGroup.CREATOR);
1065                     }
1066                     boolean requiresUnlock = source.readInt() != 0;
1067                     boolean requiresScreenOn = source.readInt() != 0;
1068                     int bannerResource = source.readInt();
1069                     int uid = source.readInt();
1070                     String settingsActivityName = source.readString();
1071                     boolean isEnabled = source.readInt() != 0;
1072                     int autoTransactSize = source.readInt();
1073                     HashMap<String, Boolean> autoTransact =
1074                             new HashMap<String, Boolean>(autoTransactSize);
1075                     source.readMap(autoTransact, getClass().getClassLoader(),
1076                             String.class, Boolean.class);
1077                     int autoTransactPatternSize = source.readInt();
1078                     HashMap<Pattern, Boolean> autoTransactPatterns =
1079                             new HashMap<Pattern, Boolean>(autoTransactSize);
1080                     source.readMap(autoTransactPatterns, getClass().getClassLoader(),
1081                             Pattern.class, Boolean.class);
1082                     return new ApduServiceInfo(info, onHost, description, staticAidGroups,
1083                             dynamicAidGroups, requiresUnlock, requiresScreenOn, bannerResource, uid,
1084                             settingsActivityName, offHostName, staticOffHostName,
1085                             isEnabled, autoTransact, autoTransactPatterns);
1086                 }
1087 
1088                 @Override
1089                 public ApduServiceInfo[] newArray(int size) {
1090                     return new ApduServiceInfo[size];
1091                 }
1092             };
1093 
1094     /**
1095      * Dump contents for debugging.
1096      * @param fd parcelfiledescriptor instance
1097      * @param pw printwriter instance
1098      * @param args args for dumping
1099      */
1100     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
dump(@onNull ParcelFileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args)1101     public void dump(@NonNull ParcelFileDescriptor fd, @NonNull PrintWriter pw,
1102                      @NonNull String[] args) {
1103         pw.println("    " + getComponent()
1104                 + " (Description: " + getDescription() + ")"
1105                 + " (UID: " + getUid() + ")");
1106         if (mOnHost) {
1107             pw.println("    On Host Service");
1108         } else {
1109             pw.println("    Off-host Service");
1110             pw.println("        " + "Current off-host SE:" + mOffHostName
1111                     + " static off-host SE:" + mStaticOffHostName);
1112         }
1113         pw.println("    Static AID groups:");
1114         for (AidGroup group : mStaticAidGroups.values()) {
1115             pw.println("        Category: " + group.getCategory()
1116                     + "(enabled: " + mCategoryOtherServiceEnabled + ")");
1117             for (String aid : group.getAids()) {
1118                 pw.println("            AID: " + aid);
1119             }
1120         }
1121         pw.println("    Dynamic AID groups:");
1122         for (AidGroup group : mDynamicAidGroups.values()) {
1123             pw.println("        Category: " + group.getCategory()
1124                     + "(enabled: " + mCategoryOtherServiceEnabled + ")");
1125             for (String aid : group.getAids()) {
1126                 pw.println("            AID: " + aid);
1127             }
1128         }
1129         pw.println("    Settings Activity: " + mSettingsActivityName);
1130         pw.println("    Requires Device Unlock: " + mRequiresDeviceUnlock);
1131         pw.println("    Requires Device ScreenOn: " + mRequiresDeviceScreenOn);
1132         pw.println("    Should Default to Observe Mode: " + mShouldDefaultToObserveMode);
1133         pw.println("    Auto-Transact Mapping: " + mAutoTransact);
1134         pw.println("    Auto-Transact Patterns: " + mAutoTransactPatterns);
1135     }
1136 
1137 
1138     /**
1139      * Enable or disable this CATEGORY_OTHER service.
1140      *
1141      * @param enabled true to indicate if user has enabled this service
1142      */
1143     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
setCategoryOtherServiceEnabled(boolean enabled)1144     public void setCategoryOtherServiceEnabled(boolean enabled) {
1145         mCategoryOtherServiceEnabled = enabled;
1146     }
1147 
1148 
1149     /**
1150      * Returns whether this CATEGORY_OTHER service is enabled or not.
1151      *
1152      * @return true to indicate if user has enabled this service
1153      */
1154     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
isCategoryOtherServiceEnabled()1155     public boolean isCategoryOtherServiceEnabled() {
1156         return mCategoryOtherServiceEnabled;
1157     }
1158 
1159     /**
1160      * Dump debugging info as ApduServiceInfoProto.
1161      *
1162      * If the output belongs to a sub message, the caller is responsible for wrapping this function
1163      * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}.
1164      * See proto definition in frameworks/base/core/proto/android/nfc/apdu_service_info.proto
1165      *
1166      * @param proto the ProtoOutputStream to write to
1167      */
1168     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
dumpDebug(@onNull ProtoOutputStream proto)1169     public void dumpDebug(@NonNull ProtoOutputStream proto) {
1170         getComponent().dumpDebug(proto, ApduServiceInfoProto.COMPONENT_NAME);
1171         proto.write(ApduServiceInfoProto.DESCRIPTION, getDescription());
1172         proto.write(ApduServiceInfoProto.ON_HOST, mOnHost);
1173         if (!mOnHost) {
1174             proto.write(ApduServiceInfoProto.OFF_HOST_NAME, mOffHostName);
1175             proto.write(ApduServiceInfoProto.STATIC_OFF_HOST_NAME, mStaticOffHostName);
1176         }
1177         for (AidGroup group : mStaticAidGroups.values()) {
1178             long token = proto.start(ApduServiceInfoProto.STATIC_AID_GROUPS);
1179             group.dump(proto);
1180             proto.end(token);
1181         }
1182         for (AidGroup group : mDynamicAidGroups.values()) {
1183             long token = proto.start(ApduServiceInfoProto.STATIC_AID_GROUPS);
1184             group.dump(proto);
1185             proto.end(token);
1186         }
1187         proto.write(ApduServiceInfoProto.SETTINGS_ACTIVITY_NAME, mSettingsActivityName);
1188         proto.write(ApduServiceInfoProto.SHOULD_DEFAULT_TO_OBSERVE_MODE,
1189                 mShouldDefaultToObserveMode);
1190         {
1191             long token = proto.start(ApduServiceInfoProto.AUTO_TRANSACT_MAPPING);
1192             for (Map.Entry<String, Boolean> entry : mAutoTransact.entrySet()) {
1193                 proto.write(ApduServiceInfoProto.AutoTransactMapping.AID, entry.getKey());
1194                 proto.write(ApduServiceInfoProto.AutoTransactMapping.SHOULD_AUTO_TRANSACT,
1195                         entry.getValue());
1196             }
1197             proto.end(token);
1198         }
1199         {
1200             long token = proto.start(ApduServiceInfoProto.AUTO_TRANSACT_PATTERNS);
1201             for (Map.Entry<Pattern, Boolean> entry : mAutoTransactPatterns.entrySet()) {
1202                 proto.write(ApduServiceInfoProto.AutoTransactPattern.REGEXP_PATTERN,
1203                         entry.getKey().pattern());
1204                 proto.write(ApduServiceInfoProto.AutoTransactPattern.SHOULD_AUTO_TRANSACT,
1205                         entry.getValue());
1206             }
1207             proto.end(token);
1208         }
1209     }
1210 
1211     private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
1212     /**
1213      * Copied over from {@link CardEmulation#isValidAid(String)}
1214      * @hide
1215      */
isValidAid(String aid)1216     private static boolean isValidAid(String aid) {
1217         if (aid == null)
1218             return false;
1219 
1220         // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
1221         if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
1222             Log.e(TAG, "AID " + aid + " is not a valid AID.");
1223             return false;
1224         }
1225 
1226         // If not a prefix/subset AID, the total length must be even (even # of AID chars)
1227         if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
1228             Log.e(TAG, "AID " + aid + " is not a valid AID.");
1229             return false;
1230         }
1231 
1232         // Verify hex characters
1233         if (!AID_PATTERN.matcher(aid).matches()) {
1234             Log.e(TAG, "AID " + aid + " is not a valid AID.");
1235             return false;
1236         }
1237 
1238         return true;
1239     }
1240 }
1241