Loading core/java/android/content/pm/RegisteredServicesCache.java +30 −16 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import android.util.SparseArray; import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.google.android.collect.Lists; Loading @@ -56,6 +57,8 @@ import java.util.Collections; import java.util.List; import java.util.Map; import libcore.io.IoUtils; /** * Cache of registered services. This cache is lazily built by interrogating * {@link PackageManager} on a per-user basis. It's updated as packages are Loading Loading @@ -113,13 +116,19 @@ public abstract class RegisteredServicesCache<V> { 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 dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); File syncDir = new File(systemDir, "registered_services"); mPersistentServicesFile = new AtomicFile(new File(syncDir, interfaceName + ".xml")); Loading Loading @@ -303,7 +312,8 @@ public abstract class RegisteredServicesCache<V> { } } private boolean inSystemImage(int callerUid) { @VisibleForTesting protected boolean inSystemImage(int callerUid) { String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid); for (String name : packages) { try { Loading @@ -319,6 +329,13 @@ public abstract class RegisteredServicesCache<V> { return false; } @VisibleForTesting protected List<ResolveInfo> queryIntentServices(int userId) { final PackageManager pm = mContext.getPackageManager(); return pm.queryIntentServicesAsUser( new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId); } /** * Populate {@link UserServices#services} by scanning installed packages for * given {@link UserHandle}. Loading @@ -331,10 +348,8 @@ public abstract class RegisteredServicesCache<V> { Slog.d(TAG, "generateServicesMap() for " + userId + ", changed UIDs = " + changedUids); } final PackageManager pm = mContext.getPackageManager(); final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<ServiceInfo<V>>(); final List<ResolveInfo> resolveInfos = pm.queryIntentServicesAsUser( new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId); final List<ResolveInfo> resolveInfos = queryIntentServices(userId); for (ResolveInfo resolveInfo : resolveInfos) { try { ServiceInfo<V> info = parseServiceInfo(resolveInfo); Loading @@ -343,9 +358,7 @@ public abstract class RegisteredServicesCache<V> { continue; } serviceInfos.add(info); } catch (XmlPullParserException e) { Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e); } catch (IOException e) { } catch (XmlPullParserException|IOException e) { Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e); } } Loading Loading @@ -481,7 +494,8 @@ public abstract class RegisteredServicesCache<V> { return false; } private ServiceInfo<V> parseServiceInfo(ResolveInfo service) @VisibleForTesting protected ServiceInfo<V> parseServiceInfo(ResolveInfo service) throws XmlPullParserException, IOException { android.content.pm.ServiceInfo si = service.serviceInfo; ComponentName componentName = new ComponentName(si.packageName, si.name); Loading Loading @@ -571,12 +585,7 @@ public abstract class RegisteredServicesCache<V> { } catch (Exception e) { Log.w(TAG, "Error reading persistent services, starting from scratch", e); } finally { if (fis != null) { try { fis.close(); } catch (java.io.IOException e1) { } } IoUtils.closeQuietly(fis); } } Loading Loading @@ -607,7 +616,7 @@ public abstract class RegisteredServicesCache<V> { out.endTag(null, "services"); out.endDocument(); mPersistentServicesFile.finishWrite(fos); } catch (java.io.IOException e1) { } catch (IOException e1) { Log.w(TAG, "Error writing accounts", e1); if (fos != null) { mPersistentServicesFile.failWrite(fos); Loading @@ -615,6 +624,11 @@ public abstract class RegisteredServicesCache<V> { } } @VisibleForTesting protected Map<V, Integer> getPersistentServices(int userId) { return findOrCreateUserLocked(userId).persistentServices; } public abstract V parseServiceAttributes(Resources res, String packageName, AttributeSet attrs); } core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java 0 → 100644 +283 −0 Original line number Diff line number Diff line /* * 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.Context; 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.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 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; @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/registered_services"); mSyncDir.mkdirs(); } 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); // Make sure all services can be loaded from xml cache = new TestServicesCache(mContext, mDataDir); assertEquals(2, cache.getPersistentServicesSize(0)); } 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); // Invalidate cache and clear update query results cache.invalidateCache(0); 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)); Collection<RegisteredServicesCache.ServiceInfo<TestServiceType>> allServices = cache .getAllServices(0); 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)); } 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)); // Re-read data from disk and verify services were saved cache = new TestServicesCache(mContext, mDataDir); assertEquals(2, cache.getPersistentServicesSize(0)); // 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)); } 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)); 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)); } /** * Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing */ private static 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); } @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()); } 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); } } 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]; } }; } } Loading
core/java/android/content/pm/RegisteredServicesCache.java +30 −16 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import android.util.SparseArray; import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.google.android.collect.Lists; Loading @@ -56,6 +57,8 @@ import java.util.Collections; import java.util.List; import java.util.Map; import libcore.io.IoUtils; /** * Cache of registered services. This cache is lazily built by interrogating * {@link PackageManager} on a per-user basis. It's updated as packages are Loading Loading @@ -113,13 +116,19 @@ public abstract class RegisteredServicesCache<V> { 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 dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); File syncDir = new File(systemDir, "registered_services"); mPersistentServicesFile = new AtomicFile(new File(syncDir, interfaceName + ".xml")); Loading Loading @@ -303,7 +312,8 @@ public abstract class RegisteredServicesCache<V> { } } private boolean inSystemImage(int callerUid) { @VisibleForTesting protected boolean inSystemImage(int callerUid) { String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid); for (String name : packages) { try { Loading @@ -319,6 +329,13 @@ public abstract class RegisteredServicesCache<V> { return false; } @VisibleForTesting protected List<ResolveInfo> queryIntentServices(int userId) { final PackageManager pm = mContext.getPackageManager(); return pm.queryIntentServicesAsUser( new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId); } /** * Populate {@link UserServices#services} by scanning installed packages for * given {@link UserHandle}. Loading @@ -331,10 +348,8 @@ public abstract class RegisteredServicesCache<V> { Slog.d(TAG, "generateServicesMap() for " + userId + ", changed UIDs = " + changedUids); } final PackageManager pm = mContext.getPackageManager(); final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<ServiceInfo<V>>(); final List<ResolveInfo> resolveInfos = pm.queryIntentServicesAsUser( new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId); final List<ResolveInfo> resolveInfos = queryIntentServices(userId); for (ResolveInfo resolveInfo : resolveInfos) { try { ServiceInfo<V> info = parseServiceInfo(resolveInfo); Loading @@ -343,9 +358,7 @@ public abstract class RegisteredServicesCache<V> { continue; } serviceInfos.add(info); } catch (XmlPullParserException e) { Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e); } catch (IOException e) { } catch (XmlPullParserException|IOException e) { Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e); } } Loading Loading @@ -481,7 +494,8 @@ public abstract class RegisteredServicesCache<V> { return false; } private ServiceInfo<V> parseServiceInfo(ResolveInfo service) @VisibleForTesting protected ServiceInfo<V> parseServiceInfo(ResolveInfo service) throws XmlPullParserException, IOException { android.content.pm.ServiceInfo si = service.serviceInfo; ComponentName componentName = new ComponentName(si.packageName, si.name); Loading Loading @@ -571,12 +585,7 @@ public abstract class RegisteredServicesCache<V> { } catch (Exception e) { Log.w(TAG, "Error reading persistent services, starting from scratch", e); } finally { if (fis != null) { try { fis.close(); } catch (java.io.IOException e1) { } } IoUtils.closeQuietly(fis); } } Loading Loading @@ -607,7 +616,7 @@ public abstract class RegisteredServicesCache<V> { out.endTag(null, "services"); out.endDocument(); mPersistentServicesFile.finishWrite(fos); } catch (java.io.IOException e1) { } catch (IOException e1) { Log.w(TAG, "Error writing accounts", e1); if (fos != null) { mPersistentServicesFile.failWrite(fos); Loading @@ -615,6 +624,11 @@ public abstract class RegisteredServicesCache<V> { } } @VisibleForTesting protected Map<V, Integer> getPersistentServices(int userId) { return findOrCreateUserLocked(userId).persistentServices; } public abstract V parseServiceAttributes(Resources res, String packageName, AttributeSet attrs); }
core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java 0 → 100644 +283 −0 Original line number Diff line number Diff line /* * 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.Context; 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.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 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; @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/registered_services"); mSyncDir.mkdirs(); } 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); // Make sure all services can be loaded from xml cache = new TestServicesCache(mContext, mDataDir); assertEquals(2, cache.getPersistentServicesSize(0)); } 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); // Invalidate cache and clear update query results cache.invalidateCache(0); 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)); Collection<RegisteredServicesCache.ServiceInfo<TestServiceType>> allServices = cache .getAllServices(0); 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)); } 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)); // Re-read data from disk and verify services were saved cache = new TestServicesCache(mContext, mDataDir); assertEquals(2, cache.getPersistentServicesSize(0)); // 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)); } 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)); 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)); } /** * Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing */ private static 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); } @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()); } 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); } } 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]; } }; } }