blob: 33bc169787210119df0c97a5b65a74fd0becfd11 [file] [log] [blame]
/*
* 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;
}
}