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