• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.HostNfcFService;
31 import android.nfc.cardemulation.NfcFCardEmulation;
32 import android.nfc.cardemulation.NfcFServiceInfo;
33 import android.os.ParcelFileDescriptor;
34 import android.os.UserHandle;
35 import android.os.UserManager;
36 import android.sysprop.NfcProperties;
37 import android.util.AtomicFile;
38 import android.util.Log;
39 import android.util.SparseArray;
40 import android.util.Xml;
41 import android.util.proto.ProtoOutputStream;
42 
43 import androidx.annotation.VisibleForTesting;
44 
45 import com.android.internal.annotations.GuardedBy;
46 import com.android.nfc.Utils;
47 
48 import org.xmlpull.v1.XmlPullParser;
49 import org.xmlpull.v1.XmlPullParserException;
50 import org.xmlpull.v1.XmlSerializer;
51 
52 import java.io.File;
53 import java.io.FileDescriptor;
54 import java.io.FileInputStream;
55 import java.io.FileOutputStream;
56 import java.io.IOException;
57 import java.io.PrintWriter;
58 import java.util.ArrayList;
59 import java.util.Collections;
60 import java.util.HashMap;
61 import java.util.List;
62 import java.util.Map;
63 import java.util.concurrent.atomic.AtomicReference;
64 
65 public class RegisteredNfcFServicesCache {
66     static final String XML_INDENT_OUTPUT_FEATURE = "http://xmlpull.org/v1/doc/features.html#indent-output";
67     static final String TAG = "RegisteredNfcFServicesCache";
68     static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
69     private static final boolean VDBG = false; // turn on for local testing.
70 
71     final Context mContext;
72     final AtomicReference<BroadcastReceiver> mReceiver;
73 
74     final Object mLock = new Object();
75     // All variables below synchronized on mLock
76 
77     // mUserHandles holds the UserHandles of all the profiles that belong to current user
78     @GuardedBy("mLock")
79     List<UserHandle> mUserHandles;
80 
81     // mUserServices holds the card emulation services that are running for each user
82     final SparseArray<UserServices> mUserServices = new SparseArray<UserServices>();
83     final Callback mCallback;
84     final AtomicFile mDynamicSystemCodeNfcid2File;
85     boolean mActivated = false;
86     boolean mUserSwitched = false;
87 
88     public interface Callback {
onNfcFServicesUpdated(int userId, final List<NfcFServiceInfo> services)89         void onNfcFServicesUpdated(int userId, final List<NfcFServiceInfo> services);
90     };
91 
92     static class DynamicSystemCode {
93         public final int uid;
94         public final String systemCode;
95 
DynamicSystemCode(int uid, String systemCode)96         DynamicSystemCode(int uid, String systemCode) {
97             this.uid = uid;
98             this.systemCode = systemCode;
99         }
100     };
101 
102     static class DynamicNfcid2 {
103         public final int uid;
104         public final String nfcid2;
105 
DynamicNfcid2(int uid, String nfcid2)106         DynamicNfcid2(int uid, String nfcid2) {
107             this.uid = uid;
108             this.nfcid2 = nfcid2;
109         }
110     };
111 
112     @VisibleForTesting
113     static class UserServices {
114         /**
115          * All services that have registered
116          */
117         final HashMap<ComponentName, NfcFServiceInfo> services =
118                 new HashMap<>(); // Re-built at run-time
119         final HashMap<ComponentName, DynamicSystemCode> dynamicSystemCode =
120                 new HashMap<>(); // In memory cache of dynamic System Code store
121         final HashMap<ComponentName, DynamicNfcid2> dynamicNfcid2 =
122                 new HashMap<>(); // In memory cache of dynamic NFCID2 store
123     };
124 
findOrCreateUserLocked(int userId)125     private UserServices findOrCreateUserLocked(int userId) {
126         UserServices userServices = mUserServices.get(userId);
127         if (userServices == null) {
128             userServices = new UserServices();
129             mUserServices.put(userId, userServices);
130         }
131         return userServices;
132     }
133 
getProfileParentId(Context context, int userId)134     private int getProfileParentId(Context context, int userId) {
135         UserManager um = context.getSystemService(UserManager.class);
136         UserHandle uh = um.getProfileParent(UserHandle.of(userId));
137         return uh == null ? userId : uh.getIdentifier();
138     }
139 
getProfileParentId(int userId)140     private int getProfileParentId(int userId) {
141         return getProfileParentId(mContext.createContextAsUser(
142                 UserHandle.of(userId), /*flags=*/0), userId);
143     }
144 
RegisteredNfcFServicesCache(Context context, Callback callback)145     public RegisteredNfcFServicesCache(Context context, Callback callback) {
146         this(context, callback, null);
147     }
148 
149     @VisibleForTesting
RegisteredNfcFServicesCache(Context context, Callback callback, AtomicFile atomicFile)150     RegisteredNfcFServicesCache(Context context, Callback callback, AtomicFile atomicFile) {
151         mContext = context;
152         mCallback = callback;
153 
154         refreshUserProfilesLocked();
155 
156         final BroadcastReceiver receiver = new BroadcastReceiver() {
157             @Override
158             public void onReceive(Context context, Intent intent) {
159                 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
160                 String action = intent.getAction();
161                 if (VDBG) Log.d(TAG, "onReceive: Intent action: " + action);
162                 if (uid == -1) return;
163                 int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
164                 int currentUser = ActivityManager.getCurrentUser();
165                 if (currentUser != getProfileParentId(context, userId)) {
166                     // Cache will automatically be updated on user switch
167                     if (VDBG) {
168                         Log.d(TAG,
169                                 "onReceive: Ignoring package change intent from non-current user");
170                     }
171                     return;
172                 }
173                 // If app not removed, check if the app has any valid CE services.
174                 if (!Intent.ACTION_PACKAGE_REMOVED.equals(action) &&
175                         !Utils.hasCeServicesWithValidPermissions(mContext, intent, userId)) {
176                     if (VDBG) {
177                         Log.d(TAG, "onReceive: Ignoring package change intent from non-CE app");
178                     }
179                     return;
180                 }
181                 boolean replaced = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) &&
182                         (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
183                                 Intent.ACTION_PACKAGE_REMOVED.equals(action));
184                 if (!replaced) {
185                     invalidateCache(UserHandle.getUserHandleForUid(uid).getIdentifier());
186                 } else {
187                     if (DBG) Log.d(TAG,
188                             "onReceive: Ignoring package intent due to package being replaced.");
189                 }
190             }
191         };
192         mReceiver = new AtomicReference<BroadcastReceiver>(receiver);
193 
194         IntentFilter intentFilter = new IntentFilter();
195         intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
196         intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
197         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
198         intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
199         intentFilter.addAction(Intent.ACTION_PACKAGE_FIRST_LAUNCH);
200         intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
201         intentFilter.addDataScheme("package");
202         mContext.registerReceiverForAllUsers(mReceiver.get(), intentFilter, null, null);
203 
204         // Register for events related to sdcard operations
205         IntentFilter sdFilter = new IntentFilter();
206         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
207         sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
208         mContext.registerReceiverForAllUsers(mReceiver.get(), sdFilter, null, null);
209 
210         File dataDir = mContext.getFilesDir();
211         if (atomicFile == null) {
212             mDynamicSystemCodeNfcid2File =
213                 new AtomicFile(new File(dataDir, "dynamic_systemcode_nfcid2.xml"));
214         } else {
215             mDynamicSystemCodeNfcid2File = atomicFile;
216         }
217     }
218 
initialize()219     void initialize() {
220         synchronized (mLock) {
221             readDynamicSystemCodeNfcid2Locked();
222             for (UserHandle uh : mUserHandles) {
223                 invalidateCache(uh.getIdentifier());
224             }
225         }
226     }
227 
dump(ArrayList<NfcFServiceInfo> services)228     void dump(ArrayList<NfcFServiceInfo> services) {
229         for (NfcFServiceInfo service : services) {
230             Log.d(TAG, service.toString());
231         }
232     }
233 
containsServiceLocked(ArrayList<NfcFServiceInfo> services, ComponentName componentName)234     boolean containsServiceLocked(ArrayList<NfcFServiceInfo> services,
235             ComponentName componentName) {
236         for (NfcFServiceInfo service : services) {
237             if (service.getComponent().equals(componentName)) return true;
238         }
239         return false;
240     }
241 
hasService(int userId, ComponentName componentName)242     public boolean hasService(int userId, ComponentName componentName) {
243         return getService(userId, componentName) != null;
244     }
245 
getService(int userId, ComponentName componentName)246     public NfcFServiceInfo getService(int userId, ComponentName componentName) {
247         synchronized (mLock) {
248             UserServices userServices = findOrCreateUserLocked(userId);
249             return userServices.services.get(componentName);
250         }
251     }
252 
getServices(int userId)253     public List<NfcFServiceInfo> getServices(int userId) {
254         final ArrayList<NfcFServiceInfo> services = new ArrayList<NfcFServiceInfo>();
255         synchronized (mLock) {
256             UserServices userServices = findOrCreateUserLocked(userId);
257             services.addAll(userServices.services.values());
258         }
259         return services;
260     }
261 
getInstalledServices(int userId)262     ArrayList<NfcFServiceInfo> getInstalledServices(int userId) {
263         if (DBG) Log.d(TAG, "getInstalledServices");
264         PackageManager pm;
265         try {
266             pm = mContext.createPackageContextAsUser("android", 0,
267                     UserHandle.of(userId)).getPackageManager();
268         } catch (NameNotFoundException e) {
269             Log.e(TAG, "getInstalledServices: Could not create user package context");
270             return null;
271         }
272 
273         ArrayList<NfcFServiceInfo> validServices = new ArrayList<NfcFServiceInfo>();
274 
275         List<ResolveInfo> resolvedServices = pm.queryIntentServicesAsUser(
276                 new Intent(HostNfcFService.SERVICE_INTERFACE),
277                 ResolveInfoFlags.of(PackageManager.GET_META_DATA), UserHandle.of(userId));
278 
279         for (ResolveInfo resolvedService : resolvedServices) {
280             try {
281                 ServiceInfo si = resolvedService.serviceInfo;
282                 ComponentName componentName = new ComponentName(si.packageName, si.name);
283                 // Check if the package holds the NFC permission
284                 if (pm.checkPermission(android.Manifest.permission.NFC, si.packageName) !=
285                         PackageManager.PERMISSION_GRANTED) {
286                     Log.e(TAG,
287                             "getInstalledServices: Skipping NfcF service " + componentName
288                                     + ": it does not require the permission "
289                                     + android.Manifest.permission.NFC);
290                     continue;
291                 }
292                 if (!android.Manifest.permission.BIND_NFC_SERVICE.equals(
293                         si.permission)) {
294                     Log.e(TAG,
295                             "getInstalledServices: Skipping NfcF service " + componentName
296                                     + ": it does not require the permission "
297                                     + android.Manifest.permission.BIND_NFC_SERVICE);
298                     continue;
299                 }
300                 NfcFServiceInfo service = new NfcFServiceInfo(pm, resolvedService);
301                 if (service != null) {
302                     validServices.add(service);
303                 }
304             } catch (XmlPullParserException e) {
305                 Log.w(TAG, "getInstalledServices: Unable to load component info "
306                         + resolvedService.toString(), e);
307             } catch (IOException e) {
308                 Log.w(TAG, "getInstalledServices: Unable to load component info "
309                         + resolvedService.toString(), e);
310             }
311         }
312 
313         return validServices;
314     }
315 
invalidateCache(int userId)316     public void invalidateCache(int userId) {
317         if (DBG) Log.d(TAG, "invalidateCache");
318         final ArrayList<NfcFServiceInfo> validServices = getInstalledServices(userId);
319         if (validServices == null) {
320             return;
321         }
322         ArrayList<NfcFServiceInfo> newServices = null;
323         ArrayList<NfcFServiceInfo> toBeAdded = new ArrayList<>();
324         ArrayList<NfcFServiceInfo> toBeRemoved = new ArrayList<>();
325         synchronized (mLock) {
326             UserServices userServices = findOrCreateUserLocked(userId);
327 
328             // Check update
329             ArrayList<NfcFServiceInfo> cachedServices =
330                     new ArrayList<NfcFServiceInfo>(userServices.services.values());
331             boolean matched = false;
332             for (NfcFServiceInfo validService : validServices) {
333                 for (NfcFServiceInfo cachedService : cachedServices) {
334                     if (validService.equals(cachedService)) {
335                         matched = true;
336                         break;
337                     }
338                 }
339                 if (!matched) {
340                     toBeAdded.add(validService);
341                 }
342                 matched = false;
343             }
344             for (NfcFServiceInfo cachedService : cachedServices) {
345                 for (NfcFServiceInfo validService : validServices) {
346                     if (cachedService.equals(validService)) {
347                         matched = true;
348                         break;
349                     }
350                 }
351                 if (!matched) {
352                     toBeRemoved.add(cachedService);
353                 }
354                 matched = false;
355             }
356             if (mUserSwitched) {
357                 Log.d(TAG, "invalidateCache: User switched, rebuild internal cache");
358                 mUserSwitched = false;
359             } else if (toBeAdded.size() == 0 && toBeRemoved.size() == 0) {
360                 Log.d(TAG, "invalidateCache: Service unchanged, not updating");
361                 return;
362             }
363 
364             // Update cache
365             for (NfcFServiceInfo service : toBeAdded) {
366                 userServices.services.put(service.getComponent(), service);
367             }
368             for (NfcFServiceInfo service : toBeRemoved) {
369                 userServices.services.remove(service.getComponent());
370             }
371             // Apply dynamic System Code mappings
372             ArrayList<ComponentName> toBeRemovedDynamicSystemCode =
373                     new ArrayList<ComponentName>();
374             for (Map.Entry<ComponentName, DynamicSystemCode> entry :
375                     userServices.dynamicSystemCode.entrySet()) {
376                 // Verify component / uid match
377                 ComponentName componentName = entry.getKey();
378                 DynamicSystemCode dynamicSystemCode = entry.getValue();
379                 NfcFServiceInfo service = userServices.services.get(componentName);
380                 if (service == null || (service.getUid() != dynamicSystemCode.uid)) {
381                     toBeRemovedDynamicSystemCode.add(componentName);
382                     continue;
383                 } else {
384                     service.setDynamicSystemCode(dynamicSystemCode.systemCode);
385                 }
386             }
387             // Apply dynamic NFCID2 mappings
388             ArrayList<ComponentName> toBeRemovedDynamicNfcid2 =
389                     new ArrayList<ComponentName>();
390             for (Map.Entry<ComponentName, DynamicNfcid2> entry :
391                     userServices.dynamicNfcid2.entrySet()) {
392                 // Verify component / uid match
393                 ComponentName componentName = entry.getKey();
394                 DynamicNfcid2 dynamicNfcid2 = entry.getValue();
395                 NfcFServiceInfo service = userServices.services.get(componentName);
396                 if (service == null || (service.getUid() != dynamicNfcid2.uid)) {
397                     toBeRemovedDynamicNfcid2.add(componentName);
398                     continue;
399                 } else {
400                     service.setDynamicNfcid2(dynamicNfcid2.nfcid2);
401                 }
402             }
403             for (ComponentName removedComponent : toBeRemovedDynamicSystemCode) {
404                 Log.d(TAG, "invalidateCache: Removing dynamic System Code registered by "
405                         + removedComponent);
406                 userServices.dynamicSystemCode.remove(removedComponent);
407             }
408             for (ComponentName removedComponent : toBeRemovedDynamicNfcid2) {
409                 Log.d(TAG, "invalidateCache: Removing dynamic NFCID2 registered by "
410                         + removedComponent);
411                 userServices.dynamicNfcid2.remove(removedComponent);
412             }
413             // Assign a NFCID2 for services requesting a random NFCID2, then apply
414             boolean nfcid2Assigned = false;
415             for (Map.Entry<ComponentName, NfcFServiceInfo> entry :
416                 userServices.services.entrySet()) {
417                 NfcFServiceInfo service = entry.getValue();
418                 if (service.getNfcid2().equalsIgnoreCase("RANDOM")) {
419                     String randomNfcid2 = generateRandomNfcid2();
420                     service.setDynamicNfcid2(randomNfcid2);
421                     DynamicNfcid2 dynamicNfcid2 =
422                             new DynamicNfcid2(service.getUid(), randomNfcid2);
423                     userServices.dynamicNfcid2.put(entry.getKey(), dynamicNfcid2);
424                     nfcid2Assigned = true;
425                 }
426             }
427 
428             // Persist to filesystem
429             if (toBeRemovedDynamicSystemCode.size() > 0 ||
430                     toBeRemovedDynamicNfcid2.size() > 0 ||
431                     nfcid2Assigned) {
432                 writeDynamicSystemCodeNfcid2Locked();
433             }
434 
435             newServices = new ArrayList<NfcFServiceInfo>(userServices.services.values());
436         }
437         mCallback.onNfcFServicesUpdated(userId, Collections.unmodifiableList(newServices));
438         if (VDBG) {
439             Log.i(TAG, "invalidateCache: Services => ");
440             dump(newServices);
441         } else {
442             // dump only new services added or removed
443             Log.i(TAG, "invalidateCache: New Services => ");
444             dump(toBeAdded);
445             Log.i(TAG, "invalidateCache: Removed Services => ");
446             dump(toBeRemoved);
447         }
448     }
449 
450     @VisibleForTesting
readDynamicSystemCodeNfcid2Locked()451     void readDynamicSystemCodeNfcid2Locked() {
452         if (DBG) Log.d(TAG, "readDynamicSystemCodeNfcid2Locked");
453         FileInputStream fis = null;
454         try {
455             if (!mDynamicSystemCodeNfcid2File.getBaseFile().exists()) {
456                 Log.d(TAG, "readDynamicSystemCodeNfcid2Locked: Dynamic System Code, "
457                         + "NFCID2 file does not exist");
458                 return;
459             }
460             fis = mDynamicSystemCodeNfcid2File.openRead();
461             XmlPullParser parser = Xml.newPullParser();
462             parser.setInput(fis, null);
463             int eventType = parser.getEventType();
464             while (eventType != XmlPullParser.START_TAG &&
465                     eventType != XmlPullParser.END_DOCUMENT) {
466                 eventType = parser.next();
467             }
468             String tagName = parser.getName();
469             if ("services".equals(tagName)) {
470                 ComponentName componentName = null;
471                 int currentUid = -1;
472                 String systemCode = null;
473                 String nfcid2 = null;
474                 String description = null;
475                 while (eventType != XmlPullParser.END_DOCUMENT) {
476                     tagName = parser.getName();
477                     if (eventType == XmlPullParser.START_TAG) {
478                         if ("service".equals(tagName) && parser.getDepth() == 2) {
479                             String compString =
480                                     parser.getAttributeValue(null, "component");
481                             String uidString =
482                                     parser.getAttributeValue(null, "uid");
483                             if (compString == null || uidString == null) {
484                                 Log.e(TAG, "readDynamicSystemCodeNfcid2Locked: "
485                                         + "Invalid service attributes");
486                             } else {
487                                 try {
488                                     componentName = ComponentName.unflattenFromString(compString);
489                                     currentUid = Integer.parseInt(uidString);
490                                     systemCode = parser.getAttributeValue(null, "system-code");
491                                     description = parser.getAttributeValue(null, "description");
492                                     nfcid2 = parser.getAttributeValue(null, "nfcid2");
493                                 } catch (NumberFormatException e) {
494                                     Log.e(TAG, "readDynamicSystemCodeNfcid2Locked: "
495                                             + "Could not parse service uid");
496                                 }
497                             }
498                         }
499                     } else if (eventType == XmlPullParser.END_TAG) {
500                         if ("service".equals(tagName)) {
501                             // See if we have a valid service
502                             if (componentName != null && currentUid >= 0) {
503                                 final int userId = UserHandle.
504                                         getUserHandleForUid(currentUid).getIdentifier();
505                                 UserServices userServices = findOrCreateUserLocked(userId);
506                                 if (systemCode != null) {
507                                     DynamicSystemCode dynamicSystemCode =
508                                             new DynamicSystemCode(currentUid, systemCode);
509                                     userServices.dynamicSystemCode.put(
510                                             componentName, dynamicSystemCode);
511                                 }
512                                 if (nfcid2 != null) {
513                                     DynamicNfcid2 dynamicNfcid2 =
514                                             new DynamicNfcid2(currentUid, nfcid2);
515                                     userServices.dynamicNfcid2.put(
516                                             componentName, dynamicNfcid2);
517                                 }
518                             }
519                             componentName = null;
520                             currentUid = -1;
521                             systemCode = null;
522                             description = null;
523                             nfcid2 = null;
524                         }
525                     }
526                     eventType = parser.next();
527                 };
528             }
529         } catch (Exception e) {
530             Log.e(TAG, "readDynamicSystemCodeNfcid2Locked: Could not parse dynamic System Code, "
531                     + "NFCID2 file, trashing");
532             mDynamicSystemCodeNfcid2File.delete();
533         } finally {
534             if (fis != null) {
535                 try {
536                     fis.close();
537                 } catch (IOException e) {
538                 }
539             }
540         }
541     }
542 
543     @VisibleForTesting
writeDynamicSystemCodeNfcid2Locked()544     boolean writeDynamicSystemCodeNfcid2Locked() {
545         if (DBG) Log.d(TAG, "writeDynamicSystemCodeNfcid2Locked");
546         FileOutputStream fos = null;
547         try {
548             fos = mDynamicSystemCodeNfcid2File.startWrite();
549             XmlSerializer out = Xml.newSerializer();
550             out.setOutput(fos, "utf-8");
551             out.startDocument(null, true);
552             out.setFeature(XML_INDENT_OUTPUT_FEATURE, true);
553             out.startTag(null, "services");
554             for (int i = 0; i < mUserServices.size(); i++) {
555                 final UserServices userServices = mUserServices.valueAt(i);
556                 for (Map.Entry<ComponentName, DynamicSystemCode> entry :
557                         userServices.dynamicSystemCode.entrySet()) {
558                     out.startTag(null, "service");
559                     out.attribute(null, "component", entry.getKey().flattenToString());
560                     out.attribute(null, "uid", Integer.toString(entry.getValue().uid));
561                     out.attribute(null, "system-code", entry.getValue().systemCode);
562                     if (userServices.dynamicNfcid2.containsKey(entry.getKey())) {
563                         out.attribute(null, "nfcid2",
564                                 userServices.dynamicNfcid2.get(entry.getKey()).nfcid2);
565                     }
566                     out.endTag(null, "service");
567                 }
568                 for (Map.Entry<ComponentName, DynamicNfcid2> entry :
569                         userServices.dynamicNfcid2.entrySet()) {
570                     if (!userServices.dynamicSystemCode.containsKey(entry.getKey())) {
571                         out.startTag(null, "service");
572                         out.attribute(null, "component", entry.getKey().flattenToString());
573                         out.attribute(null, "uid", Integer.toString(entry.getValue().uid));
574                         out.attribute(null, "nfcid2", entry.getValue().nfcid2);
575                         out.endTag(null, "service");
576                     }
577                 }
578             }
579             out.endTag(null, "services");
580             out.endDocument();
581             mDynamicSystemCodeNfcid2File.finishWrite(fos);
582             return true;
583         } catch (Exception e) {
584             Log.e(TAG,
585                     "writeDynamicSystemCodeNfcid2Locked: "
586                             + "Error writing dynamic System Code, NFCID2",
587                             e);
588             if (fos != null) {
589                 mDynamicSystemCodeNfcid2File.failWrite(fos);
590             }
591             return false;
592         }
593     }
594 
registerSystemCodeForService(int userId, int uid, ComponentName componentName, String systemCode)595     public boolean registerSystemCodeForService(int userId, int uid,
596             ComponentName componentName, String systemCode) {
597         if (DBG) Log.d(TAG, "registerSystemCodeForService");
598         ArrayList<NfcFServiceInfo> newServices = null;
599         boolean success;
600         synchronized (mLock) {
601             if (mActivated) {
602                 Log.d(TAG, "registerSystemCodeForService: failed to register "
603                         + "System Code during activation");
604                 return false;
605             }
606             UserServices userServices = findOrCreateUserLocked(userId);
607             // Check if we can find this service
608             NfcFServiceInfo service = getService(userId, componentName);
609             if (service == null) {
610                 Log.e(TAG, "registerSystemCodeForService: Service " + componentName
611                         + " does not exist.");
612                 return false;
613             }
614             if (service.getUid() != uid) {
615                 // This is probably a good indication something is wrong here.
616                 // Either newer service installed with different uid (but then
617                 // we should have known about it), or somebody calling us from
618                 // a different uid.
619                 Log.e(TAG, "registerSystemCodeForService: UID mismatch.");
620                 return false;
621             }
622             if (!systemCode.equalsIgnoreCase("NULL") &&
623                     !NfcFCardEmulation.isValidSystemCode(systemCode)) {
624                 Log.e(TAG, "registerSystemCodeForService: System Code " + systemCode
625                         + " is not a valid System Code");
626                 return false;
627             }
628             // Apply dynamic System Code mappings
629             systemCode = systemCode.toUpperCase();
630             DynamicSystemCode oldDynamicSystemCode =
631                     userServices.dynamicSystemCode.get(componentName);
632             DynamicSystemCode dynamicSystemCode = new DynamicSystemCode(uid, systemCode);
633             userServices.dynamicSystemCode.put(componentName, dynamicSystemCode);
634             success = writeDynamicSystemCodeNfcid2Locked();
635             if (success) {
636                 service.setDynamicSystemCode(systemCode);
637                 newServices = new ArrayList<NfcFServiceInfo>(userServices.services.values());
638             } else {
639                 Log.e(TAG, "registerSystemCodeForService: Failed to persist System Code.");
640                 // Undo registration
641                 if (oldDynamicSystemCode == null) {
642                     userServices.dynamicSystemCode.remove(componentName);
643                 } else {
644                     userServices.dynamicSystemCode.put(componentName, oldDynamicSystemCode);
645                 }
646             }
647         }
648         if (success) {
649             // Make callback without the lock held
650             mCallback.onNfcFServicesUpdated(userId, newServices);
651         }
652         return success;
653     }
654 
getSystemCodeForService(int userId, int uid, ComponentName componentName)655     public String getSystemCodeForService(int userId, int uid, ComponentName componentName) {
656         if (DBG) Log.d(TAG, "getSystemCodeForService");
657         NfcFServiceInfo service = getService(userId, componentName);
658         if (service != null) {
659             if (service.getUid() != uid) {
660                 Log.e(TAG, "getSystemCodeForService: UID mismatch");
661                 return null;
662             }
663             return service.getSystemCode();
664         } else {
665             Log.e(TAG, "getSystemCodeForService: Could not find service " + componentName);
666             return null;
667         }
668     }
669 
removeSystemCodeForService(int userId, int uid, ComponentName componentName)670     public boolean removeSystemCodeForService(int userId, int uid, ComponentName componentName) {
671         if (DBG) Log.d(TAG, "removeSystemCodeForService");
672         return registerSystemCodeForService(userId, uid, componentName, "NULL");
673     }
674 
setNfcid2ForService(int userId, int uid, ComponentName componentName, String nfcid2)675     public boolean setNfcid2ForService(int userId, int uid,
676             ComponentName componentName, String nfcid2) {
677         if (DBG) Log.d(TAG, "setNfcid2ForService");
678         ArrayList<NfcFServiceInfo> newServices = null;
679         boolean success;
680         synchronized (mLock) {
681             if (mActivated) {
682                 Log.d(TAG, "setNfcid2ForService: failed to set NFCID2 during activation");
683                 return false;
684             }
685             UserServices userServices = findOrCreateUserLocked(userId);
686             // Check if we can find this service
687             NfcFServiceInfo service = getService(userId, componentName);
688             if (service == null) {
689                 Log.e(TAG, "setNfcid2ForService: Service " + componentName + " does not exist");
690                 return false;
691             }
692             if (service.getUid() != uid) {
693                 // This is probably a good indication something is wrong here.
694                 // Either newer service installed with different uid (but then
695                 // we should have known about it), or somebody calling us from
696                 // a different uid.
697                 Log.e(TAG, "setNfcid2ForService: UID mismatch");
698                 return false;
699             }
700             if (!NfcFCardEmulation.isValidNfcid2(nfcid2)) {
701                 Log.e(TAG, "setNfcid2ForService: NFCID2 " + nfcid2 + " is not a valid NFCID2");
702                 return false;
703             }
704             // Apply dynamic NFCID2 mappings
705             nfcid2 = nfcid2.toUpperCase();
706             DynamicNfcid2 oldDynamicNfcid2 = userServices.dynamicNfcid2.get(componentName);
707             DynamicNfcid2 dynamicNfcid2 = new DynamicNfcid2(uid, nfcid2);
708             userServices.dynamicNfcid2.put(componentName, dynamicNfcid2);
709             success = writeDynamicSystemCodeNfcid2Locked();
710             if (success) {
711                 service.setDynamicNfcid2(nfcid2);
712                 newServices = new ArrayList<NfcFServiceInfo>(userServices.services.values());
713             } else {
714                 Log.e(TAG, "setNfcid2ForService: Failed to persist NFCID2");
715                 // Undo registration
716                 if (oldDynamicNfcid2 == null) {
717                     userServices.dynamicNfcid2.remove(componentName);
718                 } else {
719                     userServices.dynamicNfcid2.put(componentName, oldDynamicNfcid2);
720                 }
721             }
722         }
723         if (success) {
724             // Make callback without the lock held
725             mCallback.onNfcFServicesUpdated(userId, newServices);
726         }
727         return success;
728     }
729 
getNfcid2ForService(int userId, int uid, ComponentName componentName)730     public String getNfcid2ForService(int userId, int uid, ComponentName componentName) {
731         if (DBG) Log.d(TAG, "getNfcid2ForService");
732         NfcFServiceInfo service = getService(userId, componentName);
733         if (service != null) {
734             if (service.getUid() != uid) {
735                 Log.e(TAG, "getNfcid2ForService: UID mismatch");
736                 return null;
737             }
738             return service.getNfcid2();
739         } else {
740             Log.e(TAG, "getNfcid2ForService: Could not find service " + componentName);
741             return null;
742         }
743     }
744 
onHostEmulationActivated()745     public void onHostEmulationActivated() {
746         if (DBG) Log.d(TAG, "onHostEmulationActivated");
747         synchronized (mLock) {
748             mActivated = true;
749         }
750     }
751 
onHostEmulationDeactivated()752     public void onHostEmulationDeactivated() {
753         if (DBG) Log.d(TAG, "onHostEmulationDeactivated");
754         synchronized (mLock) {
755             mActivated = false;
756         }
757     }
758 
onNfcDisabled()759     public void onNfcDisabled() {
760         synchronized (mLock) {
761             mActivated = false;
762         }
763     }
764 
onUserSwitched()765     public void onUserSwitched() {
766         synchronized (mLock) {
767             mUserSwitched = true;
768             refreshUserProfilesLocked();
769         }
770     }
771 
onManagedProfileChanged()772     public void onManagedProfileChanged() {
773         synchronized (mLock) {
774             refreshUserProfilesLocked();
775         }
776     }
777 
refreshUserProfilesLocked()778     private void refreshUserProfilesLocked() {
779         UserManager um = mContext.createContextAsUser(
780                 UserHandle.of(ActivityManager.getCurrentUser()), /*flags=*/0)
781                 .getSystemService(UserManager.class);
782         mUserHandles = um.getEnabledProfiles();
783         List<UserHandle> removeUserHandles = new ArrayList<UserHandle>();
784 
785         for (UserHandle uh : mUserHandles) {
786             if (um.isQuietModeEnabled(uh)) {
787                 removeUserHandles.add(uh);
788             }
789         }
790         mUserHandles.removeAll(removeUserHandles);
791     }
792 
generateRandomNfcid2()793     private String generateRandomNfcid2() {
794         long min = 0L;
795         long max = 0xFFFFFFFFFFFFL;
796 
797         long randomNfcid2 = (long)Math.floor(Math.random() * (max-min+1)) + min;
798         return String.format("02FE%02X%02X%02X%02X%02X%02X",
799                 (randomNfcid2 >>> 8 * 5) & 0xFF, (randomNfcid2 >>> 8 * 4) & 0xFF,
800                 (randomNfcid2 >>> 8 * 3) & 0xFF, (randomNfcid2 >>> 8 * 2) & 0xFF,
801                 (randomNfcid2 >>> 8 * 1) & 0xFF, (randomNfcid2 >>> 8 * 0) & 0xFF);
802     }
803 
dump(FileDescriptor fd, PrintWriter pw, String[] args)804     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
805         pw.println("Registered HCE-F services for current user: ");
806         ParcelFileDescriptor pFd;
807         try {
808             pFd = ParcelFileDescriptor.dup(fd);
809             synchronized (mLock) {
810                 for (UserHandle uh : mUserHandles) {
811                     UserManager um = mContext.createContextAsUser(
812                             uh, /*flags=*/0).getSystemService(UserManager.class);
813                     pw.println("User " + Utils.maskSubstring(um.getUserName(), 3));
814                     UserServices userServices = findOrCreateUserLocked(uh.getIdentifier());
815                     for (NfcFServiceInfo service : userServices.services.values()) {
816                         service.dump(pFd, pw, args);
817                         pw.println("");
818                     }
819                     pw.println("");
820                 }
821             }
822             pFd.close();
823         } catch (IOException e) {
824             pw.println("Failed to dump HCE-F services: " + e);
825         }
826     }
827 
828     /**
829      * Dump debugging information as a RegisteredNfcFServicesCacheProto
830      *
831      * Note:
832      * See proto definition in frameworks/base/core/proto/android/nfc/card_emulation.proto
833      * When writing a nested message, must call {@link ProtoOutputStream#start(long)} before and
834      * {@link ProtoOutputStream#end(long)} after.
835      * Never reuse a proto field number. When removing a field, mark it as reserved.
836      */
dumpDebug(ProtoOutputStream proto)837     void dumpDebug(ProtoOutputStream proto) {
838         synchronized (mLock) {
839             UserServices userServices = findOrCreateUserLocked(ActivityManager.getCurrentUser());
840             for (NfcFServiceInfo service : userServices.services.values()) {
841                 long token = proto.start(RegisteredNfcFServicesCacheProto.NFC_FSERVICE_INFO);
842                 service.dumpDebug(proto);
843                 proto.end(token);
844             }
845         }
846     }
847 
848     @VisibleForTesting
isActivated()849     public boolean isActivated() {
850         return mActivated;
851     }
852 }
853