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