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
17package android.nfc.cardemulation;
18
19import android.content.ComponentName;
20import android.content.pm.PackageManager;
21import android.content.pm.ResolveInfo;
22import android.content.pm.ServiceInfo;
23import android.content.pm.PackageManager.NameNotFoundException;
24import android.content.res.Resources;
25import android.content.res.TypedArray;
26import android.content.res.XmlResourceParser;
27import android.graphics.drawable.Drawable;
28import android.os.Parcel;
29import android.os.Parcelable;
30import android.util.AttributeSet;
31import android.util.Log;
32import android.util.Xml;
33
34import org.xmlpull.v1.XmlPullParser;
35import org.xmlpull.v1.XmlPullParserException;
36
37import java.io.FileDescriptor;
38import java.io.IOException;
39import java.io.PrintWriter;
40
41/**
42 * @hide
43 */
44public final class NfcFServiceInfo implements Parcelable {
45    static final String TAG = "NfcFServiceInfo";
46
47    /**
48     * The service that implements this
49     */
50    final ResolveInfo mService;
51
52    /**
53     * Description of the service
54     */
55    final String mDescription;
56
57    /**
58     * System Code of the service
59     */
60    final String mSystemCode;
61
62    /**
63     * System Code of the service registered by API
64     */
65    String mDynamicSystemCode;
66
67    /**
68     * NFCID2 of the service
69     */
70    final String mNfcid2;
71
72    /**
73     * NFCID2 of the service registered by API
74     */
75    String mDynamicNfcid2;
76
77    /**
78     * The uid of the package the service belongs to
79     */
80    final int mUid;
81
82    /**
83     * @hide
84     */
85    public NfcFServiceInfo(ResolveInfo info, String description,
86            String systemCode, String dynamicSystemCode, String nfcid2, String dynamicNfcid2,
87            int uid) {
88        this.mService = info;
89        this.mDescription = description;
90        this.mSystemCode = systemCode;
91        this.mDynamicSystemCode = dynamicSystemCode;
92        this.mNfcid2 = nfcid2;
93        this.mDynamicNfcid2 = dynamicNfcid2;
94        this.mUid = uid;
95    }
96
97    public NfcFServiceInfo(PackageManager pm, ResolveInfo info)
98            throws XmlPullParserException, IOException {
99        ServiceInfo si = info.serviceInfo;
100        XmlResourceParser parser = null;
101        try {
102            parser = si.loadXmlMetaData(pm, HostNfcFService.SERVICE_META_DATA);
103            if (parser == null) {
104                throw new XmlPullParserException("No " + HostNfcFService.SERVICE_META_DATA +
105                        " meta-data");
106            }
107
108            int eventType = parser.getEventType();
109            while (eventType != XmlPullParser.START_TAG &&
110                    eventType != XmlPullParser.END_DOCUMENT) {
111                eventType = parser.next();
112            }
113
114            String tagName = parser.getName();
115            if (!"host-nfcf-service".equals(tagName)) {
116                throw new XmlPullParserException(
117                        "Meta-data does not start with <host-nfcf-service> tag");
118            }
119
120            Resources res = pm.getResourcesForApplication(si.applicationInfo);
121            AttributeSet attrs = Xml.asAttributeSet(parser);
122            TypedArray sa = res.obtainAttributes(attrs,
123                    com.android.internal.R.styleable.HostNfcFService);
124            mService = info;
125            mDescription = sa.getString(
126                    com.android.internal.R.styleable.HostNfcFService_description);
127            mDynamicSystemCode = null;
128            mDynamicNfcid2 = null;
129            sa.recycle();
130
131            String systemCode = null;
132            String nfcid2 = null;
133            final int depth = parser.getDepth();
134
135            while (((eventType = parser.next()) != XmlPullParser.END_TAG ||
136                    parser.getDepth() > depth) && eventType != XmlPullParser.END_DOCUMENT) {
137                tagName = parser.getName();
138                if (eventType == XmlPullParser.START_TAG &&
139                        "system-code-filter".equals(tagName) && systemCode == null) {
140                    final TypedArray a = res.obtainAttributes(attrs,
141                            com.android.internal.R.styleable.SystemCodeFilter);
142                    systemCode = a.getString(
143                            com.android.internal.R.styleable.SystemCodeFilter_name).toUpperCase();
144                    if (!NfcFCardEmulation.isValidSystemCode(systemCode) &&
145                            !systemCode.equalsIgnoreCase("NULL")) {
146                        Log.e(TAG, "Invalid System Code: " + systemCode);
147                        systemCode = null;
148                    }
149                    a.recycle();
150                } else if (eventType == XmlPullParser.START_TAG &&
151                        "nfcid2-filter".equals(tagName) && nfcid2 == null) {
152                    final TypedArray a = res.obtainAttributes(attrs,
153                            com.android.internal.R.styleable.Nfcid2Filter);
154                    nfcid2 = a.getString(
155                            com.android.internal.R.styleable.Nfcid2Filter_name).toUpperCase();
156                    if (!nfcid2.equalsIgnoreCase("RANDOM") &&
157                            !nfcid2.equalsIgnoreCase("NULL") &&
158                            !NfcFCardEmulation.isValidNfcid2(nfcid2)) {
159                        Log.e(TAG, "Invalid NFCID2: " + nfcid2);
160                        nfcid2 = null;
161                    }
162                    a.recycle();
163                }
164            }
165            mSystemCode = (systemCode == null ? "NULL" : systemCode);
166            mNfcid2 = (nfcid2 == null ? "NULL" : nfcid2);
167        } catch (NameNotFoundException e) {
168            throw new XmlPullParserException("Unable to create context for: " + si.packageName);
169        } finally {
170            if (parser != null) parser.close();
171        }
172        // Set uid
173        mUid = si.applicationInfo.uid;
174    }
175
176    public ComponentName getComponent() {
177        return new ComponentName(mService.serviceInfo.packageName,
178                mService.serviceInfo.name);
179    }
180
181    public String getSystemCode() {
182        return (mDynamicSystemCode == null ? mSystemCode : mDynamicSystemCode);
183    }
184
185    public void setOrReplaceDynamicSystemCode(String systemCode) {
186        mDynamicSystemCode = systemCode;
187    }
188
189    public String getNfcid2() {
190        return (mDynamicNfcid2 == null ? mNfcid2 : mDynamicNfcid2);
191    }
192
193    public void setOrReplaceDynamicNfcid2(String nfcid2) {
194        mDynamicNfcid2 = nfcid2;
195    }
196
197    public String getDescription() {
198        return mDescription;
199    }
200
201    public int getUid() {
202        return mUid;
203    }
204
205    public CharSequence loadLabel(PackageManager pm) {
206        return mService.loadLabel(pm);
207    }
208
209    public Drawable loadIcon(PackageManager pm) {
210        return mService.loadIcon(pm);
211    }
212
213    @Override
214    public String toString() {
215        StringBuilder out = new StringBuilder("NfcFService: ");
216        out.append(getComponent());
217        out.append(", description: " + mDescription);
218        out.append(", System Code: " + mSystemCode);
219        if (mDynamicSystemCode != null) {
220            out.append(", dynamic System Code: " + mDynamicSystemCode);
221        }
222        out.append(", NFCID2: " + mNfcid2);
223        if (mDynamicNfcid2 != null) {
224            out.append(", dynamic NFCID2: " + mDynamicNfcid2);
225        }
226        return out.toString();
227    }
228
229    @Override
230    public boolean equals(Object o) {
231        if (this == o) return true;
232        if (!(o instanceof NfcFServiceInfo)) return false;
233        NfcFServiceInfo thatService = (NfcFServiceInfo) o;
234
235        if (!thatService.getComponent().equals(this.getComponent())) return false;
236        if (!thatService.mSystemCode.equalsIgnoreCase(this.mSystemCode)) return false;
237        if (!thatService.mNfcid2.equalsIgnoreCase(this.mNfcid2)) return false;
238
239        return true;
240    }
241
242    @Override
243    public int hashCode() {
244        return getComponent().hashCode();
245    }
246
247    @Override
248    public int describeContents() {
249        return 0;
250    }
251
252    @Override
253    public void writeToParcel(Parcel dest, int flags) {
254        mService.writeToParcel(dest, flags);
255        dest.writeString(mDescription);
256        dest.writeString(mSystemCode);
257        dest.writeInt(mDynamicSystemCode != null ? 1 : 0);
258        if (mDynamicSystemCode != null) {
259            dest.writeString(mDynamicSystemCode);
260        }
261        dest.writeString(mNfcid2);
262        dest.writeInt(mDynamicNfcid2 != null ? 1 : 0);
263        if (mDynamicNfcid2 != null) {
264            dest.writeString(mDynamicNfcid2);
265        }
266        dest.writeInt(mUid);
267    };
268
269    public static final Parcelable.Creator<NfcFServiceInfo> CREATOR =
270            new Parcelable.Creator<NfcFServiceInfo>() {
271        @Override
272        public NfcFServiceInfo createFromParcel(Parcel source) {
273            ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
274            String description = source.readString();
275            String systemCode = source.readString();
276            String dynamicSystemCode = null;
277            if (source.readInt() != 0) {
278                dynamicSystemCode = source.readString();
279            }
280            String nfcid2 = source.readString();
281            String dynamicNfcid2 = null;
282            if (source.readInt() != 0) {
283                dynamicNfcid2 = source.readString();
284            }
285            int uid = source.readInt();
286            NfcFServiceInfo service = new NfcFServiceInfo(info, description,
287                    systemCode, dynamicSystemCode, nfcid2, dynamicNfcid2, uid);
288            return service;
289        }
290
291        @Override
292        public NfcFServiceInfo[] newArray(int size) {
293            return new NfcFServiceInfo[size];
294        }
295    };
296
297    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
298        pw.println("    " + getComponent() +
299                " (Description: " + getDescription() + ")");
300        pw.println("    System Code: " + getSystemCode());
301        pw.println("    NFCID2: " + getNfcid2());
302    }
303}
304
305