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