1 /* 2 * Copyright (C) 2014 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 package com.android.nfc.cardemulation; 17 18 import android.app.ActivityManager; 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.database.ContentObserver; 22 import android.net.Uri; 23 import android.nfc.cardemulation.ApduServiceInfo; 24 import android.nfc.cardemulation.CardEmulation; 25 import android.nfc.cardemulation.Utils; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.os.UserHandle; 29 import android.os.UserManager; 30 import android.provider.Settings; 31 import android.provider.Settings.SettingNotFoundException; 32 import android.sysprop.NfcProperties; 33 import android.util.Log; 34 import android.util.proto.ProtoOutputStream; 35 36 import com.android.nfc.ForegroundUtils; 37 38 import java.io.FileDescriptor; 39 import java.io.PrintWriter; 40 import java.util.List; 41 42 /** 43 * This class keeps track of what HCE/SE-based services are 44 * preferred by the user. It currently has 3 inputs: 45 * 1) The default set in tap&pay menu for payment category 46 * 2) An app in the foreground asking for a specific 47 * service for a specific category 48 * 3) If we had to disambiguate a previous tap (because no 49 * preferred service was there), we need to temporarily 50 * store the user's choice for the next tap. 51 * 52 * This class keeps track of all 3 inputs, and computes a new 53 * preferred services as needed. It then passes this service 54 * (if it changed) through a callback, which allows other components 55 * to adapt as necessary (ie the AID cache can update its AID 56 * mappings and the routing table). 57 */ 58 public class PreferredServices implements com.android.nfc.ForegroundUtils.Callback { 59 static final String TAG = "PreferredCardEmulationServices"; 60 static final boolean DBG = NfcProperties.debug_enabled().orElse(false); 61 static final Uri paymentDefaultUri = Settings.Secure.getUriFor( 62 Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT); 63 static final Uri paymentForegroundUri = Settings.Secure.getUriFor( 64 Settings.Secure.NFC_PAYMENT_FOREGROUND); 65 66 final SettingsObserver mSettingsObserver; 67 final Context mContext; 68 final RegisteredServicesCache mServiceCache; 69 final RegisteredAidCache mAidCache; 70 final Callback mCallback; 71 final ForegroundUtils mForegroundUtils; 72 final Handler mHandler = new Handler(Looper.getMainLooper()); 73 74 final class PaymentDefaults { 75 boolean preferForeground; // The current selection mode for this category 76 ComponentName settingsDefault; // The component preferred in settings (eg Tap&Pay) 77 ComponentName currentPreferred; // The computed preferred component 78 UserHandle mUserHandle; 79 } 80 81 final Object mLock = new Object(); 82 // Variables below synchronized on mLock 83 PaymentDefaults mPaymentDefaults = new PaymentDefaults(); 84 85 ComponentName mForegroundRequested; // The component preferred by fg app 86 int mForegroundUid; // The UID of the fg app, or -1 if fg app didn't request 87 88 ComponentName mNextTapDefault; // The component preferred by active disambig dialog 89 int mNextTapDefaultUserId; 90 boolean mClearNextTapDefault = false; // Set when the next tap default must be cleared 91 92 ComponentName mForegroundCurrent; // The currently computed foreground component 93 int mForegroundCurrentUid; // The UID of the currently computed foreground component 94 95 public interface Callback { 96 /** 97 * Notify when preferred payment service is changed 98 */ onPreferredPaymentServiceChanged(int userId, ComponentName service)99 void onPreferredPaymentServiceChanged(int userId, ComponentName service); 100 /** 101 * Notify when preferred foreground service is changed 102 */ onPreferredForegroundServiceChanged(int userId, ComponentName service)103 void onPreferredForegroundServiceChanged(int userId, ComponentName service); 104 } 105 PreferredServices(Context context, RegisteredServicesCache serviceCache, RegisteredAidCache aidCache, Callback callback)106 public PreferredServices(Context context, RegisteredServicesCache serviceCache, 107 RegisteredAidCache aidCache, Callback callback) { 108 mContext = context; 109 mForegroundUtils = ForegroundUtils.getInstance( 110 context.getSystemService(ActivityManager.class)); 111 mServiceCache = serviceCache; 112 mAidCache = aidCache; 113 mCallback = callback; 114 mSettingsObserver = new SettingsObserver(mHandler); 115 mContext.getContentResolver().registerContentObserverAsUser( 116 paymentDefaultUri, 117 true, mSettingsObserver, UserHandle.ALL); 118 119 mContext.getContentResolver().registerContentObserverAsUser( 120 paymentForegroundUri, 121 true, mSettingsObserver, UserHandle.ALL); 122 123 // Load current settings defaults for payments 124 loadDefaultsFromSettings(ActivityManager.getCurrentUser(), false); 125 } 126 127 private final class SettingsObserver extends ContentObserver { SettingsObserver(Handler handler)128 public SettingsObserver(Handler handler) { 129 super(handler); 130 } 131 132 @Override onChange(boolean selfChange, Uri uri)133 public void onChange(boolean selfChange, Uri uri) { 134 super.onChange(selfChange, uri); 135 // Do it just for the current user. If it was in fact 136 // a change made for another user, we'll sync it down 137 // on user switch. 138 int currentUser = ActivityManager.getCurrentUser(); 139 loadDefaultsFromSettings(currentUser, false); 140 } 141 }; 142 loadDefaultsFromSettings(int userId, boolean force)143 void loadDefaultsFromSettings(int userId, boolean force) { 144 boolean paymentDefaultChanged = false; 145 boolean paymentPreferForegroundChanged = false; 146 // Load current payment default from settings 147 UserHandle currentUser = UserHandle.of(ActivityManager.getCurrentUser()); 148 UserManager um = mContext.createContextAsUser(currentUser, /*flags=*/0) 149 .getSystemService(UserManager.class); 150 List<UserHandle> userHandles = um.getEnabledProfiles(); 151 152 String name = null; 153 String newDefaultName = null; 154 UserHandle newUser = null; 155 // search for default payment setting within enabled profiles 156 for (UserHandle uh : userHandles) { 157 name = Settings.Secure.getString( 158 mContext.createContextAsUser(uh, 0).getContentResolver(), 159 Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT); 160 if (name != null) { 161 newUser = uh; 162 newDefaultName = name; 163 } 164 if (uh.getIdentifier() == userId) { 165 currentUser = uh; 166 } 167 } 168 if (currentUser == null) { 169 Log.e(TAG, "NULL/ Error fetching currentUser info"); 170 return; 171 } 172 // no default payment setting in all profles 173 if (newUser == null) { 174 newUser = currentUser; 175 } 176 ComponentName newDefault = newDefaultName != null 177 ? ComponentName.unflattenFromString(newDefaultName) : null; 178 boolean preferForeground = false; 179 try { 180 // get the setting from the main user instead of from the user profiles. 181 preferForeground = Settings.Secure.getInt(mContext 182 .createContextAsUser(currentUser, 0).getContentResolver(), 183 Settings.Secure.NFC_PAYMENT_FOREGROUND) != 0; 184 } catch (SettingNotFoundException e) { 185 } 186 synchronized (mLock) { 187 paymentPreferForegroundChanged = (preferForeground != mPaymentDefaults.preferForeground); 188 mPaymentDefaults.preferForeground = preferForeground; 189 190 mPaymentDefaults.settingsDefault = newDefault; 191 if (newDefault != null && (!newDefault.equals(mPaymentDefaults.currentPreferred) 192 || mPaymentDefaults.mUserHandle.getIdentifier() != newUser.getIdentifier())) { 193 paymentDefaultChanged = true; 194 mPaymentDefaults.currentPreferred = newDefault; 195 mPaymentDefaults.mUserHandle = newUser; 196 } else if (newDefault == null && mPaymentDefaults.currentPreferred != null) { 197 paymentDefaultChanged = true; 198 mPaymentDefaults.currentPreferred = newDefault; 199 mPaymentDefaults.mUserHandle = newUser; 200 } else { 201 // Same default as before 202 } 203 } 204 // Notify if anything changed 205 if (paymentDefaultChanged || force) { 206 mCallback.onPreferredPaymentServiceChanged(newUser.getIdentifier(), newDefault); 207 } 208 if (paymentPreferForegroundChanged || force) { 209 computePreferredForegroundService(); 210 } 211 } 212 computePreferredForegroundService()213 void computePreferredForegroundService() { 214 ComponentName preferredService = null; 215 int preferredServiceUserId; 216 boolean changed = false; 217 synchronized (mLock) { 218 // Prio 1: next tap default 219 preferredService = mNextTapDefault; 220 preferredServiceUserId = mNextTapDefaultUserId; 221 if (preferredService == null) { 222 // Prio 2: foreground requested by app 223 preferredService = mForegroundRequested; 224 preferredServiceUserId = 225 UserHandle.getUserHandleForUid(mForegroundUid).getIdentifier(); 226 } 227 if (preferredService != null && (!preferredService.equals(mForegroundCurrent) 228 || preferredServiceUserId 229 != UserHandle.getUserHandleForUid(mForegroundCurrentUid).getIdentifier())) { 230 mForegroundCurrent = preferredService; 231 mForegroundCurrentUid = mForegroundUid; 232 changed = true; 233 } else if (preferredService == null && mForegroundCurrent != null){ 234 mForegroundCurrent = preferredService; 235 mForegroundCurrentUid = mForegroundUid; 236 changed = true; 237 } 238 } 239 // Notify if anything changed 240 if (changed) { 241 mCallback.onPreferredForegroundServiceChanged(preferredServiceUserId, preferredService); 242 } 243 } 244 245 /** 246 * Set default service for next tap 247 */ setDefaultForNextTap(int userId, ComponentName service)248 public boolean setDefaultForNextTap(int userId, ComponentName service) { 249 // This is a trusted API, so update without checking 250 synchronized (mLock) { 251 mNextTapDefault = service; 252 mNextTapDefaultUserId = userId; 253 } 254 computePreferredForegroundService(); 255 return true; 256 } 257 onServicesUpdated()258 public void onServicesUpdated() { 259 // If this service is the current foreground service, verify 260 // there are no conflicts 261 boolean changed = false; 262 synchronized (mLock) { 263 // Check if the current foreground service is still allowed to override; 264 // it could have registered new AIDs that make it conflict with user 265 // preferences. 266 if (mForegroundCurrent != null) { 267 if (!isForegroundAllowedLocked(mForegroundCurrent, mForegroundCurrentUid)) { 268 Log.d(TAG, "Removing foreground preferred service."); 269 mForegroundRequested = null; 270 mForegroundUid = -1; 271 mForegroundCurrentUid = -1; 272 changed = true; 273 } 274 } else { 275 // Don't care about this service 276 } 277 } 278 if (changed) { 279 computePreferredForegroundService(); 280 } 281 } 282 283 // Verifies whether a service is allowed to register as preferred isForegroundAllowedLocked(ComponentName service, int callingUid)284 boolean isForegroundAllowedLocked(ComponentName service, int callingUid) { 285 if (service.equals(mPaymentDefaults.currentPreferred)) { 286 // If the requester is already the payment default, allow it to request foreground 287 // override as well (it could use this to make sure it handles AIDs of category OTHER) 288 return true; 289 } 290 ApduServiceInfo serviceInfo = mServiceCache.getService( 291 UserHandle.getUserHandleForUid(callingUid).getIdentifier(), service); 292 if (serviceInfo == null) { 293 Log.d(TAG, "Requested foreground service unexpectedly removed"); 294 return false; 295 } 296 // Do some quick checking 297 if (!mPaymentDefaults.preferForeground) { 298 // Foreground apps are not allowed to override payment default 299 // Check if this app registers payment AIDs, in which case we'll fail anyway 300 if (serviceInfo.hasCategory(CardEmulation.CATEGORY_PAYMENT)) { 301 Log.d(TAG, "User doesn't allow payment services to be overridden."); 302 return false; 303 } 304 // If no payment AIDs, get AIDs of category other, and see if there's any 305 // conflict with payment AIDs of current default payment app. That means 306 // the current default payment app said this was a payment AID, and the 307 // foreground app says it was not. In this case we'll still prefer the payment 308 // app, since that is the one that the user has explicitly selected (and said 309 // it's not allowed to be overridden). 310 final List<String> otherAids = serviceInfo.getAids(); 311 ApduServiceInfo paymentServiceInfo = mServiceCache.getService( 312 mPaymentDefaults.mUserHandle.getIdentifier(), 313 mPaymentDefaults.currentPreferred); 314 if (paymentServiceInfo != null && otherAids != null && otherAids.size() > 0) { 315 for (String aid : otherAids) { 316 RegisteredAidCache.AidResolveInfo resolveInfo = mAidCache.resolveAid(aid); 317 if (CardEmulation.CATEGORY_PAYMENT.equals(resolveInfo.category) && 318 paymentServiceInfo.equals(resolveInfo.defaultService)) { 319 if (DBG) Log.d(TAG, "AID " + aid + " is handled by the default payment app," 320 + " and the user has not allowed payments to be overridden."); 321 return false; 322 } 323 } 324 return true; 325 } else { 326 // Could not find payment service or fg app doesn't register other AIDs; 327 // okay to proceed. 328 return true; 329 } 330 } else { 331 // Payment allows override, so allow anything. 332 return true; 333 } 334 } 335 registerPreferredForegroundService(ComponentName service, int callingUid)336 public boolean registerPreferredForegroundService(ComponentName service, int callingUid) { 337 boolean success = false; 338 synchronized (mLock) { 339 if (isForegroundAllowedLocked(service, callingUid)) { 340 if (mForegroundUtils.registerUidToBackgroundCallback(this, callingUid)) { 341 mForegroundRequested = service; 342 mForegroundUid = callingUid; 343 success = true; 344 } else { 345 Log.e(TAG, "Calling UID is not in the foreground, ignorning!"); 346 success = false; 347 } 348 } else { 349 Log.e(TAG, "Requested foreground service conflicts or was removed."); 350 } 351 } 352 if (success) { 353 computePreferredForegroundService(); 354 } 355 return success; 356 } 357 unregisterForegroundService(int uid)358 boolean unregisterForegroundService(int uid) { 359 boolean success = false; 360 synchronized (mLock) { 361 if (mForegroundUid == uid) { 362 mForegroundRequested = null; 363 mForegroundUid = -1; 364 success = true; 365 } // else, other UID in foreground 366 } 367 if (success) { 368 computePreferredForegroundService(); 369 } 370 return success; 371 } 372 unregisteredPreferredForegroundService(int callingUid)373 public boolean unregisteredPreferredForegroundService(int callingUid) { 374 // Verify the calling UID is in the foreground 375 if (mForegroundUtils.isInForeground(callingUid)) { 376 return unregisterForegroundService(callingUid); 377 } else { 378 Log.e(TAG, "Calling UID is not in the foreground, ignorning!"); 379 return false; 380 } 381 } 382 383 @Override onUidToBackground(int uid)384 public void onUidToBackground(int uid) { 385 unregisterForegroundService(uid); 386 } 387 onHostEmulationActivated()388 public void onHostEmulationActivated() { 389 synchronized (mLock) { 390 mClearNextTapDefault = (mNextTapDefault != null); 391 } 392 } 393 onHostEmulationDeactivated()394 public void onHostEmulationDeactivated() { 395 // If we had any next tap defaults set, clear them out 396 boolean changed = false; 397 synchronized (mLock) { 398 if (mClearNextTapDefault) { 399 // The reason we need to check this boolean is because the next tap 400 // default may have been set while the user held the phone 401 // on the reader; when the user then removes his phone from 402 // the reader (causing the "onHostEmulationDeactivated" event), 403 // the next tap default would immediately be cleared 404 // again. Instead, clear out defaults only if a next tap default 405 // had already been set at time of activation, which is captured 406 // by mClearNextTapDefault. 407 if (mNextTapDefault != null) { 408 mNextTapDefault = null; 409 changed = true; 410 } 411 mClearNextTapDefault = false; 412 } 413 } 414 if (changed) { 415 computePreferredForegroundService(); 416 } 417 } 418 onUserSwitched(int userId)419 public void onUserSwitched(int userId) { 420 loadDefaultsFromSettings(userId, true); 421 } 422 packageHasPreferredService(String packageName)423 public boolean packageHasPreferredService(String packageName) { 424 if (packageName == null) return false; 425 synchronized (mLock) { 426 if (mPaymentDefaults.currentPreferred != null 427 && packageName.equals(mPaymentDefaults.currentPreferred.getPackageName())) { 428 return true; 429 } 430 return (mForegroundCurrent != null 431 && packageName.equals(mForegroundCurrent.getPackageName())); 432 } 433 } 434 dump(FileDescriptor fd, PrintWriter pw, String[] args)435 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 436 synchronized (mLock) { 437 pw.println("Preferred services (in order of importance): "); 438 pw.println(" *** Current preferred foreground service: " + mForegroundCurrent 439 + " (UID:" + mForegroundCurrentUid + ")"); 440 pw.println(" *** Current preferred payment service: " 441 + mPaymentDefaults.currentPreferred + "(" 442 + getUserName(mPaymentDefaults.mUserHandle) + ")"); 443 pw.println(" Next tap default: " + mNextTapDefault 444 + " (" + getUserName(UserHandle.of(mNextTapDefaultUserId)) + ")"); 445 pw.println(" Default for foreground app (UID: " + mForegroundUid 446 + "): " + mForegroundRequested); 447 pw.println(" Default in payment settings: " + mPaymentDefaults.settingsDefault 448 + "(" + getUserName(mPaymentDefaults.mUserHandle) + ")"); 449 pw.println(" Payment settings allows override: " 450 + mPaymentDefaults.preferForeground); 451 pw.println(""); 452 } 453 } 454 getUserName(UserHandle uh)455 private String getUserName(UserHandle uh) { 456 if (uh == null) { 457 return null; 458 } 459 UserManager um = mContext.createContextAsUser( 460 uh, /*flags=*/0).getSystemService(UserManager.class); 461 if (um == null) { 462 return null; 463 } 464 return um.getUserName(); 465 } 466 467 /** 468 * Dump debugging information as a PreferredServicesProto 469 * 470 * Note: 471 * See proto definition in frameworks/base/core/proto/android/nfc/card_emulation.proto 472 * When writing a nested message, must call {@link ProtoOutputStream#start(long)} before and 473 * {@link ProtoOutputStream#end(long)} after. 474 * Never reuse a proto field number. When removing a field, mark it as reserved. 475 */ dumpDebug(ProtoOutputStream proto)476 void dumpDebug(ProtoOutputStream proto) { 477 synchronized (mLock) { 478 if (mForegroundCurrent != null) { 479 Utils.dumpDebugComponentName( 480 mForegroundCurrent, proto, PreferredServicesProto.FOREGROUND_CURRENT); 481 } 482 if (mPaymentDefaults.currentPreferred != null) { 483 mPaymentDefaults.currentPreferred.dumpDebug(proto, 484 PreferredServicesProto.FOREGROUND_CURRENT); 485 } 486 if (mNextTapDefault != null) { 487 mNextTapDefault.dumpDebug(proto, PreferredServicesProto.NEXT_TAP_DEFAULT); 488 } 489 proto.write(PreferredServicesProto.FOREGROUND_UID, mForegroundUid); 490 if (mForegroundRequested != null) { 491 Utils.dumpDebugComponentName( 492 mForegroundRequested, proto, PreferredServicesProto.FOREGROUND_REQUESTED); 493 } 494 if (mPaymentDefaults.settingsDefault != null) { 495 mPaymentDefaults.settingsDefault.dumpDebug(proto, 496 PreferredServicesProto.SETTINGS_DEFAULT); 497 } 498 proto.write(PreferredServicesProto.PREFER_FOREGROUND, 499 mPaymentDefaults.preferForeground); 500 } 501 } 502 } 503