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