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