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.annotation.TargetApi;
20 import android.annotation.FlaggedApi;
21 import android.app.ActivityManager;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.content.pm.PackageManager;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.content.pm.PackageManager.ResolveInfoFlags;
30 import android.content.pm.ResolveInfo;
31 import android.content.pm.ServiceInfo;
32 import android.nfc.cardemulation.AidGroup;
33 import android.nfc.cardemulation.ApduServiceInfo;
34 import android.nfc.cardemulation.CardEmulation;
35 import android.nfc.cardemulation.HostApduService;
36 import android.nfc.cardemulation.OffHostApduService;
37 import android.os.ParcelFileDescriptor;
38 import android.os.UserHandle;
39 import android.os.UserManager;
40 import android.sysprop.NfcProperties;
41 import android.text.TextUtils;
42 import android.util.AtomicFile;
43 import android.util.Log;
44 import android.util.Pair;
45 import android.util.SparseArray;
46 import android.util.Xml;
47 import android.util.proto.ProtoOutputStream;
48 
49 import androidx.annotation.VisibleForTesting;
50 
51 import com.android.internal.annotations.GuardedBy;
52 import com.android.internal.util.FastXmlSerializer;
53 import com.android.nfc.Utils;
54 
55 import org.xmlpull.v1.XmlPullParser;
56 import org.xmlpull.v1.XmlPullParserException;
57 import org.xmlpull.v1.XmlSerializer;
58 
59 import java.io.File;
60 import java.io.FileDescriptor;
61 import java.io.FileInputStream;
62 import java.io.FileNotFoundException;
63 import java.io.FileOutputStream;
64 import java.io.IOException;
65 import java.io.InputStream;
66 import java.io.OutputStream;
67 import java.io.PrintWriter;
68 import java.util.ArrayList;
69 import java.util.Collections;
70 import java.util.HashMap;
71 import java.util.Iterator;
72 import java.util.List;
73 import java.util.Map;
74 import java.util.concurrent.atomic.AtomicReference;
75 
76 /**
77  * This class is inspired by android.content.pm.RegisteredServicesCache
78  * That class was not re-used because it doesn't support dynamically
79  * registering additional properties, but generates everything from
80  * the manifest. Since we have some properties that are not in the manifest,
81  * it's less suited.
82  */
83 public class RegisteredServicesCache {
84     static final String XML_INDENT_OUTPUT_FEATURE = "http://xmlpull.org/v1/doc/features.html#indent-output";
85     static final String TAG = "RegisteredServicesCache";
86     static final String AID_XML_PATH = "dynamic_aids.xml";
87     static final String OTHER_STATUS_PATH = "other_status.xml";
88     static final String PACKAGE_DATA = "package";
89     static final boolean DEBUG = NfcProperties.debug_enabled().orElse(true);
90     private static final boolean VDBG = false; // turn on for local testing.
91 
92     final Context mContext;
93     final AtomicReference<BroadcastReceiver> mReceiver;
94 
95     final Object mLock = new Object();
96     // All variables below synchronized on mLock
97 
98     // mUserHandles holds the UserHandles of all the profiles that belong to current user
99     @GuardedBy("mLock")
100     List<UserHandle> mUserHandles;
101 
102     // mUserServices holds the card emulation services that are running for each user
103     final SparseArray<UserServices> mUserServices = new SparseArray<UserServices>();
104     final Callback mCallback;
105     final SettingsFile mDynamicSettingsFile;
106     final SettingsFile mOthersFile;
107     final ServiceParser mServiceParser;
108     final RoutingOptionManager mRoutingOptionManager;
109 
110     public interface Callback {
111         /**
112          * ServicesUpdated for specific userId.
113          */
onServicesUpdated(int userId, List<ApduServiceInfo> services, boolean validateInstalled)114         void onServicesUpdated(int userId, List<ApduServiceInfo> services,
115                 boolean validateInstalled);
116     };
117 
118     static class DynamicSettings {
119         public final int uid;
120         public final HashMap<String, AidGroup> aidGroups = new HashMap<>();
121         public String offHostSE;
122         public String shouldDefaultToObserveModeStr;
123 
DynamicSettings(int uid)124         DynamicSettings(int uid) {
125             this.uid = uid;
126         }
127     };
128 
129     static class OtherServiceStatus {
130         public final int uid;
131         public boolean checked;
132 
OtherServiceStatus(int uid, boolean checked)133         OtherServiceStatus(int uid, boolean checked) {
134             this.uid = uid;
135             this.checked = checked;
136         }
137     };
138 
139     @VisibleForTesting
140     static class UserServices {
141         /**
142          * All services that have registered
143          */
144         final HashMap<ComponentName, ApduServiceInfo> services =
145                 new HashMap<>(); // Re-built at run-time
146         final HashMap<ComponentName, DynamicSettings> dynamicSettings =
147                 new HashMap<>(); // In memory cache of dynamic settings
148         final HashMap<ComponentName, OtherServiceStatus> others =
149                 new HashMap<>();
150     };
151 
152     @VisibleForTesting
153     static class SettingsFile {
154         final AtomicFile mFile;
SettingsFile(Context context, String path)155         SettingsFile(Context context, String path) {
156             File dir = context.getFilesDir();
157             mFile = new AtomicFile(new File(dir, path));
158         }
159 
exists()160         boolean exists() {
161             return mFile.getBaseFile().exists();
162         }
163 
openRead()164         InputStream openRead() throws FileNotFoundException {
165             return mFile.openRead();
166         }
167 
delete()168         void delete() {
169             mFile.delete();
170         }
171 
startWrite()172         FileOutputStream startWrite() throws IOException {
173             return mFile.startWrite();
174         }
175 
finishWrite(FileOutputStream fileOutputStream)176         void finishWrite(FileOutputStream fileOutputStream) {
177             mFile.finishWrite(fileOutputStream);
178         }
179 
failWrite(FileOutputStream fileOutputStream)180         void failWrite(FileOutputStream fileOutputStream) {
181             mFile.failWrite(fileOutputStream);
182         }
183 
getBaseFile()184         File getBaseFile() {
185             return mFile.getBaseFile();
186         }
187     }
188 
189     @VisibleForTesting
190     interface ServiceParser {
parseApduService(PackageManager packageManager, ResolveInfo resolveInfo, boolean onHost)191         ApduServiceInfo parseApduService(PackageManager packageManager,
192                                          ResolveInfo resolveInfo,
193                                          boolean onHost) throws XmlPullParserException, IOException;
194     }
195 
196     private static class RealServiceParser implements ServiceParser {
197 
198         @Override
parseApduService(PackageManager packageManager, ResolveInfo resolveInfo, boolean onHost)199         public ApduServiceInfo parseApduService(PackageManager packageManager,
200                                                 ResolveInfo resolveInfo, boolean onHost)
201                 throws XmlPullParserException, IOException {
202             return new ApduServiceInfo(packageManager, resolveInfo, onHost);
203         }
204     }
205 
findOrCreateUserLocked(int userId)206     private UserServices findOrCreateUserLocked(int userId) {
207         UserServices services = mUserServices.get(userId);
208         if (services == null) {
209             services = new UserServices();
210             mUserServices.put(userId, services);
211         }
212         return services;
213     }
214 
getProfileParentId(Context context, int userId)215     private int getProfileParentId(Context context, int userId) {
216         UserManager um = context.getSystemService(UserManager.class);
217         UserHandle uh = um.getProfileParent(UserHandle.of(userId));
218         return uh == null ? userId : uh.getIdentifier();
219     }
220 
getProfileParentId(int userId)221     private int getProfileParentId(int userId) {
222         return getProfileParentId(mContext.createContextAsUser(
223                 UserHandle.of(userId), /*flags=*/0), userId);
224     }
225 
RegisteredServicesCache(Context context, Callback callback)226     public RegisteredServicesCache(Context context, Callback callback) {
227         this(context, callback, new SettingsFile(context, AID_XML_PATH),
228                 new SettingsFile(context, OTHER_STATUS_PATH), new RealServiceParser(),
229                 RoutingOptionManager.getInstance());
230     }
231 
232     @VisibleForTesting
RegisteredServicesCache(Context context, Callback callback, RoutingOptionManager routingOptionManager)233     RegisteredServicesCache(Context context, Callback callback,
234                                    RoutingOptionManager routingOptionManager) {
235         this(context, callback, new SettingsFile(context, AID_XML_PATH),
236                 new SettingsFile(context, OTHER_STATUS_PATH), new RealServiceParser(),
237                 routingOptionManager);
238     }
239 
240     @VisibleForTesting
RegisteredServicesCache(Context context, Callback callback, SettingsFile dynamicSettings, SettingsFile otherSettings, ServiceParser serviceParser, RoutingOptionManager routingOptionManager)241     RegisteredServicesCache(Context context, Callback callback, SettingsFile dynamicSettings,
242                             SettingsFile otherSettings, ServiceParser serviceParser,
243                             RoutingOptionManager routingOptionManager) {
244         mContext = context;
245         mCallback = callback;
246         mServiceParser = serviceParser;
247         mRoutingOptionManager = routingOptionManager;
248 
249         synchronized (mLock) {
250             refreshUserProfilesLocked(false);
251         }
252 
253         final BroadcastReceiver receiver = new BroadcastReceiver() {
254             @Override
255             public void onReceive(Context context, Intent intent) {
256                 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
257                 String action = intent.getAction();
258                 if (VDBG) Log.d(TAG, "Intent action: " + action);
259 
260                 if (mRoutingOptionManager.isRoutingTableOverrided()) {
261                     if (DEBUG) Log.d(TAG, "Routing table overrided. Skip invalidateCache()");
262                 }
263                 if (uid == -1) return;
264                 int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
265                 int currentUser = ActivityManager.getCurrentUser();
266                 if (currentUser != getProfileParentId(context, userId)) {
267                     // Cache will automatically be updated on user switch
268                     if (VDBG) Log.d(TAG, "Ignoring package change intent from non-current user");
269                     return;
270                 }
271                 // If app not removed, check if the app has any valid CE services.
272                 if (!Intent.ACTION_PACKAGE_REMOVED.equals(action) &&
273                         !Utils.hasCeServicesWithValidPermissions(mContext, intent, userId)) {
274                     if (VDBG) Log.d(TAG, "Ignoring package change intent from non-CE app");
275                     return;
276                 }
277                 boolean replaced = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)
278                         && (Intent.ACTION_PACKAGE_ADDED.equals(action)
279                         || Intent.ACTION_PACKAGE_REMOVED.equals(action));
280                 if (!replaced) {
281                     if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
282                         invalidateCache(UserHandle.
283                                 getUserHandleForUid(uid).getIdentifier(), true);
284                     } else {
285                         invalidateCache(UserHandle.
286                                 getUserHandleForUid(uid).getIdentifier(), false);
287                     }
288                 } else {
289                     if (DEBUG) Log.d(TAG, "Ignoring package intent due to package being replaced.");
290                 }
291             }
292         };
293         mReceiver = new AtomicReference<BroadcastReceiver>(receiver);
294 
295         IntentFilter intentFilter = new IntentFilter();
296         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
297         intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
298         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
299         intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
300         intentFilter.addAction(Intent.ACTION_PACKAGE_FIRST_LAUNCH);
301         intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
302         intentFilter.addDataScheme(PACKAGE_DATA);
303         mContext.registerReceiverForAllUsers(mReceiver.get(), intentFilter, null, null);
304 
305         // Register for events related to sdcard operations
306         IntentFilter sdFilter = new IntentFilter();
307         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
308         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
309         mContext.registerReceiverForAllUsers(mReceiver.get(), sdFilter, null, null);
310 
311         mDynamicSettingsFile = dynamicSettings;
312         mOthersFile = otherSettings;
313     }
314 
initialize()315     void initialize() {
316         synchronized (mLock) {
317             readDynamicSettingsLocked();
318             readOthersLocked();
319             for (UserHandle uh : mUserHandles) {
320                 invalidateCache(uh.getIdentifier(), false);
321             }
322         }
323     }
324 
onUserSwitched()325     public void onUserSwitched() {
326         synchronized (mLock) {
327             refreshUserProfilesLocked(false);
328         }
329     }
330 
onManagedProfileChanged()331     public void onManagedProfileChanged() {
332         synchronized (mLock) {
333             refreshUserProfilesLocked(true);
334         }
335     }
336 
refreshUserProfilesLocked(boolean invalidateCache)337     private void refreshUserProfilesLocked(boolean invalidateCache) {
338         UserManager um = mContext.createContextAsUser(
339                 UserHandle.of(ActivityManager.getCurrentUser()), /*flags=*/0)
340                 .getSystemService(UserManager.class);
341         mUserHandles = um.getEnabledProfiles();
342         List<UserHandle> removeUserHandles = new ArrayList<UserHandle>();
343 
344         for (UserHandle uh : mUserHandles) {
345             if (um.isQuietModeEnabled(uh)) {
346                 removeUserHandles.add(uh);
347             }
348         }
349         mUserHandles.removeAll(removeUserHandles);
350         if (invalidateCache) {
351             for (UserHandle uh : mUserHandles) {
352                 invalidateCache(uh.getIdentifier(), false);
353             }
354         }
355     }
356 
dump(List<ApduServiceInfo> services)357     void dump(List<ApduServiceInfo> services) {
358         for (ApduServiceInfo service : services) {
359             if (DEBUG) Log.d(TAG, service.toString());
360         }
361     }
362 
dump(ArrayList<ComponentName> services)363     void dump(ArrayList<ComponentName> services) {
364         for (ComponentName service : services) {
365             if (DEBUG) Log.d(TAG, service.toString());
366         }
367     }
368 
containsServiceLocked(ArrayList<ApduServiceInfo> services, ComponentName serviceName)369     boolean containsServiceLocked(ArrayList<ApduServiceInfo> services, ComponentName serviceName) {
370         for (ApduServiceInfo service : services) {
371             if (service.getComponent().equals(serviceName)) return true;
372         }
373         return false;
374     }
375 
hasService(int userId, ComponentName service)376     public boolean hasService(int userId, ComponentName service) {
377         return getService(userId, service) != null;
378     }
379 
getService(int userId, ComponentName service)380     public ApduServiceInfo getService(int userId, ComponentName service) {
381         synchronized (mLock) {
382             UserServices userServices = findOrCreateUserLocked(userId);
383             return userServices.services.get(service);
384         }
385     }
386 
getServices(int userId)387     public List<ApduServiceInfo> getServices(int userId) {
388         final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
389         synchronized (mLock) {
390             UserServices userServices = findOrCreateUserLocked(userId);
391             services.addAll(userServices.services.values());
392         }
393         return services;
394     }
395 
getServicesForCategory(int userId, String category)396     public List<ApduServiceInfo> getServicesForCategory(int userId, String category) {
397         final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
398         synchronized (mLock) {
399             UserServices userServices = findOrCreateUserLocked(userId);
400             for (ApduServiceInfo service : userServices.services.values()) {
401                 if (service.hasCategory(category)) services.add(service);
402             }
403         }
404         return services;
405     }
406 
getInstalledServices(int userId)407     ArrayList<ApduServiceInfo> getInstalledServices(int userId) {
408         PackageManager pm;
409         try {
410             pm = mContext.createPackageContextAsUser("android", 0,
411                     UserHandle.of(userId)).getPackageManager();
412         } catch (NameNotFoundException e) {
413             Log.e(TAG, "Could not create user package context");
414             return null;
415         }
416 
417         ArrayList<ApduServiceInfo> validServices = new ArrayList<ApduServiceInfo>();
418 
419         List<ResolveInfo> resolvedServices = new ArrayList<>(pm.queryIntentServicesAsUser(
420                 new Intent(HostApduService.SERVICE_INTERFACE),
421                 ResolveInfoFlags.of(PackageManager.GET_META_DATA), UserHandle.of(userId)));
422 
423         List<ResolveInfo> resolvedOffHostServices = pm.queryIntentServicesAsUser(
424                 new Intent(OffHostApduService.SERVICE_INTERFACE),
425                 ResolveInfoFlags.of(PackageManager.GET_META_DATA), UserHandle.of(userId));
426         resolvedServices.addAll(resolvedOffHostServices);
427         for (ResolveInfo resolvedService : resolvedServices) {
428             try {
429                 boolean onHost = !resolvedOffHostServices.contains(resolvedService);
430                 ServiceInfo si = resolvedService.serviceInfo;
431                 ComponentName componentName = new ComponentName(si.packageName, si.name);
432                 // Check if the package exported the service in manifest
433                 if (!si.exported) {
434                     Log.e(TAG, "Skipping application component " + componentName +
435                             ": it must configured as exported");
436                     continue;
437                 }
438                 // Check if the package holds the NFC permission
439                 if (pm.checkPermission(android.Manifest.permission.NFC, si.packageName) !=
440                         PackageManager.PERMISSION_GRANTED) {
441                     Log.e(TAG, "Skipping application component " + componentName +
442                             ": it must request the permission " +
443                             android.Manifest.permission.NFC);
444                     continue;
445                 }
446                 if (!android.Manifest.permission.BIND_NFC_SERVICE.equals(
447                         si.permission)) {
448                     Log.e(TAG, "Skipping APDU service " + componentName +
449                             ": it does not require the permission " +
450                             android.Manifest.permission.BIND_NFC_SERVICE);
451                     continue;
452                 }
453                 ApduServiceInfo service = mServiceParser.parseApduService(pm, resolvedService,
454                         onHost);
455                 if (service != null) {
456                     validServices.add(service);
457                 }
458             } catch (XmlPullParserException e) {
459                 Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
460             } catch (IOException e) {
461                 Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
462             }
463         }
464 
465         return validServices;
466     }
467 
468     /**
469      * invalidateCache for specific userId.
470      */
invalidateCache(int userId, boolean validateInstalled)471     public void invalidateCache(int userId, boolean validateInstalled) {
472         final ArrayList<ApduServiceInfo> validServices = getInstalledServices(userId);
473         if (validServices == null) {
474             return;
475         }
476         ArrayList<ApduServiceInfo> toBeAdded = new ArrayList<>();
477         ArrayList<ApduServiceInfo> toBeRemoved = new ArrayList<>();
478         synchronized (mLock) {
479             UserServices userServices = findOrCreateUserLocked(userId);
480 
481             // Find removed services
482             Iterator<Map.Entry<ComponentName, ApduServiceInfo>> it =
483                     userServices.services.entrySet().iterator();
484             while (it.hasNext()) {
485                 Map.Entry<ComponentName, ApduServiceInfo> entry =
486                         (Map.Entry<ComponentName, ApduServiceInfo>) it.next();
487                 if (!containsServiceLocked(validServices, entry.getKey())) {
488                     toBeRemoved.add(entry.getValue());
489                     it.remove();
490                 }
491             }
492             for (ApduServiceInfo service : validServices) {
493                 toBeAdded.add(service);
494                 userServices.services.put(service.getComponent(), service);
495             }
496 
497             // Apply dynamic settings mappings
498             ArrayList<ComponentName> toBeRemovedComponent = new ArrayList<ComponentName>();
499             for (Map.Entry<ComponentName, DynamicSettings> entry :
500                     userServices.dynamicSettings.entrySet()) {
501                 // Verify component / uid match
502                 ComponentName component = entry.getKey();
503                 DynamicSettings dynamicSettings = entry.getValue();
504                 ApduServiceInfo serviceInfo = userServices.services.get(component);
505                 if (serviceInfo == null || (serviceInfo.getUid() != dynamicSettings.uid)) {
506                     toBeRemovedComponent.add(component);
507                     continue;
508                 } else {
509                     for (AidGroup group : dynamicSettings.aidGroups.values()) {
510                         serviceInfo.setDynamicAidGroup(group);
511                     }
512                     if (dynamicSettings.offHostSE != null) {
513                         serviceInfo.setOffHostSecureElement(dynamicSettings.offHostSE);
514                     }
515                     if (dynamicSettings.shouldDefaultToObserveModeStr != null) {
516                         serviceInfo.setShouldDefaultToObserveMode(
517                                 convertValueToBoolean(dynamicSettings.shouldDefaultToObserveModeStr,
518                                 false));
519                     }
520                 }
521             }
522             if (toBeRemoved.size() > 0) {
523                 for (ComponentName component : toBeRemovedComponent) {
524                     Log.d(TAG, "Removing dynamic AIDs registered by " + component);
525                     userServices.dynamicSettings.remove(component);
526                 }
527                 // Persist to filesystem
528                 writeDynamicSettingsLocked();
529             }
530         }
531 
532         List<ApduServiceInfo> otherServices = getServicesForCategory(userId,
533                 CardEmulation.CATEGORY_OTHER);
534         invalidateOther(userId, otherServices);
535 
536         mCallback.onServicesUpdated(userId, Collections.unmodifiableList(validServices),
537                 validateInstalled);
538         if (VDBG) {
539             Log.i(TAG, "Services => ");
540             dump(validServices);
541         } else {
542             // dump only new services added or removed
543             Log.i(TAG, "New Services => ");
544             dump(toBeAdded);
545             Log.i(TAG, "Removed Services => ");
546             dump(toBeRemoved);
547         }
548     }
549 
invalidateOther(int userId, List<ApduServiceInfo> validOtherServices)550     private void invalidateOther(int userId, List<ApduServiceInfo> validOtherServices) {
551         Log.d(TAG, "invalidate : " + userId);
552         ArrayList<ComponentName> toBeAdded = new ArrayList<>();
553         ArrayList<ComponentName> toBeRemoved = new ArrayList<>();
554         // remove services
555         synchronized (mLock) {
556             UserServices userServices = findOrCreateUserLocked(userId);
557             boolean needToWrite = false;
558             Iterator<Map.Entry<ComponentName, OtherServiceStatus>> it =
559                     userServices.others.entrySet().iterator();
560 
561             while (it.hasNext()) {
562                 Map.Entry<ComponentName, OtherServiceStatus> entry = it.next();
563                 if (!containsServiceLocked((ArrayList<ApduServiceInfo>) validOtherServices,
564                         entry.getKey())) {
565                     toBeRemoved.add(entry.getKey());
566                     needToWrite = true;
567                     it.remove();
568                 }
569             }
570 
571             UserManager um = mContext.createContextAsUser(
572                             UserHandle.of(ActivityManager.getCurrentUser()), /*flags=*/0)
573                     .getSystemService(UserManager.class);
574             boolean isManagedProfile = um.isManagedProfile(userId);
575             Log.i(TAG, "current user: " + ActivityManager.getCurrentUser() +
576                     ", is managed profile : " + isManagedProfile );
577             boolean isChecked = !(isManagedProfile);
578             // TODO: b/313040065 temperatory set isChecked always true due to there's no UI in AOSP
579             isChecked = true;
580 
581             for (ApduServiceInfo service : validOtherServices) {
582                 if (VDBG) {
583                     Log.d(TAG, "update valid otherService: " + service.getComponent()
584                             + " AIDs: " + service.getAids());
585                 }
586                 if (!service.hasCategory(CardEmulation.CATEGORY_OTHER)) {
587                     Log.e(TAG, "service does not have other category");
588                     continue;
589                 }
590 
591                 ComponentName component = service.getComponent();
592                 OtherServiceStatus status = userServices.others.get(component);
593 
594                 if (status == null) {
595                     toBeAdded.add(service.getComponent());
596                     status = new OtherServiceStatus(service.getUid(), isChecked);
597                     needToWrite = true;
598                 }
599                 service.setCategoryOtherServiceEnabled(status.checked);
600                 userServices.others.put(component, status);
601             }
602 
603             if (needToWrite) {
604                 writeOthersLocked();
605             }
606         }
607         if (VDBG) {
608             Log.i(TAG, "Other Services => ");
609             dump(validOtherServices);
610         } else {
611             // dump only new services added or removed
612             Log.i(TAG, "New Other Services => ");
613             dump(toBeAdded);
614             Log.i(TAG, "Removed Other Services => ");
615             dump(toBeRemoved);
616         }
617     }
618 
convertValueToBoolean(CharSequence value, boolean defaultValue)619     private static final boolean convertValueToBoolean(CharSequence value, boolean defaultValue) {
620        boolean result = false;
621 
622         if (TextUtils.isEmpty(value)) {
623             return defaultValue;
624         }
625 
626         if (value.equals("1")
627         ||  value.equals("true")
628         ||  value.equals("TRUE"))
629             result = true;
630 
631         return result;
632     }
633 
634     @VisibleForTesting
635     static Map<Integer, List<Pair<ComponentName, DynamicSettings>>>
readDynamicSettingsFromFile(SettingsFile settingsFile)636     readDynamicSettingsFromFile(SettingsFile settingsFile) {
637         Log.d(TAG, "Reading dynamic AIDs.");
638         Map<Integer, List<Pair<ComponentName, DynamicSettings>>> readSettingsMap =
639                 new HashMap<>();
640         InputStream fis = null;
641         try {
642             if (!settingsFile.exists()) {
643                 Log.d(TAG, "Dynamic AIDs file does not exist.");
644                 return new HashMap<>();
645             }
646             fis = settingsFile.openRead();
647             XmlPullParser parser = Xml.newPullParser();
648             parser.setInput(fis, null);
649             int eventType = parser.getEventType();
650             while (eventType != XmlPullParser.START_TAG &&
651                     eventType != XmlPullParser.END_DOCUMENT) {
652                 eventType = parser.next();
653             }
654             String tagName = parser.getName();
655             if ("services".equals(tagName)) {
656                 boolean inService = false;
657                 ComponentName currentComponent = null;
658                 int currentUid = -1;
659                 String currentOffHostSE = null;
660                 String shouldDefaultToObserveModeStr = null;
661                 ArrayList<AidGroup> currentGroups = new ArrayList<AidGroup>();
662                 while (eventType != XmlPullParser.END_DOCUMENT) {
663                     tagName = parser.getName();
664                     if (eventType == XmlPullParser.START_TAG) {
665                         if ("service".equals(tagName) && parser.getDepth() == 2) {
666                             String compString = parser.getAttributeValue(null, "component");
667                             String uidString = parser.getAttributeValue(null, "uid");
668                             String offHostString
669                                     = parser.getAttributeValue(null, "offHostSE");
670                             shouldDefaultToObserveModeStr =
671                                     parser.getAttributeValue(null, "shouldDefaultToObserveMode");
672                             if (compString == null || uidString == null) {
673                                 Log.e(TAG, "Invalid service attributes");
674                             } else {
675                                 try {
676                                     currentUid = Integer.parseInt(uidString);
677                                     currentComponent = ComponentName
678                                             .unflattenFromString(compString);
679                                     currentOffHostSE = offHostString;
680                                     inService = true;
681                                 } catch (NumberFormatException e) {
682                                     Log.e(TAG, "Could not parse service uid");
683                                 }
684                             }
685                         }
686                         if ("aid-group".equals(tagName) && parser.getDepth() == 3 && inService) {
687                             AidGroup group = AidGroup.createFromXml(parser);
688                             if (group != null) {
689                                 currentGroups.add(group);
690                             } else {
691                                 Log.e(TAG, "Could not parse AID group.");
692                             }
693                         }
694                     } else if (eventType == XmlPullParser.END_TAG) {
695                         if ("service".equals(tagName)) {
696                             // See if we have a valid service
697                             if (currentComponent != null && currentUid >= 0 &&
698                                     (currentGroups.size() > 0 || currentOffHostSE != null)) {
699                                 final int userId = UserHandle.
700                                         getUserHandleForUid(currentUid).getIdentifier();
701                                 Log.d(TAG, " ## user id - " + userId);
702                                 DynamicSettings dynSettings = new DynamicSettings(currentUid);
703                                 for (AidGroup group : currentGroups) {
704                                     dynSettings.aidGroups.put(group.getCategory(), group);
705                                 }
706                                 dynSettings.offHostSE = currentOffHostSE;
707                                 dynSettings.shouldDefaultToObserveModeStr
708                                         = shouldDefaultToObserveModeStr;
709                                 if (!readSettingsMap.containsKey(userId)) {
710                                     readSettingsMap.put(userId, new ArrayList<>());
711                                 }
712                                 readSettingsMap.get(userId)
713                                         .add(new Pair<>(currentComponent, dynSettings));
714                             }
715                             currentUid = -1;
716                             currentComponent = null;
717                             currentGroups.clear();
718                             inService = false;
719                             currentOffHostSE = null;
720                         }
721                     }
722                     eventType = parser.next();
723                 };
724             }
725         } catch (Exception e) {
726             Log.e(TAG, "Could not parse dynamic AIDs file, trashing.", e);
727             settingsFile.delete();
728         } finally {
729             if (fis != null) {
730                 try {
731                     fis.close();
732                 } catch (IOException e) {
733                 }
734             }
735         }
736         return readSettingsMap;
737     }
738 
readDynamicSettingsLocked()739     private void readDynamicSettingsLocked() {
740         Map<Integer, List<Pair<ComponentName, DynamicSettings>>> readSettingsMap
741                 = readDynamicSettingsFromFile(mDynamicSettingsFile);
742         for(Integer userId: readSettingsMap.keySet()) {
743             UserServices services = findOrCreateUserLocked(userId);
744             List<Pair<ComponentName, DynamicSettings>> componentNameDynamicServiceStatusPairs
745                     = readSettingsMap.get(userId);
746             int pairsSize = componentNameDynamicServiceStatusPairs.size();
747             for(int i = 0; i < pairsSize; i++) {
748                 Pair<ComponentName, DynamicSettings> pair
749                         = componentNameDynamicServiceStatusPairs.get(i);
750                 services.dynamicSettings.put(pair.first, pair.second);
751             }
752         }
753     }
754 
755     @VisibleForTesting
756     static Map<Integer, List<Pair<ComponentName, OtherServiceStatus>>>
readOtherFromFile(SettingsFile settingsFile)757     readOtherFromFile(SettingsFile settingsFile) {
758         Map<Integer, List<Pair<ComponentName, OtherServiceStatus>>> readSettingsMap =
759                 new HashMap<>();
760         Log.d(TAG, "read others locked");
761 
762         InputStream fis = null;
763         try {
764             if (!settingsFile.exists()) {
765                 Log.d(TAG, "Other settings file does not exist.");
766                 return new HashMap<>();
767             }
768             fis = settingsFile.openRead();
769             XmlPullParser parser = Xml.newPullParser();
770             parser.setInput(fis, null);
771             int eventType = parser.getEventType();
772             while (eventType != XmlPullParser.START_TAG &&
773                     eventType != XmlPullParser.END_DOCUMENT) {
774                 eventType = parser.next();
775             }
776             String tagName = parser.getName();
777             if ("services".equals(tagName)) {
778                 boolean checked = false;
779                 ComponentName currentComponent = null;
780                 int currentUid = -1;
781 
782                 while (eventType != XmlPullParser.END_DOCUMENT) {
783                     tagName = parser.getName();
784                     if (eventType == XmlPullParser.START_TAG) {
785                         if ("service".equals(tagName) && parser.getDepth() == 2) {
786                             String compString = parser.getAttributeValue(null, "component");
787                             String uidString = parser.getAttributeValue(null, "uid");
788                             String checkedString = parser.getAttributeValue(null, "checked");
789                             if (compString == null || uidString == null || checkedString == null) {
790                                 Log.e(TAG, "Invalid service attributes");
791                             } else {
792                                 try {
793                                     currentUid = Integer.parseInt(uidString);
794                                     currentComponent =
795                                             ComponentName.unflattenFromString(compString);
796                                     checked = checkedString.equals("true") ? true : false;
797                                 } catch (NumberFormatException e) {
798                                     Log.e(TAG, "Could not parse service uid");
799                                 }
800                             }
801                         }
802                     } else if (eventType == XmlPullParser.END_TAG) {
803                         if ("service".equals(tagName)) {
804                             // See if we have a valid service
805                             if (currentComponent != null && currentUid >= 0) {
806                                 Log.d(TAG, " end of service tag");
807                                 final int userId =
808                                         UserHandle.getUserHandleForUid(currentUid).getIdentifier();
809                                 OtherServiceStatus status =
810                                         new OtherServiceStatus(currentUid, checked);
811                                 Log.d(TAG, " ## user id - " + userId);
812                                 if (!readSettingsMap.containsKey(userId)) {
813                                     readSettingsMap.put(userId, new ArrayList<>());
814                                 }
815                                 readSettingsMap.get(userId)
816                                         .add(new Pair<>(currentComponent, status));
817                             }
818                             currentUid = -1;
819                             currentComponent = null;
820                             checked = false;
821                         }
822                     }
823                     eventType = parser.next();
824                 }
825             }
826         } catch (Exception e) {
827             Log.e(TAG, "Could not parse others AIDs file, trashing.", e);
828             settingsFile.delete();
829         } finally {
830             if (fis != null) {
831                 try {
832                     fis.close();
833                 } catch (IOException e) {
834                     // It is safe to ignore I/O exceptions when closing FileInputStream
835                 }
836             }
837         }
838         return readSettingsMap;
839     }
840 
readOthersLocked()841     private void readOthersLocked() {
842         Map<Integer, List<Pair<ComponentName, OtherServiceStatus>>> readSettingsMap
843                 = readOtherFromFile(mOthersFile);
844         for(Integer userId: readSettingsMap.keySet()) {
845             UserServices services = findOrCreateUserLocked(userId);
846             List<Pair<ComponentName, OtherServiceStatus>> componentNameOtherServiceStatusPairs
847                     = readSettingsMap.get(userId);
848             int pairsSize = componentNameOtherServiceStatusPairs.size();
849             for(int i = 0; i < pairsSize; i++) {
850                 Pair<ComponentName, OtherServiceStatus> pair
851                         = componentNameOtherServiceStatusPairs.get(i);
852                 services.others.put(pair.first,
853                         pair.second);
854             }
855         }
856     }
857 
writeDynamicSettingsLocked()858     private boolean writeDynamicSettingsLocked() {
859         FileOutputStream fos = null;
860         try {
861             fos = mDynamicSettingsFile.startWrite();
862             XmlSerializer out = Xml.newSerializer();
863             out.setOutput(fos, "utf-8");
864             out.startDocument(null, true);
865             out.setFeature(XML_INDENT_OUTPUT_FEATURE, true);
866             out.startTag(null, "services");
867             for (int i = 0; i < mUserServices.size(); i++) {
868                 final UserServices user = mUserServices.valueAt(i);
869                 for (Map.Entry<ComponentName, DynamicSettings> service :
870                         user.dynamicSettings.entrySet()) {
871                     out.startTag(null, "service");
872                     out.attribute(null, "component", service.getKey().flattenToString());
873                     out.attribute(null, "uid", Integer.toString(service.getValue().uid));
874                     if(service.getValue().offHostSE != null) {
875                         out.attribute(null, "offHostSE", service.getValue().offHostSE);
876                     }
877                     if (service.getValue().shouldDefaultToObserveModeStr != null) {
878                         out.attribute(null, "shouldDefaultToObserveMode",
879                                 service.getValue().shouldDefaultToObserveModeStr);
880                     }
881                     for (AidGroup group : service.getValue().aidGroups.values()) {
882                         group.writeAsXml(out);
883                     }
884                     out.endTag(null, "service");
885                 }
886             }
887             out.endTag(null, "services");
888             out.endDocument();
889             mDynamicSettingsFile.finishWrite(fos);
890             return true;
891         } catch (Exception e) {
892             Log.e(TAG, "Error writing dynamic AIDs", e);
893             if (fos != null) {
894                 mDynamicSettingsFile.failWrite(fos);
895             }
896             return false;
897         }
898     }
899 
writeOthersLocked()900     private boolean writeOthersLocked() {
901         Log.d(TAG, "write Others Locked()");
902 
903         FileOutputStream fos = null;
904         try {
905             fos = mOthersFile.startWrite();
906             XmlSerializer out = new FastXmlSerializer();
907             out.setOutput(fos, "utf-8");
908             out.startDocument(null, true);
909             out.setFeature(XML_INDENT_OUTPUT_FEATURE, true);
910             out.startTag(null, "services");
911 
912             Log.d(TAG, "userServices.size: " + mUserServices.size());
913             for (int i = 0; i < mUserServices.size(); i++) {
914                 final UserServices user = mUserServices.valueAt(i);
915                 int userId = mUserServices.keyAt(i);
916                 // Checking for 1 times
917                 Log.d(TAG, "userId: " + userId);
918                 Log.d(TAG, "others size: " + user.others.size());
919                 ArrayList<ComponentName> currentService = new ArrayList<ComponentName>();
920                 for (Map.Entry<ComponentName, OtherServiceStatus> service :
921                         user.others.entrySet()) {
922                     Log.d(TAG, "component: " + service.getKey().flattenToString() +
923                             ", checked: " + service.getValue().checked);
924 
925                     boolean hasDupe = false;
926                     for (ComponentName cn : currentService) {
927                         if (cn.equals(service.getKey())) {
928                             hasDupe = true;
929                             break;
930                         }
931                     }
932                     if (hasDupe) {
933                         continue;
934                     } else {
935                         Log.d(TAG, "Already written.");
936                         currentService.add(service.getKey());
937                     }
938 
939                     out.startTag(null, "service");
940                     out.attribute(null, "component", service.getKey().flattenToString());
941                     out.attribute(null, "uid", Integer.toString(service.getValue().uid));
942                     out.attribute(null, "checked", Boolean.toString(service.getValue().checked));
943                     out.endTag(null, "service");
944                 }
945             }
946             out.endTag(null, "services");
947             out.endDocument();
948             mOthersFile.finishWrite(fos);
949             return true;
950         } catch (Exception e) {
951             Log.e(TAG, "Error writing other status", e);
952             if (fos != null) {
953                 mOthersFile.failWrite(fos);
954             }
955             return false;
956         }
957     }
958 
setOffHostSecureElement(int userId, int uid, ComponentName componentName, String offHostSE)959     public boolean setOffHostSecureElement(int userId, int uid, ComponentName componentName,
960             String offHostSE) {
961         ArrayList<ApduServiceInfo> newServices = null;
962         synchronized (mLock) {
963             UserServices services = findOrCreateUserLocked(userId);
964             // Check if we can find this service
965             ApduServiceInfo serviceInfo = getService(userId, componentName);
966             if (serviceInfo == null) {
967                 Log.e(TAG, "Service " + componentName + " does not exist.");
968                 return false;
969             }
970             if (serviceInfo.getUid() != uid) {
971                 // This is probably a good indication something is wrong here.
972                 // Either newer service installed with different uid (but then
973                 // we should have known about it), or somebody calling us from
974                 // a different uid.
975                 Log.e(TAG, "UID mismatch.");
976                 return false;
977             }
978             if (offHostSE == null || serviceInfo.isOnHost()) {
979                 Log.e(TAG, "OffHostSE mismatch with Service type");
980                 return false;
981             }
982 
983             DynamicSettings dynSettings = services.dynamicSettings.get(componentName);
984             if (dynSettings == null) {
985                 dynSettings = new DynamicSettings(uid);
986             }
987             dynSettings.offHostSE = offHostSE;
988             boolean success = writeDynamicSettingsLocked();
989             if (!success) {
990                 Log.e(TAG, "Failed to persist AID group.");
991                 dynSettings.offHostSE = null;
992                 return false;
993             }
994 
995             serviceInfo.setOffHostSecureElement(offHostSE);
996             newServices = new ArrayList<ApduServiceInfo>(services.services.values());
997         }
998         // Make callback without the lock held
999         mCallback.onServicesUpdated(userId, newServices, true);
1000         return true;
1001     }
1002 
resetOffHostSecureElement(int userId, int uid, ComponentName componentName)1003     public boolean resetOffHostSecureElement(int userId, int uid, ComponentName componentName) {
1004         ArrayList<ApduServiceInfo> newServices = null;
1005         synchronized (mLock) {
1006             UserServices services = findOrCreateUserLocked(userId);
1007             // Check if we can find this service
1008             ApduServiceInfo serviceInfo = getService(userId, componentName);
1009             if (serviceInfo == null) {
1010                 Log.e(TAG, "Service " + componentName + " does not exist.");
1011                 return false;
1012             }
1013             if (serviceInfo.getUid() != uid) {
1014                 // This is probably a good indication something is wrong here.
1015                 // Either newer service installed with different uid (but then
1016                 // we should have known about it), or somebody calling us from
1017                 // a different uid.
1018                 Log.e(TAG, "UID mismatch.");
1019                 return false;
1020             }
1021             if (serviceInfo.isOnHost() || serviceInfo.getOffHostSecureElement() == null) {
1022                 Log.e(TAG, "OffHostSE is not set");
1023                 return false;
1024             }
1025 
1026             DynamicSettings dynSettings = services.dynamicSettings.get(componentName);
1027             String offHostSE = dynSettings.offHostSE;
1028             dynSettings.offHostSE = null;
1029             boolean success = writeDynamicSettingsLocked();
1030             if (!success) {
1031                 Log.e(TAG, "Failed to persist AID group.");
1032                 dynSettings.offHostSE = offHostSE;
1033                 return false;
1034             }
1035 
1036             serviceInfo.resetOffHostSecureElement();
1037             newServices = new ArrayList<ApduServiceInfo>(services.services.values());
1038         }
1039         // Make callback without the lock held
1040         mCallback.onServicesUpdated(userId, newServices, true);
1041         return true;
1042     }
1043 
setShouldDefaultToObserveModeForService(int userId, int uid, ComponentName componentName, boolean enable)1044     public boolean setShouldDefaultToObserveModeForService(int userId, int uid,
1045             ComponentName componentName, boolean enable) {
1046         synchronized (mLock) {
1047             UserServices services = findOrCreateUserLocked(userId);
1048             // Check if we can find this service
1049             ApduServiceInfo serviceInfo = getService(userId, componentName);
1050             if (serviceInfo == null) {
1051                 Log.e(TAG, "Service " + componentName + " does not exist.");
1052                 return false;
1053             }
1054             if (serviceInfo.getUid() != uid) {
1055                 // This is probably a good indication something is wrong here.
1056                 // Either newer service installed with different uid (but then
1057                 // we should have known about it), or somebody calling us from
1058                 // a different uid.
1059                 Log.e(TAG, "UID mismatch.");
1060                 return false;
1061             }
1062             serviceInfo.setShouldDefaultToObserveMode(enable);
1063             DynamicSettings dynSettings = services.dynamicSettings.get(componentName);
1064             if (dynSettings == null) {
1065                 dynSettings = new DynamicSettings(uid);
1066                 dynSettings.offHostSE = null;
1067                 services.dynamicSettings.put(componentName, dynSettings);
1068             }
1069             dynSettings.shouldDefaultToObserveModeStr =  Boolean.toString(enable);
1070         }
1071         return true;
1072     }
1073 
1074     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
registerPollingLoopFilterForService(int userId, int uid, ComponentName componentName, String pollingLoopFilter, boolean autoTransact)1075     public boolean registerPollingLoopFilterForService(int userId, int uid,
1076             ComponentName componentName, String pollingLoopFilter,
1077             boolean autoTransact) {
1078         ArrayList<ApduServiceInfo> newServices = null;
1079         synchronized (mLock) {
1080             UserServices services = findOrCreateUserLocked(userId);
1081             // Check if we can find this service
1082             ApduServiceInfo serviceInfo = getService(userId, componentName);
1083             if (serviceInfo == null) {
1084                 Log.e(TAG, "Service " + componentName + " does not exist.");
1085                 return false;
1086             }
1087             if (serviceInfo.getUid() != uid) {
1088                 // This is probably a good indication something is wrong here.
1089                 // Either newer service installed with different uid (but then
1090                 // we should have known about it), or somebody calling us from
1091                 // a different uid.
1092                 Log.e(TAG, "UID mismatch.");
1093                 return false;
1094             }
1095             if (!serviceInfo.isOnHost() && !autoTransact) {
1096                 return false;
1097             }
1098             serviceInfo.addPollingLoopFilter(pollingLoopFilter, autoTransact);
1099             newServices = new ArrayList<ApduServiceInfo>(services.services.values());
1100         }
1101         mCallback.onServicesUpdated(userId, newServices, true);
1102         return true;
1103     }
1104 
1105     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
removePollingLoopFilterForService(int userId, int uid, ComponentName componentName, String pollingLoopFilter)1106     public boolean removePollingLoopFilterForService(int userId, int uid,
1107             ComponentName componentName, String pollingLoopFilter) {
1108         ArrayList<ApduServiceInfo> newServices = null;
1109         synchronized (mLock) {
1110             UserServices services = findOrCreateUserLocked(userId);
1111             // Check if we can find this service
1112             ApduServiceInfo serviceInfo = getService(userId, componentName);
1113             if (serviceInfo == null) {
1114                 Log.e(TAG, "Service " + componentName + " does not exist.");
1115                 return false;
1116             }
1117             if (serviceInfo.getUid() != uid) {
1118                 // This is probably a good indication something is wrong here.
1119                 // Either newer service installed with different uid (but then
1120                 // we should have known about it), or somebody calling us from
1121                 // a different uid.
1122                 Log.e(TAG, "UID mismatch.");
1123                 return false;
1124             }
1125             serviceInfo.removePollingLoopFilter(pollingLoopFilter);
1126             newServices = new ArrayList<ApduServiceInfo>(services.services.values());
1127         }
1128         mCallback.onServicesUpdated(userId, newServices, true);
1129         return true;
1130     }
1131 
1132     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
registerPollingLoopPatternFilterForService(int userId, int uid, ComponentName componentName, String pollingLoopPatternFilter, boolean autoTransact)1133     public boolean registerPollingLoopPatternFilterForService(int userId, int uid,
1134             ComponentName componentName, String pollingLoopPatternFilter,
1135             boolean autoTransact) {
1136         ArrayList<ApduServiceInfo> newServices = null;
1137         synchronized (mLock) {
1138             UserServices services = findOrCreateUserLocked(userId);
1139             // Check if we can find this service
1140             ApduServiceInfo serviceInfo = getService(userId, componentName);
1141             if (serviceInfo == null) {
1142                 Log.e(TAG, "Service " + componentName + " does not exist.");
1143                 return false;
1144             }
1145             if (serviceInfo.getUid() != uid) {
1146                 // This is probably a good indication something is wrong here.
1147                 // Either newer service installed with different uid (but then
1148                 // we should have known about it), or somebody calling us from
1149                 // a different uid.
1150                 Log.e(TAG, "UID mismatch.");
1151                 return false;
1152             }
1153             if (!serviceInfo.isOnHost() && !autoTransact) {
1154                 return false;
1155             }
1156             serviceInfo.addPollingLoopPatternFilter(pollingLoopPatternFilter, autoTransact);
1157             newServices = new ArrayList<ApduServiceInfo>(services.services.values());
1158         }
1159         mCallback.onServicesUpdated(userId, newServices, true);
1160         return true;
1161     }
1162 
1163     @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
removePollingLoopPatternFilterForService(int userId, int uid, ComponentName componentName, String pollingLoopPatternFilter)1164     public boolean removePollingLoopPatternFilterForService(int userId, int uid,
1165             ComponentName componentName, String pollingLoopPatternFilter) {
1166         ArrayList<ApduServiceInfo> newServices = null;
1167         synchronized (mLock) {
1168             UserServices services = findOrCreateUserLocked(userId);
1169             // Check if we can find this service
1170             ApduServiceInfo serviceInfo = getService(userId, componentName);
1171             if (serviceInfo == null) {
1172                 Log.e(TAG, "Service " + componentName + " does not exist.");
1173                 return false;
1174             }
1175             if (serviceInfo.getUid() != uid) {
1176                 // This is probably a good indication something is wrong here.
1177                 // Either newer service installed with different uid (but then
1178                 // we should have known about it), or somebody calling us from
1179                 // a different uid.
1180                 Log.e(TAG, "UID mismatch.");
1181                 return false;
1182             }
1183             serviceInfo.removePollingLoopPatternFilter(pollingLoopPatternFilter);
1184             newServices = new ArrayList<ApduServiceInfo>(services.services.values());
1185         }
1186         mCallback.onServicesUpdated(userId, newServices, true);
1187         return true;
1188     }
1189 
1190 
1191 
registerAidGroupForService(int userId, int uid, ComponentName componentName, AidGroup aidGroup)1192     public boolean registerAidGroupForService(int userId, int uid,
1193             ComponentName componentName, AidGroup aidGroup) {
1194         ArrayList<ApduServiceInfo> newServices = null;
1195         boolean success;
1196         synchronized (mLock) {
1197             UserServices services = findOrCreateUserLocked(userId);
1198             // Check if we can find this service
1199             ApduServiceInfo serviceInfo = getService(userId, componentName);
1200             if (serviceInfo == null) {
1201                 Log.e(TAG, "Service " + componentName + " does not exist.");
1202                 return false;
1203             }
1204             if (serviceInfo.getUid() != uid) {
1205                 // This is probably a good indication something is wrong here.
1206                 // Either newer service installed with different uid (but then
1207                 // we should have known about it), or somebody calling us from
1208                 // a different uid.
1209                 Log.e(TAG, "UID mismatch.");
1210                 return false;
1211             }
1212             // Do another AID validation, since a caller could have thrown in a
1213             // modified AidGroup object with invalid AIDs over Binder.
1214             List<String> aids = aidGroup.getAids();
1215             for (String aid : aids) {
1216                 if (!CardEmulation.isValidAid(aid)) {
1217                     Log.e(TAG, "AID " + aid + " is not a valid AID");
1218                     return false;
1219                 }
1220             }
1221             serviceInfo.setDynamicAidGroup(aidGroup);
1222             DynamicSettings dynSettings = services.dynamicSettings.get(componentName);
1223             if (dynSettings == null) {
1224                 dynSettings = new DynamicSettings(uid);
1225                 dynSettings.offHostSE = null;
1226                 services.dynamicSettings.put(componentName, dynSettings);
1227             }
1228             dynSettings.aidGroups.put(aidGroup.getCategory(), aidGroup);
1229             success = writeDynamicSettingsLocked();
1230             if (success) {
1231                 newServices =
1232                     new ArrayList<ApduServiceInfo>(services.services.values());
1233             } else {
1234                 Log.e(TAG, "Failed to persist AID group.");
1235                 // Undo registration
1236                 dynSettings.aidGroups.remove(aidGroup.getCategory());
1237             }
1238         }
1239         if (success) {
1240             // Make callback without the lock held
1241             mCallback.onServicesUpdated(userId, newServices, true);
1242         }
1243         return success;
1244     }
1245 
registerOtherForService(int userId, ComponentName componentName, boolean checked)1246     public boolean registerOtherForService(int userId,
1247             ComponentName componentName, boolean checked) {
1248         if (DEBUG) Log.d(TAG, "[register other] checked:" + checked + ", "  + componentName);
1249 
1250         ArrayList<ApduServiceInfo> newServices = null;
1251         boolean success = false;
1252 
1253         synchronized (mLock) {
1254 
1255             Log.d(TAG, "registerOtherForService / ComponentName" + componentName);
1256             ApduServiceInfo serviceInfo = getService(userId, componentName);
1257 
1258             if (serviceInfo == null) {
1259                 Log.e(TAG, "Service " + componentName + "does not exist");
1260                 return false;
1261             }
1262 
1263             success = updateOtherServiceStatus(userId, serviceInfo, checked);
1264 
1265             if (success) {
1266                 UserServices userService = findOrCreateUserLocked(userId);
1267                 newServices = new ArrayList<ApduServiceInfo>(userService.services.values());
1268             } else {
1269                 Log.e(TAG, "Fail to other checked");
1270             }
1271         }
1272 
1273         if (success) {
1274             if (DEBUG) Log.d(TAG, "other list update due to User Select " + componentName);
1275             mCallback.onServicesUpdated(userId, Collections.unmodifiableList(newServices),false);
1276         }
1277 
1278         return success;
1279     }
1280 
getAidGroupForService(int userId, int uid, ComponentName componentName, String category)1281     public AidGroup getAidGroupForService(int userId, int uid, ComponentName componentName,
1282             String category) {
1283         ApduServiceInfo serviceInfo = getService(userId, componentName);
1284         if (serviceInfo != null) {
1285             if (serviceInfo.getUid() != uid) {
1286                 Log.e(TAG, "UID mismatch");
1287                 return null;
1288             }
1289             return serviceInfo.getDynamicAidGroupForCategory(category);
1290         } else {
1291             Log.e(TAG, "Could not find service " + componentName);
1292             return null;
1293         }
1294     }
1295 
removeAidGroupForService(int userId, int uid, ComponentName componentName, String category)1296     public boolean removeAidGroupForService(int userId, int uid, ComponentName componentName,
1297             String category) {
1298         boolean success = false;
1299         ArrayList<ApduServiceInfo> newServices = null;
1300         synchronized (mLock) {
1301             UserServices services = findOrCreateUserLocked(userId);
1302             ApduServiceInfo serviceInfo = getService(userId, componentName);
1303             if (serviceInfo != null) {
1304                 if (serviceInfo.getUid() != uid) {
1305                     // Calling from different uid
1306                     Log.e(TAG, "UID mismatch");
1307                     return false;
1308                 }
1309                 if (!serviceInfo.removeDynamicAidGroupForCategory(category)) {
1310                     Log.e(TAG," Could not find dynamic AIDs for category " + category);
1311                     return false;
1312                 }
1313                 // Remove from local cache
1314                 DynamicSettings dynSettings = services.dynamicSettings.get(componentName);
1315                 if (dynSettings != null) {
1316                     AidGroup deletedGroup = dynSettings.aidGroups.remove(category);
1317                     success = writeDynamicSettingsLocked();
1318                     if (success) {
1319                         newServices = new ArrayList<ApduServiceInfo>(services.services.values());
1320                     } else {
1321                         Log.e(TAG, "Could not persist deleted AID group.");
1322                         dynSettings.aidGroups.put(category, deletedGroup);
1323                         return false;
1324                     }
1325                 } else {
1326                     Log.e(TAG, "Could not find aid group in local cache.");
1327                 }
1328             } else {
1329                 Log.e(TAG, "Service " + componentName + " does not exist.");
1330             }
1331         }
1332         if (success) {
1333             mCallback.onServicesUpdated(userId, newServices, true);
1334         }
1335         return success;
1336     }
1337 
doesServiceShouldDefaultToObserveMode(int userId, ComponentName service)1338     boolean doesServiceShouldDefaultToObserveMode(int userId, ComponentName service) {
1339         UserServices services = findOrCreateUserLocked(userId);
1340         ApduServiceInfo serviceInfo = services.services.get(service);
1341         if (serviceInfo == null) {
1342             Log.d(TAG, "serviceInfo is null");
1343             return false;
1344         }
1345         return serviceInfo.shouldDefaultToObserveMode();
1346     }
1347 
updateOtherServiceStatus(int userId, ApduServiceInfo service, boolean checked)1348     private boolean updateOtherServiceStatus(int userId, ApduServiceInfo service, boolean checked) {
1349         UserServices userServices = findOrCreateUserLocked(userId);
1350 
1351         OtherServiceStatus status = userServices.others.get(service.getComponent());
1352         // This is Error handling code if otherServiceStatus is null
1353         if (status == null) {
1354             Log.d(TAG, service.getComponent() + " status is could not be null");
1355             return false;
1356         }
1357 
1358         if (service.isCategoryOtherServiceEnabled() == checked) {
1359             Log.d(TAG, "already same status: " + checked);
1360             return false;
1361         }
1362 
1363         service.setCategoryOtherServiceEnabled(checked);
1364         status.checked = checked;
1365 
1366         return writeOthersLocked();
1367     }
1368 
dump(FileDescriptor fd, PrintWriter pw, String[] args)1369     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1370         pw.println("Registered HCE services for current user: ");
1371         ParcelFileDescriptor pFd;
1372         try {
1373             pFd = ParcelFileDescriptor.dup(fd);
1374             synchronized (mLock) {
1375                 for (UserHandle uh : mUserHandles) {
1376                     UserManager um = mContext.createContextAsUser(
1377                             uh, /*flags=*/0).getSystemService(UserManager.class);
1378                     pw.println("User " + Utils.maskSubstring(um.getUserName(), 3));
1379                     UserServices userServices = findOrCreateUserLocked(uh.getIdentifier());
1380                     for (ApduServiceInfo service : userServices.services.values()) {
1381                         service.dump(pFd, pw, args);
1382                         pw.println("");
1383                     }
1384                     pw.println("");
1385                 }
1386             }
1387             pFd.close();
1388         } catch (IOException e) {
1389             pw.println("Failed to dump HCE services: " + e);
1390         }
1391     }
1392 
1393     /**
1394      * Dump debugging information as a RegisteredServicesCacheProto
1395      *
1396      * Note:
1397      * See proto definition in frameworks/base/core/proto/android/nfc/card_emulation.proto
1398      * When writing a nested message, must call {@link ProtoOutputStream#start(long)} before and
1399      * {@link ProtoOutputStream#end(long)} after.
1400      * Never reuse a proto field number. When removing a field, mark it as reserved.
1401      */
dumpDebug(ProtoOutputStream proto)1402     void dumpDebug(ProtoOutputStream proto) {
1403         UserServices userServices = findOrCreateUserLocked(ActivityManager.getCurrentUser());
1404         for (ApduServiceInfo service : userServices.services.values()) {
1405             long token = proto.start(RegisteredServicesCacheProto.APDU_SERVICE_INFOS);
1406             service.dumpDebug(proto);
1407             proto.end(token);
1408         }
1409     }
1410 }
1411