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