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 package android.nfc.cardemulation;
18 
19 import android.annotation.Nullable;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.content.ComponentName;
22 import android.content.pm.PackageManager;
23 import android.content.pm.PackageManager.NameNotFoundException;
24 import android.content.pm.ResolveInfo;
25 import android.content.pm.ServiceInfo;
26 import android.content.res.Resources;
27 import android.content.res.Resources.NotFoundException;
28 import android.content.res.TypedArray;
29 import android.content.res.XmlResourceParser;
30 import android.graphics.drawable.Drawable;
31 import android.os.Parcel;
32 import android.os.Parcelable;
33 import android.util.AttributeSet;
34 import android.util.Log;
35 import android.util.Xml;
36 import android.util.proto.ProtoOutputStream;
37 
38 import org.xmlpull.v1.XmlPullParser;
39 import org.xmlpull.v1.XmlPullParserException;
40 
41 import java.io.FileDescriptor;
42 import java.io.IOException;
43 import java.io.PrintWriter;
44 import java.util.ArrayList;
45 import java.util.HashMap;
46 import java.util.List;
47 import java.util.Map;
48 
49 /**
50  * @hide
51  */
52 public final class ApduServiceInfo implements Parcelable {
53     static final String TAG = "ApduServiceInfo";
54 
55     /**
56      * The service that implements this
57      */
58     @UnsupportedAppUsage
59     final ResolveInfo mService;
60 
61     /**
62      * Description of the service
63      */
64     final String mDescription;
65 
66     /**
67      * Whether this service represents AIDs running on the host CPU
68      */
69     final boolean mOnHost;
70 
71     /**
72      * Offhost reader name.
73      * eg: SIM, eSE etc
74      */
75     String mOffHostName;
76 
77     /**
78      * Offhost reader name from manifest file.
79      * Used for unsetOffHostSecureElement()
80      */
81     final String mStaticOffHostName;
82 
83     /**
84      * Mapping from category to static AID group
85      */
86     @UnsupportedAppUsage
87     final HashMap<String, AidGroup> mStaticAidGroups;
88 
89     /**
90      * Mapping from category to dynamic AID group
91      */
92     @UnsupportedAppUsage
93     final HashMap<String, AidGroup> mDynamicAidGroups;
94 
95     /**
96      * Whether this service should only be started when the device is unlocked.
97      */
98     final boolean mRequiresDeviceUnlock;
99 
100     /**
101      * Whether this service should only be started when the device is screen on.
102      */
103     final boolean mRequiresDeviceScreenOn;
104 
105     /**
106      * The id of the service banner specified in XML.
107      */
108     final int mBannerResourceId;
109 
110     /**
111      * The uid of the package the service belongs to
112      */
113     final int mUid;
114 
115     /**
116      * Settings Activity for this service
117      */
118     final String mSettingsActivityName;
119 
120     /**
121      * @hide
122      */
123     @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)124     public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
125             ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
126             boolean requiresUnlock, int bannerResource, int uid,
127             String settingsActivityName, String offHost, String staticOffHost) {
128         this(info, onHost, description, staticAidGroups, dynamicAidGroups,
129                 requiresUnlock, onHost ? true : false, bannerResource, uid,
130                 settingsActivityName, offHost, staticOffHost);
131     }
132 
133     /**
134      * @hide
135      */
ApduServiceInfo(ResolveInfo info, boolean onHost, String description, ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups, boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid, String settingsActivityName, String offHost, String staticOffHost)136     public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
137             ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
138             boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid,
139             String settingsActivityName, String offHost, String staticOffHost) {
140         this.mService = info;
141         this.mDescription = description;
142         this.mStaticAidGroups = new HashMap<String, AidGroup>();
143         this.mDynamicAidGroups = new HashMap<String, AidGroup>();
144         this.mOffHostName = offHost;
145         this.mStaticOffHostName = staticOffHost;
146         this.mOnHost = onHost;
147         this.mRequiresDeviceUnlock = requiresUnlock;
148         this.mRequiresDeviceScreenOn = requiresScreenOn;
149         for (AidGroup aidGroup : staticAidGroups) {
150             this.mStaticAidGroups.put(aidGroup.category, aidGroup);
151         }
152         for (AidGroup aidGroup : dynamicAidGroups) {
153             this.mDynamicAidGroups.put(aidGroup.category, aidGroup);
154         }
155         this.mBannerResourceId = bannerResource;
156         this.mUid = uid;
157         this.mSettingsActivityName = settingsActivityName;
158     }
159 
160     @UnsupportedAppUsage
ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost)161     public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost) throws
162             XmlPullParserException, IOException {
163         ServiceInfo si = info.serviceInfo;
164         XmlResourceParser parser = null;
165         try {
166             if (onHost) {
167                 parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA);
168                 if (parser == null) {
169                     throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
170                             " meta-data");
171                 }
172             } else {
173                 parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA);
174                 if (parser == null) {
175                     throw new XmlPullParserException("No " + OffHostApduService.SERVICE_META_DATA +
176                             " meta-data");
177                 }
178             }
179 
180             int eventType = parser.getEventType();
181             while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) {
182                 eventType = parser.next();
183             }
184 
185             String tagName = parser.getName();
186             if (onHost && !"host-apdu-service".equals(tagName)) {
187                 throw new XmlPullParserException(
188                         "Meta-data does not start with <host-apdu-service> tag");
189             } else if (!onHost && !"offhost-apdu-service".equals(tagName)) {
190                 throw new XmlPullParserException(
191                         "Meta-data does not start with <offhost-apdu-service> tag");
192             }
193 
194             Resources res = pm.getResourcesForApplication(si.applicationInfo);
195             AttributeSet attrs = Xml.asAttributeSet(parser);
196             if (onHost) {
197                 TypedArray sa = res.obtainAttributes(attrs,
198                         com.android.internal.R.styleable.HostApduService);
199                 mService = info;
200                 mDescription = sa.getString(
201                         com.android.internal.R.styleable.HostApduService_description);
202                 mRequiresDeviceUnlock = sa.getBoolean(
203                         com.android.internal.R.styleable.HostApduService_requireDeviceUnlock,
204                         false);
205                 mRequiresDeviceScreenOn = sa.getBoolean(
206                         com.android.internal.R.styleable.HostApduService_requireDeviceScreenOn,
207                         true);
208                 mBannerResourceId = sa.getResourceId(
209                         com.android.internal.R.styleable.HostApduService_apduServiceBanner, -1);
210                 mSettingsActivityName = sa.getString(
211                         com.android.internal.R.styleable.HostApduService_settingsActivity);
212                 mOffHostName = null;
213                 mStaticOffHostName = mOffHostName;
214                 sa.recycle();
215             } else {
216                 TypedArray sa = res.obtainAttributes(attrs,
217                         com.android.internal.R.styleable.OffHostApduService);
218                 mService = info;
219                 mDescription = sa.getString(
220                         com.android.internal.R.styleable.OffHostApduService_description);
221                 mRequiresDeviceUnlock = sa.getBoolean(
222                         com.android.internal.R.styleable.OffHostApduService_requireDeviceUnlock,
223                         false);
224                 mRequiresDeviceScreenOn = sa.getBoolean(
225                         com.android.internal.R.styleable.OffHostApduService_requireDeviceScreenOn,
226                         false);
227                 mBannerResourceId = sa.getResourceId(
228                         com.android.internal.R.styleable.OffHostApduService_apduServiceBanner, -1);
229                 mSettingsActivityName = sa.getString(
230                         com.android.internal.R.styleable.HostApduService_settingsActivity);
231                 mOffHostName = sa.getString(
232                         com.android.internal.R.styleable.OffHostApduService_secureElementName);
233                 if (mOffHostName != null) {
234                     if (mOffHostName.equals("eSE")) {
235                         mOffHostName = "eSE1";
236                     } else if (mOffHostName.equals("SIM")) {
237                         mOffHostName = "SIM1";
238                     }
239                 }
240                 mStaticOffHostName = mOffHostName;
241                 sa.recycle();
242             }
243 
244             mStaticAidGroups = new HashMap<String, AidGroup>();
245             mDynamicAidGroups = new HashMap<String, AidGroup>();
246             mOnHost = onHost;
247 
248             final int depth = parser.getDepth();
249             AidGroup currentGroup = null;
250 
251             // Parsed values for the current AID group
252             while (((eventType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
253                     && eventType != XmlPullParser.END_DOCUMENT) {
254                 tagName = parser.getName();
255                 if (eventType == XmlPullParser.START_TAG && "aid-group".equals(tagName) &&
256                         currentGroup == null) {
257                     final TypedArray groupAttrs = res.obtainAttributes(attrs,
258                             com.android.internal.R.styleable.AidGroup);
259                     // Get category of AID group
260                     String groupCategory = groupAttrs.getString(
261                             com.android.internal.R.styleable.AidGroup_category);
262                     String groupDescription = groupAttrs.getString(
263                             com.android.internal.R.styleable.AidGroup_description);
264                     if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) {
265                         groupCategory = CardEmulation.CATEGORY_OTHER;
266                     }
267                     currentGroup = mStaticAidGroups.get(groupCategory);
268                     if (currentGroup != null) {
269                         if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
270                             Log.e(TAG, "Not allowing multiple aid-groups in the " +
271                                     groupCategory + " category");
272                             currentGroup = null;
273                         }
274                     } else {
275                         currentGroup = new AidGroup(groupCategory, groupDescription);
276                     }
277                     groupAttrs.recycle();
278                 } else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) &&
279                         currentGroup != null) {
280                     if (currentGroup.aids.size() > 0) {
281                         if (!mStaticAidGroups.containsKey(currentGroup.category)) {
282                             mStaticAidGroups.put(currentGroup.category, currentGroup);
283                         }
284                     } else {
285                         Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs");
286                     }
287                     currentGroup = null;
288                 } else if (eventType == XmlPullParser.START_TAG && "aid-filter".equals(tagName) &&
289                         currentGroup != null) {
290                     final TypedArray a = res.obtainAttributes(attrs,
291                             com.android.internal.R.styleable.AidFilter);
292                     String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
293                             toUpperCase();
294                     if (CardEmulation.isValidAid(aid) && !currentGroup.aids.contains(aid)) {
295                         currentGroup.aids.add(aid);
296                     } else {
297                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
298                     }
299                     a.recycle();
300                 } else if (eventType == XmlPullParser.START_TAG &&
301                         "aid-prefix-filter".equals(tagName) && currentGroup != null) {
302                     final TypedArray a = res.obtainAttributes(attrs,
303                             com.android.internal.R.styleable.AidFilter);
304                     String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
305                             toUpperCase();
306                     // Add wildcard char to indicate prefix
307                     aid = aid.concat("*");
308                     if (CardEmulation.isValidAid(aid) && !currentGroup.aids.contains(aid)) {
309                         currentGroup.aids.add(aid);
310                     } else {
311                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
312                     }
313                     a.recycle();
314                 } else if (eventType == XmlPullParser.START_TAG &&
315                         tagName.equals("aid-suffix-filter") && currentGroup != null) {
316                     final TypedArray a = res.obtainAttributes(attrs,
317                             com.android.internal.R.styleable.AidFilter);
318                     String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
319                             toUpperCase();
320                     // Add wildcard char to indicate suffix
321                     aid = aid.concat("#");
322                     if (CardEmulation.isValidAid(aid) && !currentGroup.aids.contains(aid)) {
323                         currentGroup.aids.add(aid);
324                     } else {
325                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
326                     }
327                     a.recycle();
328                 }
329             }
330         } catch (NameNotFoundException e) {
331             throw new XmlPullParserException("Unable to create context for: " + si.packageName);
332         } finally {
333             if (parser != null) parser.close();
334         }
335         // Set uid
336         mUid = si.applicationInfo.uid;
337     }
338 
getComponent()339     public ComponentName getComponent() {
340         return new ComponentName(mService.serviceInfo.packageName,
341                 mService.serviceInfo.name);
342     }
343 
getOffHostSecureElement()344     public String getOffHostSecureElement() {
345         return mOffHostName;
346     }
347 
348     /**
349      * Returns a consolidated list of AIDs from the AID groups
350      * registered by this service. Note that if a service has both
351      * a static (manifest-based) AID group for a category and a dynamic
352      * AID group, only the dynamically registered AIDs will be returned
353      * for that category.
354      * @return List of AIDs registered by the service
355      */
getAids()356     public List<String> getAids() {
357         final ArrayList<String> aids = new ArrayList<String>();
358         for (AidGroup group : getAidGroups()) {
359             aids.addAll(group.aids);
360         }
361         return aids;
362     }
363 
getPrefixAids()364     public List<String> getPrefixAids() {
365         final ArrayList<String> prefixAids = new ArrayList<String>();
366         for (AidGroup group : getAidGroups()) {
367             for (String aid : group.aids) {
368                 if (aid.endsWith("*")) {
369                     prefixAids.add(aid);
370                 }
371             }
372         }
373         return prefixAids;
374     }
375 
getSubsetAids()376     public List<String> getSubsetAids() {
377         final ArrayList<String> subsetAids = new ArrayList<String>();
378         for (AidGroup group : getAidGroups()) {
379             for (String aid : group.aids) {
380                 if (aid.endsWith("#")) {
381                     subsetAids.add(aid);
382                 }
383             }
384         }
385         return subsetAids;
386     }
387     /**
388      * Returns the registered AID group for this category.
389      */
getDynamicAidGroupForCategory(String category)390     public AidGroup getDynamicAidGroupForCategory(String category) {
391         return mDynamicAidGroups.get(category);
392     }
393 
removeDynamicAidGroupForCategory(String category)394     public boolean removeDynamicAidGroupForCategory(String category) {
395         return (mDynamicAidGroups.remove(category) != null);
396     }
397 
398     /**
399      * Returns a consolidated list of AID groups
400      * registered by this service. Note that if a service has both
401      * a static (manifest-based) AID group for a category and a dynamic
402      * AID group, only the dynamically registered AID group will be returned
403      * for that category.
404      * @return List of AIDs registered by the service
405      */
getAidGroups()406     public ArrayList<AidGroup> getAidGroups() {
407         final ArrayList<AidGroup> groups = new ArrayList<AidGroup>();
408         for (Map.Entry<String, AidGroup> entry : mDynamicAidGroups.entrySet()) {
409             groups.add(entry.getValue());
410         }
411         for (Map.Entry<String, AidGroup> entry : mStaticAidGroups.entrySet()) {
412             if (!mDynamicAidGroups.containsKey(entry.getKey())) {
413                 // Consolidate AID groups - don't return static ones
414                 // if a dynamic group exists for the category.
415                 groups.add(entry.getValue());
416             }
417         }
418         return groups;
419     }
420 
421     /**
422      * Returns the category to which this service has attributed the AID that is passed in,
423      * or null if we don't know this AID.
424      */
getCategoryForAid(String aid)425     public String getCategoryForAid(String aid) {
426         ArrayList<AidGroup> groups = getAidGroups();
427         for (AidGroup group : groups) {
428             if (group.aids.contains(aid.toUpperCase())) {
429                 return group.category;
430             }
431         }
432         return null;
433     }
434 
hasCategory(String category)435     public boolean hasCategory(String category) {
436         return (mStaticAidGroups.containsKey(category) || mDynamicAidGroups.containsKey(category));
437     }
438 
439     @UnsupportedAppUsage
isOnHost()440     public boolean isOnHost() {
441         return mOnHost;
442     }
443 
444     @UnsupportedAppUsage
requiresUnlock()445     public boolean requiresUnlock() {
446         return mRequiresDeviceUnlock;
447     }
448 
449     /**
450      * Returns whether this service should only be started when the device is screen on.
451      */
requiresScreenOn()452     public boolean requiresScreenOn() {
453         return mRequiresDeviceScreenOn;
454     }
455 
456     @UnsupportedAppUsage
getDescription()457     public String getDescription() {
458         return mDescription;
459     }
460 
461     @UnsupportedAppUsage
getUid()462     public int getUid() {
463         return mUid;
464     }
465 
setOrReplaceDynamicAidGroup(AidGroup aidGroup)466     public void setOrReplaceDynamicAidGroup(AidGroup aidGroup) {
467         mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
468     }
469 
470     /**
471      * Sets the off host Secure Element.
472      * @param  offHost  Secure Element to set. Only accept strings with prefix SIM or prefix eSE.
473      *                  Ref: GSMA TS.26 - NFC Handset Requirements
474      *                  TS26_NFC_REQ_069: For UICC, Secure Element Name SHALL be SIM[smartcard slot]
475      *                                    (e.g. SIM/SIM1, SIM2… SIMn).
476      *                  TS26_NFC_REQ_070: For embedded SE, Secure Element Name SHALL be eSE[number]
477      *                                    (e.g. eSE/eSE1, eSE2, etc.).
478      */
setOffHostSecureElement(String offHost)479     public void setOffHostSecureElement(String offHost) {
480         mOffHostName = offHost;
481     }
482 
483     /**
484      * Resets the off host Secure Element to statically defined
485      * by the service in the manifest file.
486      */
unsetOffHostSecureElement()487     public void unsetOffHostSecureElement() {
488         mOffHostName = mStaticOffHostName;
489     }
490 
loadLabel(PackageManager pm)491     public CharSequence loadLabel(PackageManager pm) {
492         return mService.loadLabel(pm);
493     }
494 
loadAppLabel(PackageManager pm)495     public CharSequence loadAppLabel(PackageManager pm) {
496         try {
497             return pm.getApplicationLabel(pm.getApplicationInfo(
498                     mService.resolvePackageName, PackageManager.GET_META_DATA));
499         } catch (PackageManager.NameNotFoundException e) {
500             return null;
501         }
502     }
503 
loadIcon(PackageManager pm)504     public Drawable loadIcon(PackageManager pm) {
505         return mService.loadIcon(pm);
506     }
507 
508     @UnsupportedAppUsage
loadBanner(PackageManager pm)509     public Drawable loadBanner(PackageManager pm) {
510         Resources res;
511         try {
512             res = pm.getResourcesForApplication(mService.serviceInfo.packageName);
513             Drawable banner = res.getDrawable(mBannerResourceId);
514             return banner;
515         } catch (NotFoundException e) {
516             Log.e(TAG, "Could not load banner.");
517             return null;
518         } catch (NameNotFoundException e) {
519             Log.e(TAG, "Could not load banner.");
520             return null;
521         }
522     }
523 
524     @UnsupportedAppUsage
getSettingsActivityName()525     public String getSettingsActivityName() { return mSettingsActivityName; }
526 
527     @Override
toString()528     public String toString() {
529         StringBuilder out = new StringBuilder("ApduService: ");
530         out.append(getComponent());
531         out.append(", UID: " + mUid);
532         out.append(", description: " + mDescription);
533         out.append(", Static AID Groups: ");
534         for (AidGroup aidGroup : mStaticAidGroups.values()) {
535             out.append(aidGroup.toString());
536         }
537         out.append(", Dynamic AID Groups: ");
538         for (AidGroup aidGroup : mDynamicAidGroups.values()) {
539             out.append(aidGroup.toString());
540         }
541         return out.toString();
542     }
543 
544     @Override
equals(@ullable Object o)545     public boolean equals(@Nullable Object o) {
546         if (this == o) return true;
547         if (!(o instanceof ApduServiceInfo)) return false;
548         ApduServiceInfo thatService = (ApduServiceInfo) o;
549 
550         return thatService.getComponent().equals(this.getComponent())
551                 && thatService.getUid() == this.getUid();
552     }
553 
554     @Override
hashCode()555     public int hashCode() {
556         return getComponent().hashCode();
557     }
558 
559 
560     @Override
describeContents()561     public int describeContents() {
562         return 0;
563     }
564 
565     @Override
writeToParcel(Parcel dest, int flags)566     public void writeToParcel(Parcel dest, int flags) {
567         mService.writeToParcel(dest, flags);
568         dest.writeString(mDescription);
569         dest.writeInt(mOnHost ? 1 : 0);
570         dest.writeString(mOffHostName);
571         dest.writeString(mStaticOffHostName);
572         dest.writeInt(mStaticAidGroups.size());
573         if (mStaticAidGroups.size() > 0) {
574             dest.writeTypedList(new ArrayList<AidGroup>(mStaticAidGroups.values()));
575         }
576         dest.writeInt(mDynamicAidGroups.size());
577         if (mDynamicAidGroups.size() > 0) {
578             dest.writeTypedList(new ArrayList<AidGroup>(mDynamicAidGroups.values()));
579         }
580         dest.writeInt(mRequiresDeviceUnlock ? 1 : 0);
581         dest.writeInt(mRequiresDeviceScreenOn ? 1 : 0);
582         dest.writeInt(mBannerResourceId);
583         dest.writeInt(mUid);
584         dest.writeString(mSettingsActivityName);
585     };
586 
587     @UnsupportedAppUsage
588     public static final @android.annotation.NonNull Parcelable.Creator<ApduServiceInfo> CREATOR =
589             new Parcelable.Creator<ApduServiceInfo>() {
590         @Override
591         public ApduServiceInfo createFromParcel(Parcel source) {
592             ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
593             String description = source.readString();
594             boolean onHost = source.readInt() != 0;
595             String offHostName = source.readString();
596             String staticOffHostName = source.readString();
597             ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>();
598             int numStaticGroups = source.readInt();
599             if (numStaticGroups > 0) {
600                 source.readTypedList(staticAidGroups, AidGroup.CREATOR);
601             }
602             ArrayList<AidGroup> dynamicAidGroups = new ArrayList<AidGroup>();
603             int numDynamicGroups = source.readInt();
604             if (numDynamicGroups > 0) {
605                 source.readTypedList(dynamicAidGroups, AidGroup.CREATOR);
606             }
607             boolean requiresUnlock = source.readInt() != 0;
608             boolean requiresScreenOn = source.readInt() != 0;
609             int bannerResource = source.readInt();
610             int uid = source.readInt();
611             String settingsActivityName = source.readString();
612             return new ApduServiceInfo(info, onHost, description, staticAidGroups,
613                     dynamicAidGroups, requiresUnlock, requiresScreenOn, bannerResource, uid,
614                     settingsActivityName, offHostName, staticOffHostName);
615         }
616 
617         @Override
618         public ApduServiceInfo[] newArray(int size) {
619             return new ApduServiceInfo[size];
620         }
621     };
622 
dump(FileDescriptor fd, PrintWriter pw, String[] args)623     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
624         pw.println("    " + getComponent()
625                 + " (Description: " + getDescription() + ")"
626                 + " (UID: " + getUid() + ")");
627         if (mOnHost) {
628             pw.println("    On Host Service");
629         } else {
630             pw.println("    Off-host Service");
631             pw.println("        " + "Current off-host SE:" + mOffHostName
632                     + " static off-host SE:" + mStaticOffHostName);
633         }
634         pw.println("    Static AID groups:");
635         for (AidGroup group : mStaticAidGroups.values()) {
636             pw.println("        Category: " + group.category);
637             for (String aid : group.aids) {
638                 pw.println("            AID: " + aid);
639             }
640         }
641         pw.println("    Dynamic AID groups:");
642         for (AidGroup group : mDynamicAidGroups.values()) {
643             pw.println("        Category: " + group.category);
644             for (String aid : group.aids) {
645                 pw.println("            AID: " + aid);
646             }
647         }
648         pw.println("    Settings Activity: " + mSettingsActivityName);
649         pw.println("    Requires Device Unlock: " + mRequiresDeviceUnlock);
650         pw.println("    Requires Device ScreenOn: " + mRequiresDeviceScreenOn);
651     }
652 
653     /**
654      * Dump debugging info as ApduServiceInfoProto
655      *
656      * If the output belongs to a sub message, the caller is responsible for wrapping this function
657      * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}.
658      * See proto definition in frameworks/base/core/proto/android/nfc/apdu_service_info.proto
659      *
660      * @param proto the ProtoOutputStream to write to
661      */
dumpDebug(ProtoOutputStream proto)662     public void dumpDebug(ProtoOutputStream proto) {
663         Utils.dumpDebugComponentName(getComponent(), proto, ApduServiceInfoProto.COMPONENT_NAME);
664         proto.write(ApduServiceInfoProto.DESCRIPTION, getDescription());
665         proto.write(ApduServiceInfoProto.ON_HOST, mOnHost);
666         if (!mOnHost) {
667             proto.write(ApduServiceInfoProto.OFF_HOST_NAME, mOffHostName);
668             proto.write(ApduServiceInfoProto.STATIC_OFF_HOST_NAME, mStaticOffHostName);
669         }
670         for (AidGroup group : mStaticAidGroups.values()) {
671             long token = proto.start(ApduServiceInfoProto.STATIC_AID_GROUPS);
672             group.dump(proto);
673             proto.end(token);
674         }
675         for (AidGroup group : mDynamicAidGroups.values()) {
676             long token = proto.start(ApduServiceInfoProto.STATIC_AID_GROUPS);
677             group.dump(proto);
678             proto.end(token);
679         }
680         proto.write(ApduServiceInfoProto.SETTINGS_ACTIVITY_NAME, mSettingsActivityName);
681     }
682 }
683