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.XmlPullParser;
20 import org.xmlpull.v1.XmlPullParserException;
21 import org.xmlpull.v1.XmlSerializer;
22 
23 import android.app.ActivityManager;
24 import android.content.BroadcastReceiver;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ResolveInfo;
31 import android.content.pm.ServiceInfo;
32 import android.content.pm.PackageManager.NameNotFoundException;
33 import android.nfc.cardemulation.AidGroup;
34 import android.nfc.cardemulation.ApduServiceInfo;
35 import android.nfc.cardemulation.CardEmulation;
36 import android.nfc.cardemulation.HostApduService;
37 import android.nfc.cardemulation.OffHostApduService;
38 import android.os.UserHandle;
39 import android.util.AtomicFile;
40 import android.util.Log;
41 import android.util.SparseArray;
42 import android.util.Xml;
43 
44 import com.android.internal.util.FastXmlSerializer;
45 import com.google.android.collect.Maps;
46 
47 import java.io.File;
48 import java.io.FileDescriptor;
49 import java.io.FileInputStream;
50 import java.io.FileOutputStream;
51 import java.io.IOException;
52 import java.io.PrintWriter;
53 import java.util.ArrayList;
54 import java.util.Collections;
55 import java.util.HashMap;
56 import java.util.Iterator;
57 import java.util.List;
58 import java.util.Map;
59 import java.util.concurrent.atomic.AtomicReference;
60 
61 /**
62  * This class is inspired by android.content.pm.RegisteredServicesCache
63  * That class was not re-used because it doesn't support dynamically
64  * registering additional properties, but generates everything from
65  * the manifest. Since we have some properties that are not in the manifest,
66  * it's less suited.
67  */
68 public class RegisteredServicesCache {
69     static final String XML_INDENT_OUTPUT_FEATURE = "http://xmlpull.org/v1/doc/features.html#indent-output";
70     static final String TAG = "RegisteredServicesCache";
71     static final boolean DEBUG = false;
72 
73     final Context mContext;
74     final AtomicReference<BroadcastReceiver> mReceiver;
75 
76     final Object mLock = new Object();
77     // All variables below synchronized on mLock
78 
79     // mUserServices holds the card emulation services that are running for each user
80     final SparseArray<UserServices> mUserServices = new SparseArray<UserServices>();
81     final Callback mCallback;
82     final AtomicFile mDynamicSettingsFile;
83 
84     public interface Callback {
onServicesUpdated(int userId, final List<ApduServiceInfo> services)85         void onServicesUpdated(int userId, final List<ApduServiceInfo> services);
86     };
87 
88     static class DynamicSettings {
89         public final int uid;
90         public final HashMap<String, AidGroup> aidGroups = Maps.newHashMap();
91         public String offHostSE;
92 
DynamicSettings(int uid)93         DynamicSettings(int uid) {
94             this.uid = uid;
95         }
96     };
97 
98     private static class UserServices {
99         /**
100          * All services that have registered
101          */
102         final HashMap<ComponentName, ApduServiceInfo> services =
103                 Maps.newHashMap(); // Re-built at run-time
104         final HashMap<ComponentName, DynamicSettings> dynamicSettings =
105                 Maps.newHashMap(); // In memory cache of dynamic settings
106     };
107 
findOrCreateUserLocked(int userId)108     private UserServices findOrCreateUserLocked(int userId) {
109         UserServices services = mUserServices.get(userId);
110         if (services == null) {
111             services = new UserServices();
112             mUserServices.put(userId, services);
113         }
114         return services;
115     }
116 
RegisteredServicesCache(Context context, Callback callback)117     public RegisteredServicesCache(Context context, Callback callback) {
118         mContext = context;
119         mCallback = callback;
120 
121         final BroadcastReceiver receiver = new BroadcastReceiver() {
122             @Override
123             public void onReceive(Context context, Intent intent) {
124                 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
125                 String action = intent.getAction();
126                 if (DEBUG) Log.d(TAG, "Intent action: " + action);
127                 if (uid != -1) {
128                     boolean replaced = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) &&
129                             (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
130                              Intent.ACTION_PACKAGE_REMOVED.equals(action));
131                     if (!replaced) {
132                         int currentUser = ActivityManager.getCurrentUser();
133                         if (currentUser == UserHandle.getUserId(uid)) {
134                             invalidateCache(UserHandle.getUserId(uid));
135                         } else {
136                             // Cache will automatically be updated on user switch
137                         }
138                     } else {
139                         if (DEBUG) Log.d(TAG, "Ignoring package intent due to package being replaced.");
140                     }
141                 }
142             }
143         };
144         mReceiver = new AtomicReference<BroadcastReceiver>(receiver);
145 
146         IntentFilter intentFilter = new IntentFilter();
147         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
148         intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
149         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
150         intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
151         intentFilter.addAction(Intent.ACTION_PACKAGE_FIRST_LAUNCH);
152         intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
153         intentFilter.addDataScheme("package");
154         mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, intentFilter, null, null);
155 
156         // Register for events related to sdcard operations
157         IntentFilter sdFilter = new IntentFilter();
158         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
159         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
160         mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, sdFilter, null, null);
161 
162         File dataDir = mContext.getFilesDir();
163         mDynamicSettingsFile = new AtomicFile(new File(dataDir, "dynamic_aids.xml"));
164     }
165 
initialize()166     void initialize() {
167         synchronized (mLock) {
168             readDynamicSettingsLocked();
169         }
170         invalidateCache(ActivityManager.getCurrentUser());
171     }
172 
dump(ArrayList<ApduServiceInfo> services)173     void dump(ArrayList<ApduServiceInfo> services) {
174         for (ApduServiceInfo service : services) {
175             if (DEBUG) Log.d(TAG, service.toString());
176         }
177     }
178 
containsServiceLocked(ArrayList<ApduServiceInfo> services, ComponentName serviceName)179     boolean containsServiceLocked(ArrayList<ApduServiceInfo> services, ComponentName serviceName) {
180         for (ApduServiceInfo service : services) {
181             if (service.getComponent().equals(serviceName)) return true;
182         }
183         return false;
184     }
185 
hasService(int userId, ComponentName service)186     public boolean hasService(int userId, ComponentName service) {
187         return getService(userId, service) != null;
188     }
189 
getService(int userId, ComponentName service)190     public ApduServiceInfo getService(int userId, ComponentName service) {
191         synchronized (mLock) {
192             UserServices userServices = findOrCreateUserLocked(userId);
193             return userServices.services.get(service);
194         }
195     }
196 
getServices(int userId)197     public List<ApduServiceInfo> getServices(int userId) {
198         final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
199         synchronized (mLock) {
200             UserServices userServices = findOrCreateUserLocked(userId);
201             services.addAll(userServices.services.values());
202         }
203         return services;
204     }
205 
getServicesForCategory(int userId, String category)206     public List<ApduServiceInfo> getServicesForCategory(int userId, String category) {
207         final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
208         synchronized (mLock) {
209             UserServices userServices = findOrCreateUserLocked(userId);
210             for (ApduServiceInfo service : userServices.services.values()) {
211                 if (service.hasCategory(category)) services.add(service);
212             }
213         }
214         return services;
215     }
216 
getInstalledServices(int userId)217     ArrayList<ApduServiceInfo> getInstalledServices(int userId) {
218         PackageManager pm;
219         try {
220             pm = mContext.createPackageContextAsUser("android", 0,
221                     new UserHandle(userId)).getPackageManager();
222         } catch (NameNotFoundException e) {
223             Log.e(TAG, "Could not create user package context");
224             return null;
225         }
226 
227         ArrayList<ApduServiceInfo> validServices = new ArrayList<ApduServiceInfo>();
228 
229         List<ResolveInfo> resolvedServices = new ArrayList<>(pm.queryIntentServicesAsUser(
230                 new Intent(HostApduService.SERVICE_INTERFACE),
231                 PackageManager.GET_META_DATA, userId));
232 
233         List<ResolveInfo> resolvedOffHostServices = pm.queryIntentServicesAsUser(
234                 new Intent(OffHostApduService.SERVICE_INTERFACE),
235                 PackageManager.GET_META_DATA, userId);
236         resolvedServices.addAll(resolvedOffHostServices);
237 
238         for (ResolveInfo resolvedService : resolvedServices) {
239             try {
240                 boolean onHost = !resolvedOffHostServices.contains(resolvedService);
241                 ServiceInfo si = resolvedService.serviceInfo;
242                 ComponentName componentName = new ComponentName(si.packageName, si.name);
243                 // Check if the package holds the NFC permission
244                 if (pm.checkPermission(android.Manifest.permission.NFC, si.packageName) !=
245                         PackageManager.PERMISSION_GRANTED) {
246                     Log.e(TAG, "Skipping application component " + componentName +
247                             ": it must request the permission " +
248                             android.Manifest.permission.NFC);
249                     continue;
250                 }
251                 if (!android.Manifest.permission.BIND_NFC_SERVICE.equals(
252                         si.permission)) {
253                     Log.e(TAG, "Skipping APDU service " + componentName +
254                             ": it does not require the permission " +
255                             android.Manifest.permission.BIND_NFC_SERVICE);
256                     continue;
257                 }
258                 ApduServiceInfo service = new ApduServiceInfo(pm, resolvedService, onHost);
259                 if (service != null) {
260                     validServices.add(service);
261                 }
262             } catch (XmlPullParserException e) {
263                 Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
264             } catch (IOException e) {
265                 Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
266             }
267         }
268 
269         return validServices;
270     }
271 
invalidateCache(int userId)272     public void invalidateCache(int userId) {
273         final ArrayList<ApduServiceInfo> validServices = getInstalledServices(userId);
274         if (validServices == null) {
275             return;
276         }
277         synchronized (mLock) {
278             UserServices userServices = findOrCreateUserLocked(userId);
279 
280             // Find removed services
281             Iterator<Map.Entry<ComponentName, ApduServiceInfo>> it =
282                     userServices.services.entrySet().iterator();
283             while (it.hasNext()) {
284                 Map.Entry<ComponentName, ApduServiceInfo> entry =
285                         (Map.Entry<ComponentName, ApduServiceInfo>) it.next();
286                 if (!containsServiceLocked(validServices, entry.getKey())) {
287                     Log.d(TAG, "Service removed: " + entry.getKey());
288                     it.remove();
289                 }
290             }
291             for (ApduServiceInfo service : validServices) {
292                 if (DEBUG) Log.d(TAG, "Adding service: " + service.getComponent() +
293                         " AIDs: " + service.getAids());
294                 userServices.services.put(service.getComponent(), service);
295             }
296 
297             // Apply dynamic settings mappings
298             ArrayList<ComponentName> toBeRemoved = new ArrayList<ComponentName>();
299             for (Map.Entry<ComponentName, DynamicSettings> entry :
300                     userServices.dynamicSettings.entrySet()) {
301                 // Verify component / uid match
302                 ComponentName component = entry.getKey();
303                 DynamicSettings dynamicSettings = entry.getValue();
304                 ApduServiceInfo serviceInfo = userServices.services.get(component);
305                 if (serviceInfo == null || (serviceInfo.getUid() != dynamicSettings.uid)) {
306                     toBeRemoved.add(component);
307                     continue;
308                 } else {
309                     for (AidGroup group : dynamicSettings.aidGroups.values()) {
310                         serviceInfo.setOrReplaceDynamicAidGroup(group);
311                     }
312                     if (dynamicSettings.offHostSE != null) {
313                         serviceInfo.setOffHostSecureElement(dynamicSettings.offHostSE);
314                     }
315                 }
316             }
317             if (toBeRemoved.size() > 0) {
318                 for (ComponentName component : toBeRemoved) {
319                     Log.d(TAG, "Removing dynamic AIDs registered by " + component);
320                     userServices.dynamicSettings.remove(component);
321                 }
322                 // Persist to filesystem
323                 writeDynamicSettingsLocked();
324             }
325         }
326         mCallback.onServicesUpdated(userId, Collections.unmodifiableList(validServices));
327         dump(validServices);
328     }
329 
readDynamicSettingsLocked()330     private void readDynamicSettingsLocked() {
331         FileInputStream fis = null;
332         try {
333             if (!mDynamicSettingsFile.getBaseFile().exists()) {
334                 Log.d(TAG, "Dynamic AIDs file does not exist.");
335                 return;
336             }
337             fis = mDynamicSettingsFile.openRead();
338             XmlPullParser parser = Xml.newPullParser();
339             parser.setInput(fis, null);
340             int eventType = parser.getEventType();
341             while (eventType != XmlPullParser.START_TAG &&
342                     eventType != XmlPullParser.END_DOCUMENT) {
343                 eventType = parser.next();
344             }
345             String tagName = parser.getName();
346             if ("services".equals(tagName)) {
347                 boolean inService = false;
348                 ComponentName currentComponent = null;
349                 int currentUid = -1;
350                 String currentOffHostSE = null;
351                 ArrayList<AidGroup> currentGroups = new ArrayList<AidGroup>();
352                 while (eventType != XmlPullParser.END_DOCUMENT) {
353                     tagName = parser.getName();
354                     if (eventType == XmlPullParser.START_TAG) {
355                         if ("service".equals(tagName) && parser.getDepth() == 2) {
356                             String compString = parser.getAttributeValue(null, "component");
357                             String uidString = parser.getAttributeValue(null, "uid");
358                             String offHostString = parser.getAttributeValue(null, "offHostSE");
359                             if (compString == null || uidString == null) {
360                                 Log.e(TAG, "Invalid service attributes");
361                             } else {
362                                 try {
363                                     currentUid = Integer.parseInt(uidString);
364                                     currentComponent = ComponentName.unflattenFromString(compString);
365                                     currentOffHostSE = offHostString;
366                                     inService = true;
367                                 } catch (NumberFormatException e) {
368                                     Log.e(TAG, "Could not parse service uid");
369                                 }
370                             }
371                         }
372                         if ("aid-group".equals(tagName) && parser.getDepth() == 3 && inService) {
373                             AidGroup group = AidGroup.createFromXml(parser);
374                             if (group != null) {
375                                 currentGroups.add(group);
376                             } else {
377                                 Log.e(TAG, "Could not parse AID group.");
378                             }
379                         }
380                     } else if (eventType == XmlPullParser.END_TAG) {
381                         if ("service".equals(tagName)) {
382                             // See if we have a valid service
383                             if (currentComponent != null && currentUid >= 0 &&
384                                     (currentGroups.size() > 0 || currentOffHostSE != null)) {
385                                 final int userId = UserHandle.getUserId(currentUid);
386                                 DynamicSettings dynSettings = new DynamicSettings(currentUid);
387                                 for (AidGroup group : currentGroups) {
388                                     dynSettings.aidGroups.put(group.getCategory(), group);
389                                 }
390                                 dynSettings.offHostSE = currentOffHostSE;
391                                 UserServices services = findOrCreateUserLocked(userId);
392                                 services.dynamicSettings.put(currentComponent, dynSettings);
393                             }
394                             currentUid = -1;
395                             currentComponent = null;
396                             currentGroups.clear();
397                             inService = false;
398                             currentOffHostSE = null;
399                         }
400                     }
401                     eventType = parser.next();
402                 };
403             }
404         } catch (Exception e) {
405             Log.e(TAG, "Could not parse dynamic AIDs file, trashing.");
406             mDynamicSettingsFile.delete();
407         } finally {
408             if (fis != null) {
409                 try {
410                     fis.close();
411                 } catch (IOException e) {
412                 }
413             }
414         }
415     }
416 
writeDynamicSettingsLocked()417     private boolean writeDynamicSettingsLocked() {
418         FileOutputStream fos = null;
419         try {
420             fos = mDynamicSettingsFile.startWrite();
421             XmlSerializer out = new FastXmlSerializer();
422             out.setOutput(fos, "utf-8");
423             out.startDocument(null, true);
424             out.setFeature(XML_INDENT_OUTPUT_FEATURE, true);
425             out.startTag(null, "services");
426             for (int i = 0; i < mUserServices.size(); i++) {
427                 final UserServices user = mUserServices.valueAt(i);
428                 for (Map.Entry<ComponentName, DynamicSettings> service : user.dynamicSettings.entrySet()) {
429                     out.startTag(null, "service");
430                     out.attribute(null, "component", service.getKey().flattenToString());
431                     out.attribute(null, "uid", Integer.toString(service.getValue().uid));
432                     if(service.getValue().offHostSE != null) {
433                         out.attribute(null, "offHostSE", service.getValue().offHostSE);
434                     }
435                     for (AidGroup group : service.getValue().aidGroups.values()) {
436                         group.writeAsXml(out);
437                     }
438                     out.endTag(null, "service");
439                 }
440             }
441             out.endTag(null, "services");
442             out.endDocument();
443             mDynamicSettingsFile.finishWrite(fos);
444             return true;
445         } catch (Exception e) {
446             Log.e(TAG, "Error writing dynamic AIDs", e);
447             if (fos != null) {
448                 mDynamicSettingsFile.failWrite(fos);
449             }
450             return false;
451         }
452     }
453 
setOffHostSecureElement(int userId, int uid, ComponentName componentName, String offHostSE)454     public boolean setOffHostSecureElement(int userId, int uid, ComponentName componentName,
455             String offHostSE) {
456         ArrayList<ApduServiceInfo> newServices = null;
457         synchronized (mLock) {
458             UserServices services = findOrCreateUserLocked(userId);
459             // Check if we can find this service
460             ApduServiceInfo serviceInfo = getService(userId, componentName);
461             if (serviceInfo == null) {
462                 Log.e(TAG, "Service " + componentName + " does not exist.");
463                 return false;
464             }
465             if (serviceInfo.getUid() != uid) {
466                 // This is probably a good indication something is wrong here.
467                 // Either newer service installed with different uid (but then
468                 // we should have known about it), or somebody calling us from
469                 // a different uid.
470                 Log.e(TAG, "UID mismatch.");
471                 return false;
472             }
473             if (offHostSE == null || serviceInfo.isOnHost()) {
474                 Log.e(TAG, "OffHostSE mismatch with Service type");
475                 return false;
476             }
477 
478             DynamicSettings dynSettings = services.dynamicSettings.get(componentName);
479             if (dynSettings == null) {
480                 dynSettings = new DynamicSettings(uid);
481             }
482             dynSettings.offHostSE = offHostSE;
483             boolean success = writeDynamicSettingsLocked();
484             if (!success) {
485                 Log.e(TAG, "Failed to persist AID group.");
486                 dynSettings.offHostSE = null;
487                 return false;
488             }
489 
490             serviceInfo.setOffHostSecureElement(offHostSE);
491             newServices = new ArrayList<ApduServiceInfo>(services.services.values());
492         }
493         // Make callback without the lock held
494         mCallback.onServicesUpdated(userId, newServices);
495         return true;
496     }
497 
unsetOffHostSecureElement(int userId, int uid, ComponentName componentName)498     public boolean unsetOffHostSecureElement(int userId, int uid, ComponentName componentName) {
499         ArrayList<ApduServiceInfo> newServices = null;
500         synchronized (mLock) {
501             UserServices services = findOrCreateUserLocked(userId);
502             // Check if we can find this service
503             ApduServiceInfo serviceInfo = getService(userId, componentName);
504             if (serviceInfo == null) {
505                 Log.e(TAG, "Service " + componentName + " does not exist.");
506                 return false;
507             }
508             if (serviceInfo.getUid() != uid) {
509                 // This is probably a good indication something is wrong here.
510                 // Either newer service installed with different uid (but then
511                 // we should have known about it), or somebody calling us from
512                 // a different uid.
513                 Log.e(TAG, "UID mismatch.");
514                 return false;
515             }
516             if (serviceInfo.isOnHost() || serviceInfo.getOffHostSecureElement() == null) {
517                 Log.e(TAG, "OffHostSE is not set");
518                 return false;
519             }
520 
521             DynamicSettings dynSettings = services.dynamicSettings.get(componentName);
522             String offHostSE = dynSettings.offHostSE;
523             dynSettings.offHostSE = null;
524             boolean success = writeDynamicSettingsLocked();
525             if (!success) {
526                 Log.e(TAG, "Failed to persist AID group.");
527                 dynSettings.offHostSE = offHostSE;
528                 return false;
529             }
530 
531             serviceInfo.unsetOffHostSecureElement();
532             newServices = new ArrayList<ApduServiceInfo>(services.services.values());
533         }
534         // Make callback without the lock held
535         mCallback.onServicesUpdated(userId, newServices);
536         return true;
537     }
538 
registerAidGroupForService(int userId, int uid, ComponentName componentName, AidGroup aidGroup)539     public boolean registerAidGroupForService(int userId, int uid,
540             ComponentName componentName, AidGroup aidGroup) {
541         ArrayList<ApduServiceInfo> newServices = null;
542         boolean success;
543         synchronized (mLock) {
544             UserServices services = findOrCreateUserLocked(userId);
545             // Check if we can find this service
546             ApduServiceInfo serviceInfo = getService(userId, componentName);
547             if (serviceInfo == null) {
548                 Log.e(TAG, "Service " + componentName + " does not exist.");
549                 return false;
550             }
551             if (serviceInfo.getUid() != uid) {
552                 // This is probably a good indication something is wrong here.
553                 // Either newer service installed with different uid (but then
554                 // we should have known about it), or somebody calling us from
555                 // a different uid.
556                 Log.e(TAG, "UID mismatch.");
557                 return false;
558             }
559             // Do another AID validation, since a caller could have thrown in a
560             // modified AidGroup object with invalid AIDs over Binder.
561             List<String> aids = aidGroup.getAids();
562             for (String aid : aids) {
563                 if (!CardEmulation.isValidAid(aid)) {
564                     Log.e(TAG, "AID " + aid + " is not a valid AID");
565                     return false;
566                 }
567             }
568             serviceInfo.setOrReplaceDynamicAidGroup(aidGroup);
569             DynamicSettings dynSettings = services.dynamicSettings.get(componentName);
570             if (dynSettings == null) {
571                 dynSettings = new DynamicSettings(uid);
572                 dynSettings.offHostSE = null;
573                 services.dynamicSettings.put(componentName, dynSettings);
574             }
575             dynSettings.aidGroups.put(aidGroup.getCategory(), aidGroup);
576             success = writeDynamicSettingsLocked();
577             if (success) {
578                 newServices =
579                     new ArrayList<ApduServiceInfo>(services.services.values());
580             } else {
581                 Log.e(TAG, "Failed to persist AID group.");
582                 // Undo registration
583                 dynSettings.aidGroups.remove(aidGroup.getCategory());
584             }
585         }
586         if (success) {
587             // Make callback without the lock held
588             mCallback.onServicesUpdated(userId, newServices);
589         }
590         return success;
591     }
592 
getAidGroupForService(int userId, int uid, ComponentName componentName, String category)593     public AidGroup getAidGroupForService(int userId, int uid, ComponentName componentName,
594             String category) {
595         ApduServiceInfo serviceInfo = getService(userId, componentName);
596         if (serviceInfo != null) {
597             if (serviceInfo.getUid() != uid) {
598                 Log.e(TAG, "UID mismatch");
599                 return null;
600             }
601             return serviceInfo.getDynamicAidGroupForCategory(category);
602         } else {
603             Log.e(TAG, "Could not find service " + componentName);
604             return null;
605         }
606     }
607 
removeAidGroupForService(int userId, int uid, ComponentName componentName, String category)608     public boolean removeAidGroupForService(int userId, int uid, ComponentName componentName,
609             String category) {
610         boolean success = false;
611         ArrayList<ApduServiceInfo> newServices = null;
612         synchronized (mLock) {
613             UserServices services = findOrCreateUserLocked(userId);
614             ApduServiceInfo serviceInfo = getService(userId, componentName);
615             if (serviceInfo != null) {
616                 if (serviceInfo.getUid() != uid) {
617                     // Calling from different uid
618                     Log.e(TAG, "UID mismatch");
619                     return false;
620                 }
621                 if (!serviceInfo.removeDynamicAidGroupForCategory(category)) {
622                     Log.e(TAG," Could not find dynamic AIDs for category " + category);
623                     return false;
624                 }
625                 // Remove from local cache
626                 DynamicSettings dynSettings = services.dynamicSettings.get(componentName);
627                 if (dynSettings != null) {
628                     AidGroup deletedGroup = dynSettings.aidGroups.remove(category);
629                     success = writeDynamicSettingsLocked();
630                     if (success) {
631                         newServices = new ArrayList<ApduServiceInfo>(services.services.values());
632                     } else {
633                         Log.e(TAG, "Could not persist deleted AID group.");
634                         dynSettings.aidGroups.put(category, deletedGroup);
635                         return false;
636                     }
637                 } else {
638                     Log.e(TAG, "Could not find aid group in local cache.");
639                 }
640             } else {
641                 Log.e(TAG, "Service " + componentName + " does not exist.");
642             }
643         }
644         if (success) {
645             mCallback.onServicesUpdated(userId, newServices);
646         }
647         return success;
648     }
649 
dump(FileDescriptor fd, PrintWriter pw, String[] args)650     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
651         pw.println("Registered HCE services for current user: ");
652         UserServices userServices = findOrCreateUserLocked(ActivityManager.getCurrentUser());
653         for (ApduServiceInfo service : userServices.services.values()) {
654             service.dump(fd, pw, args);
655             pw.println("");
656         }
657         pw.println("");
658     }
659 
660 }
661