ApduServiceInfo.java revision ed3a29ea08eec2676fe157c92948d1a7e3c215a3
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.Resources.NotFoundException; 26import android.content.res.TypedArray; 27import android.content.res.XmlResourceParser; 28import android.graphics.drawable.Drawable; 29import android.os.Parcel; 30import android.os.Parcelable; 31import android.util.AttributeSet; 32import android.util.Log; 33import android.util.Xml; 34 35import org.xmlpull.v1.XmlPullParser; 36import org.xmlpull.v1.XmlPullParserException; 37 38import java.io.FileDescriptor; 39import java.io.IOException; 40import java.io.PrintWriter; 41import java.util.ArrayList; 42import java.util.HashMap; 43import java.util.List; 44import java.util.Map; 45 46/** 47 * @hide 48 */ 49public final class ApduServiceInfo implements Parcelable { 50 static final String TAG = "ApduServiceInfo"; 51 52 /** 53 * The service that implements this 54 */ 55 final ResolveInfo mService; 56 57 /** 58 * Description of the service 59 */ 60 final String mDescription; 61 62 /** 63 * Whether this service represents AIDs running on the host CPU 64 */ 65 final boolean mOnHost; 66 67 /** 68 * Mapping from category to static AID group 69 */ 70 final HashMap<String, AidGroup> mStaticAidGroups; 71 72 /** 73 * Mapping from category to dynamic AID group 74 */ 75 final HashMap<String, AidGroup> mDynamicAidGroups; 76 77 /** 78 * Whether this service should only be started when the device is unlocked. 79 */ 80 final boolean mRequiresDeviceUnlock; 81 82 /** 83 * The id of the service banner specified in XML. 84 */ 85 final int mBannerResourceId; 86 87 /** 88 * The uid of the package the service belongs to 89 */ 90 final int mUid; 91 /** 92 * @hide 93 */ 94 public ApduServiceInfo(ResolveInfo info, boolean onHost, String description, 95 ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups, 96 boolean requiresUnlock, int bannerResource, int uid) { 97 this.mService = info; 98 this.mDescription = description; 99 this.mStaticAidGroups = new HashMap<String, AidGroup>(); 100 this.mDynamicAidGroups = new HashMap<String, AidGroup>(); 101 this.mOnHost = onHost; 102 this.mRequiresDeviceUnlock = requiresUnlock; 103 for (AidGroup aidGroup : staticAidGroups) { 104 this.mStaticAidGroups.put(aidGroup.category, aidGroup); 105 } 106 for (AidGroup aidGroup : dynamicAidGroups) { 107 this.mDynamicAidGroups.put(aidGroup.category, aidGroup); 108 } 109 this.mBannerResourceId = bannerResource; 110 this.mUid = uid; 111 } 112 113 public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost) throws 114 XmlPullParserException, IOException { 115 ServiceInfo si = info.serviceInfo; 116 XmlResourceParser parser = null; 117 try { 118 if (onHost) { 119 parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA); 120 if (parser == null) { 121 throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA + 122 " meta-data"); 123 } 124 } else { 125 parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA); 126 if (parser == null) { 127 throw new XmlPullParserException("No " + OffHostApduService.SERVICE_META_DATA + 128 " meta-data"); 129 } 130 } 131 132 int eventType = parser.getEventType(); 133 while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) { 134 eventType = parser.next(); 135 } 136 137 String tagName = parser.getName(); 138 if (onHost && !"host-apdu-service".equals(tagName)) { 139 throw new XmlPullParserException( 140 "Meta-data does not start with <host-apdu-service> tag"); 141 } else if (!onHost && !"offhost-apdu-service".equals(tagName)) { 142 throw new XmlPullParserException( 143 "Meta-data does not start with <offhost-apdu-service> tag"); 144 } 145 146 Resources res = pm.getResourcesForApplication(si.applicationInfo); 147 AttributeSet attrs = Xml.asAttributeSet(parser); 148 if (onHost) { 149 TypedArray sa = res.obtainAttributes(attrs, 150 com.android.internal.R.styleable.HostApduService); 151 mService = info; 152 mDescription = sa.getString( 153 com.android.internal.R.styleable.HostApduService_description); 154 mRequiresDeviceUnlock = sa.getBoolean( 155 com.android.internal.R.styleable.HostApduService_requireDeviceUnlock, 156 false); 157 mBannerResourceId = sa.getResourceId( 158 com.android.internal.R.styleable.HostApduService_apduServiceBanner, -1); 159 sa.recycle(); 160 } else { 161 TypedArray sa = res.obtainAttributes(attrs, 162 com.android.internal.R.styleable.OffHostApduService); 163 mService = info; 164 mDescription = sa.getString( 165 com.android.internal.R.styleable.OffHostApduService_description); 166 mRequiresDeviceUnlock = false; 167 mBannerResourceId = sa.getResourceId( 168 com.android.internal.R.styleable.OffHostApduService_apduServiceBanner, -1); 169 sa.recycle(); 170 } 171 172 mStaticAidGroups = new HashMap<String, AidGroup>(); 173 mDynamicAidGroups = new HashMap<String, AidGroup>(); 174 mOnHost = onHost; 175 176 final int depth = parser.getDepth(); 177 AidGroup currentGroup = null; 178 179 // Parsed values for the current AID group 180 while (((eventType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 181 && eventType != XmlPullParser.END_DOCUMENT) { 182 tagName = parser.getName(); 183 if (eventType == XmlPullParser.START_TAG && "aid-group".equals(tagName) && 184 currentGroup == null) { 185 final TypedArray groupAttrs = res.obtainAttributes(attrs, 186 com.android.internal.R.styleable.AidGroup); 187 // Get category of AID group 188 String groupCategory = groupAttrs.getString( 189 com.android.internal.R.styleable.AidGroup_category); 190 String groupDescription = groupAttrs.getString( 191 com.android.internal.R.styleable.AidGroup_description); 192 if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) { 193 groupCategory = CardEmulation.CATEGORY_OTHER; 194 } 195 currentGroup = mStaticAidGroups.get(groupCategory); 196 if (currentGroup != null) { 197 if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) { 198 Log.e(TAG, "Not allowing multiple aid-groups in the " + 199 groupCategory + " category"); 200 currentGroup = null; 201 } 202 } else { 203 currentGroup = new AidGroup(groupCategory, groupDescription); 204 } 205 groupAttrs.recycle(); 206 } else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) && 207 currentGroup != null) { 208 if (currentGroup.aids.size() > 0) { 209 if (!mStaticAidGroups.containsKey(currentGroup.category)) { 210 mStaticAidGroups.put(currentGroup.category, currentGroup); 211 } 212 } else { 213 Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs"); 214 } 215 currentGroup = null; 216 } else if (eventType == XmlPullParser.START_TAG && "aid-filter".equals(tagName) && 217 currentGroup != null) { 218 final TypedArray a = res.obtainAttributes(attrs, 219 com.android.internal.R.styleable.AidFilter); 220 String aid = a.getString(com.android.internal.R.styleable.AidFilter_name). 221 toUpperCase(); 222 if (CardEmulation.isValidAid(aid) && !currentGroup.aids.contains(aid)) { 223 currentGroup.aids.add(aid); 224 } else { 225 Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid); 226 } 227 a.recycle(); 228 } else if (eventType == XmlPullParser.START_TAG && 229 "aid-prefix-filter".equals(tagName) && currentGroup != null) { 230 final TypedArray a = res.obtainAttributes(attrs, 231 com.android.internal.R.styleable.AidFilter); 232 String aid = a.getString(com.android.internal.R.styleable.AidFilter_name). 233 toUpperCase(); 234 // Add wildcard char to indicate prefix 235 aid = aid.concat("*"); 236 if (CardEmulation.isValidAid(aid) && !currentGroup.aids.contains(aid)) { 237 currentGroup.aids.add(aid); 238 } else { 239 Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid); 240 } 241 a.recycle(); 242 } 243 } 244 } catch (NameNotFoundException e) { 245 throw new XmlPullParserException("Unable to create context for: " + si.packageName); 246 } finally { 247 if (parser != null) parser.close(); 248 } 249 // Set uid 250 mUid = si.applicationInfo.uid; 251 } 252 253 public ComponentName getComponent() { 254 return new ComponentName(mService.serviceInfo.packageName, 255 mService.serviceInfo.name); 256 } 257 258 /** 259 * Returns a consolidated list of AIDs from the AID groups 260 * registered by this service. Note that if a service has both 261 * a static (manifest-based) AID group for a category and a dynamic 262 * AID group, only the dynamically registered AIDs will be returned 263 * for that category. 264 * @return List of AIDs registered by the service 265 */ 266 public List<String> getAids() { 267 final ArrayList<String> aids = new ArrayList<String>(); 268 for (AidGroup group : getAidGroups()) { 269 aids.addAll(group.aids); 270 } 271 return aids; 272 } 273 274 public List<String> getPrefixAids() { 275 final ArrayList<String> prefixAids = new ArrayList<String>(); 276 for (AidGroup group : getAidGroups()) { 277 for (String aid : group.aids) { 278 if (aid.endsWith("*")) { 279 prefixAids.add(aid); 280 } 281 } 282 } 283 return prefixAids; 284 } 285 286 /** 287 * Returns the registered AID group for this category. 288 */ 289 public AidGroup getDynamicAidGroupForCategory(String category) { 290 return mDynamicAidGroups.get(category); 291 } 292 293 public boolean removeDynamicAidGroupForCategory(String category) { 294 return (mDynamicAidGroups.remove(category) != null); 295 } 296 297 /** 298 * Returns a consolidated list of AID groups 299 * registered by this service. Note that if a service has both 300 * a static (manifest-based) AID group for a category and a dynamic 301 * AID group, only the dynamically registered AID group will be returned 302 * for that category. 303 * @return List of AIDs registered by the service 304 */ 305 public ArrayList<AidGroup> getAidGroups() { 306 final ArrayList<AidGroup> groups = new ArrayList<AidGroup>(); 307 for (Map.Entry<String, AidGroup> entry : mDynamicAidGroups.entrySet()) { 308 groups.add(entry.getValue()); 309 } 310 for (Map.Entry<String, AidGroup> entry : mStaticAidGroups.entrySet()) { 311 if (!mDynamicAidGroups.containsKey(entry.getKey())) { 312 // Consolidate AID groups - don't return static ones 313 // if a dynamic group exists for the category. 314 groups.add(entry.getValue()); 315 } 316 } 317 return groups; 318 } 319 320 /** 321 * Returns the category to which this service has attributed the AID that is passed in, 322 * or null if we don't know this AID. 323 */ 324 public String getCategoryForAid(String aid) { 325 ArrayList<AidGroup> groups = getAidGroups(); 326 for (AidGroup group : groups) { 327 if (group.aids.contains(aid.toUpperCase())) { 328 return group.category; 329 } 330 } 331 return null; 332 } 333 334 public boolean hasCategory(String category) { 335 return (mStaticAidGroups.containsKey(category) || mDynamicAidGroups.containsKey(category)); 336 } 337 338 public boolean isOnHost() { 339 return mOnHost; 340 } 341 342 public boolean requiresUnlock() { 343 return mRequiresDeviceUnlock; 344 } 345 346 public String getDescription() { 347 return mDescription; 348 } 349 350 public int getUid() { 351 return mUid; 352 } 353 354 public void setOrReplaceDynamicAidGroup(AidGroup aidGroup) { 355 mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup); 356 } 357 358 public CharSequence loadLabel(PackageManager pm) { 359 return mService.loadLabel(pm); 360 } 361 362 public Drawable loadIcon(PackageManager pm) { 363 return mService.loadIcon(pm); 364 } 365 366 public Drawable loadBanner(PackageManager pm) { 367 Resources res; 368 try { 369 res = pm.getResourcesForApplication(mService.serviceInfo.packageName); 370 Drawable banner = res.getDrawable(mBannerResourceId); 371 return banner; 372 } catch (NotFoundException e) { 373 Log.e(TAG, "Could not load banner."); 374 return null; 375 } catch (NameNotFoundException e) { 376 Log.e(TAG, "Could not load banner."); 377 return null; 378 } 379 } 380 381 @Override 382 public String toString() { 383 StringBuilder out = new StringBuilder("ApduService: "); 384 out.append(getComponent()); 385 out.append(", description: " + mDescription); 386 out.append(", Static AID Groups: "); 387 for (AidGroup aidGroup : mStaticAidGroups.values()) { 388 out.append(aidGroup.toString()); 389 } 390 out.append(", Dynamic AID Groups: "); 391 for (AidGroup aidGroup : mDynamicAidGroups.values()) { 392 out.append(aidGroup.toString()); 393 } 394 return out.toString(); 395 } 396 397 @Override 398 public boolean equals(Object o) { 399 if (this == o) return true; 400 if (!(o instanceof ApduServiceInfo)) return false; 401 ApduServiceInfo thatService = (ApduServiceInfo) o; 402 403 return thatService.getComponent().equals(this.getComponent()); 404 } 405 406 @Override 407 public int hashCode() { 408 return getComponent().hashCode(); 409 } 410 411 412 @Override 413 public int describeContents() { 414 return 0; 415 } 416 417 @Override 418 public void writeToParcel(Parcel dest, int flags) { 419 mService.writeToParcel(dest, flags); 420 dest.writeString(mDescription); 421 dest.writeInt(mOnHost ? 1 : 0); 422 dest.writeInt(mStaticAidGroups.size()); 423 if (mStaticAidGroups.size() > 0) { 424 dest.writeTypedList(new ArrayList<AidGroup>(mStaticAidGroups.values())); 425 } 426 dest.writeInt(mDynamicAidGroups.size()); 427 if (mDynamicAidGroups.size() > 0) { 428 dest.writeTypedList(new ArrayList<AidGroup>(mDynamicAidGroups.values())); 429 } 430 dest.writeInt(mRequiresDeviceUnlock ? 1 : 0); 431 dest.writeInt(mBannerResourceId); 432 dest.writeInt(mUid); 433 }; 434 435 public static final Parcelable.Creator<ApduServiceInfo> CREATOR = 436 new Parcelable.Creator<ApduServiceInfo>() { 437 @Override 438 public ApduServiceInfo createFromParcel(Parcel source) { 439 ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source); 440 String description = source.readString(); 441 boolean onHost = source.readInt() != 0; 442 ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>(); 443 int numStaticGroups = source.readInt(); 444 if (numStaticGroups > 0) { 445 source.readTypedList(staticAidGroups, AidGroup.CREATOR); 446 } 447 ArrayList<AidGroup> dynamicAidGroups = new ArrayList<AidGroup>(); 448 int numDynamicGroups = source.readInt(); 449 if (numDynamicGroups > 0) { 450 source.readTypedList(dynamicAidGroups, AidGroup.CREATOR); 451 } 452 boolean requiresUnlock = source.readInt() != 0; 453 int bannerResource = source.readInt(); 454 int uid = source.readInt(); 455 return new ApduServiceInfo(info, onHost, description, staticAidGroups, 456 dynamicAidGroups, requiresUnlock, bannerResource, uid); 457 } 458 459 @Override 460 public ApduServiceInfo[] newArray(int size) { 461 return new ApduServiceInfo[size]; 462 } 463 }; 464 465 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 466 pw.println(" " + getComponent() + 467 " (Description: " + getDescription() + ")"); 468 pw.println(" Static AID groups:"); 469 for (AidGroup group : mStaticAidGroups.values()) { 470 pw.println(" Category: " + group.category); 471 for (String aid : group.aids) { 472 pw.println(" AID: " + aid); 473 } 474 } 475 pw.println(" Dynamic AID groups:"); 476 for (AidGroup group : mDynamicAidGroups.values()) { 477 pw.println(" Category: " + group.category); 478 for (String aid : group.aids) { 479 pw.println(" AID: " + aid); 480 } 481 } 482 } 483} 484