Home | History | Annotate | Download | only in cardemulation
      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 {
     89         void onPreferredPaymentServiceChanged(ComponentName service);
     90         void onPreferredForegroundServiceChanged(ComponentName service);
     91     }
     92 
     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 {
    113         public SettingsObserver(Handler handler) {
    114             super(handler);
    115         }
    116 
    117         @Override
    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 
    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.getIntForUser(mContext.getContentResolver(),
    139                     Settings.Secure.NFC_PAYMENT_FOREGROUND, userId) != 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 
    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 
    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 
    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.");
    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
    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         if (serviceInfo == null) {
    233             Log.d(TAG, "Requested foreground service unexpectedly removed");
    234             return false;
    235         }
    236         // Do some sanity checking
    237         if (!mPaymentDefaults.preferForeground) {
    238             // Foreground apps are not allowed to override payment default
    239             // Check if this app registers payment AIDs, in which case we'll fail anyway
    240             if (serviceInfo.hasCategory(CardEmulation.CATEGORY_PAYMENT)) {
    241                 Log.d(TAG, "User doesn't allow payment services to be overridden.");
    242                 return false;
    243             }
    244             // If no payment AIDs, get AIDs of category other, and see if there's any
    245             // conflict with payment AIDs of current default payment app. That means
    246             // the current default payment app said this was a payment AID, and the
    247             // foreground app says it was not. In this case we'll still prefer the payment
    248             // app, since that is the one that the user has explicitly selected (and said
    249             // it's not allowed to be overridden).
    250             final List<String> otherAids = serviceInfo.getAids();
    251             ApduServiceInfo paymentServiceInfo = mServiceCache.getService(
    252                     ActivityManager.getCurrentUser(), mPaymentDefaults.currentPreferred);
    253             if (paymentServiceInfo != null && otherAids != null && otherAids.size() > 0) {
    254                 for (String aid : otherAids) {
    255                     RegisteredAidCache.AidResolveInfo resolveInfo = mAidCache.resolveAid(aid);
    256                     if (CardEmulation.CATEGORY_PAYMENT.equals(resolveInfo.category) &&
    257                             paymentServiceInfo.equals(resolveInfo.defaultService)) {
    258                         Log.d(TAG, "AID " + aid + " is handled by the default payment app, " +
    259                                 "and the user has not allowed payments to be overridden.");
    260                         return false;
    261                     }
    262                 }
    263                 return true;
    264             } else {
    265                 // Could not find payment service or fg app doesn't register other AIDs;
    266                 // okay to proceed.
    267                 return true;
    268             }
    269         } else {
    270             // Payment allows override, so allow anything.
    271             return true;
    272         }
    273     }
    274 
    275     public boolean registerPreferredForegroundService(ComponentName service, int callingUid) {
    276         boolean success = false;
    277         synchronized (mLock) {
    278             if (isForegroundAllowedLocked(service)) {
    279                 if (mForegroundUtils.registerUidToBackgroundCallback(this, callingUid)) {
    280                     mForegroundRequested = service;
    281                     mForegroundUid = callingUid;
    282                     success = true;
    283                 } else {
    284                     Log.e(TAG, "Calling UID is not in the foreground, ignorning!");
    285                     success = false;
    286                 }
    287             } else {
    288                 Log.e(TAG, "Requested foreground service conflicts or was removed.");
    289             }
    290         }
    291         if (success) {
    292             computePreferredForegroundService();
    293         }
    294         return success;
    295     }
    296 
    297     boolean unregisterForegroundService(int uid) {
    298         boolean success = false;
    299         synchronized (mLock) {
    300             if (mForegroundUid == uid) {
    301                 mForegroundRequested = null;
    302                 mForegroundUid = -1;
    303                 success = true;
    304             } // else, other UID in foreground
    305         }
    306         if (success) {
    307             computePreferredForegroundService();
    308         }
    309         return success;
    310     }
    311 
    312     public boolean unregisteredPreferredForegroundService(int callingUid) {
    313         // Verify the calling UID is in the foreground
    314         if (mForegroundUtils.isInForeground(callingUid)) {
    315             return unregisterForegroundService(callingUid);
    316         } else {
    317             Log.e(TAG, "Calling UID is not in the foreground, ignorning!");
    318             return false;
    319         }
    320     }
    321 
    322     @Override
    323     public void onUidToBackground(int uid) {
    324         unregisterForegroundService(uid);
    325     }
    326 
    327     public void onHostEmulationActivated() {
    328         synchronized (mLock) {
    329             mClearNextTapDefault = (mNextTapDefault != null);
    330         }
    331     }
    332 
    333     public void onHostEmulationDeactivated() {
    334         // If we had any next tap defaults set, clear them out
    335         boolean changed = false;
    336         synchronized (mLock) {
    337             if (mClearNextTapDefault) {
    338                 // The reason we need to check this boolean is because the next tap
    339                 // default may have been set while the user held the phone
    340                 // on the reader; when the user then removes his phone from
    341                 // the reader (causing the "onHostEmulationDeactivated" event),
    342                 // the next tap default would immediately be cleared
    343                 // again. Instead, clear out defaults only if a next tap default
    344                 // had already been set at time of activation, which is captured
    345                 // by mClearNextTapDefault.
    346                 if (mNextTapDefault != null) {
    347                     mNextTapDefault = null;
    348                     changed = true;
    349                 }
    350                 mClearNextTapDefault = false;
    351             }
    352         }
    353         if (changed) {
    354             computePreferredForegroundService();
    355         }
    356     }
    357 
    358     public void onUserSwitched(int userId) {
    359         loadDefaultsFromSettings(userId);
    360     }
    361 
    362     public boolean packageHasPreferredService(String packageName) {
    363         if (packageName == null) return false;
    364 
    365         if (mPaymentDefaults.currentPreferred != null &&
    366                 packageName.equals(mPaymentDefaults.currentPreferred.getPackageName())) {
    367             return true;
    368         } else if (mForegroundCurrent != null &&
    369                 packageName.equals(mForegroundCurrent.getPackageName())) {
    370             return true;
    371         } else {
    372             return false;
    373         }
    374     }
    375 
    376     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    377         pw.println("Preferred services (in order of importance): ");
    378         pw.println("    *** Current preferred foreground service: " + mForegroundCurrent);
    379         pw.println("    *** Current preferred payment service: " + mPaymentDefaults.currentPreferred);
    380         pw.println("        Next tap default: " + mNextTapDefault);
    381         pw.println("        Default for foreground app (UID: " + mForegroundUid +
    382                 "): " + mForegroundRequested);
    383         pw.println("        Default in payment settings: " + mPaymentDefaults.settingsDefault);
    384         pw.println("        Payment settings allows override: " + mPaymentDefaults.preferForeground);
    385         pw.println("");
    386     }
    387 }
    388