ApduServiceInfo.java revision a7397883de67d674970d91f86d46ccf637e5e543
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 is an {@link HostApduService} or {@link OffHostApduService}
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, 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 = false;
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) throws XmlPullParserException,
95            IOException {
96        ServiceInfo si = info.serviceInfo;
97
98        XmlResourceParser parser = null;
99        try {
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
106            int eventType = parser.getEventType();
107            while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) {
108                eventType = parser.next();
109            }
110
111            String tagName = parser.getName();
112            if (!"host-apdu-service".equals(tagName)) {
113                throw new XmlPullParserException(
114                        "Meta-data does not start with <host-apdu-service> tag");
115            }
116
117            Resources res = pm.getResourcesForApplication(si.applicationInfo);
118            AttributeSet attrs = Xml.asAttributeSet(parser);
119            TypedArray sa = res.obtainAttributes(attrs,
120                    com.android.internal.R.styleable.HostApduService);
121            mService = info;
122            mDescription = sa.getString(
123                    com.android.internal.R.styleable.HostApduService_description);
124            mAidGroups = new ArrayList<AidGroup>();
125            mCategoryToGroup = new HashMap<String, AidGroup>();
126            mAids = new ArrayList<String>();
127            mOnHost = true; // TODO
128            final int depth = parser.getDepth();
129            AidGroup currentGroup = null;
130
131            // Parsed values for the current AID group
132            while (((eventType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
133                    && eventType != XmlPullParser.END_DOCUMENT) {
134                tagName = parser.getName();
135                if (eventType == XmlPullParser.START_TAG && "aid-group".equals(tagName) &&
136                        currentGroup == null) {
137                    final TypedArray groupAttrs = res.obtainAttributes(attrs,
138                            com.android.internal.R.styleable.AidGroup);
139                    // Get category of AID group
140                    String groupDescription = groupAttrs.getString(
141                            com.android.internal.R.styleable.AidGroup_description);
142                    String groupCategory = groupAttrs.getString(
143                            com.android.internal.R.styleable.AidGroup_category);
144                    if (!CardEmulationManager.CATEGORY_PAYMENT.equals(groupCategory)) {
145                        groupCategory = CardEmulationManager.CATEGORY_OTHER;
146                    }
147                    currentGroup = mCategoryToGroup.get(groupCategory);
148                    if (currentGroup != null) {
149                        if (!CardEmulationManager.CATEGORY_OTHER.equals(groupCategory)) {
150                            Log.e(TAG, "Not allowing multiple aid-groups in the " +
151                                    groupCategory + " category");
152                            currentGroup = null;
153                        }
154                    } else {
155                        currentGroup = new AidGroup(groupCategory, groupDescription);
156                    }
157                } else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) &&
158                        currentGroup != null) {
159                    if (currentGroup.aids.size() > 0) {
160                        if (!mCategoryToGroup.containsKey(currentGroup.category)) {
161                            mAidGroups.add(currentGroup);
162                            mCategoryToGroup.put(currentGroup.category, currentGroup);
163                        }
164                    } else {
165                        Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs");
166                    }
167                    currentGroup = null;
168                } else if (eventType == XmlPullParser.START_TAG && "aid-filter".equals(tagName) &&
169                        currentGroup != null) {
170                    final TypedArray a = res.obtainAttributes(attrs,
171                            com.android.internal.R.styleable.AidFilter);
172                    String aid = a.getString(com.android.internal.R.styleable.AidFilter_name);
173                    if (isValidAid(aid) && !currentGroup.aids.contains(aid)) {
174                        currentGroup.aids.add(aid);
175                        mAids.add(aid);
176                    } else {
177                        Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
178                    }
179                }
180            }
181        } catch (NameNotFoundException e) {
182            throw new XmlPullParserException("Unable to create context for: " + si.packageName);
183        } finally {
184            if (parser != null) parser.close();
185        }
186    }
187
188    public ComponentName getComponent() {
189        return new ComponentName(mService.serviceInfo.packageName,
190                mService.serviceInfo.name);
191    }
192
193    public ArrayList<String> getAids() {
194        return mAids;
195    }
196
197    public ArrayList<AidGroup> getAidGroups() {
198        return mAidGroups;
199    }
200
201    public boolean hasCategory(String category) {
202        return mCategoryToGroup.containsKey(category);
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    static boolean isValidAid(String aid) {
214        if (aid == null)
215            return false;
216
217        int aidLength = aid.length();
218        if (aidLength == 0 || (aidLength % 2) != 0) {
219            Log.e(TAG, "AID " + aid + " is not correctly formatted.");
220            return false;
221        }
222        return true;
223    }
224
225    @Override
226    public String toString() {
227        StringBuilder out = new StringBuilder("ApduService: ");
228        out.append(getComponent());
229        out.append(", description: " + mDescription);
230        out.append(", AID Groups: ");
231        for (AidGroup aidGroup : mAidGroups) {
232            out.append(aidGroup.toString());
233        }
234        return out.toString();
235    }
236
237    @Override
238    public boolean equals(Object o) {
239        if (this == o) return true;
240        if (!(o instanceof ApduServiceInfo)) return false;
241        ApduServiceInfo thatService = (ApduServiceInfo) o;
242
243        return thatService.getComponent().equals(this.getComponent());
244    }
245
246    @Override
247    public int hashCode() {
248        return getComponent().hashCode();
249    }
250
251
252    @Override
253    public int describeContents() {
254        return 0;
255    }
256
257    @Override
258    public void writeToParcel(Parcel dest, int flags) {
259        mService.writeToParcel(dest, flags);
260        dest.writeString(mDescription);
261        dest.writeInt(mAidGroups.size());
262        if (mAidGroups.size() > 0) {
263            dest.writeTypedList(mAidGroups);
264        }
265    };
266
267    public static final Parcelable.Creator<ApduServiceInfo> CREATOR =
268            new Parcelable.Creator<ApduServiceInfo>() {
269        @Override
270        public ApduServiceInfo createFromParcel(Parcel source) {
271            ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
272            String description = source.readString();
273            ArrayList<AidGroup> aidGroups = new ArrayList<AidGroup>();
274            int numGroups = source.readInt();
275            if (numGroups > 0) {
276                source.readTypedList(aidGroups, AidGroup.CREATOR);
277            }
278            return new ApduServiceInfo(info, description, aidGroups);
279        }
280
281        @Override
282        public ApduServiceInfo[] newArray(int size) {
283            return new ApduServiceInfo[size];
284        }
285    };
286
287    public static class AidGroup implements Parcelable {
288        final ArrayList<String> aids;
289        final String category;
290        final String description;
291
292        AidGroup(ArrayList<String> aids, String category, String description) {
293            this.aids = aids;
294            this.category = category;
295            this.description = description;
296        }
297
298        AidGroup(String category, String description) {
299            this.aids = new ArrayList<String>();
300            this.category = category;
301            this.description = description;
302        }
303
304        public String getCategory() {
305            return category;
306        }
307
308        public ArrayList<String> getAids() {
309            return aids;
310        }
311
312        @Override
313        public String toString() {
314            StringBuilder out = new StringBuilder("Category: " + category +
315                      ", description: " + description + ", AIDs:");
316            for (String aid : aids) {
317                out.append(aid);
318                out.append(", ");
319            }
320            return out.toString();
321        }
322
323        @Override
324        public int describeContents() {
325            return 0;
326        }
327
328        @Override
329        public void writeToParcel(Parcel dest, int flags) {
330            dest.writeString(category);
331            dest.writeString(description);
332            dest.writeInt(aids.size());
333            if (aids.size() > 0) {
334                dest.writeStringList(aids);
335            }
336        }
337
338        public static final Parcelable.Creator<ApduServiceInfo.AidGroup> CREATOR =
339                new Parcelable.Creator<ApduServiceInfo.AidGroup>() {
340
341            @Override
342            public AidGroup createFromParcel(Parcel source) {
343                String category = source.readString();
344                String description = source.readString();
345                int listSize = source.readInt();
346                ArrayList<String> aidList = new ArrayList<String>();
347                if (listSize > 0) {
348                    source.readStringList(aidList);
349                }
350                return new AidGroup(aidList, category, description);
351            }
352
353            @Override
354            public AidGroup[] newArray(int size) {
355                return new AidGroup[size];
356            }
357        };
358    }
359}
360