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