blob: 2e037d2c9803de628e4f1d04d17ab352e922bc55 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.nfc.cardemulation;
import org.xmlpull.v1.XmlPullParserException;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.nfc.cardemulation.ApduServiceInfo;
import android.nfc.cardemulation.HostApduService;
import android.nfc.cardemulation.OffHostApduService;
import android.os.UserHandle;
import android.util.Log;
import android.util.SparseArray;
import com.google.android.collect.Maps;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
public class RegisteredServicesCache {
static final String TAG = "RegisteredServicesCache";
static final boolean DEBUG = false;
final Context mContext;
final AtomicReference<BroadcastReceiver> mReceiver;
final Object mLock = new Object();
// All variables below synchronized on mLock
// mUserServices holds the card emulation services that are running for each user
final SparseArray<UserServices> mUserServices = new SparseArray<UserServices>();
final Callback mCallback;
public interface Callback {
void onServicesUpdated(int userId, final List<ApduServiceInfo> services);
};
private static class UserServices {
/**
* All services that have registered
*/
public final HashMap<ComponentName, ApduServiceInfo> services =
Maps.newHashMap(); // Re-built at run-time
};
private UserServices findOrCreateUserLocked(int userId) {
UserServices services = mUserServices.get(userId);
if (services == null) {
services = new UserServices();
mUserServices.put(userId, services);
}
return services;
}
public RegisteredServicesCache(Context context, Callback callback) {
mContext = context;
mCallback = callback;
final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
String action = intent.getAction();
if (DEBUG) Log.d(TAG, "Intent action: " + action);
if (uid != -1) {
boolean replaced = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) &&
(Intent.ACTION_PACKAGE_ADDED.equals(action) ||
Intent.ACTION_PACKAGE_REMOVED.equals(action));
if (!replaced) {
int currentUser = ActivityManager.getCurrentUser();
if (currentUser == UserHandle.getUserId(uid)) {
invalidateCache(UserHandle.getUserId(uid));
} else {
// Cache will automatically be updated on user switch
}
} else {
if (DEBUG) Log.d(TAG, "Ignoring package intent due to package being replaced.");
}
}
}
};
mReceiver = new AtomicReference<BroadcastReceiver>(receiver);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
intentFilter.addAction(Intent.ACTION_PACKAGE_FIRST_LAUNCH);
intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
intentFilter.addDataScheme("package");
mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, intentFilter, null, null);
// Register for events related to sdcard operations
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, sdFilter, null, null);
}
void dump(ArrayList<ApduServiceInfo> services) {
for (ApduServiceInfo service : services) {
if (DEBUG) Log.d(TAG, service.toString());
}
}
boolean containsServiceLocked(ArrayList<ApduServiceInfo> services, ComponentName serviceName) {
for (ApduServiceInfo service : services) {
if (service.getComponent().equals(serviceName)) return true;
}
return false;
}
public boolean hasService(int userId, ComponentName service) {
return getService(userId, service) != null;
}
public ApduServiceInfo getService(int userId, ComponentName service) {
synchronized (mLock) {
UserServices userServices = findOrCreateUserLocked(userId);
return userServices.services.get(service);
}
}
public List<ApduServiceInfo> getServices(int userId) {
final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
synchronized (mLock) {
UserServices userServices = findOrCreateUserLocked(userId);
services.addAll(userServices.services.values());
}
return services;
}
public List<ApduServiceInfo> getServicesForCategory(int userId, String category) {
final ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
synchronized (mLock) {
UserServices userServices = findOrCreateUserLocked(userId);
for (ApduServiceInfo service : userServices.services.values()) {
if (service.hasCategory(category)) services.add(service);
}
}
return services;
}
public void onNfcDisabled() {
}
public void onNfcEnabled() {
invalidateCache(ActivityManager.getCurrentUser());
}
public void invalidateCache(int userId) {
PackageManager pm;
try {
pm = mContext.createPackageContextAsUser("android", 0,
new UserHandle(userId)).getPackageManager();
} catch (NameNotFoundException e) {
Log.e(TAG, "Could not create user package context");
return;
}
ArrayList<ApduServiceInfo> validServices = new ArrayList<ApduServiceInfo>();
List<ResolveInfo> resolvedServices = pm.queryIntentServicesAsUser(
new Intent(HostApduService.SERVICE_INTERFACE),
PackageManager.GET_META_DATA, userId);
List<ResolveInfo> resolvedOffHostServices = pm.queryIntentServicesAsUser(
new Intent(OffHostApduService.SERVICE_INTERFACE),
PackageManager.GET_META_DATA, userId);
resolvedServices.addAll(resolvedOffHostServices);
for (ResolveInfo resolvedService : resolvedServices) {
try {
boolean onHost = !resolvedOffHostServices.contains(resolvedService);
ServiceInfo si = resolvedService.serviceInfo;
ComponentName componentName = new ComponentName(si.packageName, si.name);
if (!android.Manifest.permission.BIND_NFC_SERVICE.equals(
si.permission)) {
Log.e(TAG, "Skipping APDU service " + componentName +
": it does not require the permission " +
android.Manifest.permission.BIND_NFC_SERVICE);
continue;
}
ApduServiceInfo service = new ApduServiceInfo(pm, resolvedService, onHost);
if (service != null) {
validServices.add(service);
}
} catch (XmlPullParserException e) {
Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
} catch (IOException e) {
Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
}
}
synchronized (mLock) {
UserServices userServices = findOrCreateUserLocked(userId);
// Find removed services
Iterator<Map.Entry<ComponentName, ApduServiceInfo>> it =
userServices.services.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<ComponentName, ApduServiceInfo> entry =
(Map.Entry<ComponentName, ApduServiceInfo>) it.next();
if (!containsServiceLocked(validServices, entry.getKey())) {
Log.d(TAG, "Service removed: " + entry.getKey());
it.remove();
}
}
for (ApduServiceInfo service : validServices) {
if (DEBUG) Log.d(TAG, "Adding service: " + service.getComponent() +
" AIDs: " + service.getAids());
userServices.services.put(service.getComponent(), service);
}
}
mCallback.onServicesUpdated(userId, Collections.unmodifiableList(validServices));
dump(validServices);
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Registered HCE services for current user: ");
UserServices userServices = findOrCreateUserLocked(ActivityManager.getCurrentUser());
for (ApduServiceInfo service : userServices.services.values()) {
pw.println(" " + service.getComponent() +
" (Description: " + service.getDescription() + ")");
}
pw.println("");
}
}