blob: ce69f0880afb86c3cba8db9758b424b105ba8006 [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 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.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager.ResolveInfoFlags;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.nfc.cardemulation.AidGroup;
import android.nfc.cardemulation.ApduServiceInfo;
import android.nfc.cardemulation.CardEmulation;
import android.nfc.cardemulation.HostApduService;
import android.nfc.cardemulation.OffHostApduService;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.os.UserManager;
import android.sysprop.NfcProperties;
import android.util.AtomicFile;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
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;
/**
* This class is inspired by android.content.pm.RegisteredServicesCache
* That class was not re-used because it doesn't support dynamically
* registering additional properties, but generates everything from
* the manifest. Since we have some properties that are not in the manifest,
* it's less suited.
*/
public class RegisteredServicesCache {
static final String XML_INDENT_OUTPUT_FEATURE = "http://xmlpull.org/v1/doc/features.html#indent-output";
static final String TAG = "RegisteredServicesCache";
static final boolean DEBUG = NfcProperties.debug_enabled().orElse(false);
final Context mContext;
final AtomicReference<BroadcastReceiver> mReceiver;
final Object mLock = new Object();
// All variables below synchronized on mLock
// mUserHandles holds the UserHandles of all the profiles that belong to current user
@GuardedBy("mLock")
List<UserHandle> mUserHandles;
// mUserServices holds the card emulation services that are running for each user
final SparseArray<UserServices> mUserServices = new SparseArray<UserServices>();
final Callback mCallback;
final AtomicFile mDynamicSettingsFile;
final AtomicFile mOthersFile;
public interface Callback {
/**
* ServicesUpdated for specific userId.
*/
void onServicesUpdated(int userId, List<ApduServiceInfo> services,
boolean validateInstalled);
};
static class DynamicSettings {
public final int uid;
public final HashMap<String, AidGroup> aidGroups = new HashMap<>();
public String offHostSE;
public boolean defaultToObserveMode = false;
DynamicSettings(int uid) {
this.uid = uid;
}
};
static class OtherServiceStatus {
public final int uid;
public boolean checked;
OtherServiceStatus(int uid, boolean checked) {
this.uid = uid;
this.checked = checked;
}
};
private static class UserServices {
/**
* All services that have registered
*/
final HashMap<ComponentName, ApduServiceInfo> services =
new HashMap<>(); // Re-built at run-time
final HashMap<ComponentName, DynamicSettings> dynamicSettings =
new HashMap<>(); // In memory cache of dynamic settings
final HashMap<ComponentName, OtherServiceStatus> others =
new HashMap<>();
};
private UserServices findOrCreateUserLocked(int userId) {
UserServices services = mUserServices.get(userId);
if (services == null) {
services = new UserServices();
mUserServices.put(userId, services);
}
return services;
}
private int getProfileParentId(int userId) {
UserManager um = mContext.createContextAsUser(
UserHandle.of(userId), /*flags=*/0)
.getSystemService(UserManager.class);
UserHandle uh = um.getProfileParent(UserHandle.of(userId));
return uh == null ? userId : uh.getIdentifier();
}
public RegisteredServicesCache(Context context, Callback callback) {
mContext = context;
mCallback = callback;
refreshUserProfilesLocked();
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 (RoutingOptionManager.getInstance().isRoutingTableOverrided()) {
if (DEBUG) Log.d(TAG, "Routing table overrided. Skip invalidateCache()");
}
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 == getProfileParentId(UserHandle.
getUserHandleForUid(uid).getIdentifier())) {
if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
invalidateCache(UserHandle.
getUserHandleForUid(uid).getIdentifier(), true);
} else {
invalidateCache(UserHandle.
getUserHandleForUid(uid).getIdentifier(), false);
}
} 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.registerReceiverForAllUsers(mReceiver.get(), 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.registerReceiverForAllUsers(mReceiver.get(), sdFilter, null, null);
File dataDir = mContext.getFilesDir();
mDynamicSettingsFile = new AtomicFile(new File(dataDir, "dynamic_aids.xml"));
mOthersFile = new AtomicFile(new File(dataDir, "other_status.xml"));
}
void initialize() {
synchronized (mLock) {
readDynamicSettingsLocked();
readOthersLocked();
for (UserHandle uh : mUserHandles) {
invalidateCache(uh.getIdentifier(), false);
}
}
}
public void onUserSwitched() {
synchronized (mLock) {
refreshUserProfilesLocked();
}
}
public void onManagedProfileChanged() {
synchronized (mLock) {
refreshUserProfilesLocked();
}
}
private void refreshUserProfilesLocked() {
UserManager um = mContext.createContextAsUser(
UserHandle.of(ActivityManager.getCurrentUser()), /*flags=*/0)
.getSystemService(UserManager.class);
mUserHandles = um.getEnabledProfiles();
List<UserHandle> removeUserHandles = new ArrayList<UserHandle>();
for (UserHandle uh : mUserHandles) {
if (um.isQuietModeEnabled(uh)) {
removeUserHandles.add(uh);
}
}
mUserHandles.removeAll(removeUserHandles);
}
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;
}
ArrayList<ApduServiceInfo> getInstalledServices(int userId) {
PackageManager pm;
try {
pm = mContext.createPackageContextAsUser("android", 0,
UserHandle.of(userId)).getPackageManager();
} catch (NameNotFoundException e) {
Log.e(TAG, "Could not create user package context");
return null;
}
ArrayList<ApduServiceInfo> validServices = new ArrayList<ApduServiceInfo>();
List<ResolveInfo> resolvedServices = new ArrayList<>(pm.queryIntentServicesAsUser(
new Intent(HostApduService.SERVICE_INTERFACE),
ResolveInfoFlags.of(PackageManager.GET_META_DATA), UserHandle.of(userId)));
List<ResolveInfo> resolvedOffHostServices = pm.queryIntentServicesAsUser(
new Intent(OffHostApduService.SERVICE_INTERFACE),
ResolveInfoFlags.of(PackageManager.GET_META_DATA), UserHandle.of(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);
// Check if the package exported the service in manifest
if (!si.exported) {
Log.e(TAG, "Skipping application component " + componentName +
": it must configured as exported");
continue;
}
// Check if the package holds the NFC permission
if (pm.checkPermission(android.Manifest.permission.NFC, si.packageName) !=
PackageManager.PERMISSION_GRANTED) {
Log.e(TAG, "Skipping application component " + componentName +
": it must request the permission " +
android.Manifest.permission.NFC);
continue;
}
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);
}
}
return validServices;
}
/**
* invalidateCache for specific userId.
*/
public void invalidateCache(int userId, boolean validateInstalled) {
final ArrayList<ApduServiceInfo> validServices = getInstalledServices(userId);
if (validServices == null) {
return;
}
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);
}
// Apply dynamic settings mappings
ArrayList<ComponentName> toBeRemoved = new ArrayList<ComponentName>();
for (Map.Entry<ComponentName, DynamicSettings> entry :
userServices.dynamicSettings.entrySet()) {
// Verify component / uid match
ComponentName component = entry.getKey();
DynamicSettings dynamicSettings = entry.getValue();
ApduServiceInfo serviceInfo = userServices.services.get(component);
if (serviceInfo == null || (serviceInfo.getUid() != dynamicSettings.uid)) {
toBeRemoved.add(component);
continue;
} else {
for (AidGroup group : dynamicSettings.aidGroups.values()) {
serviceInfo.setDynamicAidGroup(group);
}
if (dynamicSettings.offHostSE != null) {
serviceInfo.setOffHostSecureElement(dynamicSettings.offHostSE);
}
}
}
if (toBeRemoved.size() > 0) {
for (ComponentName component : toBeRemoved) {
Log.d(TAG, "Removing dynamic AIDs registered by " + component);
userServices.dynamicSettings.remove(component);
}
// Persist to filesystem
writeDynamicSettingsLocked();
}
}
List<ApduServiceInfo> otherServices = getServicesForCategory(userId,
CardEmulation.CATEGORY_OTHER);
invalidateOther(userId, otherServices);
mCallback.onServicesUpdated(userId, Collections.unmodifiableList(validServices),
validateInstalled);
dump(validServices);
}
private void invalidateOther(int userId, List<ApduServiceInfo> validOtherServices) {
Log.d(TAG, "invalidate : " + userId);
// remove services
synchronized (mLock) {
UserServices userServices = findOrCreateUserLocked(userId);
boolean needToWrite = false;
Iterator<Map.Entry<ComponentName, OtherServiceStatus>> it =
userServices.others.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<ComponentName, OtherServiceStatus> entry = it.next();
if (!containsServiceLocked((ArrayList<ApduServiceInfo>) validOtherServices,
entry.getKey())) {
Log.d(TAG, "Service removed: " + entry.getKey());
needToWrite = true;
it.remove();
}
}
UserManager um = mContext.createContextAsUser(
UserHandle.of(ActivityManager.getCurrentUser()), /*flags=*/0)
.getSystemService(UserManager.class);
boolean isManagedProfile = um.isManagedProfile(userId);
Log.i(TAG, "current user: " + ActivityManager.getCurrentUser() +
", is managed profile : " + isManagedProfile );
boolean isChecked = !(isManagedProfile);
// TODO: b/313040065 temperatory set isChecked always true due to there's no UI in AOSP
isChecked = true;
for (ApduServiceInfo service : validOtherServices) {
Log.d(TAG, "update valid otherService: " + service.getComponent()
+ " AIDs: " + service.getAids());
if (!service.hasCategory(CardEmulation.CATEGORY_OTHER)) {
Log.e(TAG, "service does not have other category");
continue;
}
ComponentName component = service.getComponent();
OtherServiceStatus status = userServices.others.get(component);
if (status == null) {
Log.d(TAG, "New other service");
status = new OtherServiceStatus(service.getUid(), isChecked);
needToWrite = true;
} else {
Log.d(TAG, "Existed other service");
}
service.setCategoryOtherServiceEnabled(status.checked);
userServices.others.put(component, status);
}
if (needToWrite) {
writeOthersLocked();
}
}
}
private void readDynamicSettingsLocked() {
FileInputStream fis = null;
try {
if (!mDynamicSettingsFile.getBaseFile().exists()) {
Log.d(TAG, "Dynamic AIDs file does not exist.");
return;
}
fis = mDynamicSettingsFile.openRead();
XmlPullParser parser = Xml.newPullParser();
parser.setInput(fis, null);
int eventType = parser.getEventType();
while (eventType != XmlPullParser.START_TAG &&
eventType != XmlPullParser.END_DOCUMENT) {
eventType = parser.next();
}
String tagName = parser.getName();
if ("services".equals(tagName)) {
boolean inService = false;
ComponentName currentComponent = null;
int currentUid = -1;
String currentOffHostSE = null;
boolean defaultToObserveMode = false;
ArrayList<AidGroup> currentGroups = new ArrayList<AidGroup>();
while (eventType != XmlPullParser.END_DOCUMENT) {
tagName = parser.getName();
if (eventType == XmlPullParser.START_TAG) {
if ("service".equals(tagName) && parser.getDepth() == 2) {
String compString = parser.getAttributeValue(null, "component");
String uidString = parser.getAttributeValue(null, "uid");
String offHostString = parser.getAttributeValue(null, "offHostSE");
String defaultToObserveModeStr =
parser.getAttributeValue(null, "defaultToObserveMode");
if (compString == null || uidString == null) {
Log.e(TAG, "Invalid service attributes");
} else {
try {
currentUid = Integer.parseInt(uidString);
currentComponent = ComponentName.unflattenFromString(compString);
currentOffHostSE = offHostString;
inService = true;
defaultToObserveMode =
XmlUtils.convertValueToBoolean(defaultToObserveModeStr,
false);
} catch (NumberFormatException e) {
Log.e(TAG, "Could not parse service uid");
}
}
}
if ("aid-group".equals(tagName) && parser.getDepth() == 3 && inService) {
AidGroup group = AidGroup.createFromXml(parser);
if (group != null) {
currentGroups.add(group);
} else {
Log.e(TAG, "Could not parse AID group.");
}
}
} else if (eventType == XmlPullParser.END_TAG) {
if ("service".equals(tagName)) {
// See if we have a valid service
if (currentComponent != null && currentUid >= 0 &&
(currentGroups.size() > 0 || currentOffHostSE != null)) {
final int userId = UserHandle.
getUserHandleForUid(currentUid).getIdentifier();
DynamicSettings dynSettings = new DynamicSettings(currentUid);
for (AidGroup group : currentGroups) {
dynSettings.aidGroups.put(group.getCategory(), group);
}
dynSettings.offHostSE = currentOffHostSE;
dynSettings.defaultToObserveMode = defaultToObserveMode;
UserServices services = findOrCreateUserLocked(userId);
services.dynamicSettings.put(currentComponent, dynSettings);
}
currentUid = -1;
currentComponent = null;
currentGroups.clear();
inService = false;
currentOffHostSE = null;
}
}
eventType = parser.next();
};
}
} catch (Exception e) {
Log.e(TAG, "Could not parse dynamic AIDs file, trashing.");
mDynamicSettingsFile.delete();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
}
}
}
}
private void readOthersLocked() {
Log.d(TAG, "read others locked");
FileInputStream fis = null;
try {
if (!mOthersFile.getBaseFile().exists()) {
Log.d(TAG, "Dynamic AIDs file does not exist.");
return;
}
fis = mOthersFile.openRead();
XmlPullParser parser = Xml.newPullParser();
parser.setInput(fis, null);
int eventType = parser.getEventType();
while (eventType != XmlPullParser.START_TAG &&
eventType != XmlPullParser.END_DOCUMENT) {
eventType = parser.next();
}
String tagName = parser.getName();
if ("services".equals(tagName)) {
boolean checked = false;
ComponentName currentComponent = null;
int currentUid = -1;
while (eventType != XmlPullParser.END_DOCUMENT) {
tagName = parser.getName();
if (eventType == XmlPullParser.START_TAG) {
if ("service".equals(tagName) && parser.getDepth() == 2) {
String compString = parser.getAttributeValue(null, "component");
String uidString = parser.getAttributeValue(null, "uid");
String checkedString = parser.getAttributeValue(null, "checked");
if (compString == null || uidString == null || checkedString == null) {
Log.e(TAG, "Invalid service attributes");
} else {
try {
currentUid = Integer.parseInt(uidString);
currentComponent =
ComponentName.unflattenFromString(compString);
checked = checkedString.equals("true") ? true : false;
} catch (NumberFormatException e) {
Log.e(TAG, "Could not parse service uid");
}
}
}
} else if (eventType == XmlPullParser.END_TAG) {
if ("service".equals(tagName)) {
// See if we have a valid service
if (currentComponent != null && currentUid >= 0) {
Log.d(TAG, " end of service tag");
final int userId = UserHandle.getUserId(currentUid);
OtherServiceStatus status =
new OtherServiceStatus(currentUid, checked);
Log.d(TAG, " ## user id - " + userId);
UserServices services = findOrCreateUserLocked(userId);
services.others.put(currentComponent, status);
}
currentUid = -1;
currentComponent = null;
checked = false;
}
}
eventType = parser.next();
}
}
} catch (Exception e) {
Log.e(TAG, "Could not parse others AIDs file, trashing.");
mOthersFile.delete();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// It is safe to ignore I/O exceptions when closing FileInputStream
}
}
}
}
private boolean writeDynamicSettingsLocked() {
FileOutputStream fos = null;
try {
fos = mDynamicSettingsFile.startWrite();
XmlSerializer out = Xml.newSerializer();
out.setOutput(fos, "utf-8");
out.startDocument(null, true);
out.setFeature(XML_INDENT_OUTPUT_FEATURE, true);
out.startTag(null, "services");
for (int i = 0; i < mUserServices.size(); i++) {
final UserServices user = mUserServices.valueAt(i);
for (Map.Entry<ComponentName, DynamicSettings> service :
user.dynamicSettings.entrySet()) {
out.startTag(null, "service");
out.attribute(null, "component", service.getKey().flattenToString());
out.attribute(null, "uid", Integer.toString(service.getValue().uid));
if(service.getValue().offHostSE != null) {
out.attribute(null, "offHostSE", service.getValue().offHostSE);
}
out.attribute(null,"defaultToObserveMode",
Boolean.toString(service.getValue().defaultToObserveMode));
for (AidGroup group : service.getValue().aidGroups.values()) {
group.writeAsXml(out);
}
out.endTag(null, "service");
}
}
out.endTag(null, "services");
out.endDocument();
mDynamicSettingsFile.finishWrite(fos);
return true;
} catch (Exception e) {
Log.e(TAG, "Error writing dynamic AIDs", e);
if (fos != null) {
mDynamicSettingsFile.failWrite(fos);
}
return false;
}
}
private boolean writeOthersLocked() {
Log.d(TAG, "write Others Locked()");
FileOutputStream fos = null;
try {
fos = mOthersFile.startWrite();
XmlSerializer out = new FastXmlSerializer();
out.setOutput(fos, "utf-8");
out.startDocument(null, true);
out.setFeature(XML_INDENT_OUTPUT_FEATURE, true);
out.startTag(null, "services");
Log.d(TAG, "userServices.size: " + mUserServices.size());
for (int i = 0; i < mUserServices.size(); i++) {
final UserServices user = mUserServices.valueAt(i);
int userId = mUserServices.keyAt(i);
// Checking for 1 times
Log.d(TAG, "userId: " + userId);
Log.d(TAG, "others size: " + user.others.size());
ArrayList<ComponentName> currentService = new ArrayList<ComponentName>();
for (Map.Entry<ComponentName, OtherServiceStatus> service :
user.others.entrySet()) {
Log.d(TAG, "component: " + service.getKey().flattenToString() +
", checked: " + service.getValue().checked);
boolean hasDupe = false;
for (ComponentName cn : currentService) {
if (cn.equals(service.getKey())) {
hasDupe = true;
break;
}
}
if (hasDupe) {
continue;
} else {
Log.d(TAG, "Already written.");
currentService.add(service.getKey());
}
out.startTag(null, "service");
out.attribute(null, "component", service.getKey().flattenToString());
out.attribute(null, "uid", Integer.toString(service.getValue().uid));
out.attribute(null, "checked", Boolean.toString(service.getValue().checked));
out.endTag(null, "service");
}
}
out.endTag(null, "services");
out.endDocument();
mOthersFile.finishWrite(fos);
return true;
} catch (Exception e) {
Log.e(TAG, "Error writing dynamic AIDs", e);
if (fos != null) {
mOthersFile.failWrite(fos);
}
return false;
}
}
public boolean setOffHostSecureElement(int userId, int uid, ComponentName componentName,
String offHostSE) {
ArrayList<ApduServiceInfo> newServices = null;
synchronized (mLock) {
UserServices services = findOrCreateUserLocked(userId);
// Check if we can find this service
ApduServiceInfo serviceInfo = getService(userId, componentName);
if (serviceInfo == null) {
Log.e(TAG, "Service " + componentName + " does not exist.");
return false;
}
if (serviceInfo.getUid() != uid) {
// This is probably a good indication something is wrong here.
// Either newer service installed with different uid (but then
// we should have known about it), or somebody calling us from
// a different uid.
Log.e(TAG, "UID mismatch.");
return false;
}
if (offHostSE == null || serviceInfo.isOnHost()) {
Log.e(TAG, "OffHostSE mismatch with Service type");
return false;
}
DynamicSettings dynSettings = services.dynamicSettings.get(componentName);
if (dynSettings == null) {
dynSettings = new DynamicSettings(uid);
}
dynSettings.offHostSE = offHostSE;
boolean success = writeDynamicSettingsLocked();
if (!success) {
Log.e(TAG, "Failed to persist AID group.");
dynSettings.offHostSE = null;
return false;
}
serviceInfo.setOffHostSecureElement(offHostSE);
newServices = new ArrayList<ApduServiceInfo>(services.services.values());
}
// Make callback without the lock held
mCallback.onServicesUpdated(userId, newServices, true);
return true;
}
public boolean resetOffHostSecureElement(int userId, int uid, ComponentName componentName) {
ArrayList<ApduServiceInfo> newServices = null;
synchronized (mLock) {
UserServices services = findOrCreateUserLocked(userId);
// Check if we can find this service
ApduServiceInfo serviceInfo = getService(userId, componentName);
if (serviceInfo == null) {
Log.e(TAG, "Service " + componentName + " does not exist.");
return false;
}
if (serviceInfo.getUid() != uid) {
// This is probably a good indication something is wrong here.
// Either newer service installed with different uid (but then
// we should have known about it), or somebody calling us from
// a different uid.
Log.e(TAG, "UID mismatch.");
return false;
}
if (serviceInfo.isOnHost() || serviceInfo.getOffHostSecureElement() == null) {
Log.e(TAG, "OffHostSE is not set");
return false;
}
DynamicSettings dynSettings = services.dynamicSettings.get(componentName);
String offHostSE = dynSettings.offHostSE;
dynSettings.offHostSE = null;
boolean success = writeDynamicSettingsLocked();
if (!success) {
Log.e(TAG, "Failed to persist AID group.");
dynSettings.offHostSE = offHostSE;
return false;
}
serviceInfo.resetOffHostSecureElement();
newServices = new ArrayList<ApduServiceInfo>(services.services.values());
}
// Make callback without the lock held
mCallback.onServicesUpdated(userId, newServices, true);
return true;
}
public boolean setServiceObserveModeDefault(int userId, int uid,
ComponentName componentName, boolean enable) {
synchronized (mLock) {
UserServices services = findOrCreateUserLocked(userId);
// Check if we can find this service
ApduServiceInfo serviceInfo = getService(userId, componentName);
if (serviceInfo == null) {
Log.e(TAG, "Service " + componentName + " does not exist.");
return false;
}
if (serviceInfo.getUid() != uid) {
// This is probably a good indication something is wrong here.
// Either newer service installed with different uid (but then
// we should have known about it), or somebody calling us from
// a different uid.
Log.e(TAG, "UID mismatch.");
return false;
}
DynamicSettings dynSettings = services.dynamicSettings.get(componentName);
if (dynSettings == null) {
dynSettings = new DynamicSettings(uid);
dynSettings.offHostSE = null;
services.dynamicSettings.put(componentName, dynSettings);
}
dynSettings.defaultToObserveMode = enable;
}
return true;
}
public boolean registerAidGroupForService(int userId, int uid,
ComponentName componentName, AidGroup aidGroup) {
ArrayList<ApduServiceInfo> newServices = null;
boolean success;
synchronized (mLock) {
UserServices services = findOrCreateUserLocked(userId);
// Check if we can find this service
ApduServiceInfo serviceInfo = getService(userId, componentName);
if (serviceInfo == null) {
Log.e(TAG, "Service " + componentName + " does not exist.");
return false;
}
if (serviceInfo.getUid() != uid) {
// This is probably a good indication something is wrong here.
// Either newer service installed with different uid (but then
// we should have known about it), or somebody calling us from
// a different uid.
Log.e(TAG, "UID mismatch.");
return false;
}
// Do another AID validation, since a caller could have thrown in a
// modified AidGroup object with invalid AIDs over Binder.
List<String> aids = aidGroup.getAids();
for (String aid : aids) {
if (!CardEmulation.isValidAid(aid)) {
Log.e(TAG, "AID " + aid + " is not a valid AID");
return false;
}
}
serviceInfo.setDynamicAidGroup(aidGroup);
DynamicSettings dynSettings = services.dynamicSettings.get(componentName);
if (dynSettings == null) {
dynSettings = new DynamicSettings(uid);
dynSettings.offHostSE = null;
services.dynamicSettings.put(componentName, dynSettings);
}
dynSettings.aidGroups.put(aidGroup.getCategory(), aidGroup);
success = writeDynamicSettingsLocked();
if (success) {
newServices =
new ArrayList<ApduServiceInfo>(services.services.values());
} else {
Log.e(TAG, "Failed to persist AID group.");
// Undo registration
dynSettings.aidGroups.remove(aidGroup.getCategory());
}
}
if (success) {
// Make callback without the lock held
mCallback.onServicesUpdated(userId, newServices, true);
}
return success;
}
public boolean registerOtherForService(int userId,
ComponentName componentName, boolean checked) {
if (DEBUG) Log.d(TAG, "[register other] checked:" + checked + ", " + componentName);
ArrayList<ApduServiceInfo> newServices = null;
boolean success = false;
synchronized (mLock) {
Log.d(TAG, "registerOtherForService / ComponentName" + componentName);
ApduServiceInfo serviceInfo = getService(userId, componentName);
if (serviceInfo == null) {
Log.e(TAG, "Service " + componentName + "does not exist");
return false;
}
success = updateOtherServiceStatus(userId, serviceInfo, checked);
if (success) {
UserServices userService = findOrCreateUserLocked(userId);
newServices = new ArrayList<ApduServiceInfo>(userService.services.values());
} else {
Log.e(TAG, "Fail to other checked");
}
}
if (success) {
if (DEBUG) Log.d(TAG, "other list update due to User Select " + componentName);
mCallback.onServicesUpdated(userId, Collections.unmodifiableList(newServices),false);
}
return success;
}
public AidGroup getAidGroupForService(int userId, int uid, ComponentName componentName,
String category) {
ApduServiceInfo serviceInfo = getService(userId, componentName);
if (serviceInfo != null) {
if (serviceInfo.getUid() != uid) {
Log.e(TAG, "UID mismatch");
return null;
}
return serviceInfo.getDynamicAidGroupForCategory(category);
} else {
Log.e(TAG, "Could not find service " + componentName);
return null;
}
}
public boolean removeAidGroupForService(int userId, int uid, ComponentName componentName,
String category) {
boolean success = false;
ArrayList<ApduServiceInfo> newServices = null;
synchronized (mLock) {
UserServices services = findOrCreateUserLocked(userId);
ApduServiceInfo serviceInfo = getService(userId, componentName);
if (serviceInfo != null) {
if (serviceInfo.getUid() != uid) {
// Calling from different uid
Log.e(TAG, "UID mismatch");
return false;
}
if (!serviceInfo.removeDynamicAidGroupForCategory(category)) {
Log.e(TAG," Could not find dynamic AIDs for category " + category);
return false;
}
// Remove from local cache
DynamicSettings dynSettings = services.dynamicSettings.get(componentName);
if (dynSettings != null) {
AidGroup deletedGroup = dynSettings.aidGroups.remove(category);
success = writeDynamicSettingsLocked();
if (success) {
newServices = new ArrayList<ApduServiceInfo>(services.services.values());
} else {
Log.e(TAG, "Could not persist deleted AID group.");
dynSettings.aidGroups.put(category, deletedGroup);
return false;
}
} else {
Log.e(TAG, "Could not find aid group in local cache.");
}
} else {
Log.e(TAG, "Service " + componentName + " does not exist.");
}
}
if (success) {
mCallback.onServicesUpdated(userId, newServices, true);
}
return success;
}
boolean doesServiceDefaultToObserveMode(int userId, ComponentName service) {
UserServices services = findOrCreateUserLocked(userId);
DynamicSettings dynSettings = services.dynamicSettings.get(service);
return dynSettings != null && dynSettings.defaultToObserveMode;
}
private boolean updateOtherServiceStatus(int userId, ApduServiceInfo service, boolean checked) {
UserServices userServices = findOrCreateUserLocked(userId);
OtherServiceStatus status = userServices.others.get(service.getComponent());
// This is Error handling code if otherServiceStatus is null
if (status == null) {
Log.d(TAG, service.getComponent() + " status is could not be null");
return false;
}
if (service.isCategoryOtherServiceEnabled() == checked) {
Log.d(TAG, "already same status: " + checked);
return false;
}
service.setCategoryOtherServiceEnabled(checked);
status.checked = checked;
return writeOthersLocked();
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Registered HCE services for current user: ");
ParcelFileDescriptor pFd;
try {
pFd = ParcelFileDescriptor.dup(fd);
synchronized (mLock) {
for (UserHandle uh : mUserHandles) {
UserManager um = mContext.createContextAsUser(
uh, /*flags=*/0).getSystemService(UserManager.class);
pw.println("User " + um.getUserName() + " : ");
UserServices userServices = findOrCreateUserLocked(uh.getIdentifier());
for (ApduServiceInfo service : userServices.services.values()) {
service.dump(pFd, pw, args);
pw.println("");
}
pw.println("");
}
}
pFd.close();
} catch (IOException e) {
pw.println("Failed to dump HCE services: " + e);
}
}
/**
* Dump debugging information as a RegisteredServicesCacheProto
*
* Note:
* See proto definition in frameworks/base/core/proto/android/nfc/card_emulation.proto
* When writing a nested message, must call {@link ProtoOutputStream#start(long)} before and
* {@link ProtoOutputStream#end(long)} after.
* Never reuse a proto field number. When removing a field, mark it as reserved.
*/
void dumpDebug(ProtoOutputStream proto) {
UserServices userServices = findOrCreateUserLocked(ActivityManager.getCurrentUser());
for (ApduServiceInfo service : userServices.services.values()) {
long token = proto.start(RegisteredServicesCacheProto.APDU_SERVICE_INFOS);
service.dumpDebug(proto);
proto.end(token);
}
}
}