ApduServiceInfo.java revision a7397883de67d674970d91f86d46ccf637e5e543
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 is an {@link HostApduService} or {@link OffHostApduService} 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, 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 = false; 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) throws XmlPullParserException, 95 IOException { 96 ServiceInfo si = info.serviceInfo; 97 98 XmlResourceParser parser = null; 99 try { 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 106 int eventType = parser.getEventType(); 107 while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) { 108 eventType = parser.next(); 109 } 110 111 String tagName = parser.getName(); 112 if (!"host-apdu-service".equals(tagName)) { 113 throw new XmlPullParserException( 114 "Meta-data does not start with <host-apdu-service> tag"); 115 } 116 117 Resources res = pm.getResourcesForApplication(si.applicationInfo); 118 AttributeSet attrs = Xml.asAttributeSet(parser); 119 TypedArray sa = res.obtainAttributes(attrs, 120 com.android.internal.R.styleable.HostApduService); 121 mService = info; 122 mDescription = sa.getString( 123 com.android.internal.R.styleable.HostApduService_description); 124 mAidGroups = new ArrayList<AidGroup>(); 125 mCategoryToGroup = new HashMap<String, AidGroup>(); 126 mAids = new ArrayList<String>(); 127 mOnHost = true; // TODO 128 final int depth = parser.getDepth(); 129 AidGroup currentGroup = null; 130 131 // Parsed values for the current AID group 132 while (((eventType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 133 && eventType != XmlPullParser.END_DOCUMENT) { 134 tagName = parser.getName(); 135 if (eventType == XmlPullParser.START_TAG && "aid-group".equals(tagName) && 136 currentGroup == null) { 137 final TypedArray groupAttrs = res.obtainAttributes(attrs, 138 com.android.internal.R.styleable.AidGroup); 139 // Get category of AID group 140 String groupDescription = groupAttrs.getString( 141 com.android.internal.R.styleable.AidGroup_description); 142 String groupCategory = groupAttrs.getString( 143 com.android.internal.R.styleable.AidGroup_category); 144 if (!CardEmulationManager.CATEGORY_PAYMENT.equals(groupCategory)) { 145 groupCategory = CardEmulationManager.CATEGORY_OTHER; 146 } 147 currentGroup = mCategoryToGroup.get(groupCategory); 148 if (currentGroup != null) { 149 if (!CardEmulationManager.CATEGORY_OTHER.equals(groupCategory)) { 150 Log.e(TAG, "Not allowing multiple aid-groups in the " + 151 groupCategory + " category"); 152 currentGroup = null; 153 } 154 } else { 155 currentGroup = new AidGroup(groupCategory, groupDescription); 156 } 157 } else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) && 158 currentGroup != null) { 159 if (currentGroup.aids.size() > 0) { 160 if (!mCategoryToGroup.containsKey(currentGroup.category)) { 161 mAidGroups.add(currentGroup); 162 mCategoryToGroup.put(currentGroup.category, currentGroup); 163 } 164 } else { 165 Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs"); 166 } 167 currentGroup = null; 168 } else if (eventType == XmlPullParser.START_TAG && "aid-filter".equals(tagName) && 169 currentGroup != null) { 170 final TypedArray a = res.obtainAttributes(attrs, 171 com.android.internal.R.styleable.AidFilter); 172 String aid = a.getString(com.android.internal.R.styleable.AidFilter_name); 173 if (isValidAid(aid) && !currentGroup.aids.contains(aid)) { 174 currentGroup.aids.add(aid); 175 mAids.add(aid); 176 } else { 177 Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid); 178 } 179 } 180 } 181 } catch (NameNotFoundException e) { 182 throw new XmlPullParserException("Unable to create context for: " + si.packageName); 183 } finally { 184 if (parser != null) parser.close(); 185 } 186 } 187 188 public ComponentName getComponent() { 189 return new ComponentName(mService.serviceInfo.packageName, 190 mService.serviceInfo.name); 191 } 192 193 public ArrayList<String> getAids() { 194 return mAids; 195 } 196 197 public ArrayList<AidGroup> getAidGroups() { 198 return mAidGroups; 199 } 200 201 public boolean hasCategory(String category) { 202 return mCategoryToGroup.containsKey(category); 203 } 204 205 public CharSequence loadLabel(PackageManager pm) { 206 return mService.loadLabel(pm); 207 } 208 209 public Drawable loadIcon(PackageManager pm) { 210 return mService.loadIcon(pm); 211 } 212 213 static boolean isValidAid(String aid) { 214 if (aid == null) 215 return false; 216 217 int aidLength = aid.length(); 218 if (aidLength == 0 || (aidLength % 2) != 0) { 219 Log.e(TAG, "AID " + aid + " is not correctly formatted."); 220 return false; 221 } 222 return true; 223 } 224 225 @Override 226 public String toString() { 227 StringBuilder out = new StringBuilder("ApduService: "); 228 out.append(getComponent()); 229 out.append(", description: " + mDescription); 230 out.append(", AID Groups: "); 231 for (AidGroup aidGroup : mAidGroups) { 232 out.append(aidGroup.toString()); 233 } 234 return out.toString(); 235 } 236 237 @Override 238 public boolean equals(Object o) { 239 if (this == o) return true; 240 if (!(o instanceof ApduServiceInfo)) return false; 241 ApduServiceInfo thatService = (ApduServiceInfo) o; 242 243 return thatService.getComponent().equals(this.getComponent()); 244 } 245 246 @Override 247 public int hashCode() { 248 return getComponent().hashCode(); 249 } 250 251 252 @Override 253 public int describeContents() { 254 return 0; 255 } 256 257 @Override 258 public void writeToParcel(Parcel dest, int flags) { 259 mService.writeToParcel(dest, flags); 260 dest.writeString(mDescription); 261 dest.writeInt(mAidGroups.size()); 262 if (mAidGroups.size() > 0) { 263 dest.writeTypedList(mAidGroups); 264 } 265 }; 266 267 public static final Parcelable.Creator<ApduServiceInfo> CREATOR = 268 new Parcelable.Creator<ApduServiceInfo>() { 269 @Override 270 public ApduServiceInfo createFromParcel(Parcel source) { 271 ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source); 272 String description = source.readString(); 273 ArrayList<AidGroup> aidGroups = new ArrayList<AidGroup>(); 274 int numGroups = source.readInt(); 275 if (numGroups > 0) { 276 source.readTypedList(aidGroups, AidGroup.CREATOR); 277 } 278 return new ApduServiceInfo(info, description, aidGroups); 279 } 280 281 @Override 282 public ApduServiceInfo[] newArray(int size) { 283 return new ApduServiceInfo[size]; 284 } 285 }; 286 287 public static class AidGroup implements Parcelable { 288 final ArrayList<String> aids; 289 final String category; 290 final String description; 291 292 AidGroup(ArrayList<String> aids, String category, String description) { 293 this.aids = aids; 294 this.category = category; 295 this.description = description; 296 } 297 298 AidGroup(String category, String description) { 299 this.aids = new ArrayList<String>(); 300 this.category = category; 301 this.description = description; 302 } 303 304 public String getCategory() { 305 return category; 306 } 307 308 public ArrayList<String> getAids() { 309 return aids; 310 } 311 312 @Override 313 public String toString() { 314 StringBuilder out = new StringBuilder("Category: " + category + 315 ", description: " + description + ", AIDs:"); 316 for (String aid : aids) { 317 out.append(aid); 318 out.append(", "); 319 } 320 return out.toString(); 321 } 322 323 @Override 324 public int describeContents() { 325 return 0; 326 } 327 328 @Override 329 public void writeToParcel(Parcel dest, int flags) { 330 dest.writeString(category); 331 dest.writeString(description); 332 dest.writeInt(aids.size()); 333 if (aids.size() > 0) { 334 dest.writeStringList(aids); 335 } 336 } 337 338 public static final Parcelable.Creator<ApduServiceInfo.AidGroup> CREATOR = 339 new Parcelable.Creator<ApduServiceInfo.AidGroup>() { 340 341 @Override 342 public AidGroup createFromParcel(Parcel source) { 343 String category = source.readString(); 344 String description = source.readString(); 345 int listSize = source.readInt(); 346 ArrayList<String> aidList = new ArrayList<String>(); 347 if (listSize > 0) { 348 source.readStringList(aidList); 349 } 350 return new AidGroup(aidList, category, description); 351 } 352 353 @Override 354 public AidGroup[] newArray(int size) { 355 return new AidGroup[size]; 356 } 357 }; 358 } 359} 360