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