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
19af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport org.xmlpull.v1.XmlPullParser;
209f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport org.xmlpull.v1.XmlPullParserException;
21af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport org.xmlpull.v1.XmlSerializer;
22341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen
239f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.app.ActivityManager;
249f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.BroadcastReceiver;
259f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.ComponentName;
269f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.Context;
279f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.Intent;
289f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.IntentFilter;
299f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.pm.PackageManager;
309f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.pm.ResolveInfo;
319f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.content.pm.ServiceInfo;
32a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenenimport android.content.pm.PackageManager.NameNotFoundException;
33af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport android.nfc.cardemulation.AidGroup;
34a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenenimport android.nfc.cardemulation.ApduServiceInfo;
35bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenenimport android.nfc.cardemulation.CardEmulation;
366493859b424f65af79e3e13835f7dfed38495c00Martijn Coenenimport android.nfc.cardemulation.HostApduService;
3789c893312d524f50c47b0d32071f3de8631197f3Martijn Coenenimport android.nfc.cardemulation.OffHostApduService;
389f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.os.UserHandle;
39af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport android.util.AtomicFile;
409f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.util.Log;
419f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport android.util.SparseArray;
42af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport android.util.Xml;
434358172f18871d58d5bc2050e4d9d0bf9bc2d5e5Martijn Coenen
44af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport com.android.internal.util.FastXmlSerializer;
459f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport com.google.android.collect.Maps;
469f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
47af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport java.io.File;
48341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenenimport java.io.FileDescriptor;
49af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport java.io.FileInputStream;
50af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenenimport java.io.FileOutputStream;
519f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.io.IOException;
52341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenenimport java.io.PrintWriter;
539f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.ArrayList;
54451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenenimport java.util.Collections;
559f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.HashMap;
56d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenenimport java.util.Iterator;
579f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.List;
589f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.Map;
599f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenenimport java.util.concurrent.atomic.AtomicReference;
609f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
61af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen/**
62af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen * This class is inspired by android.content.pm.RegisteredServicesCache
63af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen * That class was not re-used because it doesn't support dynamically
64af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen * registering additional properties, but generates everything from
65af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen * the manifest. Since we have some properties that are not in the manifest,
66af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen * it's less suited.
67af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen */
686493859b424f65af79e3e13835f7dfed38495c00Martijn Coenenpublic class RegisteredServicesCache {
69af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    static final String XML_INDENT_OUTPUT_FEATURE = "http://xmlpull.org/v1/doc/features.html#indent-output";
7078976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen    static final String TAG = "RegisteredServicesCache";
71b58a7494a893c957a8c04397eb28a4bf2fe1db54Martijn Coenen    static final boolean DEBUG = false;
729f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
739f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    final Context mContext;
749f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    final AtomicReference<BroadcastReceiver> mReceiver;
759f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
769f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    final Object mLock = new Object();
779f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    // All variables below synchronized on mLock
789f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
799f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    // mUserServices holds the card emulation services that are running for each user
809f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    final SparseArray<UserServices> mUserServices = new SparseArray<UserServices>();
8178976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen    final Callback mCallback;
82af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    final AtomicFile mDynamicAidsFile;
839f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
8478976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen    public interface Callback {
85451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen        void onServicesUpdated(int userId, final List<ApduServiceInfo> services);
86a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    };
879f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
88af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    static class DynamicAids {
89af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        public final int uid;
90af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        public final HashMap<String, AidGroup> aidGroups = Maps.newHashMap();
91af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen
92af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        DynamicAids(int uid) {
93af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            this.uid = uid;
94af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        }
95af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    };
96af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen
979f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    private static class UserServices {
98a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        /**
99a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen         * All services that have registered
100a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen         */
101af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        final HashMap<ComponentName, ApduServiceInfo> services =
102a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                Maps.newHashMap(); // Re-built at run-time
103af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        final HashMap<ComponentName, DynamicAids> dynamicAids =
104af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                Maps.newHashMap(); // In memory cache of dynamic AID store
1059f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    };
1069f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
1079f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    private UserServices findOrCreateUserLocked(int userId) {
1089f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        UserServices services = mUserServices.get(userId);
1099f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        if (services == null) {
1109f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            services = new UserServices();
1119f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            mUserServices.put(userId, services);
1129f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        }
1139f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        return services;
1149f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    }
1159f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
11678976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen    public RegisteredServicesCache(Context context, Callback callback) {
1179f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        mContext = context;
11878976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen        mCallback = callback;
1199f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
1209f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        final BroadcastReceiver receiver = new BroadcastReceiver() {
1219f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            @Override
122d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            public void onReceive(Context context, Intent intent) {
123d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
124d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                String action = intent.getAction();
125341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen                if (DEBUG) Log.d(TAG, "Intent action: " + action);
126d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                if (uid != -1) {
127d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    boolean replaced = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) &&
128d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                            (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
129d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                             Intent.ACTION_PACKAGE_REMOVED.equals(action));
130d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    if (!replaced) {
13189c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                        int currentUser = ActivityManager.getCurrentUser();
13289c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                        if (currentUser == UserHandle.getUserId(uid)) {
13389c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                            invalidateCache(UserHandle.getUserId(uid));
13489c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                        } else {
13589c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                            // Cache will automatically be updated on user switch
13689c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                        }
137d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    } else {
138d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                        if (DEBUG) Log.d(TAG, "Ignoring package intent due to package being replaced.");
139d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    }
140d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                }
1419f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            }
1429f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        };
1439f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        mReceiver = new AtomicReference<BroadcastReceiver>(receiver);
144341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen
145341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        IntentFilter intentFilter = new IntentFilter();
146341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
147341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
148341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
149341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
150341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        intentFilter.addAction(Intent.ACTION_PACKAGE_FIRST_LAUNCH);
151341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
152341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        intentFilter.addDataScheme("package");
153341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, intentFilter, null, null);
154341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen
155341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        // Register for events related to sdcard operations
156341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        IntentFilter sdFilter = new IntentFilter();
157341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
158341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
159341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, sdFilter, null, null);
160af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen
161af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        File dataDir = mContext.getFilesDir();
162af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        mDynamicAidsFile = new AtomicFile(new File(dataDir, "dynamic_aids.xml"));
163af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    }
164af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen
165af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    void initialize() {
166af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        synchronized (mLock) {
167af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            readDynamicAidsLocked();
168af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        }
169af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        invalidateCache(ActivityManager.getCurrentUser());
1709f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    }
1719f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
172a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    void dump(ArrayList<ApduServiceInfo> services) {
173a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        for (ApduServiceInfo service : services) {
174d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            if (DEBUG) Log.d(TAG, service.toString());
1759f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        }
1769f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    }
1779f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
178a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    boolean containsServiceLocked(ArrayList<ApduServiceInfo> services, ComponentName serviceName) {
179a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        for (ApduServiceInfo service : services) {
180a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            if (service.getComponent().equals(serviceName)) return true;
181a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        }
182a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        return false;
183d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen    }
184d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen
18578976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen    public boolean hasService(int userId, ComponentName service) {
186451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen        return getService(userId, service) != null;
187451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen    }
188451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen
189451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen    public ApduServiceInfo getService(int userId, ComponentName service) {
190a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        synchronized (mLock) {
191a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            UserServices userServices = findOrCreateUserLocked(userId);
192451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen            return userServices.services.get(service);
193d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        }
194d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen    }
195d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen
19678976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen    public List<ApduServiceInfo> getServices(int userId) {
19778976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen        final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
198a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        synchronized (mLock) {
199a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            UserServices userServices = findOrCreateUserLocked(userId);
20078976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen            services.addAll(userServices.services.values());
20175f63db568f953e935e62cb3046d167b881979c8Martijn Coenen        }
20278976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen        return services;
2039f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    }
2049f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
20578976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen    public List<ApduServiceInfo> getServicesForCategory(int userId, String category) {
20678976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen        final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
207a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        synchronized (mLock) {
208a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            UserServices userServices = findOrCreateUserLocked(userId);
209a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            for (ApduServiceInfo service : userServices.services.values()) {
21078976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen                if (service.hasCategory(category)) services.add(service);
211a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            }
212a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        }
21378976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen        return services;
214a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen    }
215a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen
216af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    ArrayList<ApduServiceInfo> getInstalledServices(int userId) {
2179f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        PackageManager pm;
2189f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        try {
2199f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            pm = mContext.createPackageContextAsUser("android", 0,
220717a8bdc43aa9328899df86cb523430466cf5baaMartijn Coenen                    new UserHandle(userId)).getPackageManager();
2219f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        } catch (NameNotFoundException e) {
2229f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            Log.e(TAG, "Could not create user package context");
223af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            return null;
2249f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        }
2259f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
226a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen        ArrayList<ApduServiceInfo> validServices = new ArrayList<ApduServiceInfo>();
2279f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
2286493859b424f65af79e3e13835f7dfed38495c00Martijn Coenen        List<ResolveInfo> resolvedServices = pm.queryIntentServicesAsUser(
2296493859b424f65af79e3e13835f7dfed38495c00Martijn Coenen                new Intent(HostApduService.SERVICE_INTERFACE),
23089c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                PackageManager.GET_META_DATA, userId);
23189c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen
23289c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen        List<ResolveInfo> resolvedOffHostServices = pm.queryIntentServicesAsUser(
23389c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                new Intent(OffHostApduService.SERVICE_INTERFACE),
23489c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                PackageManager.GET_META_DATA, userId);
23589c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen        resolvedServices.addAll(resolvedOffHostServices);
2369f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
2379f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        for (ResolveInfo resolvedService : resolvedServices) {
2389f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            try {
23989c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                boolean onHost = !resolvedOffHostServices.contains(resolvedService);
240a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                ServiceInfo si = resolvedService.serviceInfo;
241a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                ComponentName componentName = new ComponentName(si.packageName, si.name);
242dba891595bc0bbcb8f0810b4d014e935aa1955c5Martijn Coenen                // Check if the package holds the NFC permission
243dba891595bc0bbcb8f0810b4d014e935aa1955c5Martijn Coenen                if (pm.checkPermission(android.Manifest.permission.NFC, si.packageName) !=
244dba891595bc0bbcb8f0810b4d014e935aa1955c5Martijn Coenen                        PackageManager.PERMISSION_GRANTED) {
2458a527f144e05da334f6add4bede8d77c1acb91a4William Roberts                    Log.e(TAG, "Skipping application component " + componentName +
2468a527f144e05da334f6add4bede8d77c1acb91a4William Roberts                            ": it must request the permission " +
247dba891595bc0bbcb8f0810b4d014e935aa1955c5Martijn Coenen                            android.Manifest.permission.NFC);
248dba891595bc0bbcb8f0810b4d014e935aa1955c5Martijn Coenen                    continue;
249dba891595bc0bbcb8f0810b4d014e935aa1955c5Martijn Coenen                }
250a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                if (!android.Manifest.permission.BIND_NFC_SERVICE.equals(
251a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        si.permission)) {
252a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    Log.e(TAG, "Skipping APDU service " + componentName +
253a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                            ": it does not require the permission " +
254a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                            android.Manifest.permission.BIND_NFC_SERVICE);
255a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                    continue;
256a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                }
25789c893312d524f50c47b0d32071f3de8631197f3Martijn Coenen                ApduServiceInfo service = new ApduServiceInfo(pm, resolvedService, onHost);
2589f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen                if (service != null) {
2599f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen                    validServices.add(service);
2609f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen                }
2619f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            } catch (XmlPullParserException e) {
2629f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen                Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
2639f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            } catch (IOException e) {
2649f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen                Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
2659f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            }
2669f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        }
2679f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen
268af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        return validServices;
269af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    }
270af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen
271af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    public void invalidateCache(int userId) {
272af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        final ArrayList<ApduServiceInfo> validServices = getInstalledServices(userId);
273af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        if (validServices == null) {
274af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            return;
275af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        }
2769f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen        synchronized (mLock) {
2779f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            UserServices userServices = findOrCreateUserLocked(userId);
278d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen
279d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            // Find removed services
280a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            Iterator<Map.Entry<ComponentName, ApduServiceInfo>> it =
281d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    userServices.services.entrySet().iterator();
282d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            while (it.hasNext()) {
283a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                Map.Entry<ComponentName, ApduServiceInfo> entry =
284a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen                        (Map.Entry<ComponentName, ApduServiceInfo>) it.next();
285d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                if (!containsServiceLocked(validServices, entry.getKey())) {
286d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    Log.d(TAG, "Service removed: " + entry.getKey());
287d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                    it.remove();
288d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen                }
289d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen            }
290a0b908c58b5ab0d242ccc545d14573901774bd29Martijn Coenen            for (ApduServiceInfo service : validServices) {
291451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen                if (DEBUG) Log.d(TAG, "Adding service: " + service.getComponent() +
29278976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen                        " AIDs: " + service.getAids());
293451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen                userServices.services.put(service.getComponent(), service);
2949f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen            }
295af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen
296af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            // Apply dynamic AID mappings
297af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            ArrayList<ComponentName> toBeRemoved = new ArrayList<ComponentName>();
298af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            for (Map.Entry<ComponentName, DynamicAids> entry :
299af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    userServices.dynamicAids.entrySet()) {
300af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                // Verify component / uid match
301af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                ComponentName component = entry.getKey();
302af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                DynamicAids dynamicAids = entry.getValue();
303af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                ApduServiceInfo serviceInfo = userServices.services.get(component);
304af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                if (serviceInfo == null || (serviceInfo.getUid() != dynamicAids.uid)) {
305af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    toBeRemoved.add(component);
306af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    continue;
307af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                } else {
308af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    for (AidGroup group : dynamicAids.aidGroups.values()) {
309af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                        serviceInfo.setOrReplaceDynamicAidGroup(group);
310af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    }
311af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                }
312af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            }
313af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen
314af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            if (toBeRemoved.size() > 0) {
315af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                for (ComponentName component : toBeRemoved) {
316af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    Log.d(TAG, "Removing dynamic AIDs registered by " + component);
317af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    userServices.dynamicAids.remove(component);
318af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                }
319af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                // Persist to filesystem
320af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                writeDynamicAidsLocked();
321af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            }
322d53c2b599c73f7404b5a604be4d9a5449cafdd72Martijn Coenen        }
32378976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen
324451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen        mCallback.onServicesUpdated(userId, Collections.unmodifiableList(validServices));
32578976de08ad5d5f9d5fcba28f3ea82350907a782Martijn Coenen        dump(validServices);
3269f8f6cf9c58405ecafe2d425801e6c14088db8c7Martijn Coenen    }
327341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen
328af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    private void readDynamicAidsLocked() {
329af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        FileInputStream fis = null;
330af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        try {
331af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            if (!mDynamicAidsFile.getBaseFile().exists()) {
332af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                Log.d(TAG, "Dynamic AIDs file does not exist.");
333af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                return;
334af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            }
335af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            fis = mDynamicAidsFile.openRead();
336af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            XmlPullParser parser = Xml.newPullParser();
337af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            parser.setInput(fis, null);
338af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            int eventType = parser.getEventType();
339af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            while (eventType != XmlPullParser.START_TAG &&
340af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    eventType != XmlPullParser.END_DOCUMENT) {
341af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                eventType = parser.next();
342af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            }
343af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            String tagName = parser.getName();
344af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            if ("services".equals(tagName)) {
345af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                boolean inService = false;
346af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                ComponentName currentComponent = null;
347af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                int currentUid = -1;
348af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                ArrayList<AidGroup> currentGroups = new ArrayList<AidGroup>();
349af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                while (eventType != XmlPullParser.END_DOCUMENT) {
350f12b6d0b08563211ad7c5903330f454bdd1970bbMartijn Coenen                    tagName = parser.getName();
351af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    if (eventType == XmlPullParser.START_TAG) {
352af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                        if ("service".equals(tagName) && parser.getDepth() == 2) {
353af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            String compString = parser.getAttributeValue(null, "component");
354af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            String uidString = parser.getAttributeValue(null, "uid");
355af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            if (compString == null || uidString == null) {
356af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                Log.e(TAG, "Invalid service attributes");
357af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            } else {
358af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                try {
359af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                    currentUid = Integer.parseInt(uidString);
360af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                    currentComponent = ComponentName.unflattenFromString(compString);
361af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                    inService = true;
362af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                } catch (NumberFormatException e) {
363af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                    Log.e(TAG, "Could not parse service uid");
364af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                }
365af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            }
366af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                        }
367af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                        if ("aid-group".equals(tagName) && parser.getDepth() == 3 && inService) {
368af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            AidGroup group = AidGroup.createFromXml(parser);
369af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            if (group != null) {
370af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                currentGroups.add(group);
371af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            } else {
372af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                Log.e(TAG, "Could not parse AID group.");
373af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            }
374af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                        }
375af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    } else if (eventType == XmlPullParser.END_TAG) {
376af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                        if ("service".equals(tagName)) {
377af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            // See if we have a valid service
378af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            if (currentComponent != null && currentUid >= 0 &&
379af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                    currentGroups.size() > 0) {
380af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                final int userId = UserHandle.getUserId(currentUid);
381af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                DynamicAids dynAids = new DynamicAids(currentUid);
382af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                for (AidGroup group : currentGroups) {
383af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                    dynAids.aidGroups.put(group.getCategory(), group);
384af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                }
385af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                UserServices services = findOrCreateUserLocked(userId);
386af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                                services.dynamicAids.put(currentComponent, dynAids);
387af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            }
388af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            currentUid = -1;
389af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            currentComponent = null;
390af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            currentGroups.clear();
391af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                            inService = false;
392af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                        }
393af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    }
394af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    eventType = parser.next();
395af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                };
396af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            }
397af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        } catch (Exception e) {
398af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            Log.e(TAG, "Could not parse dynamic AIDs file, trashing.");
399af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            mDynamicAidsFile.delete();
400af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        } finally {
401af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            if (fis != null) {
402af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                try {
403af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    fis.close();
404af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                } catch (IOException e) {
405af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                }
406af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            }
407af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        }
408af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    }
409af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen
410af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    private boolean writeDynamicAidsLocked() {
411af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        FileOutputStream fos = null;
412af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        try {
413af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            fos = mDynamicAidsFile.startWrite();
414af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            XmlSerializer out = new FastXmlSerializer();
415af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            out.setOutput(fos, "utf-8");
416af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            out.startDocument(null, true);
417af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            out.setFeature(XML_INDENT_OUTPUT_FEATURE, true);
418af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            out.startTag(null, "services");
419af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            for (int i = 0; i < mUserServices.size(); i++) {
420af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                final UserServices user = mUserServices.valueAt(i);
421af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                for (Map.Entry<ComponentName, DynamicAids> service : user.dynamicAids.entrySet()) {
422af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    out.startTag(null, "service");
423af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    out.attribute(null, "component", service.getKey().flattenToString());
424af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    out.attribute(null, "uid", Integer.toString(service.getValue().uid));
425af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    for (AidGroup group : service.getValue().aidGroups.values()) {
426af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                        group.writeAsXml(out);
427af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    }
428af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    out.endTag(null, "service");
429af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                }
430af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            }
431af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            out.endTag(null, "services");
432af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            out.endDocument();
433af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            mDynamicAidsFile.finishWrite(fos);
434af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            return true;
435af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        } catch (Exception e) {
436af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            Log.e(TAG, "Error writing dynamic AIDs", e);
437af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            if (fos != null) {
438af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                mDynamicAidsFile.failWrite(fos);
439af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            }
440af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            return false;
441af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        }
442af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    }
443af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen
444af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    public boolean registerAidGroupForService(int userId, int uid,
445af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            ComponentName componentName, AidGroup aidGroup) {
446ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen        ArrayList<ApduServiceInfo> newServices = null;
447ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen        boolean success;
448af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        synchronized (mLock) {
449af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            UserServices services = findOrCreateUserLocked(userId);
450af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            // Check if we can find this service
451af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            ApduServiceInfo serviceInfo = getService(userId, componentName);
452af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            if (serviceInfo == null) {
453af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                Log.e(TAG, "Service " + componentName + " does not exist.");
454af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                return false;
455af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            }
456af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            if (serviceInfo.getUid() != uid) {
457af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                // This is probably a good indication something is wrong here.
458af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                // Either newer service installed with different uid (but then
459af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                // we should have known about it), or somebody calling us from
460af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                // a different uid.
461af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                Log.e(TAG, "UID mismatch.");
462af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                return false;
463af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            }
464bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen            // Do another AID validation, since a caller could have thrown in a modified
465bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen            // AidGroup object with invalid AIDs over Binder.
466bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen            List<String> aids = aidGroup.getAids();
467bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen            for (String aid : aids) {
468bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen                if (!CardEmulation.isValidAid(aid)) {
469bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen                    Log.e(TAG, "AID " + aid + " is not a valid AID");
470bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen                    return false;
471bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen                }
472bb02d46705215b9ed44fd19584c8a9b8f6f6268cMartijn Coenen            }
473af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            serviceInfo.setOrReplaceDynamicAidGroup(aidGroup);
474af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            DynamicAids dynAids = services.dynamicAids.get(componentName);
475af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            if (dynAids == null) {
476af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                dynAids = new DynamicAids(uid);
477af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                services.dynamicAids.put(componentName, dynAids);
478af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            }
479af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            dynAids.aidGroups.put(aidGroup.getCategory(), aidGroup);
480ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen            success = writeDynamicAidsLocked();
481ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen            if (success) {
482ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                newServices = new ArrayList<ApduServiceInfo>(services.services.values());
483af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            } else {
484af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                Log.e(TAG, "Failed to persist AID group.");
485af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                // Undo registration
486af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                dynAids.aidGroups.remove(aidGroup.getCategory());
487af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            }
488af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        }
489ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen        if (success) {
490ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen            // Make callback without the lock held
491ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen            mCallback.onServicesUpdated(userId, newServices);
492ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen        }
493ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen        return success;
494af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    }
495af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen
496af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    public AidGroup getAidGroupForService(int userId, int uid, ComponentName componentName,
497af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            String category) {
498af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        ApduServiceInfo serviceInfo = getService(userId, componentName);
499af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        if (serviceInfo != null) {
500af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            if (serviceInfo.getUid() != uid) {
501af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                Log.e(TAG, "UID mismatch");
502af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                return null;
503af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            }
504af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            return serviceInfo.getDynamicAidGroupForCategory(category);
505af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        } else {
506af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            Log.e(TAG, "Could not find service " + componentName);
507af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            return null;
508af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        }
509af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    }
510af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen
511af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    public boolean removeAidGroupForService(int userId, int uid, ComponentName componentName,
512af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            String category) {
513ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen        boolean success = false;
514ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen        ArrayList<ApduServiceInfo> newServices = null;
515af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        synchronized (mLock) {
516af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            UserServices services = findOrCreateUserLocked(userId);
517af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            ApduServiceInfo serviceInfo = getService(userId, componentName);
518af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            if (serviceInfo != null) {
519af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                if (serviceInfo.getUid() != uid) {
520af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    // Calling from different uid
521af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    Log.e(TAG, "UID mismatch");
522af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    return false;
523af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                }
524ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                if (!serviceInfo.removeDynamicAidGroupForCategory(category)) {
525ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                    Log.e(TAG," Could not find dynamic AIDs for category " + category);
526ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                    return false;
527ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                }
528af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                // Remove from local cache
529af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                DynamicAids dynAids = services.dynamicAids.get(componentName);
530af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                if (dynAids != null) {
531ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                    AidGroup deletedGroup = dynAids.aidGroups.remove(category);
532ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                    success = writeDynamicAidsLocked();
533ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                    if (success) {
534ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                        newServices = new ArrayList<ApduServiceInfo>(services.services.values());
535ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                    } else {
536ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                        Log.e(TAG, "Could not persist deleted AID group.");
537ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                        dynAids.aidGroups.put(category, deletedGroup);
538ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                        return false;
539af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                    }
540ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                } else {
541ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                    Log.e(TAG, "Could not find aid group in local cache.");
542af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen                }
543af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            } else {
544ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen                Log.e(TAG, "Service " + componentName + " does not exist.");
545af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            }
546af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen        }
547ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen        if (success) {
548ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen            mCallback.onServicesUpdated(userId, newServices);
549ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen        }
550ea8bbf3e6a41b4ace768f0b2c74bdcf21e2aee64Martijn Coenen        return success;
551af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen    }
552af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen
553341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
554341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        pw.println("Registered HCE services for current user: ");
555341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        UserServices userServices = findOrCreateUserLocked(ActivityManager.getCurrentUser());
556341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        for (ApduServiceInfo service : userServices.services.values()) {
557af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            service.dump(fd, pw, args);
558af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen            pw.println("");
559341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        }
560341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen        pw.println("");
561341b2c02da8b4d2a681f3fbcc5657921ad421e32Martijn Coenen    }
562af3e301d7820bc0a2447db8af16ab5335e6bd520Martijn Coenen
563451ba48faa87d78bfbec0597ff06af1747cf6acbMartijn Coenen}
564