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