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