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