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