19f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen/* 29f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * Copyright (C) 2013 The Android Open Source Project 39f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * 49f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * Licensed under the Apache License, Version 2.0 (the "License"); 59f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * you may not use this file except in compliance with the License. 69f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * You may obtain a copy of the License at 79f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * 89f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * http://www.apache.org/licenses/LICENSE-2.0 99f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * 109f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * Unless required by applicable law or agreed to in writing, software 119f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * distributed under the License is distributed on an "AS IS" BASIS, 129f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 139f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * See the License for the specific language governing permissions and 149f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * limitations under the License. 159f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen */ 169f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 179f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenpackage com.android.nfc.cardemulation; 189f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 19af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport org.xmlpull.v1.XmlPullParser; 209f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport org.xmlpull.v1.XmlPullParserException; 21af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport org.xmlpull.v1.XmlSerializer; 22341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen 239f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.app.ActivityManager; 249f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.BroadcastReceiver; 259f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.ComponentName; 269f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.Context; 279f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.Intent; 289f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.IntentFilter; 299f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.pm.PackageManager; 309f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.pm.ResolveInfo; 319f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.pm.ServiceInfo; 32a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenenimport android.content.pm.PackageManager.NameNotFoundException; 33af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport android.nfc.cardemulation.AidGroup; 34a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenenimport android.nfc.cardemulation.ApduServiceInfo; 35bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenenimport android.nfc.cardemulation.CardEmulation; 366493859b424f65af79e3e13835f7dfed38495c00Martijn Coenenimport android.nfc.cardemulation.HostApduService; 3789c893312d524f50c47b0d32071f3de8631197f3Martijn Coenenimport android.nfc.cardemulation.OffHostApduService; 389f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.os.UserHandle; 39af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport android.util.AtomicFile; 409f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.util.Log; 419f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.util.SparseArray; 42af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport android.util.Xml; 434358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen 44af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport com.android.internal.util.FastXmlSerializer; 459f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport com.google.android.collect.Maps; 469f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 47af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport java.io.File; 48341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenenimport java.io.FileDescriptor; 49af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport java.io.FileInputStream; 50af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport java.io.FileOutputStream; 519f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.io.IOException; 52341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenenimport java.io.PrintWriter; 539f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.ArrayList; 54451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenenimport java.util.Collections; 559f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.HashMap; 56d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenenimport java.util.Iterator; 579f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.List; 589f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.Map; 599f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.concurrent.atomic.AtomicReference; 609f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 61af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen/** 62af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen * This class is inspired by android.content.pm.RegisteredServicesCache 63af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen * That class was not re-used because it doesn't support dynamically 64af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen * registering additional properties, but generates everything from 65af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen * the manifest. Since we have some properties that are not in the manifest, 66af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen * it's less suited. 67af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen */ 686493859b424f65af79e3e13835f7dfed38495c00Martijn Coenenpublic class RegisteredServicesCache { 69af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen static final String XML_INDENT_OUTPUT_FEATURE = "http://xmlpull.org/v1/doc/features.html#indent-output"; 7078976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen static final String TAG = "RegisteredServicesCache"; 71b58a7494a893c957a8c04397eb28a4bf2fe1db54Martijn Coenen static final boolean DEBUG = false; 729f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 739f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen final Context mContext; 749f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen final AtomicReference<BroadcastReceiver> mReceiver; 759f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 769f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen final Object mLock = new Object(); 779f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen // All variables below synchronized on mLock 789f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 799f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen // mUserServices holds the card emulation services that are running for each user 809f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen final SparseArray<UserServices> mUserServices = new SparseArray<UserServices>(); 8178976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen final Callback mCallback; 82af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen final AtomicFile mDynamicAidsFile; 839f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 8478976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen public interface Callback { 85451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen void onServicesUpdated(int userId, final List<ApduServiceInfo> services); 86a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen }; 879f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 88af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen static class DynamicAids { 89af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen public final int uid; 90af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen public final HashMap<String, AidGroup> aidGroups = Maps.newHashMap(); 91af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen 92af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen DynamicAids(int uid) { 93af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen this.uid = uid; 94af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 95af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen }; 96af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen 979f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen private static class UserServices { 98a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen /** 99a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen * All services that have registered 100a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen */ 101af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen final HashMap<ComponentName, ApduServiceInfo> services = 102a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen Maps.newHashMap(); // Re-built at run-time 103af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen final HashMap<ComponentName, DynamicAids> dynamicAids = 104af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen Maps.newHashMap(); // In memory cache of dynamic AID store 1059f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen }; 1069f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 1079f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen private UserServices findOrCreateUserLocked(int userId) { 1089f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen UserServices services = mUserServices.get(userId); 1099f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen if (services == null) { 1109f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen services = new UserServices(); 1119f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen mUserServices.put(userId, services); 1129f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } 1139f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen return services; 1149f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } 1159f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 11678976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen public RegisteredServicesCache(Context context, Callback callback) { 1179f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen mContext = context; 11878976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen mCallback = callback; 1199f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 1209f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen final BroadcastReceiver receiver = new BroadcastReceiver() { 1219f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen @Override 122d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen public void onReceive(Context context, Intent intent) { 123d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); 124d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen String action = intent.getAction(); 125341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen if (DEBUG) Log.d(TAG, "Intent action: " + action); 126d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen if (uid != -1) { 127d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen boolean replaced = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) && 128d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen (Intent.ACTION_PACKAGE_ADDED.equals(action) || 129d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen Intent.ACTION_PACKAGE_REMOVED.equals(action)); 130d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen if (!replaced) { 13189c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen int currentUser = ActivityManager.getCurrentUser(); 13289c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen if (currentUser == UserHandle.getUserId(uid)) { 13389c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen invalidateCache(UserHandle.getUserId(uid)); 13489c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen } else { 13589c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen // Cache will automatically be updated on user switch 13689c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen } 137d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen } else { 138d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen if (DEBUG) Log.d(TAG, "Ignoring package intent due to package being replaced."); 139d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen } 140d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen } 1419f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } 1429f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen }; 1439f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen mReceiver = new AtomicReference<BroadcastReceiver>(receiver); 144341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen 145341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen IntentFilter intentFilter = new IntentFilter(); 146341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); 147341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); 148341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 149341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); 150341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen intentFilter.addAction(Intent.ACTION_PACKAGE_FIRST_LAUNCH); 151341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED); 152341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen intentFilter.addDataScheme("package"); 153341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, intentFilter, null, null); 154341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen 155341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen // Register for events related to sdcard operations 156341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen IntentFilter sdFilter = new IntentFilter(); 157341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); 158341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); 159341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, sdFilter, null, null); 160af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen 161af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen File dataDir = mContext.getFilesDir(); 162af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen mDynamicAidsFile = new AtomicFile(new File(dataDir, "dynamic_aids.xml")); 163af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 164af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen 165af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen void initialize() { 166af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen synchronized (mLock) { 167af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen readDynamicAidsLocked(); 168af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 169af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen invalidateCache(ActivityManager.getCurrentUser()); 1709f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } 1719f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 172a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen void dump(ArrayList<ApduServiceInfo> services) { 173a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen for (ApduServiceInfo service : services) { 174d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen if (DEBUG) Log.d(TAG, service.toString()); 1759f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } 1769f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } 1779f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 178a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen boolean containsServiceLocked(ArrayList<ApduServiceInfo> services, ComponentName serviceName) { 179a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen for (ApduServiceInfo service : services) { 180a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen if (service.getComponent().equals(serviceName)) return true; 181a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen } 182a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen return false; 183d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen } 184d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen 18578976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen public boolean hasService(int userId, ComponentName service) { 186451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen return getService(userId, service) != null; 187451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen } 188451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen 189451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen public ApduServiceInfo getService(int userId, ComponentName service) { 190a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen synchronized (mLock) { 191a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen UserServices userServices = findOrCreateUserLocked(userId); 192451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen return userServices.services.get(service); 193d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen } 194d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen } 195d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen 19678976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen public List<ApduServiceInfo> getServices(int userId) { 19778976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>(); 198a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen synchronized (mLock) { 199a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen UserServices userServices = findOrCreateUserLocked(userId); 20078976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen services.addAll(userServices.services.values()); 20175f63db568f953e935e62cb3046d167b881979c8Martijn Coenen } 20278976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen return services; 2039f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } 2049f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 20578976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen public List<ApduServiceInfo> getServicesForCategory(int userId, String category) { 20678976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>(); 207a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen synchronized (mLock) { 208a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen UserServices userServices = findOrCreateUserLocked(userId); 209a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen for (ApduServiceInfo service : userServices.services.values()) { 21078976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen if (service.hasCategory(category)) services.add(service); 211a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen } 212a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen } 21378976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen return services; 214a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen } 215a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen 216af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen ArrayList<ApduServiceInfo> getInstalledServices(int userId) { 2179f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen PackageManager pm; 2189f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen try { 2199f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen pm = mContext.createPackageContextAsUser("android", 0, 220717a8bdc43aa9328899df86cb523430466cf5baaMartijn Coenen new UserHandle(userId)).getPackageManager(); 2219f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } catch (NameNotFoundException e) { 2229f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen Log.e(TAG, "Could not create user package context"); 223af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen return null; 2249f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } 2259f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 226a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen ArrayList<ApduServiceInfo> validServices = new ArrayList<ApduServiceInfo>(); 2279f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 2286493859b424f65af79e3e13835f7dfed38495c00Martijn Coenen List<ResolveInfo> resolvedServices = pm.queryIntentServicesAsUser( 2296493859b424f65af79e3e13835f7dfed38495c00Martijn Coenen new Intent(HostApduService.SERVICE_INTERFACE), 23089c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen PackageManager.GET_META_DATA, userId); 23189c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen 23289c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen List<ResolveInfo> resolvedOffHostServices = pm.queryIntentServicesAsUser( 23389c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen new Intent(OffHostApduService.SERVICE_INTERFACE), 23489c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen PackageManager.GET_META_DATA, userId); 23589c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen resolvedServices.addAll(resolvedOffHostServices); 2369f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 2379f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen for (ResolveInfo resolvedService : resolvedServices) { 2389f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen try { 23989c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen boolean onHost = !resolvedOffHostServices.contains(resolvedService); 240a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen ServiceInfo si = resolvedService.serviceInfo; 241a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen ComponentName componentName = new ComponentName(si.packageName, si.name); 242dba891595bc0bbcb8f0810b4d014e935aa1955c5Martijn Coenen // Check if the package holds the NFC permission 243dba891595bc0bbcb8f0810b4d014e935aa1955c5Martijn Coenen if (pm.checkPermission(android.Manifest.permission.NFC, si.packageName) != 244dba891595bc0bbcb8f0810b4d014e935aa1955c5Martijn Coenen PackageManager.PERMISSION_GRANTED) { 2458a527f144e05da334f6add4bede8d77c1acb91a4William Roberts Log.e(TAG, "Skipping application component " + componentName + 2468a527f144e05da334f6add4bede8d77c1acb91a4William Roberts ": it must request the permission " + 247dba891595bc0bbcb8f0810b4d014e935aa1955c5Martijn Coenen android.Manifest.permission.NFC); 248dba891595bc0bbcb8f0810b4d014e935aa1955c5Martijn Coenen continue; 249dba891595bc0bbcb8f0810b4d014e935aa1955c5Martijn Coenen } 250a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen if (!android.Manifest.permission.BIND_NFC_SERVICE.equals( 251a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen si.permission)) { 252a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen Log.e(TAG, "Skipping APDU service " + componentName + 253a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen ": it does not require the permission " + 254a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen android.Manifest.permission.BIND_NFC_SERVICE); 255a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen continue; 256a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen } 25789c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen ApduServiceInfo service = new ApduServiceInfo(pm, resolvedService, onHost); 2589f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen if (service != null) { 2599f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen validServices.add(service); 2609f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } 2619f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } catch (XmlPullParserException e) { 2629f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e); 2639f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } catch (IOException e) { 2649f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e); 2659f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } 2669f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } 2679f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen 268af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen return validServices; 269af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 270af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen 271af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen public void invalidateCache(int userId) { 272af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen final ArrayList<ApduServiceInfo> validServices = getInstalledServices(userId); 273af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (validServices == null) { 274af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen return; 275af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 2769f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen synchronized (mLock) { 2779f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen UserServices userServices = findOrCreateUserLocked(userId); 278d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen 279d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen // Find removed services 280a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen Iterator<Map.Entry<ComponentName, ApduServiceInfo>> it = 281d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen userServices.services.entrySet().iterator(); 282d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen while (it.hasNext()) { 283a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen Map.Entry<ComponentName, ApduServiceInfo> entry = 284a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen (Map.Entry<ComponentName, ApduServiceInfo>) it.next(); 285d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen if (!containsServiceLocked(validServices, entry.getKey())) { 286d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen Log.d(TAG, "Service removed: " + entry.getKey()); 287d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen it.remove(); 288d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen } 289d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen } 290a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen for (ApduServiceInfo service : validServices) { 291451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen if (DEBUG) Log.d(TAG, "Adding service: " + service.getComponent() + 29278976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen " AIDs: " + service.getAids()); 293451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen userServices.services.put(service.getComponent(), service); 2949f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } 295af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen 296af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen // Apply dynamic AID mappings 297af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen ArrayList<ComponentName> toBeRemoved = new ArrayList<ComponentName>(); 298af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen for (Map.Entry<ComponentName, DynamicAids> entry : 299af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen userServices.dynamicAids.entrySet()) { 300af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen // Verify component / uid match 301af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen ComponentName component = entry.getKey(); 302af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen DynamicAids dynamicAids = entry.getValue(); 303af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen ApduServiceInfo serviceInfo = userServices.services.get(component); 304af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (serviceInfo == null || (serviceInfo.getUid() != dynamicAids.uid)) { 305af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen toBeRemoved.add(component); 306af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen continue; 307af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } else { 308af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen for (AidGroup group : dynamicAids.aidGroups.values()) { 309af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen serviceInfo.setOrReplaceDynamicAidGroup(group); 310af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 311af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 312af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 313af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen 314af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (toBeRemoved.size() > 0) { 315af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen for (ComponentName component : toBeRemoved) { 316af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen Log.d(TAG, "Removing dynamic AIDs registered by " + component); 317af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen userServices.dynamicAids.remove(component); 318af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 319af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen // Persist to filesystem 320af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen writeDynamicAidsLocked(); 321af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 322d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen } 32378976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen 324451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen mCallback.onServicesUpdated(userId, Collections.unmodifiableList(validServices)); 32578976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen dump(validServices); 3269f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen } 327341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen 328af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen private void readDynamicAidsLocked() { 329af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen FileInputStream fis = null; 330af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen try { 331af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (!mDynamicAidsFile.getBaseFile().exists()) { 332af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen Log.d(TAG, "Dynamic AIDs file does not exist."); 333af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen return; 334af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 335af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen fis = mDynamicAidsFile.openRead(); 336af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen XmlPullParser parser = Xml.newPullParser(); 337af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen parser.setInput(fis, null); 338af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen int eventType = parser.getEventType(); 339af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen while (eventType != XmlPullParser.START_TAG && 340af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen eventType != XmlPullParser.END_DOCUMENT) { 341af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen eventType = parser.next(); 342af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 343af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen String tagName = parser.getName(); 344af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if ("services".equals(tagName)) { 345af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen boolean inService = false; 346af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen ComponentName currentComponent = null; 347af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen int currentUid = -1; 348af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen ArrayList<AidGroup> currentGroups = new ArrayList<AidGroup>(); 349af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen while (eventType != XmlPullParser.END_DOCUMENT) { 350f12b6d0b08563211ad7c5903330f454bdd1970bbMartijn Coenen tagName = parser.getName(); 351af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (eventType == XmlPullParser.START_TAG) { 352af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if ("service".equals(tagName) && parser.getDepth() == 2) { 353af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen String compString = parser.getAttributeValue(null, "component"); 354af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen String uidString = parser.getAttributeValue(null, "uid"); 355af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (compString == null || uidString == null) { 356af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen Log.e(TAG, "Invalid service attributes"); 357af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } else { 358af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen try { 359af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen currentUid = Integer.parseInt(uidString); 360af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen currentComponent = ComponentName.unflattenFromString(compString); 361af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen inService = true; 362af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } catch (NumberFormatException e) { 363af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen Log.e(TAG, "Could not parse service uid"); 364af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 365af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 366af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 367af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if ("aid-group".equals(tagName) && parser.getDepth() == 3 && inService) { 368af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen AidGroup group = AidGroup.createFromXml(parser); 369af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (group != null) { 370af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen currentGroups.add(group); 371af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } else { 372af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen Log.e(TAG, "Could not parse AID group."); 373af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 374af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 375af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } else if (eventType == XmlPullParser.END_TAG) { 376af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if ("service".equals(tagName)) { 377af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen // See if we have a valid service 378af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (currentComponent != null && currentUid >= 0 && 379af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen currentGroups.size() > 0) { 380af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen final int userId = UserHandle.getUserId(currentUid); 381af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen DynamicAids dynAids = new DynamicAids(currentUid); 382af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen for (AidGroup group : currentGroups) { 383af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen dynAids.aidGroups.put(group.getCategory(), group); 384af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 385af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen UserServices services = findOrCreateUserLocked(userId); 386af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen services.dynamicAids.put(currentComponent, dynAids); 387af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 388af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen currentUid = -1; 389af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen currentComponent = null; 390af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen currentGroups.clear(); 391af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen inService = false; 392af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 393af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 394af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen eventType = parser.next(); 395af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen }; 396af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 397af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } catch (Exception e) { 398af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen Log.e(TAG, "Could not parse dynamic AIDs file, trashing."); 399af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen mDynamicAidsFile.delete(); 400af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } finally { 401af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (fis != null) { 402af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen try { 403af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen fis.close(); 404af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } catch (IOException e) { 405af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 406af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 407af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 408af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 409af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen 410af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen private boolean writeDynamicAidsLocked() { 411af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen FileOutputStream fos = null; 412af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen try { 413af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen fos = mDynamicAidsFile.startWrite(); 414af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen XmlSerializer out = new FastXmlSerializer(); 415af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen out.setOutput(fos, "utf-8"); 416af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen out.startDocument(null, true); 417af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen out.setFeature(XML_INDENT_OUTPUT_FEATURE, true); 418af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen out.startTag(null, "services"); 419af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen for (int i = 0; i < mUserServices.size(); i++) { 420af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen final UserServices user = mUserServices.valueAt(i); 421af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen for (Map.Entry<ComponentName, DynamicAids> service : user.dynamicAids.entrySet()) { 422af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen out.startTag(null, "service"); 423af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen out.attribute(null, "component", service.getKey().flattenToString()); 424af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen out.attribute(null, "uid", Integer.toString(service.getValue().uid)); 425af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen for (AidGroup group : service.getValue().aidGroups.values()) { 426af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen group.writeAsXml(out); 427af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 428af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen out.endTag(null, "service"); 429af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 430af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 431af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen out.endTag(null, "services"); 432af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen out.endDocument(); 433af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen mDynamicAidsFile.finishWrite(fos); 434af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen return true; 435af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } catch (Exception e) { 436af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen Log.e(TAG, "Error writing dynamic AIDs", e); 437af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (fos != null) { 438af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen mDynamicAidsFile.failWrite(fos); 439af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 440af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen return false; 441af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 442af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 443af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen 444af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen public boolean registerAidGroupForService(int userId, int uid, 445af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen ComponentName componentName, AidGroup aidGroup) { 446ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen ArrayList<ApduServiceInfo> newServices = null; 447ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen boolean success; 448af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen synchronized (mLock) { 449af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen UserServices services = findOrCreateUserLocked(userId); 450af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen // Check if we can find this service 451af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen ApduServiceInfo serviceInfo = getService(userId, componentName); 452af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (serviceInfo == null) { 453af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen Log.e(TAG, "Service " + componentName + " does not exist."); 454af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen return false; 455af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 456af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (serviceInfo.getUid() != uid) { 457af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen // This is probably a good indication something is wrong here. 458af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen // Either newer service installed with different uid (but then 459af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen // we should have known about it), or somebody calling us from 460af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen // a different uid. 461af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen Log.e(TAG, "UID mismatch."); 462af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen return false; 463af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 464bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen // Do another AID validation, since a caller could have thrown in a modified 465bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen // AidGroup object with invalid AIDs over Binder. 466bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen List<String> aids = aidGroup.getAids(); 467bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen for (String aid : aids) { 468bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen if (!CardEmulation.isValidAid(aid)) { 469bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen Log.e(TAG, "AID " + aid + " is not a valid AID"); 470bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen return false; 471bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen } 472bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen } 473af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen serviceInfo.setOrReplaceDynamicAidGroup(aidGroup); 474af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen DynamicAids dynAids = services.dynamicAids.get(componentName); 475af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (dynAids == null) { 476af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen dynAids = new DynamicAids(uid); 477af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen services.dynamicAids.put(componentName, dynAids); 478af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 479af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen dynAids.aidGroups.put(aidGroup.getCategory(), aidGroup); 480ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen success = writeDynamicAidsLocked(); 481ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen if (success) { 482ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen newServices = new ArrayList<ApduServiceInfo>(services.services.values()); 483af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } else { 484af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen Log.e(TAG, "Failed to persist AID group."); 485af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen // Undo registration 486af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen dynAids.aidGroups.remove(aidGroup.getCategory()); 487af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 488af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 489ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen if (success) { 490ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen // Make callback without the lock held 491ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen mCallback.onServicesUpdated(userId, newServices); 492ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen } 493ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen return success; 494af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 495af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen 496af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen public AidGroup getAidGroupForService(int userId, int uid, ComponentName componentName, 497af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen String category) { 498af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen ApduServiceInfo serviceInfo = getService(userId, componentName); 499af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (serviceInfo != null) { 500af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (serviceInfo.getUid() != uid) { 501af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen Log.e(TAG, "UID mismatch"); 502af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen return null; 503af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 504af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen return serviceInfo.getDynamicAidGroupForCategory(category); 505af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } else { 506af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen Log.e(TAG, "Could not find service " + componentName); 507af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen return null; 508af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 509af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 510af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen 511af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen public boolean removeAidGroupForService(int userId, int uid, ComponentName componentName, 512af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen String category) { 513ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen boolean success = false; 514ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen ArrayList<ApduServiceInfo> newServices = null; 515af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen synchronized (mLock) { 516af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen UserServices services = findOrCreateUserLocked(userId); 517af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen ApduServiceInfo serviceInfo = getService(userId, componentName); 518af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (serviceInfo != null) { 519af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (serviceInfo.getUid() != uid) { 520af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen // Calling from different uid 521af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen Log.e(TAG, "UID mismatch"); 522af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen return false; 523af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 524ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen if (!serviceInfo.removeDynamicAidGroupForCategory(category)) { 525ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen Log.e(TAG," Could not find dynamic AIDs for category " + category); 526ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen return false; 527ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen } 528af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen // Remove from local cache 529af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen DynamicAids dynAids = services.dynamicAids.get(componentName); 530af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen if (dynAids != null) { 531ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen AidGroup deletedGroup = dynAids.aidGroups.remove(category); 532ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen success = writeDynamicAidsLocked(); 533ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen if (success) { 534ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen newServices = new ArrayList<ApduServiceInfo>(services.services.values()); 535ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen } else { 536ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen Log.e(TAG, "Could not persist deleted AID group."); 537ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen dynAids.aidGroups.put(category, deletedGroup); 538ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen return false; 539af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 540ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen } else { 541ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen Log.e(TAG, "Could not find aid group in local cache."); 542af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 543af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } else { 544ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen Log.e(TAG, "Service " + componentName + " does not exist."); 545af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 546af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 547ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen if (success) { 548ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen mCallback.onServicesUpdated(userId, newServices); 549ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen } 550ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen return success; 551af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen } 552af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen 553341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 554341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen pw.println("Registered HCE services for current user: "); 555341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen UserServices userServices = findOrCreateUserLocked(ActivityManager.getCurrentUser()); 556341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen for (ApduServiceInfo service : userServices.services.values()) { 557af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen service.dump(fd, pw, args); 558af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen pw.println(""); 559341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen } 560341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen pw.println(""); 561341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen } 562af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen 563451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen} 564