RegisteredServicesCache.java revision 78976de08ad5d5f9d5fcba28f3ea82350907a782
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.nfc.cardemulation;
18
19import org.xmlpull.v1.XmlPullParserException;
20import android.app.ActivityManager;
21import android.content.BroadcastReceiver;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.pm.PackageManager;
27import android.content.pm.ResolveInfo;
28import android.content.pm.ServiceInfo;
29import android.content.pm.PackageManager.NameNotFoundException;
30import android.nfc.cardemulation.ApduServiceInfo;
31import android.nfc.cardemulation.CardEmulationManager;
32import android.nfc.cardemulation.HostApduService;
33import android.nfc.cardemulation.OffHostApduService;
34import android.os.UserHandle;
35import android.util.Log;
36import android.util.SparseArray;
37
38import com.google.android.collect.Maps;
39
40import java.io.IOException;
41import java.util.ArrayList;
42import java.util.HashMap;
43import java.util.Iterator;
44import java.util.List;
45import java.util.Map;
46import java.util.concurrent.atomic.AtomicReference;
47
48public class RegisteredServicesCache {
49    static final String TAG = "RegisteredServicesCache";
50    static final boolean DEBUG = true;
51
52    final Context mContext;
53    final AtomicReference<BroadcastReceiver> mReceiver;
54
55    final Object mLock = new Object();
56    // All variables below synchronized on mLock
57
58    // mUserServices holds the card emulation services that are running for each user
59    final SparseArray<UserServices> mUserServices = new SparseArray<UserServices>();
60    final Callback mCallback;
61
62    public interface Callback {
63        void onServicesUpdated(int userId);
64        void onServiceRemoved(ComponentName service);
65        void onServiceCategoryRemoved(ApduServiceInfo service, String category);
66    };
67
68    private static class UserServices {
69        /**
70         * All services that have registered
71         */
72        public final HashMap<ComponentName, ApduServiceInfo> services =
73                Maps.newHashMap(); // Re-built at run-time
74    };
75
76    private UserServices findOrCreateUserLocked(int userId) {
77        UserServices services = mUserServices.get(userId);
78        if (services == null) {
79            services = new UserServices();
80            mUserServices.put(userId, services);
81        }
82        return services;
83    }
84
85    public RegisteredServicesCache(Context context, Callback callback) {
86        mContext = context;
87        mCallback = callback;
88
89        final BroadcastReceiver receiver = new BroadcastReceiver() {
90            @Override
91            public void onReceive(Context context, Intent intent) {
92                final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
93                String action = intent.getAction();
94                if (uid != -1) {
95                    if (DEBUG) Log.d(TAG, "Intent action: " + action);
96                    boolean replaced = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) &&
97                            (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
98                             Intent.ACTION_PACKAGE_REMOVED.equals(action));
99                    if (!replaced) {
100                        int currentUser = ActivityManager.getCurrentUser();
101                        if (currentUser == UserHandle.getUserId(uid)) {
102                            invalidateCache(UserHandle.getUserId(uid));
103                        } else {
104                            // Cache will automatically be updated on user switch
105                        }
106                    } else {
107                        if (DEBUG) Log.d(TAG, "Ignoring package intent due to package being replaced.");
108                    }
109                }
110            }
111        };
112
113        mReceiver = new AtomicReference<BroadcastReceiver>(receiver);
114
115        IntentFilter intentFilter = new IntentFilter();
116        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
117        intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
118        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
119        intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
120        intentFilter.addDataScheme("package");
121        mContext.registerReceiverAsUser(receiver, UserHandle.ALL, intentFilter, null, null);
122
123        // Register for events related to sdcard operations
124        IntentFilter sdFilter = new IntentFilter();
125        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
126        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
127        mContext.registerReceiverAsUser(receiver, UserHandle.ALL, sdFilter, null, null);
128
129    }
130
131    void dump(ArrayList<ApduServiceInfo> services) {
132        for (ApduServiceInfo service : services) {
133            if (DEBUG) Log.d(TAG, service.toString());
134        }
135    }
136
137    boolean containsServiceLocked(ArrayList<ApduServiceInfo> services, ComponentName serviceName) {
138        for (ApduServiceInfo service : services) {
139            if (service.getComponent().equals(serviceName)) return true;
140        }
141        return false;
142    }
143
144    public boolean hasService(int userId, ComponentName service) {
145        synchronized (mLock) {
146            UserServices userServices = findOrCreateUserLocked(userId);
147            return userServices.services.containsKey(service);
148        }
149    }
150
151    public List<ApduServiceInfo> getServices(int userId) {
152        final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
153        synchronized (mLock) {
154            UserServices userServices = findOrCreateUserLocked(userId);
155            services.addAll(userServices.services.values());
156        }
157        return services;
158    }
159
160    public List<ApduServiceInfo> getServicesForCategory(int userId, String category) {
161        final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
162        synchronized (mLock) {
163            UserServices userServices = findOrCreateUserLocked(userId);
164            for (ApduServiceInfo service : userServices.services.values()) {
165                if (service.hasCategory(category)) services.add(service);
166            }
167        }
168        return services;
169    }
170
171    public void invalidateCache(int userId) {
172        PackageManager pm;
173        try {
174            pm = mContext.createPackageContextAsUser("android", 0,
175                    new UserHandle(userId)).getPackageManager();
176        } catch (NameNotFoundException e) {
177            Log.e(TAG, "Could not create user package context");
178            return;
179        }
180
181        ArrayList<ApduServiceInfo> validServices = new ArrayList<ApduServiceInfo>();
182
183        List<ResolveInfo> resolvedServices = pm.queryIntentServicesAsUser(
184                new Intent(HostApduService.SERVICE_INTERFACE),
185                PackageManager.GET_META_DATA, userId);
186
187        List<ResolveInfo> resolvedOffHostServices = pm.queryIntentServicesAsUser(
188                new Intent(OffHostApduService.SERVICE_INTERFACE),
189                PackageManager.GET_META_DATA, userId);
190        resolvedServices.addAll(resolvedOffHostServices);
191
192        for (ResolveInfo resolvedService : resolvedServices) {
193            try {
194                boolean onHost = !resolvedOffHostServices.contains(resolvedService);
195                ServiceInfo si = resolvedService.serviceInfo;
196                ComponentName componentName = new ComponentName(si.packageName, si.name);
197                if (!android.Manifest.permission.BIND_NFC_SERVICE.equals(
198                        si.permission)) {
199                    Log.e(TAG, "Skipping APDU service " + componentName +
200                            ": it does not require the permission " +
201                            android.Manifest.permission.BIND_NFC_SERVICE);
202                    continue;
203                }
204                ApduServiceInfo service = new ApduServiceInfo(pm, resolvedService, onHost);
205                if (service != null) {
206                    validServices.add(service);
207                }
208            } catch (XmlPullParserException e) {
209                Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
210            } catch (IOException e) {
211                Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
212            }
213        }
214
215        final ArrayList<ComponentName> removedServices = new ArrayList<ComponentName>();
216        final ArrayList<ApduServiceInfo> categoryRemovedServices = new ArrayList<ApduServiceInfo>();
217        synchronized (mLock) {
218            UserServices userServices = findOrCreateUserLocked(userId);
219
220            // Find removed services
221            Iterator<Map.Entry<ComponentName, ApduServiceInfo>> it =
222                    userServices.services.entrySet().iterator();
223            while (it.hasNext()) {
224                Map.Entry<ComponentName, ApduServiceInfo> entry =
225                        (Map.Entry<ComponentName, ApduServiceInfo>) it.next();
226                if (!containsServiceLocked(validServices, entry.getKey())) {
227                    Log.d(TAG, "Service removed: " + entry.getKey());
228                    removedServices.add(entry.getKey());
229                    it.remove();
230                }
231            }
232
233            for (ApduServiceInfo service : validServices) {
234                if (DEBUG) Log.d(TAG, "Processing service: " + service.getComponent() +
235                        " AIDs: " + service.getAids());
236                ApduServiceInfo existingService =
237                        userServices.services.put(service.getComponent(), service);
238                if (existingService != null &&
239                        existingService.hasCategory(CardEmulationManager.CATEGORY_PAYMENT) &&
240                        !service.hasCategory(CardEmulationManager.CATEGORY_PAYMENT)) {
241                    categoryRemovedServices.add(service);
242                }
243            }
244
245        }
246
247        for (ComponentName service : removedServices) {
248            mCallback.onServiceRemoved(service);
249        }
250        for (ApduServiceInfo service : categoryRemovedServices) {
251            mCallback.onServiceCategoryRemoved(service, CardEmulationManager.CATEGORY_PAYMENT);
252        }
253
254        mCallback.onServicesUpdated(userId);
255        dump(validServices);
256    }
257}