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;
20
21import android.app.ActivityManager;
22import android.content.BroadcastReceiver;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.pm.PackageManager;
28import android.content.pm.ResolveInfo;
29import android.content.pm.ServiceInfo;
30import android.content.pm.PackageManager.NameNotFoundException;
31import android.nfc.cardemulation.ApduServiceInfo;
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.FileDescriptor;
41import java.io.IOException;
42import java.io.PrintWriter;
43import java.util.ArrayList;
44import java.util.Collections;
45import java.util.HashMap;
46import java.util.Iterator;
47import java.util.List;
48import java.util.Map;
49import java.util.concurrent.atomic.AtomicReference;
50
51public class RegisteredServicesCache {
52    static final String TAG = "RegisteredServicesCache";
53    static final boolean DEBUG = false;
54
55    final Context mContext;
56    final AtomicReference<BroadcastReceiver> mReceiver;
57
58    final Object mLock = new Object();
59    // All variables below synchronized on mLock
60
61    // mUserServices holds the card emulation services that are running for each user
62    final SparseArray<UserServices> mUserServices = new SparseArray<UserServices>();
63    final Callback mCallback;
64
65    public interface Callback {
66        void onServicesUpdated(int userId, final List<ApduServiceInfo> services);
67    };
68
69    private static class UserServices {
70        /**
71         * All services that have registered
72         */
73        public final HashMap<ComponentName, ApduServiceInfo> services =
74                Maps.newHashMap(); // Re-built at run-time
75    };
76
77    private UserServices findOrCreateUserLocked(int userId) {
78        UserServices services = mUserServices.get(userId);
79        if (services == null) {
80            services = new UserServices();
81            mUserServices.put(userId, services);
82        }
83        return services;
84    }
85
86    public RegisteredServicesCache(Context context, Callback callback) {
87        mContext = context;
88        mCallback = callback;
89
90        final BroadcastReceiver receiver = new BroadcastReceiver() {
91            @Override
92            public void onReceive(Context context, Intent intent) {
93                final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
94                String action = intent.getAction();
95                if (DEBUG) Log.d(TAG, "Intent action: " + action);
96                if (uid != -1) {
97                    boolean replaced = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) &&
98                            (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
99                             Intent.ACTION_PACKAGE_REMOVED.equals(action));
100                    if (!replaced) {
101                        int currentUser = ActivityManager.getCurrentUser();
102                        if (currentUser == UserHandle.getUserId(uid)) {
103                            invalidateCache(UserHandle.getUserId(uid));
104                        } else {
105                            // Cache will automatically be updated on user switch
106                        }
107                    } else {
108                        if (DEBUG) Log.d(TAG, "Ignoring package intent due to package being replaced.");
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.addAction(Intent.ACTION_PACKAGE_FIRST_LAUNCH);
121        intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
122        intentFilter.addDataScheme("package");
123        mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, intentFilter, null, null);
124
125        // Register for events related to sdcard operations
126        IntentFilter sdFilter = new IntentFilter();
127        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
128        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
129        mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, sdFilter, null, null);
130    }
131
132    void dump(ArrayList<ApduServiceInfo> services) {
133        for (ApduServiceInfo service : services) {
134            if (DEBUG) Log.d(TAG, service.toString());
135        }
136    }
137
138    boolean containsServiceLocked(ArrayList<ApduServiceInfo> services, ComponentName serviceName) {
139        for (ApduServiceInfo service : services) {
140            if (service.getComponent().equals(serviceName)) return true;
141        }
142        return false;
143    }
144
145    public boolean hasService(int userId, ComponentName service) {
146        return getService(userId, service) != null;
147    }
148
149    public ApduServiceInfo getService(int userId, ComponentName service) {
150        synchronized (mLock) {
151            UserServices userServices = findOrCreateUserLocked(userId);
152            return userServices.services.get(service);
153        }
154    }
155
156    public List<ApduServiceInfo> getServices(int userId) {
157        final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
158        synchronized (mLock) {
159            UserServices userServices = findOrCreateUserLocked(userId);
160            services.addAll(userServices.services.values());
161        }
162        return services;
163    }
164
165    public List<ApduServiceInfo> getServicesForCategory(int userId, String category) {
166        final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
167        synchronized (mLock) {
168            UserServices userServices = findOrCreateUserLocked(userId);
169            for (ApduServiceInfo service : userServices.services.values()) {
170                if (service.hasCategory(category)) services.add(service);
171            }
172        }
173        return services;
174    }
175
176    public void onNfcDisabled() {
177
178    }
179
180    public void onNfcEnabled() {
181        invalidateCache(ActivityManager.getCurrentUser());
182    }
183
184    public void invalidateCache(int userId) {
185        PackageManager pm;
186        try {
187            pm = mContext.createPackageContextAsUser("android", 0,
188                    new UserHandle(userId)).getPackageManager();
189        } catch (NameNotFoundException e) {
190            Log.e(TAG, "Could not create user package context");
191            return;
192        }
193
194        ArrayList<ApduServiceInfo> validServices = new ArrayList<ApduServiceInfo>();
195
196        List<ResolveInfo> resolvedServices = pm.queryIntentServicesAsUser(
197                new Intent(HostApduService.SERVICE_INTERFACE),
198                PackageManager.GET_META_DATA, userId);
199
200        List<ResolveInfo> resolvedOffHostServices = pm.queryIntentServicesAsUser(
201                new Intent(OffHostApduService.SERVICE_INTERFACE),
202                PackageManager.GET_META_DATA, userId);
203        resolvedServices.addAll(resolvedOffHostServices);
204
205        for (ResolveInfo resolvedService : resolvedServices) {
206            try {
207                boolean onHost = !resolvedOffHostServices.contains(resolvedService);
208                ServiceInfo si = resolvedService.serviceInfo;
209                ComponentName componentName = new ComponentName(si.packageName, si.name);
210                // Check if the package holds the NFC permission
211                if (pm.checkPermission(android.Manifest.permission.NFC, si.packageName) !=
212                        PackageManager.PERMISSION_GRANTED) {
213                    Log.e(TAG, "Skipping APDU service " + componentName +
214                            ": it does not require the permission " +
215                            android.Manifest.permission.NFC);
216                    continue;
217                }
218                if (!android.Manifest.permission.BIND_NFC_SERVICE.equals(
219                        si.permission)) {
220                    Log.e(TAG, "Skipping APDU service " + componentName +
221                            ": it does not require the permission " +
222                            android.Manifest.permission.BIND_NFC_SERVICE);
223                    continue;
224                }
225                ApduServiceInfo service = new ApduServiceInfo(pm, resolvedService, onHost);
226                if (service != null) {
227                    validServices.add(service);
228                }
229            } catch (XmlPullParserException e) {
230                Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
231            } catch (IOException e) {
232                Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
233            }
234        }
235
236        synchronized (mLock) {
237            UserServices userServices = findOrCreateUserLocked(userId);
238
239            // Find removed services
240            Iterator<Map.Entry<ComponentName, ApduServiceInfo>> it =
241                    userServices.services.entrySet().iterator();
242            while (it.hasNext()) {
243                Map.Entry<ComponentName, ApduServiceInfo> entry =
244                        (Map.Entry<ComponentName, ApduServiceInfo>) it.next();
245                if (!containsServiceLocked(validServices, entry.getKey())) {
246                    Log.d(TAG, "Service removed: " + entry.getKey());
247                    it.remove();
248                }
249            }
250            for (ApduServiceInfo service : validServices) {
251                if (DEBUG) Log.d(TAG, "Adding service: " + service.getComponent() +
252                        " AIDs: " + service.getAids());
253                userServices.services.put(service.getComponent(), service);
254            }
255        }
256
257        mCallback.onServicesUpdated(userId, Collections.unmodifiableList(validServices));
258        dump(validServices);
259    }
260
261    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
262        pw.println("Registered HCE services for current user: ");
263        UserServices userServices = findOrCreateUserLocked(ActivityManager.getCurrentUser());
264        for (ApduServiceInfo service : userServices.services.values()) {
265            pw.println("    " + service.getComponent() +
266                    " (Description: " + service.getDescription() + ")");
267        }
268        pw.println("");
269    }
270}
271