Merge "RegisteredServicesCache now saves files to a user-specific dir"
This commit is contained in:
committed by
Android (Google) Code Review
commit
bc26d2eafd
@@ -27,6 +27,7 @@ import android.content.res.XmlResourceParser;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
@@ -47,9 +48,9 @@ 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.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -74,6 +75,7 @@ import libcore.io.IoUtils;
|
||||
public abstract class RegisteredServicesCache<V> {
|
||||
private static final String TAG = "PackageManager";
|
||||
private static final boolean DEBUG = false;
|
||||
protected static final String REGISTERED_SERVICES_DIR = "registered_services";
|
||||
|
||||
public final Context mContext;
|
||||
private final String mInterfaceName;
|
||||
@@ -83,58 +85,68 @@ public abstract class RegisteredServicesCache<V> {
|
||||
|
||||
private final Object mServicesLock = new Object();
|
||||
|
||||
@GuardedBy("mServicesLock")
|
||||
private boolean mPersistentServicesFileDidNotExist;
|
||||
@GuardedBy("mServicesLock")
|
||||
private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
|
||||
|
||||
private static class UserServices<V> {
|
||||
@GuardedBy("mServicesLock")
|
||||
public final Map<V, Integer> persistentServices = Maps.newHashMap();
|
||||
final Map<V, Integer> persistentServices = Maps.newHashMap();
|
||||
@GuardedBy("mServicesLock")
|
||||
public Map<V, ServiceInfo<V>> services = null;
|
||||
Map<V, ServiceInfo<V>> services = null;
|
||||
@GuardedBy("mServicesLock")
|
||||
boolean mPersistentServicesFileDidNotExist = true;
|
||||
}
|
||||
|
||||
@GuardedBy("mServicesLock")
|
||||
private UserServices<V> findOrCreateUserLocked(int userId) {
|
||||
return findOrCreateUserLocked(userId, true);
|
||||
}
|
||||
|
||||
@GuardedBy("mServicesLock")
|
||||
private UserServices<V> findOrCreateUserLocked(int userId, boolean loadFromFileIfNew) {
|
||||
UserServices<V> services = mUserServices.get(userId);
|
||||
if (services == null) {
|
||||
services = new UserServices<V>();
|
||||
mUserServices.put(userId, services);
|
||||
if (loadFromFileIfNew && mSerializerAndParser != null) {
|
||||
// Check if user exists and try loading data from file
|
||||
// clear existing data if there was an error during migration
|
||||
UserInfo user = getUser(userId);
|
||||
if (user != null) {
|
||||
AtomicFile file = createFileForUser(user.id);
|
||||
if (file.getBaseFile().exists()) {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, String.format("Loading u%s data from %s", user.id, file));
|
||||
}
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = file.openRead();
|
||||
readPersistentServicesLocked(is);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Error reading persistent services for user " + user.id, e);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(is);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
/**
|
||||
* This file contains the list of known services. We would like to maintain this forever
|
||||
* so we store it as an XML file.
|
||||
*/
|
||||
private final AtomicFile mPersistentServicesFile;
|
||||
|
||||
// the listener and handler are synchronized on "this" and must be updated together
|
||||
private RegisteredServicesCacheListener<V> mListener;
|
||||
private Handler mHandler;
|
||||
|
||||
public RegisteredServicesCache(Context context, String interfaceName, String metaDataName,
|
||||
String attributeName, XmlSerializerAndParser<V> serializerAndParser) {
|
||||
this(context, interfaceName, metaDataName, attributeName, serializerAndParser,
|
||||
Environment.getDataDirectory());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected RegisteredServicesCache(Context context, String interfaceName, String metaDataName,
|
||||
String attributeName, XmlSerializerAndParser<V> serializerAndParser, File dataDir) {
|
||||
mContext = context;
|
||||
mInterfaceName = interfaceName;
|
||||
mMetaDataName = metaDataName;
|
||||
mAttributesName = attributeName;
|
||||
mSerializerAndParser = serializerAndParser;
|
||||
|
||||
File systemDir = new File(dataDir, "system");
|
||||
File syncDir = new File(systemDir, "registered_services");
|
||||
mPersistentServicesFile = new AtomicFile(new File(syncDir, interfaceName + ".xml"));
|
||||
|
||||
// Load persisted services from disk
|
||||
readPersistentServicesLocked();
|
||||
migrateIfNecessaryLocked();
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
||||
@@ -148,6 +160,11 @@ public abstract class RegisteredServicesCache<V> {
|
||||
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
|
||||
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
|
||||
mContext.registerReceiver(mExternalReceiver, sdFilter);
|
||||
|
||||
// Register for user-related events
|
||||
IntentFilter userFilter = new IntentFilter();
|
||||
sdFilter.addAction(Intent.ACTION_USER_REMOVED);
|
||||
mContext.registerReceiver(mUserRemovedReceiver, userFilter);
|
||||
}
|
||||
|
||||
private final void handlePackageEvent(Intent intent, int userId) {
|
||||
@@ -199,6 +216,17 @@ public abstract class RegisteredServicesCache<V> {
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver mUserRemovedReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "u" + userId + " removed - cleaning up");
|
||||
}
|
||||
onUserRemoved(userId);
|
||||
}
|
||||
};
|
||||
|
||||
public void invalidateCache(int userId) {
|
||||
synchronized (mServicesLock) {
|
||||
final UserServices<V> user = findOrCreateUserLocked(userId);
|
||||
@@ -390,7 +418,7 @@ public abstract class RegisteredServicesCache<V> {
|
||||
changed = true;
|
||||
user.services.put(info.type, info);
|
||||
user.persistentServices.put(info.type, info.uid);
|
||||
if (!(mPersistentServicesFileDidNotExist && firstScan)) {
|
||||
if (!(user.mPersistentServicesFileDidNotExist && firstScan)) {
|
||||
notifyListener(info.type, userId, false /* removed */);
|
||||
}
|
||||
} else if (previousUid == info.uid) {
|
||||
@@ -460,7 +488,7 @@ public abstract class RegisteredServicesCache<V> {
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
writePersistentServicesLocked();
|
||||
writePersistentServicesLocked(user, userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -542,88 +570,151 @@ public abstract class RegisteredServicesCache<V> {
|
||||
/**
|
||||
* Read all sync status back in to the initial engine state.
|
||||
*/
|
||||
private void readPersistentServicesLocked() {
|
||||
mUserServices.clear();
|
||||
private void readPersistentServicesLocked(InputStream is)
|
||||
throws XmlPullParserException, IOException {
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setInput(is, 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)) {
|
||||
eventType = parser.next();
|
||||
do {
|
||||
if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) {
|
||||
tagName = parser.getName();
|
||||
if ("service".equals(tagName)) {
|
||||
V service = mSerializerAndParser.createFromXml(parser);
|
||||
if (service == null) {
|
||||
break;
|
||||
}
|
||||
String uidString = parser.getAttributeValue(null, "uid");
|
||||
final int uid = Integer.parseInt(uidString);
|
||||
final int userId = UserHandle.getUserId(uid);
|
||||
final UserServices<V> user = findOrCreateUserLocked(userId,
|
||||
false /*loadFromFileIfNew*/) ;
|
||||
user.persistentServices.put(service, uid);
|
||||
}
|
||||
}
|
||||
eventType = parser.next();
|
||||
} while (eventType != XmlPullParser.END_DOCUMENT);
|
||||
}
|
||||
}
|
||||
|
||||
private void migrateIfNecessaryLocked() {
|
||||
if (mSerializerAndParser == null) {
|
||||
return;
|
||||
}
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
mPersistentServicesFileDidNotExist = !mPersistentServicesFile.getBaseFile().exists();
|
||||
if (mPersistentServicesFileDidNotExist) {
|
||||
return;
|
||||
}
|
||||
fis = mPersistentServicesFile.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)) {
|
||||
eventType = parser.next();
|
||||
do {
|
||||
if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) {
|
||||
tagName = parser.getName();
|
||||
if ("service".equals(tagName)) {
|
||||
V service = mSerializerAndParser.createFromXml(parser);
|
||||
if (service == null) {
|
||||
break;
|
||||
File systemDir = new File(getDataDirectory(), "system");
|
||||
File syncDir = new File(systemDir, REGISTERED_SERVICES_DIR);
|
||||
AtomicFile oldFile = new AtomicFile(new File(syncDir, mInterfaceName + ".xml"));
|
||||
boolean oldFileExists = oldFile.getBaseFile().exists();
|
||||
|
||||
if (oldFileExists) {
|
||||
File marker = new File(syncDir, mInterfaceName + ".xml.migrated");
|
||||
// if not migrated, perform the migration and add a marker
|
||||
if (!marker.exists()) {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Marker file " + marker + " does not exist - running migration");
|
||||
}
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = oldFile.openRead();
|
||||
mUserServices.clear();
|
||||
readPersistentServicesLocked(is);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Error reading persistent services, starting from scratch", e);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(is);
|
||||
}
|
||||
try {
|
||||
for (UserInfo user : getUsers()) {
|
||||
UserServices<V> userServices = mUserServices.get(user.id);
|
||||
if (userServices != null) {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Migrating u" + user.id + " services "
|
||||
+ userServices.persistentServices);
|
||||
}
|
||||
String uidString = parser.getAttributeValue(null, "uid");
|
||||
final int uid = Integer.parseInt(uidString);
|
||||
final int userId = UserHandle.getUserId(uid);
|
||||
final UserServices<V> user = findOrCreateUserLocked(userId);
|
||||
user.persistentServices.put(service, uid);
|
||||
writePersistentServicesLocked(userServices, user.id);
|
||||
}
|
||||
}
|
||||
eventType = parser.next();
|
||||
} while (eventType != XmlPullParser.END_DOCUMENT);
|
||||
marker.createNewFile();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Migration failed", e);
|
||||
}
|
||||
// Migration is complete and we don't need to keep data for all users anymore,
|
||||
// It will be loaded from a new location when requested
|
||||
mUserServices.clear();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Error reading persistent services, starting from scratch", e);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(fis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write all sync status to the sync status file.
|
||||
* Writes services of a specified user to the file.
|
||||
*/
|
||||
private void writePersistentServicesLocked() {
|
||||
private void writePersistentServicesLocked(UserServices<V> user, int userId) {
|
||||
if (mSerializerAndParser == null) {
|
||||
return;
|
||||
}
|
||||
AtomicFile atomicFile = createFileForUser(userId);
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = mPersistentServicesFile.startWrite();
|
||||
fos = atomicFile.startWrite();
|
||||
XmlSerializer out = new FastXmlSerializer();
|
||||
out.setOutput(fos, "utf-8");
|
||||
out.startDocument(null, true);
|
||||
out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
|
||||
out.startTag(null, "services");
|
||||
for (int i = 0; i < mUserServices.size(); i++) {
|
||||
final UserServices<V> user = mUserServices.valueAt(i);
|
||||
for (Map.Entry<V, Integer> service : user.persistentServices.entrySet()) {
|
||||
out.startTag(null, "service");
|
||||
out.attribute(null, "uid", Integer.toString(service.getValue()));
|
||||
mSerializerAndParser.writeAsXml(service.getKey(), out);
|
||||
out.endTag(null, "service");
|
||||
}
|
||||
for (Map.Entry<V, Integer> service : user.persistentServices.entrySet()) {
|
||||
out.startTag(null, "service");
|
||||
out.attribute(null, "uid", Integer.toString(service.getValue()));
|
||||
mSerializerAndParser.writeAsXml(service.getKey(), out);
|
||||
out.endTag(null, "service");
|
||||
}
|
||||
out.endTag(null, "services");
|
||||
out.endDocument();
|
||||
mPersistentServicesFile.finishWrite(fos);
|
||||
atomicFile.finishWrite(fos);
|
||||
} catch (IOException e1) {
|
||||
Log.w(TAG, "Error writing accounts", e1);
|
||||
if (fos != null) {
|
||||
mPersistentServicesFile.failWrite(fos);
|
||||
atomicFile.failWrite(fos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected void onUserRemoved(int userId) {
|
||||
mUserServices.remove(userId);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected List<UserInfo> getUsers() {
|
||||
return UserManager.get(mContext).getUsers(true);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected UserInfo getUser(int userId) {
|
||||
return UserManager.get(mContext).getUserInfo(userId);
|
||||
}
|
||||
|
||||
private AtomicFile createFileForUser(int userId) {
|
||||
File userDir = getUserSystemDirectory(userId);
|
||||
File userFile = new File(userDir, REGISTERED_SERVICES_DIR + "/" + mInterfaceName + ".xml");
|
||||
return new AtomicFile(userFile);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected File getUserSystemDirectory(int userId) {
|
||||
return Environment.getUserSystemDirectory(userId);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected File getDataDirectory() {
|
||||
return Environment.getDataDirectory();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected Map<V, Integer> getPersistentServices(int userId) {
|
||||
return findOrCreateUserLocked(userId).persistentServices;
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
package android.content.pm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.FileUtils;
|
||||
import android.os.Parcel;
|
||||
@@ -30,6 +29,7 @@ import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -44,6 +44,12 @@ import java.util.Set;
|
||||
* Tests for {@link android.content.pm.RegisteredServicesCache}
|
||||
*/
|
||||
public class RegisteredServicesCacheTest extends AndroidTestCase {
|
||||
private static final int U0 = 0;
|
||||
private static final int U1 = 1;
|
||||
private static final int UID1 = 1;
|
||||
private static final int UID2 = 2;
|
||||
// Represents UID of a system image process
|
||||
private static final int SYSTEM_IMAGE_UID = 20;
|
||||
|
||||
private final ResolveInfo r1 = new ResolveInfo();
|
||||
private final ResolveInfo r2 = new ResolveInfo();
|
||||
@@ -51,6 +57,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
|
||||
private final TestServiceType t2 = new TestServiceType("t2", "value2");
|
||||
private File mDataDir;
|
||||
private File mSyncDir;
|
||||
private List<UserInfo> mUsers;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
@@ -58,97 +65,152 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
|
||||
File cacheDir = mContext.getCacheDir();
|
||||
mDataDir = new File(cacheDir, "testServicesCache");
|
||||
FileUtils.deleteContents(mDataDir);
|
||||
mSyncDir = new File(mDataDir, "system/registered_services");
|
||||
mSyncDir = new File(mDataDir, "system/"+RegisteredServicesCache.REGISTERED_SERVICES_DIR);
|
||||
mSyncDir.mkdirs();
|
||||
mUsers = new ArrayList<>();
|
||||
mUsers.add(new UserInfo(0, "Owner", UserInfo.FLAG_ADMIN));
|
||||
mUsers.add(new UserInfo(1, "User1", 0));
|
||||
}
|
||||
|
||||
public void testGetAllServicesHappyPath() {
|
||||
TestServicesCache cache = new TestServicesCache(mContext, mDataDir);
|
||||
cache.addServiceForQuerying(0, r1, new RegisteredServicesCache.ServiceInfo<>(t1, null, 1));
|
||||
cache.addServiceForQuerying(0, r2, new RegisteredServicesCache.ServiceInfo<>(t2, null, 2));
|
||||
assertEquals(2, cache.getAllServicesSize(0));
|
||||
assertEquals(2, cache.getPersistentServicesSize(0));
|
||||
File file = new File(mSyncDir, TestServicesCache.SERVICE_INTERFACE + ".xml");
|
||||
assertTrue("File should be created at " + file, file.length() > 0);
|
||||
TestServicesCache cache = new TestServicesCache();
|
||||
cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
|
||||
cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2));
|
||||
assertEquals(2, cache.getAllServicesSize(U0));
|
||||
assertEquals(2, cache.getPersistentServicesSize(U0));
|
||||
assertNotEmptyFileCreated(cache, U0);
|
||||
// Make sure all services can be loaded from xml
|
||||
cache = new TestServicesCache(mContext, mDataDir);
|
||||
assertEquals(2, cache.getPersistentServicesSize(0));
|
||||
cache = new TestServicesCache();
|
||||
assertEquals(2, cache.getPersistentServicesSize(U0));
|
||||
}
|
||||
|
||||
public void testGetAllServicesReplaceUid() {
|
||||
TestServicesCache cache = new TestServicesCache(mContext, mDataDir);
|
||||
cache.addServiceForQuerying(0, r1, new RegisteredServicesCache.ServiceInfo<>(t1, null, 1));
|
||||
cache.addServiceForQuerying(0, r2, new RegisteredServicesCache.ServiceInfo<>(t2, null, 2));
|
||||
cache.getAllServices(0);
|
||||
TestServicesCache cache = new TestServicesCache();
|
||||
cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
|
||||
cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2));
|
||||
cache.getAllServices(U0);
|
||||
// Invalidate cache and clear update query results
|
||||
cache.invalidateCache(0);
|
||||
cache.invalidateCache(U0);
|
||||
cache.clearServicesForQuerying();
|
||||
cache.addServiceForQuerying(0, r1, new RegisteredServicesCache.ServiceInfo<>(t1, null, 1));
|
||||
cache.addServiceForQuerying(0, r2, new RegisteredServicesCache.ServiceInfo<>(t2, null,
|
||||
TestServicesCache.SYSTEM_IMAGE_UID));
|
||||
cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
|
||||
cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, SYSTEM_IMAGE_UID));
|
||||
Collection<RegisteredServicesCache.ServiceInfo<TestServiceType>> allServices = cache
|
||||
.getAllServices(0);
|
||||
.getAllServices(U0);
|
||||
assertEquals(2, allServices.size());
|
||||
Set<Integer> uids = new HashSet<>();
|
||||
for (RegisteredServicesCache.ServiceInfo<TestServiceType> srv : allServices) {
|
||||
uids.add(srv.uid);
|
||||
}
|
||||
assertTrue("UID must be updated to the new value",
|
||||
uids.contains(TestServicesCache.SYSTEM_IMAGE_UID));
|
||||
assertFalse("UID must be updated to the new value", uids.contains(2));
|
||||
uids.contains(SYSTEM_IMAGE_UID));
|
||||
assertFalse("UID must be updated to the new value", uids.contains(UID2));
|
||||
}
|
||||
|
||||
public void testGetAllServicesServiceRemoved() {
|
||||
TestServicesCache cache = new TestServicesCache(mContext, mDataDir);
|
||||
cache.addServiceForQuerying(0, r1, new RegisteredServicesCache.ServiceInfo<>(t1, null, 1));
|
||||
cache.addServiceForQuerying(0, r2, new RegisteredServicesCache.ServiceInfo<>(t2, null, 2));
|
||||
assertEquals(2, cache.getAllServicesSize(0));
|
||||
assertEquals(2, cache.getPersistentServicesSize(0));
|
||||
TestServicesCache cache = new TestServicesCache();
|
||||
cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
|
||||
cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2));
|
||||
assertEquals(2, cache.getAllServicesSize(U0));
|
||||
assertEquals(2, cache.getPersistentServicesSize(U0));
|
||||
// Re-read data from disk and verify services were saved
|
||||
cache = new TestServicesCache(mContext, mDataDir);
|
||||
assertEquals(2, cache.getPersistentServicesSize(0));
|
||||
cache = new TestServicesCache();
|
||||
assertEquals(2, cache.getPersistentServicesSize(U0));
|
||||
// Now register only one service and verify that another one is removed
|
||||
cache.addServiceForQuerying(0, r1, new RegisteredServicesCache.ServiceInfo<>(t1, null, 1));
|
||||
assertEquals(1, cache.getAllServicesSize(0));
|
||||
assertEquals(1, cache.getPersistentServicesSize(0));
|
||||
cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
|
||||
assertEquals(1, cache.getAllServicesSize(U0));
|
||||
assertEquals(1, cache.getPersistentServicesSize(U0));
|
||||
}
|
||||
|
||||
public void testGetAllServicesMultiUser() {
|
||||
TestServicesCache cache = new TestServicesCache(mContext, mDataDir);
|
||||
int u0 = 0;
|
||||
int u1 = 1;
|
||||
int pid1 = 1;
|
||||
cache.addServiceForQuerying(u0, r1, new RegisteredServicesCache.ServiceInfo<>(t1, null,
|
||||
pid1));
|
||||
int u1uid = UserHandle.getUid(u1, 0);
|
||||
cache.addServiceForQuerying(u1, r2, new RegisteredServicesCache.ServiceInfo<>(t2, null,
|
||||
u1uid));
|
||||
assertEquals(u1, cache.getAllServicesSize(u0));
|
||||
assertEquals(u1, cache.getPersistentServicesSize(u0));
|
||||
assertEquals(u1, cache.getAllServicesSize(u1));
|
||||
assertEquals(u1, cache.getPersistentServicesSize(u1));
|
||||
TestServicesCache cache = new TestServicesCache();
|
||||
cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
|
||||
int u1uid = UserHandle.getUid(U1, 0);
|
||||
cache.addServiceForQuerying(U1, r2, newServiceInfo(t2, u1uid));
|
||||
assertEquals(1, cache.getAllServicesSize(U0));
|
||||
assertEquals(1, cache.getPersistentServicesSize(U0));
|
||||
assertEquals(1, cache.getAllServicesSize(U1));
|
||||
assertEquals(1, cache.getPersistentServicesSize(U1));
|
||||
assertEquals("No services should be available for user 3", 0, cache.getAllServicesSize(3));
|
||||
// Re-read data from disk and verify services were saved
|
||||
cache = new TestServicesCache(mContext, mDataDir);
|
||||
assertEquals(u1, cache.getPersistentServicesSize(u0));
|
||||
assertEquals(u1, cache.getPersistentServicesSize(u1));
|
||||
cache = new TestServicesCache();
|
||||
assertEquals(1, cache.getPersistentServicesSize(U0));
|
||||
assertEquals(1, cache.getPersistentServicesSize(U1));
|
||||
assertNotEmptyFileCreated(cache, U0);
|
||||
assertNotEmptyFileCreated(cache, U1);
|
||||
}
|
||||
|
||||
public void testOnRemove() {
|
||||
TestServicesCache cache = new TestServicesCache();
|
||||
cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
|
||||
int u1uid = UserHandle.getUid(U1, 0);
|
||||
cache.addServiceForQuerying(U1, r2, newServiceInfo(t2, u1uid));
|
||||
assertEquals(1, cache.getAllServicesSize(U0));
|
||||
assertEquals(1, cache.getAllServicesSize(U1));
|
||||
// Simulate ACTION_USER_REMOVED
|
||||
cache.onUserRemoved(U1);
|
||||
// Make queryIntentServices(u1) return no results for U1
|
||||
cache.clearServicesForQuerying();
|
||||
assertEquals(1, cache.getAllServicesSize(U0));
|
||||
assertEquals(0, cache.getAllServicesSize(U1));
|
||||
}
|
||||
|
||||
public void testMigration() {
|
||||
// Prepare "old" file for testing
|
||||
String oldFile = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
|
||||
+ "<services>\n"
|
||||
+ " <service uid=\"1\" type=\"type1\" value=\"value1\" />\n"
|
||||
+ " <service uid=\"100002\" type=\"type2\" value=\"value2\" />\n"
|
||||
+ "<services>\n";
|
||||
|
||||
File file = new File(mSyncDir, TestServicesCache.SERVICE_INTERFACE + ".xml");
|
||||
FileUtils.copyToFile(new ByteArrayInputStream(oldFile.getBytes()), file);
|
||||
|
||||
int u0 = 0;
|
||||
int u1 = 1;
|
||||
TestServicesCache cache = new TestServicesCache();
|
||||
assertEquals(1, cache.getPersistentServicesSize(u0));
|
||||
assertEquals(1, cache.getPersistentServicesSize(u1));
|
||||
assertNotEmptyFileCreated(cache, u0);
|
||||
assertNotEmptyFileCreated(cache, u1);
|
||||
// Check that marker was created
|
||||
File markerFile = new File(mSyncDir, TestServicesCache.SERVICE_INTERFACE + ".xml.migrated");
|
||||
assertTrue("Marker file should be created at " + markerFile, markerFile.exists());
|
||||
// Now introduce 2 service types for u0: t1, t2. type1 will be removed
|
||||
cache.addServiceForQuerying(0, r1, newServiceInfo(t1, 1));
|
||||
cache.addServiceForQuerying(0, r2, newServiceInfo(t2, 2));
|
||||
assertEquals(2, cache.getAllServicesSize(u0));
|
||||
assertEquals(0, cache.getAllServicesSize(u1));
|
||||
// Re-read data from disk. Verify that services were saved and old file was ignored
|
||||
cache = new TestServicesCache();
|
||||
assertEquals(2, cache.getPersistentServicesSize(u0));
|
||||
assertEquals(0, cache.getPersistentServicesSize(u1));
|
||||
}
|
||||
|
||||
private static RegisteredServicesCache.ServiceInfo<TestServiceType> newServiceInfo(
|
||||
TestServiceType type, int uid) {
|
||||
return new RegisteredServicesCache.ServiceInfo<>(type, null, uid);
|
||||
}
|
||||
|
||||
private void assertNotEmptyFileCreated(TestServicesCache cache, int userId) {
|
||||
File dir = new File(cache.getUserSystemDirectory(userId),
|
||||
RegisteredServicesCache.REGISTERED_SERVICES_DIR);
|
||||
File file = new File(dir, TestServicesCache.SERVICE_INTERFACE+".xml");
|
||||
assertTrue("File should be created at " + file, file.length() > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing
|
||||
*/
|
||||
private static class TestServicesCache extends RegisteredServicesCache<TestServiceType> {
|
||||
private class TestServicesCache extends RegisteredServicesCache<TestServiceType> {
|
||||
static final String SERVICE_INTERFACE = "RegisteredServicesCacheTest";
|
||||
static final String SERVICE_META_DATA = "RegisteredServicesCacheTest";
|
||||
static final String ATTRIBUTES_NAME = "test";
|
||||
// Represents UID of a system image process
|
||||
static final int SYSTEM_IMAGE_UID = 20;
|
||||
private SparseArray<Map<ResolveInfo, ServiceInfo<TestServiceType>>> mServices
|
||||
= new SparseArray<>();
|
||||
|
||||
public TestServicesCache(Context context, File dir) {
|
||||
super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME,
|
||||
new TestSerializer(), dir);
|
||||
public TestServicesCache() {
|
||||
super(RegisteredServicesCacheTest.this.mContext,
|
||||
SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, new TestSerializer());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,6 +226,33 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
|
||||
return new ArrayList<>(map.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File getUserSystemDirectory(int userId) {
|
||||
File dir = new File(mDataDir, "users/" + userId);
|
||||
dir.mkdirs();
|
||||
return dir;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<UserInfo> getUsers() {
|
||||
return mUsers;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UserInfo getUser(int userId) {
|
||||
for (UserInfo user : getUsers()) {
|
||||
if (user.id == userId) {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File getDataDirectory() {
|
||||
return mDataDir;
|
||||
}
|
||||
|
||||
void addServiceForQuerying(int userId, ResolveInfo resolveInfo,
|
||||
ServiceInfo<TestServiceType> serviceInfo) {
|
||||
Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.get(userId);
|
||||
@@ -204,6 +293,11 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
|
||||
}
|
||||
throw new IllegalArgumentException("Unexpected service " + resolveInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserRemoved(int userId) {
|
||||
super.onUserRemoved(userId);
|
||||
}
|
||||
}
|
||||
|
||||
static class TestSerializer implements XmlSerializerAndParser<TestServiceType> {
|
||||
|
||||
Reference in New Issue
Block a user