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.compat.annotation.UnsupportedAppUsage; 20 import android.os.Parcel; 21 import android.os.Parcelable; 22 import android.util.Log; 23 import android.util.proto.ProtoOutputStream; 24 25 import org.xmlpull.v1.XmlPullParser; 26 import org.xmlpull.v1.XmlPullParserException; 27 import org.xmlpull.v1.XmlSerializer; 28 29 import java.io.IOException; 30 import java.util.ArrayList; 31 import java.util.List; 32 33 /** 34 * The AidGroup class represents a group of Application Identifiers (AIDs). 35 * 36 * <p>The format of AIDs is defined in the ISO/IEC 7816-4 specification. This class 37 * requires the AIDs to be input as a hexadecimal string, with an even amount of 38 * hexadecimal characters, e.g. "F014811481". 39 * 40 * @hide 41 */ 42 public final class AidGroup implements Parcelable { 43 /** 44 * The maximum number of AIDs that can be present in any one group. 45 */ 46 public static final int MAX_NUM_AIDS = 256; 47 48 static final String TAG = "AidGroup"; 49 50 @UnsupportedAppUsage 51 final List<String> aids; 52 @UnsupportedAppUsage 53 final String category; 54 @UnsupportedAppUsage 55 final String description; 56 57 /** 58 * Creates a new AidGroup object. 59 * 60 * @param aids The list of AIDs present in the group 61 * @param category The category of this group, e.g. {@link CardEmulation#CATEGORY_PAYMENT} 62 */ AidGroup(List<String> aids, String category)63 public AidGroup(List<String> aids, String category) { 64 if (aids == null || aids.size() == 0) { 65 throw new IllegalArgumentException("No AIDS in AID group."); 66 } 67 if (aids.size() > MAX_NUM_AIDS) { 68 throw new IllegalArgumentException("Too many AIDs in AID group."); 69 } 70 for (String aid : aids) { 71 if (!CardEmulation.isValidAid(aid)) { 72 throw new IllegalArgumentException("AID " + aid + " is not a valid AID."); 73 } 74 } 75 if (isValidCategory(category)) { 76 this.category = category; 77 } else { 78 this.category = CardEmulation.CATEGORY_OTHER; 79 } 80 this.aids = new ArrayList<String>(aids.size()); 81 for (String aid : aids) { 82 this.aids.add(aid.toUpperCase()); 83 } 84 this.description = null; 85 } 86 87 @UnsupportedAppUsage AidGroup(String category, String description)88 AidGroup(String category, String description) { 89 this.aids = new ArrayList<String>(); 90 this.category = category; 91 this.description = description; 92 } 93 94 /** 95 * @return the category of this AID group 96 */ 97 @UnsupportedAppUsage getCategory()98 public String getCategory() { 99 return category; 100 } 101 102 /** 103 * @return the list of AIDs in this group 104 */ 105 @UnsupportedAppUsage getAids()106 public List<String> getAids() { 107 return aids; 108 } 109 110 @Override toString()111 public String toString() { 112 StringBuilder out = new StringBuilder("Category: " + category + 113 ", AIDs:"); 114 for (String aid : aids) { 115 out.append(aid); 116 out.append(", "); 117 } 118 return out.toString(); 119 } 120 121 /** 122 * Dump debugging info as AidGroupProto 123 * 124 * If the output belongs to a sub message, the caller is responsible for wrapping this function 125 * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}. 126 * 127 * @param proto the ProtoOutputStream to write to 128 */ dump(ProtoOutputStream proto)129 public void dump(ProtoOutputStream proto) { 130 proto.write(AidGroupProto.CATEGORY, category); 131 for (String aid : aids) { 132 proto.write(AidGroupProto.AIDS, aid); 133 } 134 } 135 136 @Override describeContents()137 public int describeContents() { 138 return 0; 139 } 140 141 @Override writeToParcel(Parcel dest, int flags)142 public void writeToParcel(Parcel dest, int flags) { 143 dest.writeString(category); 144 dest.writeInt(aids.size()); 145 if (aids.size() > 0) { 146 dest.writeStringList(aids); 147 } 148 } 149 150 @UnsupportedAppUsage 151 public static final @android.annotation.NonNull Parcelable.Creator<AidGroup> CREATOR = 152 new Parcelable.Creator<AidGroup>() { 153 154 @Override 155 public AidGroup createFromParcel(Parcel source) { 156 String category = source.readString(); 157 int listSize = source.readInt(); 158 ArrayList<String> aidList = new ArrayList<String>(); 159 if (listSize > 0) { 160 source.readStringList(aidList); 161 } 162 return new AidGroup(aidList, category); 163 } 164 165 @Override 166 public AidGroup[] newArray(int size) { 167 return new AidGroup[size]; 168 } 169 }; 170 171 @UnsupportedAppUsage createFromXml(XmlPullParser parser)172 static public AidGroup createFromXml(XmlPullParser parser) throws XmlPullParserException, IOException { 173 String category = null; 174 ArrayList<String> aids = new ArrayList<String>(); 175 AidGroup group = null; 176 boolean inGroup = false; 177 178 int eventType = parser.getEventType(); 179 int minDepth = parser.getDepth(); 180 while (eventType != XmlPullParser.END_DOCUMENT && parser.getDepth() >= minDepth) { 181 String tagName = parser.getName(); 182 if (eventType == XmlPullParser.START_TAG) { 183 if (tagName.equals("aid")) { 184 if (inGroup) { 185 String aid = parser.getAttributeValue(null, "value"); 186 if (aid != null) { 187 aids.add(aid.toUpperCase()); 188 } 189 } else { 190 Log.d(TAG, "Ignoring <aid> tag while not in group"); 191 } 192 } else if (tagName.equals("aid-group")) { 193 category = parser.getAttributeValue(null, "category"); 194 if (category == null) { 195 Log.e(TAG, "<aid-group> tag without valid category"); 196 return null; 197 } 198 inGroup = true; 199 } else { 200 Log.d(TAG, "Ignoring unexpected tag: " + tagName); 201 } 202 } else if (eventType == XmlPullParser.END_TAG) { 203 if (tagName.equals("aid-group") && inGroup && aids.size() > 0) { 204 group = new AidGroup(aids, category); 205 break; 206 } 207 } 208 eventType = parser.next(); 209 } 210 return group; 211 } 212 213 @UnsupportedAppUsage writeAsXml(XmlSerializer out)214 public void writeAsXml(XmlSerializer out) throws IOException { 215 out.startTag(null, "aid-group"); 216 out.attribute(null, "category", category); 217 for (String aid : aids) { 218 out.startTag(null, "aid"); 219 out.attribute(null, "value", aid); 220 out.endTag(null, "aid"); 221 } 222 out.endTag(null, "aid-group"); 223 } 224 isValidCategory(String category)225 static boolean isValidCategory(String category) { 226 return CardEmulation.CATEGORY_PAYMENT.equals(category) || 227 CardEmulation.CATEGORY_OTHER.equals(category); 228 } 229 } 230