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