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                 } else if (eventType == XmlPullParser.START_TAG &&
    257                         tagName.equals("aid-suffix-filter") && currentGroup != null) {
    258                     final TypedArray a = res.obtainAttributes(attrs,
    259                             com.android.internal.R.styleable.AidFilter);
    260                     String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
    261                             toUpperCase();
    262                     // Add wildcard char to indicate suffix
    263                     aid = aid.concat("#");
    264                     if (CardEmulation.isValidAid(aid) && !currentGroup.aids.contains(aid)) {
    265                         currentGroup.aids.add(aid);
    266                     } else {
    267                         Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
    268                     }
    269                     a.recycle();
    270                 }
    271             }
    272         } catch (NameNotFoundException e) {
    273             throw new XmlPullParserException("Unable to create context for: " + si.packageName);
    274         } finally {
    275             if (parser != null) parser.close();
    276         }
    277         // Set uid
    278         mUid = si.applicationInfo.uid;
    279     }
    280 
    281     public ComponentName getComponent() {
    282         return new ComponentName(mService.serviceInfo.packageName,
    283                 mService.serviceInfo.name);
    284     }
    285 
    286     /**
    287      * Returns a consolidated list of AIDs from the AID groups
    288      * registered by this service. Note that if a service has both
    289      * a static (manifest-based) AID group for a category and a dynamic
    290      * AID group, only the dynamically registered AIDs will be returned
    291      * for that category.
    292      * @return List of AIDs registered by the service
    293      */
    294     public List<String> getAids() {
    295         final ArrayList<String> aids = new ArrayList<String>();
    296         for (AidGroup group : getAidGroups()) {
    297             aids.addAll(group.aids);
    298         }
    299         return aids;
    300     }
    301 
    302     public List<String> getPrefixAids() {
    303         final ArrayList<String> prefixAids = new ArrayList<String>();
    304         for (AidGroup group : getAidGroups()) {
    305             for (String aid : group.aids) {
    306                 if (aid.endsWith("*")) {
    307                     prefixAids.add(aid);
    308                 }
    309             }
    310         }
    311         return prefixAids;
    312     }
    313 
    314     public List<String> getSubsetAids() {
    315         final ArrayList<String> subsetAids = new ArrayList<String>();
    316         for (AidGroup group : getAidGroups()) {
    317             for (String aid : group.aids) {
    318                 if (aid.endsWith("#")) {
    319                     subsetAids.add(aid);
    320                 }
    321             }
    322         }
    323         return subsetAids;
    324     }
    325     /**
    326      * Returns the registered AID group for this category.
    327      */
    328     public AidGroup getDynamicAidGroupForCategory(String category) {
    329         return mDynamicAidGroups.get(category);
    330     }
    331 
    332     public boolean removeDynamicAidGroupForCategory(String category) {
    333         return (mDynamicAidGroups.remove(category) != null);
    334     }
    335 
    336     /**
    337      * Returns a consolidated list of AID groups
    338      * registered by this service. Note that if a service has both
    339      * a static (manifest-based) AID group for a category and a dynamic
    340      * AID group, only the dynamically registered AID group will be returned
    341      * for that category.
    342      * @return List of AIDs registered by the service
    343      */
    344     public ArrayList<AidGroup> getAidGroups() {
    345         final ArrayList<AidGroup> groups = new ArrayList<AidGroup>();
    346         for (Map.Entry<String, AidGroup> entry : mDynamicAidGroups.entrySet()) {
    347             groups.add(entry.getValue());
    348         }
    349         for (Map.Entry<String, AidGroup> entry : mStaticAidGroups.entrySet()) {
    350             if (!mDynamicAidGroups.containsKey(entry.getKey())) {
    351                 // Consolidate AID groups - don't return static ones
    352                 // if a dynamic group exists for the category.
    353                 groups.add(entry.getValue());
    354             }
    355         }
    356         return groups;
    357     }
    358 
    359     /**
    360      * Returns the category to which this service has attributed the AID that is passed in,
    361      * or null if we don't know this AID.
    362      */
    363     public String getCategoryForAid(String aid) {
    364         ArrayList<AidGroup> groups = getAidGroups();
    365         for (AidGroup group : groups) {
    366             if (group.aids.contains(aid.toUpperCase())) {
    367                 return group.category;
    368             }
    369         }
    370         return null;
    371     }
    372 
    373     public boolean hasCategory(String category) {
    374         return (mStaticAidGroups.containsKey(category) || mDynamicAidGroups.containsKey(category));
    375     }
    376 
    377     public boolean isOnHost() {
    378         return mOnHost;
    379     }
    380 
    381     public boolean requiresUnlock() {
    382         return mRequiresDeviceUnlock;
    383     }
    384 
    385     public String getDescription() {
    386         return mDescription;
    387     }
    388 
    389     public int getUid() {
    390         return mUid;
    391     }
    392 
    393     public void setOrReplaceDynamicAidGroup(AidGroup aidGroup) {
    394         mDynamicAidGroups.put(aidGroup.getCategory(), aidGroup);
    395     }
    396 
    397     public CharSequence loadLabel(PackageManager pm) {
    398         return mService.loadLabel(pm);
    399     }
    400 
    401     public CharSequence loadAppLabel(PackageManager pm) {
    402         try {
    403             return pm.getApplicationLabel(pm.getApplicationInfo(
    404                     mService.resolvePackageName, PackageManager.GET_META_DATA));
    405         } catch (PackageManager.NameNotFoundException e) {
    406             return null;
    407         }
    408     }
    409 
    410     public Drawable loadIcon(PackageManager pm) {
    411         return mService.loadIcon(pm);
    412     }
    413 
    414     public Drawable loadBanner(PackageManager pm) {
    415         Resources res;
    416         try {
    417             res = pm.getResourcesForApplication(mService.serviceInfo.packageName);
    418             Drawable banner = res.getDrawable(mBannerResourceId);
    419             return banner;
    420         } catch (NotFoundException e) {
    421             Log.e(TAG, "Could not load banner.");
    422             return null;
    423         } catch (NameNotFoundException e) {
    424             Log.e(TAG, "Could not load banner.");
    425             return null;
    426         }
    427     }
    428 
    429     public String getSettingsActivityName() { return mSettingsActivityName; }
    430 
    431     @Override
    432     public String toString() {
    433         StringBuilder out = new StringBuilder("ApduService: ");
    434         out.append(getComponent());
    435         out.append(", description: " + mDescription);
    436         out.append(", Static AID Groups: ");
    437         for (AidGroup aidGroup : mStaticAidGroups.values()) {
    438             out.append(aidGroup.toString());
    439         }
    440         out.append(", Dynamic AID Groups: ");
    441         for (AidGroup aidGroup : mDynamicAidGroups.values()) {
    442             out.append(aidGroup.toString());
    443         }
    444         return out.toString();
    445     }
    446 
    447     @Override
    448     public boolean equals(Object o) {
    449         if (this == o) return true;
    450         if (!(o instanceof ApduServiceInfo)) return false;
    451         ApduServiceInfo thatService = (ApduServiceInfo) o;
    452 
    453         return thatService.getComponent().equals(this.getComponent());
    454     }
    455 
    456     @Override
    457     public int hashCode() {
    458         return getComponent().hashCode();
    459     }
    460 
    461 
    462     @Override
    463     public int describeContents() {
    464         return 0;
    465     }
    466 
    467     @Override
    468     public void writeToParcel(Parcel dest, int flags) {
    469         mService.writeToParcel(dest, flags);
    470         dest.writeString(mDescription);
    471         dest.writeInt(mOnHost ? 1 : 0);
    472         dest.writeInt(mStaticAidGroups.size());
    473         if (mStaticAidGroups.size() > 0) {
    474             dest.writeTypedList(new ArrayList<AidGroup>(mStaticAidGroups.values()));
    475         }
    476         dest.writeInt(mDynamicAidGroups.size());
    477         if (mDynamicAidGroups.size() > 0) {
    478             dest.writeTypedList(new ArrayList<AidGroup>(mDynamicAidGroups.values()));
    479         }
    480         dest.writeInt(mRequiresDeviceUnlock ? 1 : 0);
    481         dest.writeInt(mBannerResourceId);
    482         dest.writeInt(mUid);
    483         dest.writeString(mSettingsActivityName);
    484     };
    485 
    486     public static final Parcelable.Creator<ApduServiceInfo> CREATOR =
    487             new Parcelable.Creator<ApduServiceInfo>() {
    488         @Override
    489         public ApduServiceInfo createFromParcel(Parcel source) {
    490             ResolveInfo info = ResolveInfo.CREATOR.createFromParcel(source);
    491             String description = source.readString();
    492             boolean onHost = source.readInt() != 0;
    493             ArrayList<AidGroup> staticAidGroups = new ArrayList<AidGroup>();
    494             int numStaticGroups = source.readInt();
    495             if (numStaticGroups > 0) {
    496                 source.readTypedList(staticAidGroups, AidGroup.CREATOR);
    497             }
    498             ArrayList<AidGroup> dynamicAidGroups = new ArrayList<AidGroup>();
    499             int numDynamicGroups = source.readInt();
    500             if (numDynamicGroups > 0) {
    501                 source.readTypedList(dynamicAidGroups, AidGroup.CREATOR);
    502             }
    503             boolean requiresUnlock = source.readInt() != 0;
    504             int bannerResource = source.readInt();
    505             int uid = source.readInt();
    506             String settingsActivityName = source.readString();
    507             return new ApduServiceInfo(info, onHost, description, staticAidGroups,
    508                     dynamicAidGroups, requiresUnlock, bannerResource, uid,
    509                     settingsActivityName);
    510         }
    511 
    512         @Override
    513         public ApduServiceInfo[] newArray(int size) {
    514             return new ApduServiceInfo[size];
    515         }
    516     };
    517 
    518     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    519         pw.println("    " + getComponent() +
    520                 " (Description: " + getDescription() + ")");
    521         pw.println("    Static AID groups:");
    522         for (AidGroup group : mStaticAidGroups.values()) {
    523             pw.println("        Category: " + group.category);
    524             for (String aid : group.aids) {
    525                 pw.println("            AID: " + aid);
    526             }
    527         }
    528         pw.println("    Dynamic AID groups:");
    529         for (AidGroup group : mDynamicAidGroups.values()) {
    530             pw.println("        Category: " + group.category);
    531             for (String aid : group.aids) {
    532                 pw.println("            AID: " + aid);
    533             }
    534         }
    535         pw.println("    Settings Activity: " + mSettingsActivityName);
    536     }
    537 }
    538