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 com.android.nfc.cardemulation;
     18 
     19 import org.xmlpull.v1.XmlPullParserException;
     20 
     21 import android.app.ActivityManager;
     22 import android.content.BroadcastReceiver;
     23 import android.content.ComponentName;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.IntentFilter;
     27 import android.content.pm.PackageManager;
     28 import android.content.pm.ResolveInfo;
     29 import android.content.pm.ServiceInfo;
     30 import android.content.pm.PackageManager.NameNotFoundException;
     31 import android.nfc.cardemulation.ApduServiceInfo;
     32 import android.nfc.cardemulation.HostApduService;
     33 import android.nfc.cardemulation.OffHostApduService;
     34 import android.os.UserHandle;
     35 import android.util.Log;
     36 import android.util.SparseArray;
     37 
     38 import com.google.android.collect.Maps;
     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.Collections;
     45 import java.util.HashMap;
     46 import java.util.Iterator;
     47 import java.util.List;
     48 import java.util.Map;
     49 import java.util.concurrent.atomic.AtomicReference;
     50 
     51 public class RegisteredServicesCache {
     52     static final String TAG = "RegisteredServicesCache";
     53     static final boolean DEBUG = false;
     54 
     55     final Context mContext;
     56     final AtomicReference<BroadcastReceiver> mReceiver;
     57 
     58     final Object mLock = new Object();
     59     // All variables below synchronized on mLock
     60 
     61     // mUserServices holds the card emulation services that are running for each user
     62     final SparseArray<UserServices> mUserServices = new SparseArray<UserServices>();
     63     final Callback mCallback;
     64 
     65     public interface Callback {
     66         void onServicesUpdated(int userId, final List<ApduServiceInfo> services);
     67     };
     68 
     69     private static class UserServices {
     70         /**
     71          * All services that have registered
     72          */
     73         public final HashMap<ComponentName, ApduServiceInfo> services =
     74                 Maps.newHashMap(); // Re-built at run-time
     75     };
     76 
     77     private UserServices findOrCreateUserLocked(int userId) {
     78         UserServices services = mUserServices.get(userId);
     79         if (services == null) {
     80             services = new UserServices();
     81             mUserServices.put(userId, services);
     82         }
     83         return services;
     84     }
     85 
     86     public RegisteredServicesCache(Context context, Callback callback) {
     87         mContext = context;
     88         mCallback = callback;
     89 
     90         final BroadcastReceiver receiver = new BroadcastReceiver() {
     91             @Override
     92             public void onReceive(Context context, Intent intent) {
     93                 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
     94                 String action = intent.getAction();
     95                 if (DEBUG) Log.d(TAG, "Intent action: " + action);
     96                 if (uid != -1) {
     97                     boolean replaced = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) &&
     98                             (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
     99                              Intent.ACTION_PACKAGE_REMOVED.equals(action));
    100                     if (!replaced) {
    101                         int currentUser = ActivityManager.getCurrentUser();
    102                         if (currentUser == UserHandle.getUserId(uid)) {
    103                             invalidateCache(UserHandle.getUserId(uid));
    104                         } else {
    105                             // Cache will automatically be updated on user switch
    106                         }
    107                     } else {
    108                         if (DEBUG) Log.d(TAG, "Ignoring package intent due to package being replaced.");
    109                     }
    110                 }
    111             }
    112         };
    113         mReceiver = new AtomicReference<BroadcastReceiver>(receiver);
    114 
    115         IntentFilter intentFilter = new IntentFilter();
    116         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
    117         intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
    118         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
    119         intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
    120         intentFilter.addAction(Intent.ACTION_PACKAGE_FIRST_LAUNCH);
    121         intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
    122         intentFilter.addDataScheme("package");
    123         mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, intentFilter, null, null);
    124 
    125         // Register for events related to sdcard operations
    126         IntentFilter sdFilter = new IntentFilter();
    127         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
    128         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
    129         mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, sdFilter, null, null);
    130     }
    131 
    132     void dump(ArrayList<ApduServiceInfo> services) {
    133         for (ApduServiceInfo service : services) {
    134             if (DEBUG) Log.d(TAG, service.toString());
    135         }
    136     }
    137 
    138     boolean containsServiceLocked(ArrayList<ApduServiceInfo> services, ComponentName serviceName) {
    139         for (ApduServiceInfo service : services) {
    140             if (service.getComponent().equals(serviceName)) return true;
    141         }
    142         return false;
    143     }
    144 
    145     public boolean hasService(int userId, ComponentName service) {
    146         return getService(userId, service) != null;
    147     }
    148 
    149     public ApduServiceInfo getService(int userId, ComponentName service) {
    150         synchronized (mLock) {
    151             UserServices userServices = findOrCreateUserLocked(userId);
    152             return userServices.services.get(service);
    153         }
    154     }
    155 
    156     public List<ApduServiceInfo> getServices(int userId) {
    157         final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
    158         synchronized (mLock) {
    159             UserServices userServices = findOrCreateUserLocked(userId);
    160             services.addAll(userServices.services.values());
    161         }
    162         return services;
    163     }
    164 
    165     public List<ApduServiceInfo> getServicesForCategory(int userId, String category) {
    166         final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
    167         synchronized (mLock) {
    168             UserServices userServices = findOrCreateUserLocked(userId);
    169             for (ApduServiceInfo service : userServices.services.values()) {
    170                 if (service.hasCategory(category)) services.add(service);
    171             }
    172         }
    173         return services;
    174     }
    175 
    176     public void onNfcDisabled() {
    177 
    178     }
    179 
    180     public void onNfcEnabled() {
    181         invalidateCache(ActivityManager.getCurrentUser());
    182     }
    183 
    184     public void invalidateCache(int userId) {
    185         PackageManager pm;
    186         try {
    187             pm = mContext.createPackageContextAsUser("android", 0,
    188                     new UserHandle(userId)).getPackageManager();
    189         } catch (NameNotFoundException e) {
    190             Log.e(TAG, "Could not create user package context");
    191             return;
    192         }
    193 
    194         ArrayList<ApduServiceInfo> validServices = new ArrayList<ApduServiceInfo>();
    195 
    196         List<ResolveInfo> resolvedServices = pm.queryIntentServicesAsUser(
    197                 new Intent(HostApduService.SERVICE_INTERFACE),
    198                 PackageManager.GET_META_DATA, userId);
    199 
    200         List<ResolveInfo> resolvedOffHostServices = pm.queryIntentServicesAsUser(
    201                 new Intent(OffHostApduService.SERVICE_INTERFACE),
    202                 PackageManager.GET_META_DATA, userId);
    203         resolvedServices.addAll(resolvedOffHostServices);
    204 
    205         for (ResolveInfo resolvedService : resolvedServices) {
    206             try {
    207                 boolean onHost = !resolvedOffHostServices.contains(resolvedService);
    208                 ServiceInfo si = resolvedService.serviceInfo;
    209                 ComponentName componentName = new ComponentName(si.packageName, si.name);
    210                 // Check if the package holds the NFC permission
    211                 if (pm.checkPermission(android.Manifest.permission.NFC, si.packageName) !=
    212                         PackageManager.PERMISSION_GRANTED) {
    213                     Log.e(TAG, "Skipping APDU service " + componentName +
    214                             ": it does not require the permission " +
    215                             android.Manifest.permission.NFC);
    216                     continue;
    217                 }
    218                 if (!android.Manifest.permission.BIND_NFC_SERVICE.equals(
    219                         si.permission)) {
    220                     Log.e(TAG, "Skipping APDU service " + componentName +
    221                             ": it does not require the permission " +
    222                             android.Manifest.permission.BIND_NFC_SERVICE);
    223                     continue;
    224                 }
    225                 ApduServiceInfo service = new ApduServiceInfo(pm, resolvedService, onHost);
    226                 if (service != null) {
    227                     validServices.add(service);
    228                 }
    229             } catch (XmlPullParserException e) {
    230                 Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
    231             } catch (IOException e) {
    232                 Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
    233             }
    234         }
    235 
    236         synchronized (mLock) {
    237             UserServices userServices = findOrCreateUserLocked(userId);
    238 
    239             // Find removed services
    240             Iterator<Map.Entry<ComponentName, ApduServiceInfo>> it =
    241                     userServices.services.entrySet().iterator();
    242             while (it.hasNext()) {
    243                 Map.Entry<ComponentName, ApduServiceInfo> entry =
    244                         (Map.Entry<ComponentName, ApduServiceInfo>) it.next();
    245                 if (!containsServiceLocked(validServices, entry.getKey())) {
    246                     Log.d(TAG, "Service removed: " + entry.getKey());
    247                     it.remove();
    248                 }
    249             }
    250             for (ApduServiceInfo service : validServices) {
    251                 if (DEBUG) Log.d(TAG, "Adding service: " + service.getComponent() +
    252                         " AIDs: " + service.getAids());
    253                 userServices.services.put(service.getComponent(), service);
    254             }
    255         }
    256 
    257         mCallback.onServicesUpdated(userId, Collections.unmodifiableList(validServices));
    258         dump(validServices);
    259     }
    260 
    261     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    262         pw.println("Registered HCE services for current user: ");
    263         UserServices userServices = findOrCreateUserLocked(ActivityManager.getCurrentUser());
    264         for (ApduServiceInfo service : userServices.services.values()) {
    265             pw.println("    " + service.getComponent() +
    266                     " (Description: " + service.getDescription() + ")");
    267         }
    268         pw.println("");
    269     }
    270 }
    271