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