1 /* 2 * Copyright (C) 2015 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 17 /********************************************************************** 18 * This file is not a part of the NFC mainline module * 19 * *******************************************************************/ 20 21 package android.nfc.cardemulation; 22 23 import android.annotation.FlaggedApi; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.SystemApi; 27 import android.content.ComponentName; 28 import android.content.pm.PackageManager; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.content.pm.ResolveInfo; 31 import android.content.pm.ServiceInfo; 32 import android.content.res.Resources; 33 import android.content.res.TypedArray; 34 import android.content.res.XmlResourceParser; 35 import android.graphics.drawable.Drawable; 36 import android.nfc.Flags; 37 import android.os.Parcel; 38 import android.os.ParcelFileDescriptor; 39 import android.os.Parcelable; 40 import android.util.AttributeSet; 41 import android.util.Log; 42 import android.util.Xml; 43 import android.util.proto.ProtoOutputStream; 44 45 import org.xmlpull.v1.XmlPullParser; 46 import org.xmlpull.v1.XmlPullParserException; 47 48 import java.io.IOException; 49 import java.io.PrintWriter; 50 51 /** 52 * Class to hold NfcF service info. 53 * 54 * @hide 55 */ 56 @SystemApi 57 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) 58 public final class NfcFServiceInfo implements Parcelable { 59 static final String TAG = "NfcFServiceInfo"; 60 61 private static final String DEFAULT_T3T_PMM = "FFFFFFFFFFFFFFFF"; 62 63 /** 64 * The service that implements this 65 */ 66 private final ResolveInfo mService; 67 68 /** 69 * Description of the service 70 */ 71 private final String mDescription; 72 73 /** 74 * System Code of the service 75 */ 76 private final String mSystemCode; 77 78 /** 79 * System Code of the service registered by API 80 */ 81 private String mDynamicSystemCode; 82 83 /** 84 * NFCID2 of the service 85 */ 86 private final String mNfcid2; 87 88 /** 89 * NFCID2 of the service registered by API 90 */ 91 private String mDynamicNfcid2; 92 93 /** 94 * The uid of the package the service belongs to 95 */ 96 private final int mUid; 97 98 /** 99 * LF_T3T_PMM of the service 100 */ 101 private final String mT3tPmm; 102 103 /** 104 * @hide 105 */ NfcFServiceInfo(ResolveInfo info, String description, String systemCode, String dynamicSystemCode, String nfcid2, String dynamicNfcid2, int uid, String t3tPmm)106 public NfcFServiceInfo(ResolveInfo info, String description, 107 String systemCode, String dynamicSystemCode, String nfcid2, String dynamicNfcid2, 108 int uid, String t3tPmm) { 109 this.mService = info; 110 this.mDescription = description; 111 this.mSystemCode = systemCode; 112 this.mDynamicSystemCode = dynamicSystemCode; 113 this.mNfcid2 = nfcid2; 114 this.mDynamicNfcid2 = dynamicNfcid2; 115 this.mUid = uid; 116 this.mT3tPmm = t3tPmm; 117 } 118 119 /** 120 * Creates a new NfcFServiceInfo object. 121 * 122 * @param pm packageManager instance 123 * @param info app component info 124 * @throws XmlPullParserException If an error occurs parsing the element. 125 * @throws IOException If an error occurs reading the element. 126 */ 127 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) NfcFServiceInfo(@onNull PackageManager pm, @NonNull ResolveInfo info)128 public NfcFServiceInfo(@NonNull PackageManager pm, @NonNull ResolveInfo info) 129 throws XmlPullParserException, IOException { 130 ServiceInfo si = info.serviceInfo; 131 XmlResourceParser parser = null; 132 try { 133 parser = si.loadXmlMetaData(pm, HostNfcFService.SERVICE_META_DATA); 134 if (parser == null) { 135 throw new XmlPullParserException("No " + HostNfcFService.SERVICE_META_DATA + 136 " meta-data"); 137 } 138 139 int eventType = parser.getEventType(); 140 while (eventType != XmlPullParser.START_TAG && 141 eventType != XmlPullParser.END_DOCUMENT) { 142 eventType = parser.next(); 143 } 144 145 String tagName = parser.getName(); 146 if (!"host-nfcf-service".equals(tagName)) { 147 throw new XmlPullParserException( 148 "Meta-data does not start with <host-nfcf-service> tag"); 149 } 150 151 Resources res = pm.getResourcesForApplication(si.applicationInfo); 152 AttributeSet attrs = Xml.asAttributeSet(parser); 153 TypedArray sa = res.obtainAttributes(attrs, 154 com.android.internal.R.styleable.HostNfcFService); 155 mService = info; 156 mDescription = sa.getString( 157 com.android.internal.R.styleable.HostNfcFService_description); 158 mDynamicSystemCode = null; 159 mDynamicNfcid2 = null; 160 sa.recycle(); 161 162 String systemCode = null; 163 String nfcid2 = null; 164 String t3tPmm = null; 165 final int depth = parser.getDepth(); 166 167 while (((eventType = parser.next()) != XmlPullParser.END_TAG || 168 parser.getDepth() > depth) && eventType != XmlPullParser.END_DOCUMENT) { 169 tagName = parser.getName(); 170 if (eventType == XmlPullParser.START_TAG && 171 "system-code-filter".equals(tagName) && systemCode == null) { 172 final TypedArray a = res.obtainAttributes(attrs, 173 com.android.internal.R.styleable.SystemCodeFilter); 174 systemCode = a.getString( 175 com.android.internal.R.styleable.SystemCodeFilter_name).toUpperCase(); 176 if (!isValidSystemCode(systemCode) && 177 !systemCode.equalsIgnoreCase("NULL")) { 178 Log.e(TAG, "Invalid System Code: " + systemCode); 179 systemCode = null; 180 } 181 a.recycle(); 182 } else if (eventType == XmlPullParser.START_TAG && 183 "nfcid2-filter".equals(tagName) && nfcid2 == null) { 184 final TypedArray a = res.obtainAttributes(attrs, 185 com.android.internal.R.styleable.Nfcid2Filter); 186 nfcid2 = a.getString( 187 com.android.internal.R.styleable.Nfcid2Filter_name).toUpperCase(); 188 if (!nfcid2.equalsIgnoreCase("RANDOM") && 189 !nfcid2.equalsIgnoreCase("NULL") && 190 !isValidNfcid2(nfcid2)) { 191 Log.e(TAG, "Invalid NFCID2: " + nfcid2); 192 nfcid2 = null; 193 } 194 a.recycle(); 195 } else if (eventType == XmlPullParser.START_TAG && tagName.equals("t3tPmm-filter") 196 && t3tPmm == null) { 197 final TypedArray a = res.obtainAttributes(attrs, 198 com.android.internal.R.styleable.T3tPmmFilter); 199 t3tPmm = a.getString( 200 com.android.internal.R.styleable.T3tPmmFilter_name).toUpperCase(); 201 a.recycle(); 202 } 203 } 204 mSystemCode = (systemCode == null ? "NULL" : systemCode); 205 mNfcid2 = (nfcid2 == null ? "NULL" : nfcid2); 206 mT3tPmm = (t3tPmm == null ? DEFAULT_T3T_PMM : t3tPmm); 207 } catch (NameNotFoundException e) { 208 throw new XmlPullParserException("Unable to create context for: " + si.packageName); 209 } finally { 210 if (parser != null) parser.close(); 211 } 212 // Set uid 213 mUid = si.applicationInfo.uid; 214 } 215 216 /** 217 * Returns the app component corresponding to this NFCF service. 218 * 219 * @return app component for this service 220 */ 221 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) 222 @NonNull getComponent()223 public ComponentName getComponent() { 224 return new ComponentName(mService.serviceInfo.packageName, 225 mService.serviceInfo.name); 226 } 227 228 /** 229 * Returns the system code corresponding to this service. 230 * 231 * @return system code for this service 232 */ 233 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) 234 @NonNull getSystemCode()235 public String getSystemCode() { 236 return (mDynamicSystemCode == null ? mSystemCode : mDynamicSystemCode); 237 } 238 239 /** 240 * Add or replace a system code to this service. 241 * @param systemCode system code to set or replace 242 */ 243 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) setDynamicSystemCode(@onNull String systemCode)244 public void setDynamicSystemCode(@NonNull String systemCode) { 245 mDynamicSystemCode = systemCode; 246 } 247 248 /** 249 * Returns NFC ID2. 250 * 251 * @return nfc id2 to return 252 */ 253 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) 254 @NonNull getNfcid2()255 public String getNfcid2() { 256 return (mDynamicNfcid2 == null ? mNfcid2 : mDynamicNfcid2); 257 } 258 259 /** 260 * Set or replace NFC ID2 261 * 262 * @param nfcid2 NFC ID2 string 263 */ 264 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) setDynamicNfcid2(@onNull String nfcid2)265 public void setDynamicNfcid2(@NonNull String nfcid2) { 266 mDynamicNfcid2 = nfcid2; 267 } 268 269 /** 270 * Returns description of service. 271 * @return user readable description of service 272 */ 273 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) 274 @NonNull getDescription()275 public String getDescription() { 276 return mDescription; 277 } 278 279 /** 280 * Returns uid of service. 281 * @return uid of the service 282 */ 283 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) getUid()284 public int getUid() { 285 return mUid; 286 } 287 288 /** 289 * Returns LF_T3T_PMM of the service 290 * @return returns LF_T3T_PMM of the service 291 */ 292 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) 293 @NonNull getT3tPmm()294 public String getT3tPmm() { 295 return mT3tPmm; 296 } 297 298 /** 299 * Load application label for this service. 300 * @param pm packagemanager instance 301 * @return label name corresponding to service 302 */ 303 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) 304 @NonNull loadLabel(@onNull PackageManager pm)305 public CharSequence loadLabel(@NonNull PackageManager pm) { 306 return mService.loadLabel(pm); 307 } 308 309 /** 310 * Load application icon for this service. 311 * @param pm packagemanager instance 312 * @return app icon corresponding to service 313 */ 314 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) 315 @NonNull loadIcon(@onNull PackageManager pm)316 public Drawable loadIcon(@NonNull PackageManager pm) { 317 return mService.loadIcon(pm); 318 } 319 320 @Override toString()321 public String toString() { 322 StringBuilder out = new StringBuilder("NfcFService: "); 323 out.append(getComponent()); 324 out.append(", UID: " + mUid); 325 out.append(", description: " + mDescription); 326 out.append(", System Code: " + mSystemCode); 327 if (mDynamicSystemCode != null) { 328 out.append(", dynamic System Code: " + mDynamicSystemCode); 329 } 330 out.append(", NFCID2: " + mNfcid2); 331 if (mDynamicNfcid2 != null) { 332 out.append(", dynamic NFCID2: " + mDynamicNfcid2); 333 } 334 out.append(", T3T PMM:" + mT3tPmm); 335 return out.toString(); 336 } 337 338 @Override equals(@ullable Object o)339 public boolean equals(@Nullable Object o) { 340 if (this == o) return true; 341 if (!(o instanceof NfcFServiceInfo)) return false; 342 NfcFServiceInfo thatService = (NfcFServiceInfo) o; 343 344 if (!thatService.getComponent().equals(this.getComponent())) return false; 345 if (thatService.getUid() != this.getUid()) return false; 346 if (!thatService.mSystemCode.equalsIgnoreCase(this.mSystemCode)) return false; 347 if (!thatService.mNfcid2.equalsIgnoreCase(this.mNfcid2)) return false; 348 if (!thatService.mT3tPmm.equalsIgnoreCase(this.mT3tPmm)) return false; 349 return true; 350 } 351 352 @Override hashCode()353 public int hashCode() { 354 return getComponent().hashCode(); 355 } 356 357 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) 358 @Override describeContents()359 public int describeContents() { 360 return 0; 361 } 362 363 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) 364 @Override writeToParcel(@onNull Parcel dest, int flags)365 public void writeToParcel(@NonNull Parcel dest, int flags) { 366 mService.writeToParcel(dest, flags); 367 dest.writeString(mDescription); 368 dest.writeString(mSystemCode); 369 dest.writeInt(mDynamicSystemCode != null ? 1 : 0); 370 if (mDynamicSystemCode != null) { 371 dest.writeString(mDynamicSystemCode); 372 } 373 dest.writeString(mNfcid2); 374 dest.writeInt(mDynamicNfcid2 != null ? 1 : 0); 375 if (mDynamicNfcid2 != null) { 376 dest.writeString(mDynamicNfcid2); 377 } 378 dest.writeInt(mUid); 379 dest.writeString(mT3tPmm); 380 }; 381 382 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) 383 public static final @NonNull Parcelable.Creator<NfcFServiceInfo> CREATOR = 384 new Parcelable.Creator<NfcFServiceInfo>() { 385 @Override 386 public NfcFServiceInfo createFromParcel(Parcel source) { 387 ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source); 388 String description = source.readString(); 389 String systemCode = source.readString(); 390 String dynamicSystemCode = null; 391 if (source.readInt() != 0) { 392 dynamicSystemCode = source.readString(); 393 } 394 String nfcid2 = source.readString(); 395 String dynamicNfcid2 = null; 396 if (source.readInt() != 0) { 397 dynamicNfcid2 = source.readString(); 398 } 399 int uid = source.readInt(); 400 String t3tPmm = source.readString(); 401 NfcFServiceInfo service = new NfcFServiceInfo(info, description, 402 systemCode, dynamicSystemCode, nfcid2, dynamicNfcid2, uid, t3tPmm); 403 return service; 404 } 405 406 @Override 407 public NfcFServiceInfo[] newArray(int size) { 408 return new NfcFServiceInfo[size]; 409 } 410 }; 411 412 /** 413 * Dump contents of the service for debugging. 414 * @param fd parcelfiledescriptor instance 415 * @param pw printwriter instance 416 * @param args args for dumping 417 */ 418 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) dump(@onNull ParcelFileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args)419 public void dump(@NonNull ParcelFileDescriptor fd, @NonNull PrintWriter pw, 420 @NonNull String[] args) { 421 pw.println(" " + getComponent() 422 + " (Description: " + getDescription() + ")" 423 + " (UID: " + getUid() + ")"); 424 pw.println(" System Code: " + getSystemCode()); 425 pw.println(" NFCID2: " + getNfcid2()); 426 pw.println(" T3tPmm: " + getT3tPmm()); 427 } 428 429 /** 430 * Dump debugging info as NfcFServiceInfoProto. 431 * 432 * If the output belongs to a sub message, the caller is responsible for wrapping this function 433 * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}. 434 * 435 * @param proto the ProtoOutputStream to write to 436 */ 437 @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) dumpDebug(@onNull ProtoOutputStream proto)438 public void dumpDebug(@NonNull ProtoOutputStream proto) { 439 getComponent().dumpDebug(proto, NfcFServiceInfoProto.COMPONENT_NAME); 440 proto.write(NfcFServiceInfoProto.DESCRIPTION, getDescription()); 441 proto.write(NfcFServiceInfoProto.SYSTEM_CODE, getSystemCode()); 442 proto.write(NfcFServiceInfoProto.NFCID2, getNfcid2()); 443 proto.write(NfcFServiceInfoProto.T3T_PMM, getT3tPmm()); 444 } 445 446 /** 447 * Copied over from {@link NfcFCardEmulation#isValidSystemCode(String)} 448 * @hide 449 */ isValidSystemCode(String systemCode)450 private static boolean isValidSystemCode(String systemCode) { 451 if (systemCode == null) { 452 return false; 453 } 454 if (systemCode.length() != 4) { 455 Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); 456 return false; 457 } 458 // check if the value is between "4000" and "4FFF" (excluding "4*FF") 459 if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) { 460 Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); 461 return false; 462 } 463 try { 464 Integer.parseInt(systemCode, 16); 465 } catch (NumberFormatException e) { 466 Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); 467 return false; 468 } 469 return true; 470 } 471 472 /** 473 * Copied over from {@link NfcFCardEmulation#isValidNfcid2(String)} 474 * @hide 475 */ isValidNfcid2(String nfcid2)476 private static boolean isValidNfcid2(String nfcid2) { 477 if (nfcid2 == null) { 478 return false; 479 } 480 if (nfcid2.length() != 16) { 481 Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); 482 return false; 483 } 484 // check if the the value starts with "02FE" 485 if (!nfcid2.toUpperCase().startsWith("02FE")) { 486 Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); 487 return false; 488 } 489 try { 490 Long.parseLong(nfcid2, 16); 491 } catch (NumberFormatException e) { 492 Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); 493 return false; 494 } 495 return true; 496 } 497 } 498