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