PreferredServices.java revision ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64
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 Callback mCallback;
65    final ForegroundUtils mForegroundUtils = ForegroundUtils.getInstance();
66    final Handler mHandler = new Handler(Looper.getMainLooper());
67
68    final class PaymentDefaults {
69        boolean preferForeground; // The current selection mode for this category
70        ComponentName settingsDefault; // The component preferred in settings (eg Tap&Pay)
71        ComponentName currentPreferred; // The computed preferred component
72    }
73
74    final Object mLock = new Object();
75    // Variables below synchronized on mLock
76    PaymentDefaults mPaymentDefaults = new PaymentDefaults();
77
78    ComponentName mForegroundRequested; // The component preferred by fg app
79    int mForegroundUid; // The UID of the fg app, or -1 if fg app didn't request
80
81    ComponentName mNextTapDefault; // The component preferred by active disambig dialog
82    boolean mClearNextTapDefault = false; // Set when the next tap default must be cleared
83
84    ComponentName mForegroundCurrent; // The currently computed foreground component
85
86    public interface Callback {
87        void onPreferredPaymentServiceChanged(ComponentName service);
88        void onPreferredForegroundServiceChanged(ComponentName service);
89    }
90
91    public PreferredServices(Context context, RegisteredServicesCache serviceCache,
92            Callback callback) {
93        mContext = context;
94        mServiceCache = serviceCache;
95        mCallback = callback;
96        mSettingsObserver = new SettingsObserver(mHandler);
97        mContext.getContentResolver().registerContentObserver(
98                paymentDefaultUri,
99                true, mSettingsObserver, UserHandle.USER_ALL);
100
101        mContext.getContentResolver().registerContentObserver(
102                paymentForegroundUri,
103                true, mSettingsObserver, UserHandle.USER_ALL);
104
105        // Load current settings defaults for payments
106        loadDefaultsFromSettings(ActivityManager.getCurrentUser());
107    }
108
109    private final class SettingsObserver extends ContentObserver {
110        public SettingsObserver(Handler handler) {
111            super(handler);
112        }
113
114        @Override
115        public void onChange(boolean selfChange, Uri uri) {
116            super.onChange(selfChange, uri);
117            // Do it just for the current user. If it was in fact
118            // a change made for another user, we'll sync it down
119            // on user switch.
120            int currentUser = ActivityManager.getCurrentUser();
121            loadDefaultsFromSettings(currentUser);
122        }
123    };
124
125    void loadDefaultsFromSettings(int userId) {
126        boolean paymentDefaultChanged = false;
127        boolean paymentPreferForegroundChanged = false;
128        // Load current payment default from settings
129        String name = Settings.Secure.getStringForUser(
130                mContext.getContentResolver(), Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
131                userId);
132        ComponentName newDefault = name != null ? ComponentName.unflattenFromString(name) : null;
133        boolean preferForeground = false;
134        try {
135            preferForeground = Settings.Secure.getInt(mContext.getContentResolver(),
136                    Settings.Secure.NFC_PAYMENT_FOREGROUND) != 0;
137        } catch (SettingNotFoundException e) {
138        }
139        synchronized (mLock) {
140            paymentPreferForegroundChanged = (preferForeground != mPaymentDefaults.preferForeground);
141            mPaymentDefaults.preferForeground = preferForeground;
142
143            mPaymentDefaults.settingsDefault = newDefault;
144            if (newDefault != null && !newDefault.equals(mPaymentDefaults.currentPreferred)) {
145                paymentDefaultChanged = true;
146                mPaymentDefaults.currentPreferred = newDefault;
147            } else if (newDefault == null && mPaymentDefaults.currentPreferred != null) {
148                paymentDefaultChanged = true;
149                mPaymentDefaults.currentPreferred = newDefault;
150            } else {
151                // Same default as before
152            }
153        }
154        // Notify if anything changed
155        if (paymentDefaultChanged) {
156            mCallback.onPreferredPaymentServiceChanged(newDefault);
157        }
158        if (paymentPreferForegroundChanged) {
159            computePreferredForegroundService();
160        }
161    }
162
163    void computePreferredForegroundService() {
164        ComponentName preferredService = null;
165        boolean changed = false;
166        synchronized (mLock) {
167            // Prio 1: next tap default
168            preferredService = mNextTapDefault;
169            if (preferredService == null) {
170                // Prio 2: foreground requested by app
171                preferredService = mForegroundRequested;
172            }
173            if (preferredService != null && !preferredService.equals(mForegroundCurrent)) {
174                mForegroundCurrent = preferredService;
175                changed = true;
176            } else if (preferredService == null && mForegroundCurrent != null){
177                mForegroundCurrent = preferredService;
178                changed = true;
179            }
180        }
181        // Notify if anything changed
182        if (changed) {
183            mCallback.onPreferredForegroundServiceChanged(preferredService);
184        }
185    }
186
187    public boolean setDefaultForNextTap(ComponentName service) {
188        // This is a trusted API, so update without checking
189        synchronized (mLock) {
190            mNextTapDefault = service;
191        }
192        computePreferredForegroundService();
193        return true;
194    }
195
196    public void onServicesUpdated() {
197        // If this service is the current foreground service, verify
198        // there are no conflicts
199        boolean changed = false;
200        synchronized (mLock) {
201            // Check if the current foreground service is still allowed to override;
202            // it could have registered new AIDs that make it conflict with user
203            // preferences.
204            if (mForegroundCurrent != null) {
205                if (!isForegroundAllowedLocked(mForegroundCurrent))  {
206                    Log.d(TAG, "Removing foreground preferred service because of conflict.");
207                    mForegroundRequested = null;
208                    mForegroundUid = -1;
209                    changed = true;
210                }
211            } else {
212                // Don't care about this service
213            }
214        }
215        if (changed) {
216            computePreferredForegroundService();
217        }
218    }
219
220    // Verifies whether a service is allowed to register as preferred
221    boolean isForegroundAllowedLocked(ComponentName service) {
222        ApduServiceInfo serviceInfo = mServiceCache.getService(ActivityManager.getCurrentUser(),
223                service);
224        boolean allowed = false;
225        // Do some sanity checking
226        if (!mPaymentDefaults.preferForeground) {
227            // Foreground apps are not allowed to override payment default
228            // Check if this app registers payment AIDs, in which case we'll fail anyway
229            if (serviceInfo.hasCategory(CardEmulation.CATEGORY_PAYMENT)) {
230                Log.d(TAG, "User doesn't allow payment services to be overridden.");
231                return false;
232            }
233            // If no payment AIDs, get AIDs of category other, and see if there's any
234            // conflict with payment AIDs of current default payment app. That means
235            // the current default payment app said this was a payment AID, and the
236            // foreground app says it was not. In this case we'll still prefer the payment
237            // app, since that is the one that the user has explicitly selected (and said
238            // it's not allowed to be overridden).
239            final ArrayList<String> otherAids = serviceInfo.getAids();
240            ApduServiceInfo paymentServiceInfo = mServiceCache.getService(
241                    ActivityManager.getCurrentUser(), mPaymentDefaults.currentPreferred);
242            if (paymentServiceInfo != null && otherAids != null && otherAids.size() > 0) {
243                for (String aid : otherAids) {
244                    if (CardEmulation.CATEGORY_PAYMENT.equals(
245                            paymentServiceInfo.getCategoryForAid(aid))) {
246                        Log.e(TAG, "AID " + aid + " is registered by the default payment app, " +
247                                "and the user has not allowed payments to be overridden.");
248                        return false;
249                    }
250                }
251                allowed = true;
252            } else {
253                // Could not find payment service or fg app doesn't register other AIDs;
254                // okay to proceed.
255                allowed = true;
256            }
257        }
258
259        return allowed;
260    }
261
262    public boolean registerPreferredForegroundService(ComponentName service, int callingUid) {
263        boolean success = false;
264        synchronized (mLock) {
265            if (isForegroundAllowedLocked(service)) {
266                if (mForegroundUtils.registerUidToBackgroundCallback(this, callingUid)) {
267                    mForegroundRequested = service;
268                    mForegroundUid = callingUid;
269                    success = true;
270                } else {
271                    Log.e(TAG, "Calling UID is not in the foreground, ignorning!");
272                    success = false;
273                }
274            }
275        }
276        if (success) {
277            computePreferredForegroundService();
278        }
279        return success;
280    }
281
282    boolean unregisterForegroundService(int uid) {
283        boolean success = false;
284        synchronized (mLock) {
285            if (mForegroundUid == uid) {
286                mForegroundRequested = null;
287                mForegroundUid = -1;
288                success = true;
289            } // else, other UID in foreground
290        }
291        if (success) {
292            computePreferredForegroundService();
293        }
294        return success;
295    }
296
297    public boolean unregisteredPreferredForegroundService(int callingUid) {
298        // Verify the calling UID is in the foreground
299        if (mForegroundUtils.isInForeground(callingUid)) {
300            return unregisterForegroundService(callingUid);
301        } else {
302            Log.e(TAG, "Calling UID is not in the foreground, ignorning!");
303            return false;
304        }
305    }
306
307    @Override
308    public void onUidToBackground(int uid) {
309        unregisterForegroundService(uid);
310    }
311
312    public void onHostEmulationActivated() {
313        synchronized (mLock) {
314            mClearNextTapDefault = (mNextTapDefault != null);
315        }
316    }
317
318    public void onHostEmulationDeactivated() {
319        // If we had any next tap defaults set, clear them out
320        boolean changed = false;
321        synchronized (mLock) {
322            if (mClearNextTapDefault) {
323                // The reason we need to check this boolean is because the next tap
324                // default may have been set while the user held the phone
325                // on the reader; when the user then removes his phone from
326                // the reader (causing the "onHostEmulationDeactivated" event),
327                // the next tap default would immediately be cleared
328                // again. Instead, clear out defaults only if a next tap default
329                // had already been set at time of activation, which is captured
330                // by mClearNextTapDefault.
331                if (mNextTapDefault != null) {
332                    mNextTapDefault = null;
333                    changed = true;
334                }
335                mClearNextTapDefault = false;
336            }
337        }
338        if (changed) {
339            computePreferredForegroundService();
340        }
341    }
342
343    public void onUserSwitched(int userId) {
344        loadDefaultsFromSettings(userId);
345    }
346
347    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
348        pw.println("Preferred services (in order of importance): ");
349        pw.println("    *** Current preferred foreground service: " + mForegroundCurrent);
350        pw.println("    *** Current preferred payment service: " + mPaymentDefaults.currentPreferred);
351        pw.println("        Next tap default: " + mNextTapDefault);
352        pw.println("        Default for foreground app (UID: " + mForegroundUid +
353                "): " + mForegroundRequested);
354        pw.println("        Default in payment settings: " + mPaymentDefaults.settingsDefault);
355        pw.println("        Payment settings allows override: " + mPaymentDefaults.preferForeground);
356        pw.println("");
357    }
358}
359