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