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