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