ApduServiceInfo.java revision 8abf680e007a378c2828fe43ed8d71131390ec2e
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.Context;
21import android.content.pm.PackageManager;
22import android.content.pm.ResolveInfo;
23import android.content.pm.ServiceInfo;
24import android.content.pm.PackageManager.NameNotFoundException;
25import android.content.res.Resources;
26import android.content.res.Resources.NotFoundException;
27import android.content.res.TypedArray;
28import android.content.res.XmlResourceParser;
29import android.graphics.drawable.Drawable;
30import android.os.Parcel;
31import android.os.Parcelable;
32import android.os.ResultReceiver;
33import android.util.AttributeSet;
34import android.util.Log;
35import android.util.Xml;
36
37import org.xmlpull.v1.XmlPullParser;
38import org.xmlpull.v1.XmlPullParserException;
39
40import java.io.FileDescriptor;
41import java.io.IOException;
42import java.io.PrintWriter;
43import java.util.ArrayList;
44import java.util.HashMap;
45import java.util.List;
46import java.util.Map;
47
48/**
49 * @hide
50 */
51public final class ApduServiceInfo implements Parcelable {
52    static final String TAG = "ApduServiceInfo";
53
54    /**
55     * The service that implements this
56     */
57    final ResolveInfo mService;
58
59    /**
60     * Description of the service
61     */
62    final String mDescription;
63
64    /**
65     * Whether this service represents AIDs running on the host CPU
66     */
67    final boolean mOnHost;
68
69    /**
70     * Mapping from category to static AID group
71     */
72    final HashMap<String, AidGroup> mStaticAidGroups;
73
74    /**
75     * Mapping from category to dynamic AID group
76     */
77    final HashMap<String, AidGroup> mDynamicAidGroups;
78
79    /**
80     * Whether this service should only be started when the device is unlocked.
81     */
82    final boolean mRequiresDeviceUnlock;
83
84    /**
85     * The id of the service banner specified in XML.
86     */
87    final int mBannerResourceId;
88
89    /**
90     * The uid of the package the service belongs to
91     */
92    final int mUid;
93
94    /**
95     * Whether this service has dynamic resources
96     */
97    final boolean mHasDynamicResources;
98
99    /**
100     * Settings Activity for this service
101     */
102    final String mSettingsActivityName;
103
104    /**
105     * @hide
106     */
107    public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
108            ArrayList<AidGroup> staticAidGroups, ArrayList<AidGroup> dynamicAidGroups,
109            boolean requiresUnlock, int bannerResource, int uid, boolean hasDynamicResources,
110            String settingsActivityName) {
111        this.mService = info;
112        this.mDescription = description;
113        this.mStaticAidGroups = new HashMap<String, AidGroup>();
114        this.mDynamicAidGroups = new HashMap<String, AidGroup>();
115        this.mOnHost = onHost;
116        this.mRequiresDeviceUnlock = requiresUnlock;
117        for (AidGroup aidGroup : staticAidGroups) {
118            this.mStaticAidGroups.put(aidGroup.category, aidGroup);
119        }
120        for (AidGroup aidGroup : dynamicAidGroups) {
121            this.mDynamicAidGroups.put(aidGroup.category, aidGroup);
122        }
123        this.mBannerResourceId = bannerResource;
124        this.mUid = uid;
125        this.mHasDynamicResources = hasDynamicResources;
126        this.mSettingsActivityName = settingsActivityName;
127    }
128
129    public ApduServiceInfo(PackageManager pm, ResolveInfo info, boolean onHost) throws
130            XmlPullParserException, IOException {
131        ServiceInfo si = info.serviceInfo;
132        XmlResourceParser parser = null;
133        try {
134            if (onHost) {
135                parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA);
136                if (parser == null) {
137                    throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
138                            " meta-data");
139                }
140            } else {
141                parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA);
142                if (parser == null) {
143                    throw new XmlPullParserException("No " + OffHostApduService.SERVICE_META_DATA +
144                            " meta-data");
145                }
146            }
147
148            int eventType = parser.getEventType();
149            while (eventType != XmlPullParser.START_TAG && eventType != XmlPullParser.END_DOCUMENT) {
150                eventType = parser.next();
151            }
152
153            String tagName = parser.getName();
154            if (onHost && !"host-apdu-service".equals(tagName)) {
155                throw new XmlPullParserException(
156                        "Meta-data does not start with <host-apdu-service> tag");
157            } else if (!onHost && !"offhost-apdu-service".equals(tagName)) {
158                throw new XmlPullParserException(
159                        "Meta-data does not start with <offhost-apdu-service> tag");
160            }
161
162            Resources res = pm.getResourcesForApplication(si.applicationInfo);
163            AttributeSet attrs = Xml.asAttributeSet(parser);
164            if (onHost) {
165                TypedArray sa = res.obtainAttributes(attrs,
166                        com.android.internal.R.styleable.HostApduService);
167                mService = info;
168                mDescription = sa.getString(
169                        com.android.internal.R.styleable.HostApduService_description);
170                mRequiresDeviceUnlock = sa.getBoolean(
171                        com.android.internal.R.styleable.HostApduService_requireDeviceUnlock,
172                        false);
173                mBannerResourceId = sa.getResourceId(
174                        com.android.internal.R.styleable.HostApduService_apduServiceBanner, -1);
175                mHasDynamicResources = sa.getBoolean(
176                        com.android.internal.R.styleable.HostApduService_dynamicResources, false);
177                mSettingsActivityName = sa.getString(
178                        com.android.internal.R.styleable.HostApduService_settingsActivity);
179                sa.recycle();
180            } else {
181                TypedArray sa = res.obtainAttributes(attrs,
182                        com.android.internal.R.styleable.OffHostApduService);
183                mService = info;
184                mDescription = sa.getString(
185                        com.android.internal.R.styleable.OffHostApduService_description);
186                mRequiresDeviceUnlock = false;
187                mBannerResourceId = sa.getResourceId(
188                        com.android.internal.R.styleable.OffHostApduService_apduServiceBanner, -1);
189                mHasDynamicResources = sa.getBoolean(
190                        com.android.internal.R.styleable.OffHostApduService_dynamicResources, false);
191                mSettingsActivityName = sa.getString(
192                        com.android.internal.R.styleable.HostApduService_settingsActivity);
193                sa.recycle();
194            }
195
196            mStaticAidGroups = new HashMap<String, AidGroup>();
197            mDynamicAidGroups = new HashMap<String, AidGroup>();
198            mOnHost = onHost;
199
200            final int depth = parser.getDepth();
201            AidGroup currentGroup = null;
202
203            // Parsed values for the current AID group
204            while (((eventType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
205                    && eventType != XmlPullParser.END_DOCUMENT) {
206                tagName = parser.getName();
207                if (eventType == XmlPullParser.START_TAG && "aid-group".equals(tagName) &&
208                        currentGroup == null) {
209                    final TypedArray groupAttrs = res.obtainAttributes(attrs,
210                            com.android.internal.R.styleable.AidGroup);
211                    // Get category of AID group
212                    String groupCategory = groupAttrs.getString(
213                            com.android.internal.R.styleable.AidGroup_category);
214                    String groupDescription = groupAttrs.getString(
215                            com.android.internal.R.styleable.AidGroup_description);
216                    if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) {
217                        groupCategory = CardEmulation.CATEGORY_OTHER;
218                    }
219                    currentGroup = mStaticAidGroups.get(groupCategory);
220                    if (currentGroup != null) {
221                        if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
222                            Log.e(TAG, "Not allowing multiple aid-groups in the " +
223                                    groupCategory + " category");
224                            currentGroup = null;
225                        }
226                    } else {
227                        currentGroup = new AidGroup(groupCategory, groupDescription);
228                    }
229                    groupAttrs.recycle();
230                } else if (eventType == XmlPullParser.END_TAG && "aid-group".equals(tagName) &&
231                        currentGroup != null) {
232                    if (currentGroup.aids.size() > 0) {
233                        if (!mStaticAidGroups.containsKey(currentGroup.category)) {
234                            mStaticAidGroups.put(currentGroup.category, currentGroup);
235                        }
236                    } else {
237                        Log.e(TAG, "Not adding <aid-group> with empty or invalid AIDs");
238                    }
239                    currentGroup = null;
240                } else if (eventType == XmlPullParser.START_TAG && "aid-filter".equals(tagName) &&
241                        currentGroup != null) {
242                    final TypedArray a = res.obtainAttributes(attrs,
243                            com.android.internal.R.styleable.AidFilter);
244                    String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
245                            toUpperCase();
246                    if (CardEmulation.isValidAid(aid) && !currentGroup.aids.contains(aid)) {
247                        currentGroup.aids.add(aid);
248                    } else {
249                        Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
250                    }
251                    a.recycle();
252                } else if (eventType == XmlPullParser.START_TAG &&
253                        "aid-prefix-filter".equals(tagName) && currentGroup != null) {
254                    final TypedArray a = res.obtainAttributes(attrs,
255                            com.android.internal.R.styleable.AidFilter);
256                    String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
257                            toUpperCase();
258                    // Add wildcard char to indicate prefix
259                    aid = aid.concat("*");
260                    if (CardEmulation.isValidAid(aid) && !currentGroup.aids.contains(aid)) {
261                        currentGroup.aids.add(aid);
262                    } else {
263                        Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
264                    }
265                    a.recycle();
266                }
267            }
268        } catch (NameNotFoundException e) {
269            throw new XmlPullParserException("Unable to create context for: " + si.packageName);
270        } finally {
271            if (parser != null) parser.close();
272        }
273        // Set uid
274        mUid = si.applicationInfo.uid;
275    }
276
277    public ComponentName getComponent() {
278        return new ComponentName(mService.serviceInfo.packageName,
279                mService.serviceInfo.name);
280    }
281
282    /**
283     * Returns a consolidated list of AIDs from the AID groups
284     * registered by this service. Note that if a service has both
285     * a static (manifest-based) AID group for a category and a dynamic
286     * AID group, only the dynamically registered AIDs will be returned
287     * for that category.
288     * @return List of AIDs registered by the service
289     */
290    public List<String> getAids() {
291        final ArrayList<String> aids = new ArrayList<String>();
292        for (AidGroup group : getAidGroups()) {
293            aids.addAll(group.aids);
294        }
295        return aids;
296    }
297
298    public List<String> getPrefixAids() {
299        final ArrayList<String> prefixAids = new ArrayList<String>();
300        for (AidGroup group : getAidGroups()) {
301            for (String aid : group.aids) {
302                if (aid.endsWith("*")) {
303                    prefixAids.add(aid);
304                }
305            }
306        }
307        return prefixAids;
308    }
309
310    /**
311     * Returns the registered AID group for this category.
312     */
313    public AidGroup getDynamicAidGroupForCategory(String category) {
314        return mDynamicAidGroups.get(category);
315    }
316
317    public boolean removeDynamicAidGroupForCategory(String category) {
318        return (mDynamicAidGroups.remove(category) != null);
319    }
320
321    /**
322     * Returns a consolidated list of AID groups
323     * registered by this service. Note that if a service has both
324     * a static (manifest-based) AID group for a category and a dynamic
325     * AID group, only the dynamically registered AID group will be returned
326     * for that category.
327     * @return List of AIDs registered by the service
328     */
329    public ArrayList<AidGroup> getAidGroups() {
330        final ArrayList<AidGroup> groups = new ArrayList<AidGroup>();
331        for (Map.Entry<String, AidGroup> entry : mDynamicAidGroups.entrySet()) {
332            groups.add(entry.getValue());
333        }
334        for (Map.Entry<String, AidGroup> entry : mStaticAidGroups.entrySet()) {
335            if (!mDynamicAidGroups.containsKey(entry.getKey())) {
336                // Consolidate AID groups - don't return static ones
337                // if a dynamic group exists for the category.
338                groups.add(entry.getValue());
339            }
340        }
341        return groups;
342    }
343
344    /**
345     * Returns the category to which this service has attributed the AID that is passed in,
346     * or null if we don't know this AID.
347     */
348    public String getCategoryForAid(String aid) {
349        ArrayList<AidGroup> groups = getAidGroups();
350        for (AidGroup group : groups) {
351            if (group.aids.contains(aid.toUpperCase())) {
352                return group.category;
353            }
354        }
355        return null;
356    }
357
358    public boolean hasCategory(String category) {
359        return (mStaticAidGroups.containsKey(category) || mDynamicAidGroups.containsKey(category));
360    }
361
362    public boolean isOnHost() {
363        return mOnHost;
364    }
365
366    public boolean requiresUnlock() {
367        return mRequiresDeviceUnlock;
368    }
369
370    public String getDescription() {
371        return mDescription;
372    }
373
374    public int getUid() {
375        return mUid;
376    }
377
378    public void setOrReplaceDynamicAidGroup(AidGroup aidGroup) {
379        mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
380    }
381
382    public CharSequence loadLabel(PackageManager pm) {
383        return mService.loadLabel(pm);
384    }
385
386    public CharSequence loadAppLabel(PackageManager pm) {
387        try {
388            return pm.getApplicationLabel(pm.getApplicationInfo(
389                    mService.resolvePackageName, PackageManager.GET_META_DATA));
390        } catch (PackageManager.NameNotFoundException e) {
391            return null;
392        }
393    }
394
395    public Drawable loadIcon(PackageManager pm) {
396        return mService.loadIcon(pm);
397    }
398
399    public Drawable loadBanner(PackageManager pm) {
400        Resources res;
401        try {
402            res = pm.getResourcesForApplication(mService.serviceInfo.packageName);
403            Drawable banner = res.getDrawable(mBannerResourceId);
404            return banner;
405        } catch (NotFoundException e) {
406            Log.e(TAG, "Could not load banner.");
407            return null;
408        } catch (NameNotFoundException e) {
409            Log.e(TAG, "Could not load banner.");
410            return null;
411        }
412    }
413    public boolean hasDynamicResources() {
414        return mHasDynamicResources;
415    }
416
417    public String getSettingsActivityName() { return mSettingsActivityName; }
418
419    @Override
420    public String toString() {
421        StringBuilder out = new StringBuilder("ApduService: ");
422        out.append(getComponent());
423        out.append(", description: " + mDescription);
424        out.append(", Static AID Groups: ");
425        for (AidGroup aidGroup : mStaticAidGroups.values()) {
426            out.append(aidGroup.toString());
427        }
428        out.append(", Dynamic AID Groups: ");
429        for (AidGroup aidGroup : mDynamicAidGroups.values()) {
430            out.append(aidGroup.toString());
431        }
432        return out.toString();
433    }
434
435    @Override
436    public boolean equals(Object o) {
437        if (this == o) return true;
438        if (!(o instanceof ApduServiceInfo)) return false;
439        ApduServiceInfo thatService = (ApduServiceInfo) o;
440
441        return thatService.getComponent().equals(this.getComponent());
442    }
443
444    @Override
445    public int hashCode() {
446        return getComponent().hashCode();
447    }
448
449
450    @Override
451    public int describeContents() {
452        return 0;
453    }
454
455    @Override
456    public void writeToParcel(Parcel dest, int flags) {
457        mService.writeToParcel(dest, flags);
458        dest.writeString(mDescription);
459        dest.writeInt(mOnHost ? 1 : 0);
460        dest.writeInt(mStaticAidGroups.size());
461        if (mStaticAidGroups.size() > 0) {
462            dest.writeTypedList(new ArrayList<AidGroup>(mStaticAidGroups.values()));
463        }
464        dest.writeInt(mDynamicAidGroups.size());
465        if (mDynamicAidGroups.size() > 0) {
466            dest.writeTypedList(new ArrayList<AidGroup>(mDynamicAidGroups.values()));
467        }
468        dest.writeInt(mRequiresDeviceUnlock ? 1 : 0);
469        dest.writeInt(mBannerResourceId);
470        dest.writeInt(mUid);
471        dest.writeInt(mHasDynamicResources ? 1 : 0);
472        dest.writeString(mSettingsActivityName);
473    };
474
475    public static final Parcelable.Creator<ApduServiceInfo> CREATOR =
476            new Parcelable.Creator<ApduServiceInfo>() {
477        @Override
478        public ApduServiceInfo createFromParcel(Parcel source) {
479            ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
480            String description = source.readString();
481            boolean onHost = source.readInt() != 0;
482            ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>();
483            int numStaticGroups = source.readInt();
484            if (numStaticGroups > 0) {
485                source.readTypedList(staticAidGroups, AidGroup.CREATOR);
486            }
487            ArrayList<AidGroup> dynamicAidGroups = new ArrayList<AidGroup>();
488            int numDynamicGroups = source.readInt();
489            if (numDynamicGroups > 0) {
490                source.readTypedList(dynamicAidGroups, AidGroup.CREATOR);
491            }
492            boolean requiresUnlock = source.readInt() != 0;
493            int bannerResource = source.readInt();
494            int uid = source.readInt();
495            boolean dynamicResources = source.readInt() != 0;
496            String settingsActivityName = source.readString();
497            return new ApduServiceInfo(info, onHost, description, staticAidGroups,
498                    dynamicAidGroups, requiresUnlock, bannerResource, uid, dynamicResources,
499                    settingsActivityName);
500        }
501
502        @Override
503        public ApduServiceInfo[] newArray(int size) {
504            return new ApduServiceInfo[size];
505        }
506    };
507
508    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
509        pw.println("    " + getComponent() +
510                " (Description: " + getDescription() + ")");
511        pw.println("    Static AID groups:");
512        for (AidGroup group : mStaticAidGroups.values()) {
513            pw.println("        Category: " + group.category);
514            for (String aid : group.aids) {
515                pw.println("            AID: " + aid);
516            }
517        }
518        pw.println("    Dynamic AID groups:");
519        for (AidGroup group : mDynamicAidGroups.values()) {
520            pw.println("        Category: " + group.category);
521            for (String aid : group.aids) {
522                pw.println("            AID: " + aid);
523            }
524        }
525        pw.println("    Settings Activity: " + mSettingsActivityName);
526    }
527}
528