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