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