ApduServiceInfo.java revision 38d3bb76967e115b81c9f804e4de9189adfd9680
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 17package android.nfc.cardemulation; 18 19import android.content.ComponentName; 20import android.content.pm.PackageManager; 21import android.content.pm.ResolveInfo; 22import android.content.pm.ServiceInfo; 23import android.content.pm.PackageManager.NameNotFoundException; 24import android.content.res.Resources; 25import android.content.res.TypedArray; 26import android.content.res.XmlResourceParser; 27import android.graphics.drawable.Drawable; 28import android.os.Parcel; 29import android.os.Parcelable; 30import android.util.AttributeSet; 31import android.util.Log; 32import android.util.Xml; 33 34import org.xmlpull.v1.XmlPullParser; 35import org.xmlpull.v1.XmlPullParserException; 36 37import java.io.IOException; 38import java.util.ArrayList; 39import java.util.HashMap; 40 41/** 42 * @hide 43 */ 44public final class ApduServiceInfo implements Parcelable { 45 static final String TAG = "ApduServiceInfo"; 46 47 /** 48 * The service that implements this 49 */ 50 final ResolveInfo mService; 51 52 /** 53 * Description of the service 54 */ 55 final String mDescription; 56 57 /** 58 * Convenience AID list 59 */ 60 final ArrayList<String> mAids; 61 62 /** 63 * Whether this service represents AIDs running on the host CPU 64 */ 65 final boolean mOnHost; 66 67 /** 68 * All AID groups this service handles 69 */ 70 final ArrayList<AidGroup> mAidGroups; 71 72 /** 73 * Convenience hashmap 74 */ 75 final HashMap<String, AidGroup> mCategoryToGroup; 76 77 /** 78 * @hide 79 */ 80 public ApduServiceInfo(ResolveInfo info, boolean onHost, String description, 81 ArrayList<AidGroup> aidGroups) { 82 this.mService = info; 83 this.mDescription = description; 84 this.mAidGroups = aidGroups; 85 this.mAids = new ArrayList<String>(); 86 this.mCategoryToGroup = new HashMap<String, AidGroup>(); 87 this.mOnHost = onHost; 88 for (AidGroup aidGroup : aidGroups) { 89 this.mCategoryToGroup.put(aidGroup.category, aidGroup); 90 this.mAids.addAll(aidGroup.aids); 91 } 92 } 93 94 public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost) 95 throws XmlPullParserException, IOException { 96 ServiceInfo si = info.serviceInfo; 97 XmlResourceParser parser = null; 98 try { 99 if (onHost) { 100 parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA); 101 if (parser == null) { 102 throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA + 103 " meta-data"); 104 } 105 } else { 106 parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA); 107 if (parser == null) { 108 throw new XmlPullParserException("No " + OffHostApduService.SERVICE_META_DATA + 109 " meta-data"); 110 } 111 } 112 113 int eventType = parser.getEventType(); 114 while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) { 115 eventType = parser.next(); 116 } 117 118 String tagName = parser.getName(); 119 if (onHost && !"host-apdu-service".equals(tagName)) { 120 throw new XmlPullParserException( 121 "Meta-data does not start with <host-apdu-service> tag"); 122 } else if (!onHost && !"offhost-apdu-service".equals(tagName)) { 123 throw new XmlPullParserException( 124 "Meta-data does not start with <offhost-apdu-service> tag"); 125 } 126 127 Resources res = pm.getResourcesForApplication(si.applicationInfo); 128 AttributeSet attrs = Xml.asAttributeSet(parser); 129 if (onHost) { 130 TypedArray sa = res.obtainAttributes(attrs, 131 com.android.internal.R.styleable.HostApduService); 132 mService = info; 133 mDescription = sa.getString( 134 com.android.internal.R.styleable.HostApduService_description); 135 } else { 136 TypedArray sa = res.obtainAttributes(attrs, 137 com.android.internal.R.styleable.OffHostApduService); 138 mService = info; 139 mDescription = sa.getString( 140 com.android.internal.R.styleable.OffHostApduService_description); 141 } 142 143 mAidGroups = new ArrayList<AidGroup>(); 144 mCategoryToGroup = new HashMap<String, AidGroup>(); 145 mAids = new ArrayList<String>(); 146 mOnHost = onHost; 147 final int depth = parser.getDepth(); 148 AidGroup currentGroup = null; 149 150 // Parsed values for the current AID group 151 while (((eventType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 152 && eventType != XmlPullParser.END_DOCUMENT) { 153 tagName = parser.getName(); 154 if (eventType == XmlPullParser.START_TAG && "aid-group".equals(tagName) && 155 currentGroup == null) { 156 final TypedArray groupAttrs = res.obtainAttributes(attrs, 157 com.android.internal.R.styleable.AidGroup); 158 // Get category of AID group 159 String groupDescription = groupAttrs.getString( 160 com.android.internal.R.styleable.AidGroup_description); 161 String groupCategory = groupAttrs.getString( 162 com.android.internal.R.styleable.AidGroup_category); 163 if (!CardEmulationManager.CATEGORY_PAYMENT.equals(groupCategory)) { 164 groupCategory = CardEmulationManager.CATEGORY_OTHER; 165 } 166 currentGroup = mCategoryToGroup.get(groupCategory); 167 if (currentGroup != null) { 168 if (!CardEmulationManager.CATEGORY_OTHER.equals(groupCategory)) { 169 Log.e(TAG, "Not allowing multiple aid-groups in the " + 170 groupCategory + " category"); 171 currentGroup = null; 172 } 173 } else { 174 currentGroup = new AidGroup(groupCategory, groupDescription); 175 } 176 } else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) && 177 currentGroup != null) { 178 if (currentGroup.aids.size() > 0) { 179 if (!mCategoryToGroup.containsKey(currentGroup.category)) { 180 mAidGroups.add(currentGroup); 181 mCategoryToGroup.put(currentGroup.category, currentGroup); 182 } 183 } else { 184 Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs"); 185 } 186 currentGroup = null; 187 } else if (eventType == XmlPullParser.START_TAG && "aid-filter".equals(tagName) && 188 currentGroup != null) { 189 final TypedArray a = res.obtainAttributes(attrs, 190 com.android.internal.R.styleable.AidFilter); 191 String aid = a.getString(com.android.internal.R.styleable.AidFilter_name); 192 if (isValidAid(aid) && !currentGroup.aids.contains(aid)) { 193 currentGroup.aids.add(aid); 194 mAids.add(aid); 195 } else { 196 Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid); 197 } 198 } 199 } 200 } catch (NameNotFoundException e) { 201 throw new XmlPullParserException("Unable to create context for: " + si.packageName); 202 } finally { 203 if (parser != null) parser.close(); 204 } 205 } 206 207 public ComponentName getComponent() { 208 return new ComponentName(mService.serviceInfo.packageName, 209 mService.serviceInfo.name); 210 } 211 212 public ArrayList<String> getAids() { 213 return mAids; 214 } 215 216 public ArrayList<AidGroup> getAidGroups() { 217 return mAidGroups; 218 } 219 220 public boolean hasCategory(String category) { 221 return mCategoryToGroup.containsKey(category); 222 } 223 224 public boolean isOnHost() { 225 return mOnHost; 226 } 227 228 public CharSequence loadLabel(PackageManager pm) { 229 return mService.loadLabel(pm); 230 } 231 232 public Drawable loadIcon(PackageManager pm) { 233 return mService.loadIcon(pm); 234 } 235 236 static boolean isValidAid(String aid) { 237 if (aid == null) 238 return false; 239 240 int aidLength = aid.length(); 241 if (aidLength == 0 || (aidLength % 2) != 0) { 242 Log.e(TAG, "AID " + aid + " is not correctly formatted."); 243 return false; 244 } 245 return true; 246 } 247 248 @Override 249 public String toString() { 250 StringBuilder out = new StringBuilder("ApduService: "); 251 out.append(getComponent()); 252 out.append(", description: " + mDescription); 253 out.append(", AID Groups: "); 254 for (AidGroup aidGroup : mAidGroups) { 255 out.append(aidGroup.toString()); 256 } 257 return out.toString(); 258 } 259 260 @Override 261 public boolean equals(Object o) { 262 if (this == o) return true; 263 if (!(o instanceof ApduServiceInfo)) return false; 264 ApduServiceInfo thatService = (ApduServiceInfo) o; 265 266 return thatService.getComponent().equals(this.getComponent()); 267 } 268 269 @Override 270 public int hashCode() { 271 return getComponent().hashCode(); 272 } 273 274 275 @Override 276 public int describeContents() { 277 return 0; 278 } 279 280 @Override 281 public void writeToParcel(Parcel dest, int flags) { 282 mService.writeToParcel(dest, flags); 283 dest.writeString(mDescription); 284 dest.writeInt(mOnHost ? 1 : 0); 285 dest.writeInt(mAidGroups.size()); 286 if (mAidGroups.size() > 0) { 287 dest.writeTypedList(mAidGroups); 288 } 289 }; 290 291 public static final Parcelable.Creator<ApduServiceInfo> CREATOR = 292 new Parcelable.Creator<ApduServiceInfo>() { 293 @Override 294 public ApduServiceInfo createFromParcel(Parcel source) { 295 ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source); 296 String description = source.readString(); 297 boolean onHost = (source.readInt() != 0) ? true : false; 298 ArrayList<AidGroup> aidGroups = new ArrayList<AidGroup>(); 299 int numGroups = source.readInt(); 300 if (numGroups > 0) { 301 source.readTypedList(aidGroups, AidGroup.CREATOR); 302 } 303 return new ApduServiceInfo(info, onHost, description, aidGroups); 304 } 305 306 @Override 307 public ApduServiceInfo[] newArray(int size) { 308 return new ApduServiceInfo[size]; 309 } 310 }; 311 312 public static class AidGroup implements Parcelable { 313 final ArrayList<String> aids; 314 final String category; 315 final String description; 316 317 AidGroup(ArrayList<String> aids, String category, String description) { 318 this.aids = aids; 319 this.category = category; 320 this.description = description; 321 } 322 323 AidGroup(String category, String description) { 324 this.aids = new ArrayList<String>(); 325 this.category = category; 326 this.description = description; 327 } 328 329 public String getCategory() { 330 return category; 331 } 332 333 public ArrayList<String> getAids() { 334 return aids; 335 } 336 337 @Override 338 public String toString() { 339 StringBuilder out = new StringBuilder("Category: " + category + 340 ", description: " + description + ", AIDs:"); 341 for (String aid : aids) { 342 out.append(aid); 343 out.append(", "); 344 } 345 return out.toString(); 346 } 347 348 @Override 349 public int describeContents() { 350 return 0; 351 } 352 353 @Override 354 public void writeToParcel(Parcel dest, int flags) { 355 dest.writeString(category); 356 dest.writeString(description); 357 dest.writeInt(aids.size()); 358 if (aids.size() > 0) { 359 dest.writeStringList(aids); 360 } 361 } 362 363 public static final Parcelable.Creator<ApduServiceInfo.AidGroup> CREATOR = 364 new Parcelable.Creator<ApduServiceInfo.AidGroup>() { 365 366 @Override 367 public AidGroup createFromParcel(Parcel source) { 368 String category = source.readString(); 369 String description = source.readString(); 370 int listSize = source.readInt(); 371 ArrayList<String> aidList = new ArrayList<String>(); 372 if (listSize > 0) { 373 source.readStringList(aidList); 374 } 375 return new AidGroup(aidList, category, description); 376 } 377 378 @Override 379 public AidGroup[] newArray(int size) { 380 return new AidGroup[size]; 381 } 382 }; 383 } 384} 385