blob: c8b449e68da7fd21158509f13cd5bc171c299b32 [file] [log] [blame]
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package android.content.pm;
18
Ng Zhi An1033db82019-01-24 13:45:57 -080019import android.content.Intent;
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -080020import android.content.res.Resources;
21import android.os.FileUtils;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.os.UserHandle;
Aurimas Liutikasbdbde552017-12-19 13:21:10 -080025import android.support.test.filters.LargeTest;
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -080026import android.test.AndroidTestCase;
27import android.util.AttributeSet;
28import android.util.SparseArray;
29
30import org.xmlpull.v1.XmlPullParser;
31import org.xmlpull.v1.XmlPullParserException;
32import org.xmlpull.v1.XmlSerializer;
33
Fyodor Kupolov259e7612015-02-11 14:13:34 -080034import java.io.ByteArrayInputStream;
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -080035import java.io.File;
36import java.io.IOException;
37import java.util.ArrayList;
38import java.util.Collection;
39import java.util.HashMap;
40import java.util.HashSet;
41import java.util.List;
42import java.util.Map;
43import java.util.Set;
44
45/**
46 * Tests for {@link android.content.pm.RegisteredServicesCache}
47 */
Aurimas Liutikasbdbde552017-12-19 13:21:10 -080048@LargeTest
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -080049public class RegisteredServicesCacheTest extends AndroidTestCase {
Fyodor Kupolov259e7612015-02-11 14:13:34 -080050 private static final int U0 = 0;
51 private static final int U1 = 1;
52 private static final int UID1 = 1;
53 private static final int UID2 = 2;
54 // Represents UID of a system image process
55 private static final int SYSTEM_IMAGE_UID = 20;
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -080056
57 private final ResolveInfo r1 = new ResolveInfo();
58 private final ResolveInfo r2 = new ResolveInfo();
59 private final TestServiceType t1 = new TestServiceType("t1", "value1");
60 private final TestServiceType t2 = new TestServiceType("t2", "value2");
61 private File mDataDir;
62 private File mSyncDir;
Fyodor Kupolov259e7612015-02-11 14:13:34 -080063 private List<UserInfo> mUsers;
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -080064
65 @Override
66 protected void setUp() throws Exception {
67 super.setUp();
68 File cacheDir = mContext.getCacheDir();
69 mDataDir = new File(cacheDir, "testServicesCache");
70 FileUtils.deleteContents(mDataDir);
Fyodor Kupolov259e7612015-02-11 14:13:34 -080071 mSyncDir = new File(mDataDir, "system/"+RegisteredServicesCache.REGISTERED_SERVICES_DIR);
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -080072 mSyncDir.mkdirs();
Fyodor Kupolov259e7612015-02-11 14:13:34 -080073 mUsers = new ArrayList<>();
74 mUsers.add(new UserInfo(0, "Owner", UserInfo.FLAG_ADMIN));
75 mUsers.add(new UserInfo(1, "User1", 0));
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -080076 }
77
78 public void testGetAllServicesHappyPath() {
Fyodor Kupolov259e7612015-02-11 14:13:34 -080079 TestServicesCache cache = new TestServicesCache();
80 cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
81 cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2));
82 assertEquals(2, cache.getAllServicesSize(U0));
83 assertEquals(2, cache.getPersistentServicesSize(U0));
84 assertNotEmptyFileCreated(cache, U0);
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -080085 // Make sure all services can be loaded from xml
Fyodor Kupolov259e7612015-02-11 14:13:34 -080086 cache = new TestServicesCache();
87 assertEquals(2, cache.getPersistentServicesSize(U0));
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -080088 }
89
90 public void testGetAllServicesReplaceUid() {
Fyodor Kupolov259e7612015-02-11 14:13:34 -080091 TestServicesCache cache = new TestServicesCache();
92 cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
93 cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2));
94 cache.getAllServices(U0);
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -080095 // Invalidate cache and clear update query results
Fyodor Kupolov259e7612015-02-11 14:13:34 -080096 cache.invalidateCache(U0);
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -080097 cache.clearServicesForQuerying();
Fyodor Kupolov259e7612015-02-11 14:13:34 -080098 cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
99 cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, SYSTEM_IMAGE_UID));
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800100 Collection<RegisteredServicesCache.ServiceInfo<TestServiceType>> allServices = cache
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800101 .getAllServices(U0);
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800102 assertEquals(2, allServices.size());
103 Set<Integer> uids = new HashSet<>();
104 for (RegisteredServicesCache.ServiceInfo<TestServiceType> srv : allServices) {
105 uids.add(srv.uid);
106 }
107 assertTrue("UID must be updated to the new value",
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800108 uids.contains(SYSTEM_IMAGE_UID));
109 assertFalse("UID must be updated to the new value", uids.contains(UID2));
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800110 }
111
112 public void testGetAllServicesServiceRemoved() {
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800113 TestServicesCache cache = new TestServicesCache();
114 cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
115 cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2));
116 assertEquals(2, cache.getAllServicesSize(U0));
117 assertEquals(2, cache.getPersistentServicesSize(U0));
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800118 // Re-read data from disk and verify services were saved
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800119 cache = new TestServicesCache();
120 assertEquals(2, cache.getPersistentServicesSize(U0));
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800121 // Now register only one service and verify that another one is removed
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800122 cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
123 assertEquals(1, cache.getAllServicesSize(U0));
124 assertEquals(1, cache.getPersistentServicesSize(U0));
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800125 }
126
127 public void testGetAllServicesMultiUser() {
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800128 TestServicesCache cache = new TestServicesCache();
129 cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
130 int u1uid = UserHandle.getUid(U1, 0);
131 cache.addServiceForQuerying(U1, r2, newServiceInfo(t2, u1uid));
132 assertEquals(1, cache.getAllServicesSize(U0));
133 assertEquals(1, cache.getPersistentServicesSize(U0));
134 assertEquals(1, cache.getAllServicesSize(U1));
135 assertEquals(1, cache.getPersistentServicesSize(U1));
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800136 assertEquals("No services should be available for user 3", 0, cache.getAllServicesSize(3));
137 // Re-read data from disk and verify services were saved
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800138 cache = new TestServicesCache();
139 assertEquals(1, cache.getPersistentServicesSize(U0));
140 assertEquals(1, cache.getPersistentServicesSize(U1));
141 assertNotEmptyFileCreated(cache, U0);
142 assertNotEmptyFileCreated(cache, U1);
143 }
144
145 public void testOnRemove() {
146 TestServicesCache cache = new TestServicesCache();
147 cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
148 int u1uid = UserHandle.getUid(U1, 0);
149 cache.addServiceForQuerying(U1, r2, newServiceInfo(t2, u1uid));
150 assertEquals(1, cache.getAllServicesSize(U0));
151 assertEquals(1, cache.getAllServicesSize(U1));
152 // Simulate ACTION_USER_REMOVED
153 cache.onUserRemoved(U1);
154 // Make queryIntentServices(u1) return no results for U1
155 cache.clearServicesForQuerying();
156 assertEquals(1, cache.getAllServicesSize(U0));
157 assertEquals(0, cache.getAllServicesSize(U1));
158 }
159
160 public void testMigration() {
161 // Prepare "old" file for testing
162 String oldFile = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
163 + "<services>\n"
164 + " <service uid=\"1\" type=\"type1\" value=\"value1\" />\n"
165 + " <service uid=\"100002\" type=\"type2\" value=\"value2\" />\n"
166 + "<services>\n";
167
168 File file = new File(mSyncDir, TestServicesCache.SERVICE_INTERFACE + ".xml");
169 FileUtils.copyToFile(new ByteArrayInputStream(oldFile.getBytes()), file);
170
171 int u0 = 0;
172 int u1 = 1;
173 TestServicesCache cache = new TestServicesCache();
174 assertEquals(1, cache.getPersistentServicesSize(u0));
175 assertEquals(1, cache.getPersistentServicesSize(u1));
176 assertNotEmptyFileCreated(cache, u0);
177 assertNotEmptyFileCreated(cache, u1);
178 // Check that marker was created
179 File markerFile = new File(mSyncDir, TestServicesCache.SERVICE_INTERFACE + ".xml.migrated");
180 assertTrue("Marker file should be created at " + markerFile, markerFile.exists());
181 // Now introduce 2 service types for u0: t1, t2. type1 will be removed
182 cache.addServiceForQuerying(0, r1, newServiceInfo(t1, 1));
183 cache.addServiceForQuerying(0, r2, newServiceInfo(t2, 2));
184 assertEquals(2, cache.getAllServicesSize(u0));
185 assertEquals(0, cache.getAllServicesSize(u1));
186 // Re-read data from disk. Verify that services were saved and old file was ignored
187 cache = new TestServicesCache();
188 assertEquals(2, cache.getPersistentServicesSize(u0));
189 assertEquals(0, cache.getPersistentServicesSize(u1));
190 }
191
Ng Zhi An1033db82019-01-24 13:45:57 -0800192 /**
193 * Check that an optimization to skip a call to PackageManager handles an invalidated cache.
194 *
195 * We added an optimization in generateServicesMap to only query PackageManager for packages
196 * that have been changed, because if a package is unchanged, we have already cached the
197 * services info for it, so we can save a query to PackageManager (and save some memory).
198 * However, if invalidateCache was called, we cannot optimize, and must do a full query.
199 * The initial optimization was buggy because it failed to check for an invalidated cache, and
200 * only scanned the changed packages, given in the ACTION_PACKAGE_CHANGED intent (b/122912184).
201 */
202 public void testParseServiceInfoOptimizationHandlesInvalidatedCache() {
203 TestServicesCache cache = new TestServicesCache();
204 cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
205 cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2));
206 assertEquals(2, cache.getAllServicesSize(U0));
207
208 // simulate the client of the cache invalidating it
209 cache.invalidateCache(U0);
210
211 // there should be 0 services (userServices.services == null ) at this point, but we don't
212 // call getAllServicesSize since that would force a full scan of packages,
213 // instead we trigger a package change in a package that is in the list of services
214 Intent intent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
215 intent.putExtra(Intent.EXTRA_UID, UID1);
216 cache.handlePackageEvent(intent, U0);
217
218 // check that the optimization does a full query and caches both services
219 assertEquals(2, cache.getAllServicesSize(U0));
220 }
221
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800222 private static RegisteredServicesCache.ServiceInfo<TestServiceType> newServiceInfo(
223 TestServiceType type, int uid) {
Jeff Sharkey9d8a1042015-12-03 17:56:20 -0700224 final ComponentInfo info = new ComponentInfo();
225 info.applicationInfo = new ApplicationInfo();
226 info.applicationInfo.uid = uid;
227 return new RegisteredServicesCache.ServiceInfo<>(type, info, null);
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800228 }
229
230 private void assertNotEmptyFileCreated(TestServicesCache cache, int userId) {
231 File dir = new File(cache.getUserSystemDirectory(userId),
232 RegisteredServicesCache.REGISTERED_SERVICES_DIR);
233 File file = new File(dir, TestServicesCache.SERVICE_INTERFACE+".xml");
234 assertTrue("File should be created at " + file, file.length() > 0);
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800235 }
236
237 /**
238 * Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing
239 */
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800240 private class TestServicesCache extends RegisteredServicesCache<TestServiceType> {
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800241 static final String SERVICE_INTERFACE = "RegisteredServicesCacheTest";
242 static final String SERVICE_META_DATA = "RegisteredServicesCacheTest";
243 static final String ATTRIBUTES_NAME = "test";
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800244 private SparseArray<Map<ResolveInfo, ServiceInfo<TestServiceType>>> mServices
245 = new SparseArray<>();
246
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800247 public TestServicesCache() {
248 super(RegisteredServicesCacheTest.this.mContext,
249 SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, new TestSerializer());
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800250 }
251
252 @Override
253 public TestServiceType parseServiceAttributes(Resources res, String packageName,
254 AttributeSet attrs) {
255 return null;
256 }
257
258 @Override
259 protected List<ResolveInfo> queryIntentServices(int userId) {
260 Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices
261 .get(userId, new HashMap<ResolveInfo, ServiceInfo<TestServiceType>>());
262 return new ArrayList<>(map.keySet());
263 }
264
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800265 @Override
266 protected File getUserSystemDirectory(int userId) {
267 File dir = new File(mDataDir, "users/" + userId);
268 dir.mkdirs();
269 return dir;
270 }
271
272 @Override
273 protected List<UserInfo> getUsers() {
274 return mUsers;
275 }
276
277 @Override
278 protected UserInfo getUser(int userId) {
279 for (UserInfo user : getUsers()) {
280 if (user.id == userId) {
281 return user;
282 }
283 }
284 return null;
285 }
286
287 @Override
288 protected File getDataDirectory() {
289 return mDataDir;
290 }
291
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800292 void addServiceForQuerying(int userId, ResolveInfo resolveInfo,
293 ServiceInfo<TestServiceType> serviceInfo) {
294 Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.get(userId);
295 if (map == null) {
296 map = new HashMap<>();
297 mServices.put(userId, map);
298 }
Ng Zhi An1033db82019-01-24 13:45:57 -0800299 // in actual cases, resolveInfo should always have a serviceInfo, since we specifically
300 // query for intent services
301 resolveInfo.serviceInfo = new android.content.pm.ServiceInfo();
302 resolveInfo.serviceInfo.applicationInfo =
303 new ApplicationInfo(serviceInfo.componentInfo.applicationInfo);
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800304 map.put(resolveInfo, serviceInfo);
305 }
306
307 void clearServicesForQuerying() {
308 mServices.clear();
309 }
310
311 int getPersistentServicesSize(int user) {
312 return getPersistentServices(user).size();
313 }
314
315 int getAllServicesSize(int user) {
316 return getAllServices(user).size();
317 }
318
319 @Override
320 protected boolean inSystemImage(int callerUid) {
321 return callerUid == SYSTEM_IMAGE_UID;
322 }
323
324 @Override
325 protected ServiceInfo<TestServiceType> parseServiceInfo(
326 ResolveInfo resolveInfo) throws XmlPullParserException, IOException {
327 int size = mServices.size();
328 for (int i = 0; i < size; i++) {
329 Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i);
330 ServiceInfo<TestServiceType> serviceInfo = map.get(resolveInfo);
331 if (serviceInfo != null) {
332 return serviceInfo;
333 }
334 }
335 throw new IllegalArgumentException("Unexpected service " + resolveInfo);
336 }
Fyodor Kupolov259e7612015-02-11 14:13:34 -0800337
338 @Override
339 public void onUserRemoved(int userId) {
340 super.onUserRemoved(userId);
341 }
Ng Zhi An1033db82019-01-24 13:45:57 -0800342
343 @Override
344 public void handlePackageEvent(Intent intent, int userId) {
345 super.handlePackageEvent(intent, userId);
346 }
Fyodor Kupolov9e0d81e2015-02-10 10:45:55 -0800347 }
348
349 static class TestSerializer implements XmlSerializerAndParser<TestServiceType> {
350
351 public void writeAsXml(TestServiceType item, XmlSerializer out) throws IOException {
352 out.attribute(null, "type", item.type);
353 out.attribute(null, "value", item.value);
354 }
355
356 public TestServiceType createFromXml(XmlPullParser parser)
357 throws IOException, XmlPullParserException {
358 final String type = parser.getAttributeValue(null, "type");
359 final String value = parser.getAttributeValue(null, "value");
360 return new TestServiceType(type, value);
361 }
362 }
363
364 static class TestServiceType implements Parcelable {
365 final String type;
366 final String value;
367
368 public TestServiceType(String type, String value) {
369 this.type = type;
370 this.value = value;
371 }
372
373 @Override
374 public boolean equals(Object o) {
375 if (this == o) {
376 return true;
377 }
378 if (o == null || getClass() != o.getClass()) {
379 return false;
380 }
381
382 TestServiceType that = (TestServiceType) o;
383
384 return type.equals(that.type) && value.equals(that.value);
385 }
386
387 @Override
388 public int hashCode() {
389 return 31 * type.hashCode() + value.hashCode();
390 }
391
392 @Override
393 public String toString() {
394 return "TestServiceType{" +
395 "type='" + type + '\'' +
396 ", value='" + value + '\'' +
397 '}';
398 }
399
400 public int describeContents() {
401 return 0;
402 }
403
404 public void writeToParcel(Parcel dest, int flags) {
405 dest.writeString(type);
406 dest.writeString(value);
407 }
408
409 public TestServiceType(Parcel source) {
410 this(source.readString(), source.readString());
411 }
412
413 public static final Creator<TestServiceType> CREATOR = new Creator<TestServiceType>() {
414 public TestServiceType createFromParcel(Parcel source) {
415 return new TestServiceType(source);
416 }
417
418 public TestServiceType[] newArray(int size) {
419 return new TestServiceType[size];
420 }
421 };
422 }
423}