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