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