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 
     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