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