PreferredServices.java revision 0ad654d57bf17d73f2a7de5c44c9d69b7b450962
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;
21
22import com.android.nfc.ForegroundUtils;
23
24import android.app.ActivityManager;
25import android.content.ComponentName;
26import android.content.Context;
27import android.database.ContentObserver;
28import android.net.Uri;
29import android.nfc.cardemulation.ApduServiceInfo;
30import android.nfc.cardemulation.CardEmulation;
31import android.os.Handler;
32import android.os.Looper;
33import android.os.UserHandle;
34import android.provider.Settings;
35import android.provider.Settings.SettingNotFoundException;
36import 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 */
54public 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