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