RegisteredServicesCache.java revision 4358172f18871d58d5bc2050e4d9d0bf9bc2d5e5
19f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen/*
29f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * Copyright (C) 2013 The Android Open Source Project
39f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen *
49f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * Licensed under the Apache License, Version 2.0 (the "License");
59f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * you may not use this file except in compliance with the License.
69f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * You may obtain a copy of the License at
79f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen *
89f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen *      http://www.apache.org/licenses/LICENSE-2.0
99f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen *
109f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * Unless required by applicable law or agreed to in writing, software
119f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * distributed under the License is distributed on an "AS IS" BASIS,
129f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * See the License for the specific language governing permissions and
149f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen * limitations under the License.
159f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen */
169f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
179f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenpackage com.android.nfc.cardemulation;
189f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
199f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport org.xmlpull.v1.XmlPullParserException;
209f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.app.ActivityManager;
219f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.BroadcastReceiver;
229f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.ComponentName;
239f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.Context;
249f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.Intent;
259f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.IntentFilter;
269f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.pm.PackageManager;
279f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.pm.ResolveInfo;
289f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.pm.ServiceInfo;
29a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenenimport android.content.pm.PackageManager.NameNotFoundException;
30a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenenimport android.database.ContentObserver;
31a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenenimport android.net.Uri;
32a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenenimport android.nfc.cardemulation.ApduServiceInfo;
33a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenenimport android.nfc.cardemulation.ApduServiceInfo.AidGroup;
34a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenenimport android.nfc.cardemulation.CardEmulationManager;
356493859b424f65af79e3e13835f7dfed38495c00Martijn Coenenimport android.nfc.cardemulation.HostApduService;
3689c893312d524f50c47b0d32071f3de8631197f3Martijn Coenenimport android.nfc.cardemulation.OffHostApduService;
37a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenenimport android.os.Handler;
38a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenenimport android.os.Looper;
399f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.os.UserHandle;
40a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenenimport android.provider.Settings;
419f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.util.Log;
429f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.util.SparseArray;
434358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen
449f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport com.google.android.collect.Maps;
459f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
469f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.io.IOException;
479f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.ArrayList;
489f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.HashMap;
499f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.HashSet;
50d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenenimport java.util.Iterator;
519f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.List;
529f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.Map;
539f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.Set;
549f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.SortedMap;
559f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.TreeMap;
569f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.concurrent.atomic.AtomicReference;
579f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
586493859b424f65af79e3e13835f7dfed38495c00Martijn Coenenpublic class RegisteredServicesCache {
599f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    static final String TAG = "RegisteredAidCache";
609f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    static final boolean DEBUG = true;
619f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
62a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    static final String AID_CATEGORY_PAYMENT = CardEmulationManager.CATEGORY_PAYMENT;
63a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
649f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    final Context mContext;
659f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    final AtomicReference<BroadcastReceiver> mReceiver;
66d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen    final AidRoutingManager mRoutingManager;
679f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
689f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    final Object mLock = new Object();
699f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    // All variables below synchronized on mLock
709f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
719f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    // mUserServices holds the card emulation services that are running for each user
729f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    final SparseArray<UserServices> mUserServices = new SparseArray<UserServices>();
739f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
749f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    // mAidServices is a tree that maps an AID to a list of handling services
75a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    // on Android. It is only valid for the current user.
76a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    final TreeMap<String, ArrayList<ApduServiceInfo>> mAidToServices =
77a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            new TreeMap<String, ArrayList<ApduServiceInfo>>();
78a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
79a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    // mAidCache is a lookup table for quickly mapping an AID to one or
80a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    // more services. It differs from mAidServices in the sense that it
81a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    // has already accounted for defaults, and hence its return value
82a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    // is authoritative for the current set of services and defaults.
83a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    // It is only valid for the current user.
84a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    final HashMap<String, ArrayList<ApduServiceInfo>> mAidCache =
85a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            Maps.newHashMap();
86a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
87a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    final Handler mHandler = new Handler(Looper.getMainLooper());
88a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
8975f63db568f953e935e62cb3046d167b881979c8Martijn Coenen    ComponentName mNextTapComponent = null;
9075f63db568f953e935e62cb3046d167b881979c8Martijn Coenen
91a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    private final class SettingsObserver extends ContentObserver {
92a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        public SettingsObserver(Handler handler) {
93a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            super(handler);
949f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        }
959f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
969f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        @Override
97a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        public void onChange(boolean selfChange, Uri uri) {
98a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            super.onChange(selfChange, uri);
99a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            synchronized (mLock) {
100a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                // Do it just for the current user. If it was in fact
101a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                // a change made for another user, we'll sync it down
102a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                // on user switch.
103a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                int currentUser = ActivityManager.getCurrentUser();
104a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                UserServices userServices = findOrCreateUserLocked(currentUser);
10589c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                boolean changed = updateFromSettingsLocked(currentUser);
10689c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                if (changed) {
10789c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                    generateAidCacheLocked(userServices);
10889c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                    updateRoutingLocked(userServices);
10989c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                } else {
11089c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                    Log.d(TAG, "Not updating aid cache + routing: nothing changed.");
11189c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                }
1129f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            }
1139f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        }
114a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    };
1159f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
1169f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    private static class UserServices {
117a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        /**
118a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen         * All services that have registered
119a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen         */
120a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        public final HashMap<ComponentName, ApduServiceInfo> services =
121a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                Maps.newHashMap(); // Re-built at run-time
122a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
123a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        /**
124a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen         * AIDs per category
125a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen         */
126a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        public final HashMap<String, Set<String>> categoryAids =
1279f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen                Maps.newHashMap();
128a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
129a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        /**
130a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen         * Default component per category
131a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen         */
1329f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        public final HashMap<String, ComponentName> defaults =
133a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                Maps.newHashMap();
134a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
135a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        /**
136a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen         * Whether auto-select mode is enabled per category
137a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen         */
138a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        public final HashMap<String, Boolean> mode =
139a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                Maps.newHashMap();
1409f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    };
1419f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
1429f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    private UserServices findOrCreateUserLocked(int userId) {
1439f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        UserServices services = mUserServices.get(userId);
1449f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        if (services == null) {
1459f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            services = new UserServices();
1469f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            mUserServices.put(userId, services);
1479f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        }
1489f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        return services;
1499f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    }
1509f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
1516493859b424f65af79e3e13835f7dfed38495c00Martijn Coenen    public RegisteredServicesCache(Context context, AidRoutingManager routingManager) {
1529f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        mContext = context;
153d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        mRoutingManager = routingManager;
1549f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
1559f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        final BroadcastReceiver receiver = new BroadcastReceiver() {
1569f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            @Override
157d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            public void onReceive(Context context, Intent intent) {
158d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
159d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                String action = intent.getAction();
160d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                if (uid != -1) {
161d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    if (DEBUG) Log.d(TAG, "Intent action: " + action);
162d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    boolean replaced = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) &&
163d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                            (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
164d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                             Intent.ACTION_PACKAGE_REMOVED.equals(action));
165d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    if (!replaced) {
16689c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                        int currentUser = ActivityManager.getCurrentUser();
16789c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                        if (currentUser == UserHandle.getUserId(uid)) {
16889c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                            invalidateCache(UserHandle.getUserId(uid));
16989c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                        } else {
17089c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                            // Cache will automatically be updated on user switch
17189c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                        }
172d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    } else {
173d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                        if (DEBUG) Log.d(TAG, "Ignoring package intent due to package being replaced.");
174d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    }
175d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                }
1769f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            }
1779f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        };
1789f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
1799f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        mReceiver = new AtomicReference<BroadcastReceiver>(receiver);
1809f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
1819f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        IntentFilter intentFilter = new IntentFilter();
1829f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
1839f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
1849f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
185d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
1869f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        intentFilter.addDataScheme("package");
1879f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        mContext.registerReceiverAsUser(receiver, UserHandle.ALL, intentFilter, null, null);
1889f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
1899f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        // Register for events related to sdcard operations
1909f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        IntentFilter sdFilter = new IntentFilter();
1919f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
1929f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
1939f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        mContext.registerReceiverAsUser(receiver, UserHandle.ALL, sdFilter, null, null);
194a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
195a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        SettingsObserver observer = new SettingsObserver(mHandler);
196a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        context.getContentResolver().registerContentObserver(
197a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                Settings.Secure.getUriFor(Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT),
198a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                true, observer, UserHandle.USER_ALL);
199a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        context.getContentResolver().registerContentObserver(
200a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                Settings.Secure.getUriFor(Settings.Secure.NFC_PAYMENT_MODE),
201a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                true, observer, UserHandle.USER_ALL);
2029f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    }
2039f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
20489c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen    public ArrayList<ApduServiceInfo> resolveAidPrefix(String aid) {
205a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        synchronized (mLock) {
206a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            char nextAidChar = (char) (aid.charAt(aid.length() - 1) + 1);
207a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            String nextAid = aid.substring(0, aid.length() - 1) + nextAidChar;
208a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            SortedMap<String, ArrayList<ApduServiceInfo>> matches =
209a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    mAidToServices.subMap(aid, nextAid);
210a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            // The first match is lexicographically closest to what the reader asked;
211a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            if (matches.isEmpty()) {
212a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                return null;
213a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            } else {
21489c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                return mAidCache.get(matches.firstKey());
2159f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            }
216a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        }
217a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    }
218717a8bdc43aa9328899df86cb523430466cf5baaMartijn Coenen
219a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    /**
220a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen     * Resolves an AID to a set of services that can handle it.
221a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen     */
22275f63db568f953e935e62cb3046d167b881979c8Martijn Coenen     ArrayList<ApduServiceInfo> resolveAidLocked(UserServices userServices, String aid) {
223a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        ArrayList<ApduServiceInfo> resolvedServices = mAidToServices.get(aid);
224a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        if (resolvedServices == null || resolvedServices.size() == 0) {
225a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            Log.d(TAG, "Could not resolve AID " + aid + " to any service.");
226a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            return null;
227a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        }
228a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        Log.e(TAG, "resolveAidLocked: resolving AID " + aid);
229a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        ArrayList<ApduServiceInfo> resolved = new ArrayList<ApduServiceInfo>();
230a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
23175f63db568f953e935e62cb3046d167b881979c8Martijn Coenen        ComponentName defaultComponent = mNextTapComponent;
23275f63db568f953e935e62cb3046d167b881979c8Martijn Coenen        Log.d(TAG, "resolveAidLocked: next tap component is " + defaultComponent);
233a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        Set<String> paymentAids = userServices.categoryAids.get(AID_CATEGORY_PAYMENT);
234a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        if (paymentAids != null && paymentAids.contains(aid)) {
235a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            Log.d(TAG, "resolveAidLocked: AID " + aid + " is a payment AID");
236a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            // This AID has been registered as a payment AID by at least one service.
23775f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            // Get default component for payment if no next tap default.
23875f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            if (defaultComponent == null) {
23975f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                defaultComponent = userServices.defaults.get(AID_CATEGORY_PAYMENT);
24075f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            }
241a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            Log.d(TAG, "resolveAidLocked: default payment component is " + defaultComponent);
242a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            if (resolvedServices.size() == 1) {
243a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                ApduServiceInfo resolvedService = resolvedServices.get(0);
244a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                Log.d(TAG, "resolveAidLocked: resolved single service " +
245a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        resolvedService.getComponent());
246a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                if (resolvedService.equals(defaultComponent)) {
247a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    Log.d(TAG, "resolveAidLocked: DECISION: routing to (default) " +
248a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        resolvedService.getComponent());
249a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    resolvedServices.clear();
250a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    resolved.add(resolvedService);
251a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                } else {
252a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    // Route to this one service if uncontended for all AIDs.
253a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    boolean foundConflict = false;
254a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    for (String registeredAid : resolvedService.getAids()) {
255a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        ArrayList<ApduServiceInfo> servicesForAid =
256a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                                mAidToServices.get(registeredAid);
257a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        if (servicesForAid != null && servicesForAid.size() > 1) {
258a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                            foundConflict = true;
259a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        }
260a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    }
261a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    if (!foundConflict) {
262a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        Log.d(TAG, "resolveAidLocked: DECISION: routing to " +
263a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                            resolvedService.getComponent());
264a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        resolved.add(resolvedService);
265a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    } else {
266a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        // will drop out below with empty list
267a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        Log.d(TAG, "resolveAidLocked: DECISION: not routing AID " + aid +
268a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                                " because only part of it's <aid-group> is uncontended");
269a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    }
270a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                }
271a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            } else if (resolvedServices.size() > 1) {
272a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                // More services have registered. If there's a default and it
273a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                // registered this AID, go with the default. Otherwise, add all.
27475f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                Log.d(TAG, "resolveAidLocked: multiple services matched.");
275a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                if (defaultComponent != null) {
276a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    for (ApduServiceInfo service : resolvedServices) {
277a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        if (service.getComponent().equals(defaultComponent)) {
278a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                            Log.d(TAG, "resolveAidLocked: DECISION: routing to (default) " +
279a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                                    service.getComponent());
280a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                            resolved.add(service);
281a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                            break;
282a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        }
283a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    }
284a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    if (resolved.size() == 0) {
285a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        // If we didn't find the default, just add all
28675f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                        // This happens when multiple payment apps have registered
28775f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                        // for an AID, but the default does not handle it. In this
28875f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                        // case, let the user pick, and that app will be the default
28975f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                        // for it's aid groups for the next tap.
29075f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                        // TODO do we want to show different UI in this case?
29175f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                        Log.d(TAG, "resolveAidLocked: DECISION: routing all");
292a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        resolved.addAll(resolvedServices);
293a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    }
29475f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                } else {
29575f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                    Log.d(TAG, "resolveAidLocked: DECISION: no default, routing all");
29675f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                    resolved.addAll(resolvedServices);
297a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                }
298a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            } // else -> should not hit, we checked for 0 before.
299a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        } else {
300a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            // This AID is not a payment AID, just return all components
301a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            // that can handle it.
302a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            Log.d(TAG, "resolveAidLocked: DECISION: cat OTHER AID, routing all");
303a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            for (ApduServiceInfo service : resolvedServices) {
304a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                resolved.add(service);
305a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            }
306d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        }
307a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        return resolved;
3089f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    }
3099f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
310a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    void dump(ArrayList<ApduServiceInfo> services) {
311a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        for (ApduServiceInfo service : services) {
312d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            if (DEBUG) Log.d(TAG, service.toString());
3139f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        }
3149f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    }
3159f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
316a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    void generateAidCategoriesLocked(UserServices userServices) {
317a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        // Trash existing mapping
318a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        userServices.categoryAids.clear();
319a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
320a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        for (ApduServiceInfo service : userServices.services.values()) {
321a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            ArrayList<AidGroup> aidGroups = service.getAidGroups();
322a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            if (aidGroups == null) continue;
323a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            for (AidGroup aidGroup : aidGroups) {
324a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                String groupCategory = aidGroup.getCategory();
325a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                Set<String> categoryAids = userServices.categoryAids.get(groupCategory);
326a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                if (categoryAids == null) {
327a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    categoryAids = new HashSet<String>();
328a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                }
329a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                categoryAids.addAll(aidGroup.getAids());
330a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                userServices.categoryAids.put(groupCategory, categoryAids);
331a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            }
332a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        }
333a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    }
334a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
335d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen    void generateAidTreeForUserLocked(UserServices userServices) {
336d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        // Easiest is to just build the entire tree again
337d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        mAidToServices.clear();
338a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        for (ApduServiceInfo service : userServices.services.values()) {
339a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            Log.d(TAG, "generateAidTree component: " + service.getComponent());
340a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            for (String aid : service.getAids()) {
341d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                Log.d(TAG, "generateAidTree AID: " + aid);
342d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                // Check if a mapping exists for this AID
343d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                if (mAidToServices.containsKey(aid)) {
344a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    final ArrayList<ApduServiceInfo> aidServices = mAidToServices.get(aid);
345d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    aidServices.add(service);
346d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                } else {
347a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    final ArrayList<ApduServiceInfo> aidServices =
348a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                            new ArrayList<ApduServiceInfo>();
349d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    aidServices.add(service);
350d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    mAidToServices.put(aid, aidServices);
351d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                }
352d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            }
353d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        }
354d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen    }
355d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen
356a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    void generateAidCacheLocked(UserServices userServices) {
357a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        mAidCache.clear();
358a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        for (Map.Entry<String, ArrayList<ApduServiceInfo>> aidEntry:
359a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            mAidToServices.entrySet()) {
360a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            String aid = aidEntry.getKey();
3614358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen            Log.e(TAG, "Mapping aid " + aid + "to: " + resolveAidLocked(userServices, aid));
362a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            mAidCache.put(aid, resolveAidLocked(userServices, aid));
363a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        }
364a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    }
365a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
366a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    boolean containsServiceLocked(ArrayList<ApduServiceInfo> services, ComponentName serviceName) {
367a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        for (ApduServiceInfo service : services) {
368a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            if (service.getComponent().equals(serviceName)) return true;
369a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        }
370a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        return false;
371d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen    }
372d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen
37389c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen    boolean updateFromSettingsLocked(int userId) {
374a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        UserServices userServices = findOrCreateUserLocked(userId);
37589c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen        boolean changed = false;
376a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
377a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        // Load current payment default from settings
378a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        String name = Settings.Secure.getStringForUser(
379a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                mContext.getContentResolver(), Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
380a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                userId);
38189c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen        ComponentName newDefault = name != null ? ComponentName.unflattenFromString(name) : null;
38289c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen        ComponentName oldDefault = userServices.defaults.put(AID_CATEGORY_PAYMENT, newDefault);
38389c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen        changed |= newDefault != oldDefault;
38489c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen        Log.e(TAG, "Set default component to: " + (name != null ?
385a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                ComponentName.unflattenFromString(name) : "null"));
38675f63db568f953e935e62cb3046d167b881979c8Martijn Coenen
387a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        // Load payment mode from settings
38889c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen        String newModeString = Settings.Secure.getStringForUser(mContext.getContentResolver(),
389a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                Settings.Secure.NFC_PAYMENT_MODE, userId);
39089c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen        boolean newMode = !CardEmulationManager.PAYMENT_MODE_MANUAL.equals(newModeString);
39189c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen        Log.e(TAG, "Setting mode to: " + newMode);
39289c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen        Boolean oldMode = userServices.mode.put(AID_CATEGORY_PAYMENT, newMode);
39389c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen        changed |= (oldMode == null) || (newMode != oldMode.booleanValue());
39489c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen
39589c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen        return changed;
396a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    }
397a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
39875f63db568f953e935e62cb3046d167b881979c8Martijn Coenen    public String getCategoryForAid(int userId, String aid) {
39975f63db568f953e935e62cb3046d167b881979c8Martijn Coenen        synchronized (mLock) {
40075f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            UserServices userServices = findOrCreateUserLocked(userId);
40175f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            // Optimize this later
40275f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            Set<String> paymentAids = userServices.categoryAids.get(AID_CATEGORY_PAYMENT);
40375f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            if (paymentAids != null && paymentAids.contains(aid)) {
40475f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                return CardEmulationManager.CATEGORY_PAYMENT;
40575f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            } else {
40675f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                return CardEmulationManager.CATEGORY_OTHER;
40775f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            }
40875f63db568f953e935e62cb3046d167b881979c8Martijn Coenen        }
40975f63db568f953e935e62cb3046d167b881979c8Martijn Coenen    }
410a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    public ApduServiceInfo getDefaultServiceForCategory(int userId, String category) {
411a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        if (!CardEmulationManager.CATEGORY_PAYMENT.equals(category)) {
412a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            Log.e(TAG, "Not allowing defaults for category " + category);
413a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            return null;
414a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        }
415a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        synchronized (mLock) {
416a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            UserServices userServices = findOrCreateUserLocked(userId);
417a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            // Load current payment default from settings
418a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            String name = Settings.Secure.getStringForUser(
419a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    mContext.getContentResolver(), Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
420a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    userId);
421a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            if (name != null) {
422a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                ComponentName serviceComponent = ComponentName.unflattenFromString(name);
423a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                return serviceComponent != null ?
424a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        userServices.services.get(serviceComponent) : null;
425a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            } else {
426a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                return null;
427d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            }
428d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        }
429d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen    }
430d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen
431a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    public boolean isDefaultServiceForAid(int userId, ComponentName service, String aid) {
432a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        ArrayList<ApduServiceInfo> serviceList = mAidCache.get(aid);
433a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        if (serviceList == null || serviceList.size() == 0) return false;
434a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
435a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        ApduServiceInfo serviceInfo = serviceList.get(0);
436a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
437a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        return service.equals(serviceInfo);
438a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    }
439a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
440a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    public boolean setDefaultServiceForCategory(int userId, ComponentName service,
441a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            String category) {
442a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        if (!CardEmulationManager.CATEGORY_PAYMENT.equals(category)) {
443a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            Log.e(TAG, "Not allowing defaults for category " + category);
444a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            return false;
4459f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        }
446a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        synchronized (mLock) {
447a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            UserServices userServices = findOrCreateUserLocked(userId);
44875f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            // TODO Not really nice to be writing to Settings.Secure here...
44975f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            // ideally we overlay our local changes over whatever is in
45075f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            // Settings.Secure
451a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            if (userServices.services.get(service) != null) {
452a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                Settings.Secure.putStringForUser(mContext.getContentResolver(),
453a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT, service.flattenToString(),
454a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        userId);
45575f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                Settings.Secure.putStringForUser(mContext.getContentResolver(),
45675f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                        Settings.Secure.NFC_PAYMENT_MODE, CardEmulationManager.PAYMENT_MODE_AUTO,
45775f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                        userId);
45875f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            } else {
45975f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                Settings.Secure.putStringForUser(mContext.getContentResolver(),
46075f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                        Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT, null,
46175f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                        userId);
46275f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                Settings.Secure.putStringForUser(mContext.getContentResolver(),
46375f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                        Settings.Secure.NFC_PAYMENT_MODE, CardEmulationManager.PAYMENT_MODE_MANUAL,
46475f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                        userId);
46575f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            }
46675f63db568f953e935e62cb3046d167b881979c8Martijn Coenen        }
46775f63db568f953e935e62cb3046d167b881979c8Martijn Coenen        return true;
46875f63db568f953e935e62cb3046d167b881979c8Martijn Coenen    }
46975f63db568f953e935e62cb3046d167b881979c8Martijn Coenen
47075f63db568f953e935e62cb3046d167b881979c8Martijn Coenen    public boolean isNextTapOverriden() {
47175f63db568f953e935e62cb3046d167b881979c8Martijn Coenen        return mNextTapComponent != null;
47275f63db568f953e935e62cb3046d167b881979c8Martijn Coenen    }
47375f63db568f953e935e62cb3046d167b881979c8Martijn Coenen
47475f63db568f953e935e62cb3046d167b881979c8Martijn Coenen    public boolean setDefaultForNextTap(int userId, ComponentName service) {
47575f63db568f953e935e62cb3046d167b881979c8Martijn Coenen        synchronized (mLock) {
47675f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            if (service != null) {
47775f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                mNextTapComponent = service;
47875f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            } else {
47975f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                mNextTapComponent = null;
480a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            }
48175f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            UserServices userServices = findOrCreateUserLocked(userId);
48275f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            // Update state and routing table
48375f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            generateAidCacheLocked(userServices);
48475f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            updateRoutingLocked(userServices);
485a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        }
486a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        return true;
4879f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    }
4889f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
489a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    public ArrayList<ApduServiceInfo> getServicesForCategory(int userId, String category) {
490a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        final ArrayList<ApduServiceInfo> enabledServices = new ArrayList<ApduServiceInfo>();
491a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        synchronized (mLock) {
492a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            UserServices userServices = findOrCreateUserLocked(userId);
493a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            for (ApduServiceInfo service : userServices.services.values()) {
494a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                if (service.hasCategory(category)) enabledServices.add(service);
495a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            }
496a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        }
497a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        return enabledServices;
498a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    }
499a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
50062372fdecd77cc07db475e79e366e6864b44b956Martijn Coenen    public void invalidateCache(int userId) {
501a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        // This code consists of a few phases:
502a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        // - Finding all HCE services and making sure mUserServices.services is correct
503a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        // - Rebuilding the UserServices.categoryAids HashMap from userServices
504d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        // - Rebuilding the mAidToServices lookup table from userServices
505a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        // - Rebuilding the mAidCache lookup table from mAidToServices
506d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        // - Rebuilding the AID routing table from mAidToServices lookup table
507d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        // 1. Finding all HCE services and making sure mUserServices is correct
5089f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        PackageManager pm;
5099f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        try {
5109f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            pm = mContext.createPackageContextAsUser("android", 0,
511717a8bdc43aa9328899df86cb523430466cf5baaMartijn Coenen                    new UserHandle(userId)).getPackageManager();
5129f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        } catch (NameNotFoundException e) {
5139f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            Log.e(TAG, "Could not create user package context");
5149f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            return;
5159f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        }
5169f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
517a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        ArrayList<ApduServiceInfo> validServices = new ArrayList<ApduServiceInfo>();
5189f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
5196493859b424f65af79e3e13835f7dfed38495c00Martijn Coenen        List<ResolveInfo> resolvedServices = pm.queryIntentServicesAsUser(
5206493859b424f65af79e3e13835f7dfed38495c00Martijn Coenen                new Intent(HostApduService.SERVICE_INTERFACE),
52189c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                PackageManager.GET_META_DATA, userId);
52289c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen
52389c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen        List<ResolveInfo> resolvedOffHostServices = pm.queryIntentServicesAsUser(
52489c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                new Intent(OffHostApduService.SERVICE_INTERFACE),
52589c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                PackageManager.GET_META_DATA, userId);
52689c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen        resolvedServices.addAll(resolvedOffHostServices);
5279f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
5289f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        for (ResolveInfo resolvedService : resolvedServices) {
5299f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            try {
53089c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                boolean onHost = !resolvedOffHostServices.contains(resolvedService);
531a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                ServiceInfo si = resolvedService.serviceInfo;
532a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                ComponentName componentName = new ComponentName(si.packageName, si.name);
533a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                if (!android.Manifest.permission.BIND_NFC_SERVICE.equals(
534a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        si.permission)) {
535a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    Log.e(TAG, "Skipping APDU service " + componentName +
536a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                            ": it does not require the permission " +
537a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                            android.Manifest.permission.BIND_NFC_SERVICE);
538a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    continue;
539a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                }
54089c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                ApduServiceInfo service = new ApduServiceInfo(pm, resolvedService, onHost);
5419f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen                if (service != null) {
5429f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen                    validServices.add(service);
5439f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen                }
5449f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            } catch (XmlPullParserException e) {
5459f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen                Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
5469f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            } catch (IOException e) {
5479f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen                Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
5489f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            }
5499f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        }
5509f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
5519f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        synchronized (mLock) {
5529f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            UserServices userServices = findOrCreateUserLocked(userId);
553d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen
554a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            updateFromSettingsLocked(userId);
555a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
5564358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen            // Deal with defaults; if a default app is removed, notify the user.
557a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            ComponentName defaultForPayment = userServices.defaults.get(AID_CATEGORY_PAYMENT);
5584358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen            boolean paymentModeAuto = userServices.mode.get(AID_CATEGORY_PAYMENT);
559a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
560d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            // Find removed services
561a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            Iterator<Map.Entry<ComponentName, ApduServiceInfo>> it =
562d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    userServices.services.entrySet().iterator();
563d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            while (it.hasNext()) {
564a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                Map.Entry<ComponentName, ApduServiceInfo> entry =
565a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        (Map.Entry<ComponentName, ApduServiceInfo>) it.next();
566d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                if (!containsServiceLocked(validServices, entry.getKey())) {
567d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    Log.d(TAG, "Service removed: " + entry.getKey());
5684358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen                    if (entry.getKey().equals(defaultForPayment) && paymentModeAuto) {
569a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        Log.d(TAG, "Clearing as default for payment");
5704358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen                        notifyDefaultServiceRemoved(entry.getValue().loadLabel(pm));
571a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    }
572d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    it.remove();
573d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                }
574d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            }
575a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
57675f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            int numPaymentApps = 0;
57775f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            ApduServiceInfo paymentService = null;
578a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            for (ApduServiceInfo service : validServices) {
579a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                if (DEBUG) Log.d(TAG, "Processing service: " + service.getComponent() +
580a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        "AIDs: " + service.getAids());
5814358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen                ApduServiceInfo existingService =
5824358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen                        userServices.services.put(service.getComponent(), service);
58375f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                if (service.hasCategory(AID_CATEGORY_PAYMENT)) {
58475f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                    numPaymentApps++;
58575f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                    paymentService = service;
58675f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                }
5874358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen                if (existingService != null && existingService.hasCategory(AID_CATEGORY_PAYMENT) &&
588a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                            !service.hasCategory(AID_CATEGORY_PAYMENT)) {
5894358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen                    // This is a rather special case but we have to deal with it: what if your default
5904358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen                    // payment app suddenly stops offering payment AIDs.
5914358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen                    if (service.getComponent().equals(defaultForPayment) && paymentModeAuto) {
5924358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen                        notifyDefaultServiceRemoved(existingService.loadLabel(pm));
593d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    }
594d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                }
5959f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            }
596a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
5974358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen            if (numPaymentApps == 1) {
5984358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen                Log.d(TAG, "Only one payment app installed, setting default to: " + paymentService.getComponent());
59975f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                setDefaultServiceForCategory(userId, paymentService.getComponent(),
60075f63db568f953e935e62cb3046d167b881979c8Martijn Coenen                        AID_CATEGORY_PAYMENT);
60175f63db568f953e935e62cb3046d167b881979c8Martijn Coenen            }
60275f63db568f953e935e62cb3046d167b881979c8Martijn Coenen
603a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            // 2. Generate AID category hashmap
604a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            generateAidCategoriesLocked(userServices);
605a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
606a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            // 3. Rebuild mAidToServices
607d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            generateAidTreeForUserLocked(userServices);
6089f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
609a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            // 4. Generate a quick-lookup AID cache
610a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            generateAidCacheLocked(userServices);
611a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
612a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            // 5. Recompute routing table from the AID cache
613d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            updateRoutingLocked(userServices);
614d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        }
6159f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        dump(validServices);
6169f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    }
6179f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
6184358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen    void notifyDefaultServiceRemoved(CharSequence serviceName) {
6194358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen        Intent intent = new Intent(mContext, DefaultRemovedActivity.class);
6204358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen        intent.putExtra(DefaultRemovedActivity.EXTRA_DEFAULT_NAME, serviceName);
6214358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
6224358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen    }
6234358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen
624d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen    void updateRoutingLocked(UserServices userServices) {
625d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        final Set<String> handledAids = new HashSet<String>();
626d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        // For each AID, find interested services
627a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        for (Map.Entry<String, ArrayList<ApduServiceInfo>> aidEntry:
628a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                mAidCache.entrySet()) {
629d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            String aid = aidEntry.getKey();
630a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            ArrayList<ApduServiceInfo> aidServices = aidEntry.getValue();
631d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            if (aidServices.size() == 0) {
632d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                // No interested services, if there is a current routing remove it
633d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                mRoutingManager.removeAid(aid);
634d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            } else if (aidServices.size() == 1) {
635d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                // Only one service, make sure that is the current default route
636a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                ApduServiceInfo service = aidServices.get(0);
63789c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                mRoutingManager.setRouteForAid(aid, service.isOnHost());
638d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            } else if (aidServices.size() > 1) {
639a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                // Multiple services, need to route to host to ask
64089c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                mRoutingManager.setRouteForAid(aid, true);
6419f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            }
642d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            handledAids.add(aid);
6439f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        }
644d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        // Now, find AIDs in the routing table that are no longer routed to
645d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        // and remove them.
646d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        Set<String> routedAids = mRoutingManager.getRoutedAids();
647d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        for (String aid : routedAids) {
648d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            if (!handledAids.contains(aid)) {
649d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                if (DEBUG) Log.d(TAG, "Removing routing for AID " + aid + ", because " +
650d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                        "there are no no interested services.");
651d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                mRoutingManager.removeAid(aid);
652d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            }
653d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        }
654d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        // And commit the routing
655d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        mRoutingManager.commitRouting();
6569f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    }
6579f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen}
658