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