/*
* Copyright (C) 2015 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 android.content.pm;
import android.content.res.Resources;
import android.os.FileUtils;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.test.AndroidTestCase;
import android.util.AttributeSet;
import android.util.SparseArray;
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;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
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();
private final TestServiceType t1 = new TestServiceType("t1", "value1");
private final TestServiceType t2 = new TestServiceType("t2", "value2");
private File mDataDir;
private File mSyncDir;
private List<UserInfo> mUsers;
@Override
protected void setUp() throws Exception {
super.setUp();
File cacheDir = mContext.getCacheDir();
mDataDir = new File(cacheDir, "testServicesCache");
FileUtils.deleteContents(mDataDir);
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();
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();
assertEquals(2, cache.getPersistentServicesSize(U0));
}
public void testGetAllServicesReplaceUid() {
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(U0);
cache.clearServicesForQuerying();
cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, SYSTEM_IMAGE_UID));
Collection<RegisteredServicesCache.ServiceInfo<TestServiceType>> allServices = cache
.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(SYSTEM_IMAGE_UID));
assertFalse("UID must be updated to the new value", uids.contains(UID2));
}
public void testGetAllServicesServiceRemoved() {
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();
assertEquals(2, cache.getPersistentServicesSize(U0));
// Now register only one service and verify that another one is removed
cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
assertEquals(1, cache.getAllServicesSize(U0));
assertEquals(1, cache.getPersistentServicesSize(U0));
}
public void testGetAllServicesMultiUser() {
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();
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 class TestServicesCache extends RegisteredServicesCache<TestServiceType> {
static final String SERVICE_INTERFACE = "RegisteredServicesCacheTest";
static final String SERVICE_META_DATA = "RegisteredServicesCacheTest";
static final String ATTRIBUTES_NAME = "test";
private SparseArray<Map<ResolveInfo, ServiceInfo<TestServiceType>>> mServices
= new SparseArray<>();
public TestServicesCache() {
super(RegisteredServicesCacheTest.this.mContext,
SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, new TestSerializer());
}
@Override
public TestServiceType parseServiceAttributes(Resources res, String packageName,
AttributeSet attrs) {
return null;
}
@Override
protected List<ResolveInfo> queryIntentServices(int userId) {
Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices
.get(userId, new HashMap<ResolveInfo, ServiceInfo<TestServiceType>>());
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);
if (map == null) {
map = new HashMap<>();
mServices.put(userId, map);
}
map.put(resolveInfo, serviceInfo);
}
void clearServicesForQuerying() {
mServices.clear();
}
int getPersistentServicesSize(int user) {
return getPersistentServices(user).size();
}
int getAllServicesSize(int user) {
return getAllServices(user).size();
}
@Override
protected boolean inSystemImage(int callerUid) {
return callerUid == SYSTEM_IMAGE_UID;
}
@Override
protected ServiceInfo<TestServiceType> parseServiceInfo(
ResolveInfo resolveInfo) throws XmlPullParserException, IOException {
int size = mServices.size();
for (int i = 0; i < size; i++) {
Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i);
ServiceInfo<TestServiceType> serviceInfo = map.get(resolveInfo);
if (serviceInfo != null) {
return serviceInfo;
}
}
throw new IllegalArgumentException("Unexpected service " + resolveInfo);
}
@Override
public void onUserRemoved(int userId) {
super.onUserRemoved(userId);
}
}
static class TestSerializer implements XmlSerializerAndParser<TestServiceType> {
public void writeAsXml(TestServiceType item, XmlSerializer out) throws IOException {
out.attribute(null, "type", item.type);
out.attribute(null, "value", item.value);
}
public TestServiceType createFromXml(XmlPullParser parser)
throws IOException, XmlPullParserException {
final String type = parser.getAttributeValue(null, "type");
final String value = parser.getAttributeValue(null, "value");
return new TestServiceType(type, value);
}
}
static class TestServiceType implements Parcelable {
final String type;
final String value;
public TestServiceType(String type, String value) {
this.type = type;
this.value = value;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TestServiceType that = (TestServiceType) o;
return type.equals(that.type) && value.equals(that.value);
}
@Override
public int hashCode() {
return 31 * type.hashCode() + value.hashCode();
}
@Override
public String toString() {
return "TestServiceType{" +
"type='" + type + '\'' +
", value='" + value + '\'' +
'}';
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(type);
dest.writeString(value);
}
public TestServiceType(Parcel source) {
this(source.readString(), source.readString());
}
public static final Creator<TestServiceType> CREATOR = new Creator<TestServiceType>() {
public TestServiceType createFromParcel(Parcel source) {
return new TestServiceType(source);
}
public TestServiceType[] newArray(int size) {
return new TestServiceType[size];
}
};
}
}