/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /********************************************************************** * This file is not a part of the NFC mainline module * * *******************************************************************/ package android.nfc.cardemulation; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.nfc.Flags; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; import android.util.proto.ProtoOutputStream; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.PrintWriter; /** * Class to hold NfcF service info. * * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public final class NfcFServiceInfo implements Parcelable { static final String TAG = "NfcFServiceInfo"; private static final String DEFAULT_T3T_PMM = "FFFFFFFFFFFFFFFF"; /** * The service that implements this */ private final ResolveInfo mService; /** * Description of the service */ private final String mDescription; /** * System Code of the service */ private final String mSystemCode; /** * System Code of the service registered by API */ private String mDynamicSystemCode; /** * NFCID2 of the service */ private final String mNfcid2; /** * NFCID2 of the service registered by API */ private String mDynamicNfcid2; /** * The uid of the package the service belongs to */ private final int mUid; /** * LF_T3T_PMM of the service */ private final String mT3tPmm; /** * @hide */ public NfcFServiceInfo(ResolveInfo info, String description, String systemCode, String dynamicSystemCode, String nfcid2, String dynamicNfcid2, int uid, String t3tPmm) { this.mService = info; this.mDescription = description; this.mSystemCode = systemCode; this.mDynamicSystemCode = dynamicSystemCode; this.mNfcid2 = nfcid2; this.mDynamicNfcid2 = dynamicNfcid2; this.mUid = uid; this.mT3tPmm = t3tPmm; } /** * Creates a new NfcFServiceInfo object. * * @param pm packageManager instance * @param info app component info * @throws XmlPullParserException If an error occurs parsing the element. * @throws IOException If an error occurs reading the element. */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public NfcFServiceInfo(@NonNull PackageManager pm, @NonNull ResolveInfo info) throws XmlPullParserException, IOException { ServiceInfo si = info.serviceInfo; XmlResourceParser parser = null; try { parser = si.loadXmlMetaData(pm, HostNfcFService.SERVICE_META_DATA); if (parser == null) { throw new XmlPullParserException("No " + HostNfcFService.SERVICE_META_DATA + " meta-data"); } int eventType = parser.getEventType(); while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) { eventType = parser.next(); } String tagName = parser.getName(); if (!"host-nfcf-service".equals(tagName)) { throw new XmlPullParserException( "Meta-data does not start with tag"); } Resources res = pm.getResourcesForApplication(si.applicationInfo); AttributeSet attrs = Xml.asAttributeSet(parser); TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.HostNfcFService); mService = info; mDescription = sa.getString( com.android.internal.R.styleable.HostNfcFService_description); mDynamicSystemCode = null; mDynamicNfcid2 = null; sa.recycle(); String systemCode = null; String nfcid2 = null; String t3tPmm = null; final int depth = parser.getDepth(); while (((eventType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && eventType != XmlPullParser.END_DOCUMENT) { tagName = parser.getName(); if (eventType == XmlPullParser.START_TAG && "system-code-filter".equals(tagName) && systemCode == null) { final TypedArray a = res.obtainAttributes(attrs, com.android.internal.R.styleable.SystemCodeFilter); systemCode = a.getString( com.android.internal.R.styleable.SystemCodeFilter_name).toUpperCase(); if (!isValidSystemCode(systemCode) && !systemCode.equalsIgnoreCase("NULL")) { Log.e(TAG, "Invalid System Code: " + systemCode); systemCode = null; } a.recycle(); } else if (eventType == XmlPullParser.START_TAG && "nfcid2-filter".equals(tagName) && nfcid2 == null) { final TypedArray a = res.obtainAttributes(attrs, com.android.internal.R.styleable.Nfcid2Filter); nfcid2 = a.getString( com.android.internal.R.styleable.Nfcid2Filter_name).toUpperCase(); if (!nfcid2.equalsIgnoreCase("RANDOM") && !nfcid2.equalsIgnoreCase("NULL") && !isValidNfcid2(nfcid2)) { Log.e(TAG, "Invalid NFCID2: " + nfcid2); nfcid2 = null; } a.recycle(); } else if (eventType == XmlPullParser.START_TAG && tagName.equals("t3tPmm-filter") && t3tPmm == null) { final TypedArray a = res.obtainAttributes(attrs, com.android.internal.R.styleable.T3tPmmFilter); t3tPmm = a.getString( com.android.internal.R.styleable.T3tPmmFilter_name).toUpperCase(); a.recycle(); } } mSystemCode = (systemCode == null ? "NULL" : systemCode); mNfcid2 = (nfcid2 == null ? "NULL" : nfcid2); mT3tPmm = (t3tPmm == null ? DEFAULT_T3T_PMM : t3tPmm); } catch (NameNotFoundException e) { throw new XmlPullParserException("Unable to create context for: " + si.packageName); } finally { if (parser != null) parser.close(); } // Set uid mUid = si.applicationInfo.uid; } /** * Returns the app component corresponding to this NFCF service. * * @return app component for this service */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public ComponentName getComponent() { return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name); } /** * Returns the system code corresponding to this service. * * @return system code for this service */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getSystemCode() { return (mDynamicSystemCode == null ? mSystemCode : mDynamicSystemCode); } /** * Add or replace a system code to this service. * @param systemCode system code to set or replace */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void setDynamicSystemCode(@NonNull String systemCode) { mDynamicSystemCode = systemCode; } /** * Returns NFC ID2. * * @return nfc id2 to return */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getNfcid2() { return (mDynamicNfcid2 == null ? mNfcid2 : mDynamicNfcid2); } /** * Set or replace NFC ID2 * * @param nfcid2 NFC ID2 string */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void setDynamicNfcid2(@NonNull String nfcid2) { mDynamicNfcid2 = nfcid2; } /** * Returns description of service. * @return user readable description of service */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getDescription() { return mDescription; } /** * Returns uid of service. * @return uid of the service */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public int getUid() { return mUid; } /** * Returns LF_T3T_PMM of the service * @return returns LF_T3T_PMM of the service */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public String getT3tPmm() { return mT3tPmm; } /** * Load application label for this service. * @param pm packagemanager instance * @return label name corresponding to service */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public CharSequence loadLabel(@NonNull PackageManager pm) { return mService.loadLabel(pm); } /** * Load application icon for this service. * @param pm packagemanager instance * @return app icon corresponding to service */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public Drawable loadIcon(@NonNull PackageManager pm) { return mService.loadIcon(pm); } @Override public String toString() { StringBuilder out = new StringBuilder("NfcFService: "); out.append(getComponent()); out.append(", UID: " + mUid); out.append(", description: " + mDescription); out.append(", System Code: " + mSystemCode); if (mDynamicSystemCode != null) { out.append(", dynamic System Code: " + mDynamicSystemCode); } out.append(", NFCID2: " + mNfcid2); if (mDynamicNfcid2 != null) { out.append(", dynamic NFCID2: " + mDynamicNfcid2); } out.append(", T3T PMM:" + mT3tPmm); return out.toString(); } @Override public boolean equals(@Nullable Object o) { if (this == o) return true; if (!(o instanceof NfcFServiceInfo)) return false; NfcFServiceInfo thatService = (NfcFServiceInfo) o; if (!thatService.getComponent().equals(this.getComponent())) return false; if (thatService.getUid() != this.getUid()) return false; if (!thatService.mSystemCode.equalsIgnoreCase(this.mSystemCode)) return false; if (!thatService.mNfcid2.equalsIgnoreCase(this.mNfcid2)) return false; if (!thatService.mT3tPmm.equalsIgnoreCase(this.mT3tPmm)) return false; return true; } @Override public int hashCode() { return getComponent().hashCode(); } @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Override public int describeContents() { return 0; } @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @Override public void writeToParcel(@NonNull Parcel dest, int flags) { mService.writeToParcel(dest, flags); dest.writeString(mDescription); dest.writeString(mSystemCode); dest.writeInt(mDynamicSystemCode != null ? 1 : 0); if (mDynamicSystemCode != null) { dest.writeString(mDynamicSystemCode); } dest.writeString(mNfcid2); dest.writeInt(mDynamicNfcid2 != null ? 1 : 0); if (mDynamicNfcid2 != null) { dest.writeString(mDynamicNfcid2); } dest.writeInt(mUid); dest.writeString(mT3tPmm); }; @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public static final @NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public NfcFServiceInfo createFromParcel(Parcel source) { ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source); String description = source.readString(); String systemCode = source.readString(); String dynamicSystemCode = null; if (source.readInt() != 0) { dynamicSystemCode = source.readString(); } String nfcid2 = source.readString(); String dynamicNfcid2 = null; if (source.readInt() != 0) { dynamicNfcid2 = source.readString(); } int uid = source.readInt(); String t3tPmm = source.readString(); NfcFServiceInfo service = new NfcFServiceInfo(info, description, systemCode, dynamicSystemCode, nfcid2, dynamicNfcid2, uid, t3tPmm); return service; } @Override public NfcFServiceInfo[] newArray(int size) { return new NfcFServiceInfo[size]; } }; /** * Dump contents of the service for debugging. * @param fd parcelfiledescriptor instance * @param pw printwriter instance * @param args args for dumping */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dump(@NonNull ParcelFileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.println(" " + getComponent() + " (Description: " + getDescription() + ")" + " (UID: " + getUid() + ")"); pw.println(" System Code: " + getSystemCode()); pw.println(" NFCID2: " + getNfcid2()); pw.println(" T3tPmm: " + getT3tPmm()); } /** * Dump debugging info as NfcFServiceInfoProto. * * If the output belongs to a sub message, the caller is responsible for wrapping this function * between {@link ProtoOutputStream#start(long)} and {@link ProtoOutputStream#end(long)}. * * @param proto the ProtoOutputStream to write to */ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public void dumpDebug(@NonNull ProtoOutputStream proto) { getComponent().dumpDebug(proto, NfcFServiceInfoProto.COMPONENT_NAME); proto.write(NfcFServiceInfoProto.DESCRIPTION, getDescription()); proto.write(NfcFServiceInfoProto.SYSTEM_CODE, getSystemCode()); proto.write(NfcFServiceInfoProto.NFCID2, getNfcid2()); proto.write(NfcFServiceInfoProto.T3T_PMM, getT3tPmm()); } /** * Copied over from {@link NfcFCardEmulation#isValidSystemCode(String)} * @hide */ private static boolean isValidSystemCode(String systemCode) { if (systemCode == null) { return false; } if (systemCode.length() != 4) { Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); return false; } // check if the value is between "4000" and "4FFF" (excluding "4*FF") if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) { Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); return false; } try { Integer.parseInt(systemCode, 16); } catch (NumberFormatException e) { Log.e(TAG, "System Code " + systemCode + " is not a valid System Code."); return false; } return true; } /** * Copied over from {@link NfcFCardEmulation#isValidNfcid2(String)} * @hide */ private static boolean isValidNfcid2(String nfcid2) { if (nfcid2 == null) { return false; } if (nfcid2.length() != 16) { Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); return false; } // check if the the value starts with "02FE" if (!nfcid2.toUpperCase().startsWith("02FE")) { Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); return false; } try { Long.parseLong(nfcid2, 16); } catch (NumberFormatException e) { Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2."); return false; } return true; } }