ApduServiceInfo.java revision 58d2065984646a61145df285d1c77b68b1f700fb
1/*
2 * Copyright (C) 2013 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.Resources.NotFoundException;
26import android.content.res.TypedArray;
27import android.content.res.XmlResourceParser;
28import android.graphics.drawable.Drawable;
29import android.os.Parcel;
30import android.os.Parcelable;
31import android.util.AttributeSet;
32import android.util.Log;
33import android.util.Xml;
34
35import org.xmlpull.v1.XmlPullParser;
36import org.xmlpull.v1.XmlPullParserException;
37
38import java.io.IOException;
39import java.util.ArrayList;
40import java.util.HashMap;
41
42/**
43 * @hide
44 */
45public final class ApduServiceInfo implements Parcelable {
46    static final String TAG = "ApduServiceInfo";
47
48    /**
49     * The service that implements this
50     */
51    final ResolveInfo mService;
52
53    /**
54     * Description of the service
55     */
56    final String mDescription;
57
58    /**
59     * Convenience AID list
60     */
61    final ArrayList<String> mAids;
62
63    /**
64     * Whether this service represents AIDs running on the host CPU
65     */
66    final boolean mOnHost;
67
68    /**
69     * All AID groups this service handles
70     */
71    final ArrayList<AidGroup> mAidGroups;
72
73    /**
74     * Convenience hashmap
75     */
76    final HashMap<String, AidGroup> mCategoryToGroup;
77
78    /**
79     * Whether this service should only be started when the device is unlocked.
80     */
81    final boolean mRequiresDeviceUnlock;
82
83    /**
84     * The id of the service banner specified in XML.
85     */
86    final int mBannerResourceId;
87
88    /**
89     * @hide
90     */
91    public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
92            ArrayList<AidGroup> aidGroups, boolean requiresUnlock, int bannerResource) {
93        this.mService = info;
94        this.mDescription = description;
95        this.mAidGroups = aidGroups;
96        this.mAids = new ArrayList<String>();
97        this.mCategoryToGroup = new HashMap<String, AidGroup>();
98        this.mOnHost = onHost;
99        this.mRequiresDeviceUnlock = requiresUnlock;
100        for (AidGroup aidGroup : aidGroups) {
101            this.mCategoryToGroup.put(aidGroup.category, aidGroup);
102            this.mAids.addAll(aidGroup.aids);
103        }
104        this.mBannerResourceId = bannerResource;
105    }
106
107    public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost)
108            throws XmlPullParserException, IOException {
109        ServiceInfo si = info.serviceInfo;
110        XmlResourceParser parser = null;
111        try {
112            if (onHost) {
113                parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA);
114                if (parser == null) {
115                    throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
116                            " meta-data");
117                }
118            } else {
119                parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA);
120                if (parser == null) {
121                    throw new XmlPullParserException("No " + OffHostApduService.SERVICE_META_DATA +
122                            " meta-data");
123                }
124            }
125
126            int eventType = parser.getEventType();
127            while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) {
128                eventType = parser.next();
129            }
130
131            String tagName = parser.getName();
132            if (onHost && !"host-apdu-service".equals(tagName)) {
133                throw new XmlPullParserException(
134                        "Meta-data does not start with <host-apdu-service> tag");
135            } else if (!onHost && !"offhost-apdu-service".equals(tagName)) {
136                throw new XmlPullParserException(
137                        "Meta-data does not start with <offhost-apdu-service> tag");
138            }
139
140            Resources res = pm.getResourcesForApplication(si.applicationInfo);
141            AttributeSet attrs = Xml.asAttributeSet(parser);
142            if (onHost) {
143                TypedArray sa = res.obtainAttributes(attrs,
144                        com.android.internal.R.styleable.HostApduService);
145                mService = info;
146                mDescription = sa.getString(
147                        com.android.internal.R.styleable.HostApduService_description);
148                mRequiresDeviceUnlock = sa.getBoolean(
149                        com.android.internal.R.styleable.HostApduService_requireDeviceUnlock,
150                        false);
151                mBannerResourceId = sa.getResourceId(
152                        com.android.internal.R.styleable.HostApduService_apduServiceBanner, -1);
153                sa.recycle();
154            } else {
155                TypedArray sa = res.obtainAttributes(attrs,
156                        com.android.internal.R.styleable.OffHostApduService);
157                mService = info;
158                mDescription = sa.getString(
159                        com.android.internal.R.styleable.OffHostApduService_description);
160                mRequiresDeviceUnlock = false;
161                mBannerResourceId = sa.getResourceId(
162                        com.android.internal.R.styleable.HostApduService_apduServiceBanner, -1);
163                sa.recycle();
164            }
165
166            mAidGroups = new ArrayList<AidGroup>();
167            mCategoryToGroup = new HashMap<String, AidGroup>();
168            mAids = new ArrayList<String>();
169            mOnHost = onHost;
170            final int depth = parser.getDepth();
171            AidGroup currentGroup = null;
172
173            // Parsed values for the current AID group
174            while (((eventType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
175                    && eventType != XmlPullParser.END_DOCUMENT) {
176                tagName = parser.getName();
177                if (eventType == XmlPullParser.START_TAG && "aid-group".equals(tagName) &&
178                        currentGroup == null) {
179                    final TypedArray groupAttrs = res.obtainAttributes(attrs,
180                            com.android.internal.R.styleable.AidGroup);
181                    // Get category of AID group
182                    String groupDescription = groupAttrs.getString(
183                            com.android.internal.R.styleable.AidGroup_description);
184                    String groupCategory = groupAttrs.getString(
185                            com.android.internal.R.styleable.AidGroup_category);
186                    if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) {
187                        groupCategory = CardEmulation.CATEGORY_OTHER;
188                    }
189                    currentGroup = mCategoryToGroup.get(groupCategory);
190                    if (currentGroup != null) {
191                        if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
192                            Log.e(TAG, "Not allowing multiple aid-groups in the " +
193                                    groupCategory + " category");
194                            currentGroup = null;
195                        }
196                    } else {
197                        currentGroup = new AidGroup(groupCategory, groupDescription);
198                    }
199                    groupAttrs.recycle();
200                } else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) &&
201                        currentGroup != null) {
202                    if (currentGroup.aids.size() > 0) {
203                        if (!mCategoryToGroup.containsKey(currentGroup.category)) {
204                            mAidGroups.add(currentGroup);
205                            mCategoryToGroup.put(currentGroup.category, currentGroup);
206                        }
207                    } else {
208                        Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs");
209                    }
210                    currentGroup = null;
211                } else if (eventType == XmlPullParser.START_TAG && "aid-filter".equals(tagName) &&
212                        currentGroup != null) {
213                    final TypedArray a = res.obtainAttributes(attrs,
214                            com.android.internal.R.styleable.AidFilter);
215                    String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
216                            toUpperCase();
217                    if (isValidAid(aid) && !currentGroup.aids.contains(aid)) {
218                        currentGroup.aids.add(aid);
219                        mAids.add(aid);
220                    } else {
221                        Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
222                    }
223                    a.recycle();
224                }
225            }
226        } catch (NameNotFoundException e) {
227            throw new XmlPullParserException("Unable to create context for: " + si.packageName);
228        } finally {
229            if (parser != null) parser.close();
230        }
231    }
232
233    public ComponentName getComponent() {
234        return new ComponentName(mService.serviceInfo.packageName,
235                mService.serviceInfo.name);
236    }
237
238    public ArrayList<String> getAids() {
239        return mAids;
240    }
241
242    public ArrayList<AidGroup> getAidGroups() {
243        return mAidGroups;
244    }
245
246    public boolean hasCategory(String category) {
247        return mCategoryToGroup.containsKey(category);
248    }
249
250    public boolean isOnHost() {
251        return mOnHost;
252    }
253
254    public boolean requiresUnlock() {
255        return mRequiresDeviceUnlock;
256    }
257
258    public CharSequence loadLabel(PackageManager pm) {
259        return mService.loadLabel(pm);
260    }
261
262    public Drawable loadIcon(PackageManager pm) {
263        return mService.loadIcon(pm);
264    }
265
266    public Drawable loadBanner(PackageManager pm) {
267        Resources res;
268        try {
269            res = pm.getResourcesForApplication(mService.serviceInfo.packageName);
270            Drawable banner = res.getDrawable(mBannerResourceId);
271            return banner;
272        } catch (NotFoundException e) {
273            Log.e(TAG, "Could not load banner.");
274            return null;
275        } catch (NameNotFoundException e) {
276            Log.e(TAG, "Could not load banner.");
277            return null;
278        }
279    }
280
281    static boolean isValidAid(String aid) {
282        if (aid == null)
283            return false;
284
285        int aidLength = aid.length();
286        if (aidLength == 0 || (aidLength % 2) != 0) {
287            Log.e(TAG, "AID " + aid + " is not correctly formatted.");
288            return false;
289        }
290        return true;
291    }
292
293    @Override
294    public String toString() {
295        StringBuilder out = new StringBuilder("ApduService: ");
296        out.append(getComponent());
297        out.append(", description: " + mDescription);
298        out.append(", AID Groups: ");
299        for (AidGroup aidGroup : mAidGroups) {
300            out.append(aidGroup.toString());
301        }
302        return out.toString();
303    }
304
305    @Override
306    public boolean equals(Object o) {
307        if (this == o) return true;
308        if (!(o instanceof ApduServiceInfo)) return false;
309        ApduServiceInfo thatService = (ApduServiceInfo) o;
310
311        return thatService.getComponent().equals(this.getComponent());
312    }
313
314    @Override
315    public int hashCode() {
316        return getComponent().hashCode();
317    }
318
319
320    @Override
321    public int describeContents() {
322        return 0;
323    }
324
325    @Override
326    public void writeToParcel(Parcel dest, int flags) {
327        mService.writeToParcel(dest, flags);
328        dest.writeString(mDescription);
329        dest.writeInt(mOnHost ? 1 : 0);
330        dest.writeInt(mAidGroups.size());
331        if (mAidGroups.size() > 0) {
332            dest.writeTypedList(mAidGroups);
333        }
334        dest.writeInt(mRequiresDeviceUnlock ? 1 : 0);
335        dest.writeInt(mBannerResourceId);
336    };
337
338    public static final Parcelable.Creator<ApduServiceInfo> CREATOR =
339            new Parcelable.Creator<ApduServiceInfo>() {
340        @Override
341        public ApduServiceInfo createFromParcel(Parcel source) {
342            ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
343            String description = source.readString();
344            boolean onHost = (source.readInt() != 0) ? true : false;
345            ArrayList<AidGroup> aidGroups = new ArrayList<AidGroup>();
346            int numGroups = source.readInt();
347            if (numGroups > 0) {
348                source.readTypedList(aidGroups, AidGroup.CREATOR);
349            }
350            boolean requiresUnlock = (source.readInt() != 0) ? true : false;
351            int bannerResource = source.readInt();
352            return new ApduServiceInfo(info, onHost, description, aidGroups, requiresUnlock, bannerResource);
353        }
354
355        @Override
356        public ApduServiceInfo[] newArray(int size) {
357            return new ApduServiceInfo[size];
358        }
359    };
360
361    public static class AidGroup implements Parcelable {
362        final ArrayList<String> aids;
363        final String category;
364        final String description;
365
366        AidGroup(ArrayList<String> aids, String category, String description) {
367            this.aids = aids;
368            this.category = category;
369            this.description = description;
370        }
371
372        AidGroup(String category, String description) {
373            this.aids = new ArrayList<String>();
374            this.category = category;
375            this.description = description;
376        }
377
378        public String getCategory() {
379            return category;
380        }
381
382        public ArrayList<String> getAids() {
383            return aids;
384        }
385
386        @Override
387        public String toString() {
388            StringBuilder out = new StringBuilder("Category: " + category +
389                      ", description: " + description + ", AIDs:");
390            for (String aid : aids) {
391                out.append(aid);
392                out.append(", ");
393            }
394            return out.toString();
395        }
396
397        @Override
398        public int describeContents() {
399            return 0;
400        }
401
402        @Override
403        public void writeToParcel(Parcel dest, int flags) {
404            dest.writeString(category);
405            dest.writeString(description);
406            dest.writeInt(aids.size());
407            if (aids.size() > 0) {
408                dest.writeStringList(aids);
409            }
410        }
411
412        public static final Parcelable.Creator<ApduServiceInfo.AidGroup> CREATOR =
413                new Parcelable.Creator<ApduServiceInfo.AidGroup>() {
414
415            @Override
416            public AidGroup createFromParcel(Parcel source) {
417                String category = source.readString();
418                String description = source.readString();
419                int listSize = source.readInt();
420                ArrayList<String> aidList = new ArrayList<String>();
421                if (listSize > 0) {
422                    source.readStringList(aidList);
423                }
424                return new AidGroup(aidList, category, description);
425            }
426
427            @Override
428            public AidGroup[] newArray(int size) {
429                return new AidGroup[size];
430            }
431        };
432    }
433}
434