• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.FlaggedApi;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.SystemApi;
23 import android.nfc.Flags;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.util.Log;
27 import android.util.proto.ProtoOutputStream;
28 
29 import org.xmlpull.v1.XmlPullParser;
30 import org.xmlpull.v1.XmlPullParserException;
31 import org.xmlpull.v1.XmlSerializer;
32 
33 import java.io.IOException;
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.regex.Pattern;
38 
39 /**********************************************************************
40  * This file is not a part of the NFC mainline module                 *
41  * *******************************************************************/
42 
43 /**
44  * The AidGroup class represents a group of Application Identifiers (AIDs).
45  *
46  * <p>The format of AIDs is defined in the ISO/IEC 7816-4 specification. This class
47  * requires the AIDs to be input as a hexadecimal string, with an even amount of
48  * hexadecimal characters, e.g. "F014811481".
49  *
50  * @hide
51  */
52 @SystemApi
53 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
54 public final class AidGroup implements Parcelable {
55     /**
56      * The maximum number of AIDs that can be present in any one group.
57      */
58     private static final int MAX_NUM_AIDS = 256;
59 
60     private static final String TAG = "AidGroup";
61 
62 
63     private final List<String> mAids;
64     private final String mCategory;
65     @SuppressWarnings("unused") // Unused as of now, but part of the XML input.
66     private final String mDescription;
67 
68     /**
69      * Creates a new AidGroup object.
70      *
71      * @param aids list of AIDs present in the group
72      * @param category category of this group, e.g. {@link CardEmulation#CATEGORY_PAYMENT}
73      */
74     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
AidGroup(@onNull List<String> aids, @Nullable String category)75     public AidGroup(@NonNull List<String> aids, @Nullable String category) {
76         if (aids == null || aids.size() == 0) {
77             throw new IllegalArgumentException("No AIDS in AID group.");
78         }
79         if (aids.size() > MAX_NUM_AIDS) {
80             throw new IllegalArgumentException("Too many AIDs in AID group.");
81         }
82         for (String aid : aids) {
83             if (!isValidAid(aid)) {
84                 throw new IllegalArgumentException("AID " + aid + " is not a valid AID.");
85             }
86         }
87         if (isValidCategory(category)) {
88             this.mCategory = category;
89         } else {
90             this.mCategory = CardEmulation.CATEGORY_OTHER;
91         }
92         this.mAids = new ArrayList<String>(aids.size());
93         for (String aid : aids) {
94             this.mAids.add(aid.toUpperCase(Locale.US));
95         }
96         this.mDescription = null;
97     }
98 
99     /**
100      * Creates a new AidGroup object.
101      *
102      * @param category category of this group, e.g. {@link CardEmulation#CATEGORY_PAYMENT}
103      * @param description description of this group
104      */
AidGroup(@onNull String category, @NonNull String description)105     AidGroup(@NonNull String category, @NonNull String description) {
106         this.mAids = new ArrayList<String>();
107         this.mCategory = category;
108         this.mDescription = description;
109     }
110 
111     /**
112      * Returns the category of this group.
113      * @return the category of this AID group
114      */
115     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
116     @NonNull
getCategory()117     public String getCategory() {
118         return mCategory;
119     }
120 
121     /**
122      * Returns the list of AIDs in this group.
123      *
124      * @return the list of AIDs in this group
125      */
126     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
127     @NonNull
getAids()128     public List<String> getAids() {
129         return mAids;
130     }
131 
132     @Override
toString()133     public String toString() {
134         StringBuilder out = new StringBuilder("Category: " + mCategory
135                 + ", AIDs:");
136         for (String aid : mAids) {
137             out.append(aid);
138             out.append(", ");
139         }
140         return out.toString();
141     }
142 
143     /**
144      * Dump debugging info as AidGroupProto.
145      *
146      * If the output belongs to a sub message, the caller is responsible for wrapping this function
147      * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}.
148      *
149      * @param proto the ProtoOutputStream to write to
150      */
151     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
dump(@onNull ProtoOutputStream proto)152     public void dump(@NonNull ProtoOutputStream proto) {
153         proto.write(AidGroupProto.CATEGORY, mCategory);
154         for (String aid : mAids) {
155             proto.write(AidGroupProto.AIDS, aid);
156         }
157     }
158 
159     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
160     @Override
describeContents()161     public int describeContents() {
162         return 0;
163     }
164 
165     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
166     @Override
writeToParcel(@onNull Parcel dest, int flags)167     public void writeToParcel(@NonNull Parcel dest, int flags) {
168         dest.writeString8(mCategory);
169         dest.writeInt(mAids.size());
170         if (mAids.size() > 0) {
171             dest.writeStringList(mAids);
172         }
173     }
174 
175     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
176     public static final @NonNull Parcelable.Creator<AidGroup> CREATOR =
177             new Parcelable.Creator<AidGroup>() {
178 
179         @Override
180         public AidGroup createFromParcel(Parcel source) {
181             String category = source.readString8();
182             int listSize = source.readInt();
183             ArrayList<String> aidList = new ArrayList<String>();
184             if (listSize > 0) {
185                 source.readStringList(aidList);
186             }
187             return new AidGroup(aidList, category);
188         }
189 
190         @Override
191         public AidGroup[] newArray(int size) {
192             return new AidGroup[size];
193         }
194     };
195 
196     /**
197      * Create an instance of AID group from XML file.
198      *
199      * @param parser input xml parser stream
200      * @throws XmlPullParserException If an error occurs parsing the element.
201      * @throws IOException If an error occurs reading the element.
202      */
203     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
204     @Nullable
createFromXml(@onNull XmlPullParser parser)205     public static AidGroup createFromXml(@NonNull XmlPullParser parser)
206             throws XmlPullParserException, IOException {
207         String category = null;
208         ArrayList<String> aids = new ArrayList<String>();
209         AidGroup group = null;
210         boolean inGroup = false;
211 
212         int eventType = parser.getEventType();
213         int minDepth = parser.getDepth();
214         while (eventType != XmlPullParser.END_DOCUMENT && parser.getDepth() >= minDepth) {
215             String tagName = parser.getName();
216             if (eventType == XmlPullParser.START_TAG) {
217                 if (tagName.equals("aid")) {
218                     if (inGroup) {
219                         String aid = parser.getAttributeValue(null, "value");
220                         if (aid != null) {
221                             aids.add(aid.toUpperCase());
222                         }
223                     } else {
224                         Log.d(TAG, "Ignoring <aid> tag while not in group");
225                     }
226                 } else if (tagName.equals("aid-group")) {
227                     category = parser.getAttributeValue(null, "category");
228                     if (category == null) {
229                         Log.e(TAG, "<aid-group> tag without valid category");
230                         return null;
231                     }
232                     inGroup = true;
233                 } else {
234                     Log.d(TAG, "Ignoring unexpected tag: " + tagName);
235                 }
236             } else if (eventType == XmlPullParser.END_TAG) {
237                 if (tagName.equals("aid-group") && inGroup && aids.size() > 0) {
238                     group = new AidGroup(aids, category);
239                     break;
240                 }
241             }
242             eventType = parser.next();
243         }
244         return group;
245     }
246 
247     /**
248      * Serialize instance of AID group to XML file.
249      * @param out XML serializer stream
250      * @throws IOException If an error occurs reading the element.
251      */
252     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
writeAsXml(@onNull XmlSerializer out)253     public void writeAsXml(@NonNull XmlSerializer out) throws IOException {
254         out.startTag(null, "aid-group");
255         out.attribute(null, "category", mCategory);
256         for (String aid : mAids) {
257             out.startTag(null, "aid");
258             out.attribute(null, "value", aid);
259             out.endTag(null, "aid");
260         }
261         out.endTag(null, "aid-group");
262     }
263 
isValidCategory(String category)264     private static boolean isValidCategory(String category) {
265         return CardEmulation.CATEGORY_PAYMENT.equals(category) ||
266                 CardEmulation.CATEGORY_OTHER.equals(category);
267     }
268 
269     private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
270     /**
271      * Copied over from {@link CardEmulation#isValidAid(String)}
272      * @hide
273      */
isValidAid(String aid)274     private static boolean isValidAid(String aid) {
275         if (aid == null)
276             return false;
277 
278         // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
279         if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
280             Log.e(TAG, "AID " + aid + " is not a valid AID.");
281             return false;
282         }
283 
284         // If not a prefix/subset AID, the total length must be even (even # of AID chars)
285         if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
286             Log.e(TAG, "AID " + aid + " is not a valid AID.");
287             return false;
288         }
289 
290         // Verify hex characters
291         if (!AID_PATTERN.matcher(aid).matches()) {
292             Log.e(TAG, "AID " + aid + " is not a valid AID.");
293             return false;
294         }
295 
296         return true;
297     }
298 }
299