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.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 
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.");
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         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 
registerPreferredForegroundService(ComponentName service, int callingUid)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 
unregisterForegroundService(int uid)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 
unregisteredPreferredForegroundService(int callingUid)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
onUidToBackground(int uid)323     public void onUidToBackground(int uid) {
324         unregisterForegroundService(uid);
325     }
326 
onHostEmulationActivated()327     public void onHostEmulationActivated() {
328         synchronized (mLock) {
329             mClearNextTapDefault = (mNextTapDefault != null);
330         }
331     }
332 
onHostEmulationDeactivated()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 
onUserSwitched(int userId)358     public void onUserSwitched(int userId) {
359         loadDefaultsFromSettings(userId);
360     }
361 
packageHasPreferredService(String packageName)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 
dump(FileDescriptor fd, PrintWriter pw, String[] args)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