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