| /* |
| * 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 <host-nfcf-service> 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<NfcFServiceInfo> CREATOR = |
| new Parcelable.Creator<NfcFServiceInfo>() { |
| @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; |
| } |
| } |