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