1 /* 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 17 package android.content.pm; 18 19 import android.content.res.Resources; 20 import android.os.FileUtils; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 import android.os.UserHandle; 24 import android.support.test.filters.LargeTest; 25 import android.test.AndroidTestCase; 26 import android.util.AttributeSet; 27 import android.util.SparseArray; 28 29 import org.xmlpull.v1.XmlPullParser; 30 import org.xmlpull.v1.XmlPullParserException; 31 import org.xmlpull.v1.XmlSerializer; 32 33 import java.io.ByteArrayInputStream; 34 import java.io.File; 35 import java.io.IOException; 36 import java.util.ArrayList; 37 import java.util.Collection; 38 import java.util.HashMap; 39 import java.util.HashSet; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 44 /** 45 * Tests for {@link android.content.pm.RegisteredServicesCache} 46 */ 47 @LargeTest 48 public class RegisteredServicesCacheTest extends AndroidTestCase { 49 private static final int U0 = 0; 50 private static final int U1 = 1; 51 private static final int UID1 = 1; 52 private static final int UID2 = 2; 53 // Represents UID of a system image process 54 private static final int SYSTEM_IMAGE_UID = 20; 55 56 private final ResolveInfo r1 = new ResolveInfo(); 57 private final ResolveInfo r2 = new ResolveInfo(); 58 private final TestServiceType t1 = new TestServiceType("t1", "value1"); 59 private final TestServiceType t2 = new TestServiceType("t2", "value2"); 60 private File mDataDir; 61 private File mSyncDir; 62 private List<UserInfo> mUsers; 63 64 @Override 65 protected void setUp() throws Exception { 66 super.setUp(); 67 File cacheDir = mContext.getCacheDir(); 68 mDataDir = new File(cacheDir, "testServicesCache"); 69 FileUtils.deleteContents(mDataDir); 70 mSyncDir = new File(mDataDir, "system/"+RegisteredServicesCache.REGISTERED_SERVICES_DIR); 71 mSyncDir.mkdirs(); 72 mUsers = new ArrayList<>(); 73 mUsers.add(new UserInfo(0, "Owner", UserInfo.FLAG_ADMIN)); 74 mUsers.add(new UserInfo(1, "User1", 0)); 75 } 76 77 public void testGetAllServicesHappyPath() { 78 TestServicesCache cache = new TestServicesCache(); 79 cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1)); 80 cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2)); 81 assertEquals(2, cache.getAllServicesSize(U0)); 82 assertEquals(2, cache.getPersistentServicesSize(U0)); 83 assertNotEmptyFileCreated(cache, U0); 84 // Make sure all services can be loaded from xml 85 cache = new TestServicesCache(); 86 assertEquals(2, cache.getPersistentServicesSize(U0)); 87 } 88 89 public void testGetAllServicesReplaceUid() { 90 TestServicesCache cache = new TestServicesCache(); 91 cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1)); 92 cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2)); 93 cache.getAllServices(U0); 94 // Invalidate cache and clear update query results 95 cache.invalidateCache(U0); 96 cache.clearServicesForQuerying(); 97 cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1)); 98 cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, SYSTEM_IMAGE_UID)); 99 Collection<RegisteredServicesCache.ServiceInfo<TestServiceType>> allServices = cache 100 .getAllServices(U0); 101 assertEquals(2, allServices.size()); 102 Set<Integer> uids = new HashSet<>(); 103 for (RegisteredServicesCache.ServiceInfo<TestServiceType> srv : allServices) { 104 uids.add(srv.uid); 105 } 106 assertTrue("UID must be updated to the new value", 107 uids.contains(SYSTEM_IMAGE_UID)); 108 assertFalse("UID must be updated to the new value", uids.contains(UID2)); 109 } 110 111 public void testGetAllServicesServiceRemoved() { 112 TestServicesCache cache = new TestServicesCache(); 113 cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1)); 114 cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2)); 115 assertEquals(2, cache.getAllServicesSize(U0)); 116 assertEquals(2, cache.getPersistentServicesSize(U0)); 117 // Re-read data from disk and verify services were saved 118 cache = new TestServicesCache(); 119 assertEquals(2, cache.getPersistentServicesSize(U0)); 120 // Now register only one service and verify that another one is removed 121 cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1)); 122 assertEquals(1, cache.getAllServicesSize(U0)); 123 assertEquals(1, cache.getPersistentServicesSize(U0)); 124 } 125 126 public void testGetAllServicesMultiUser() { 127 TestServicesCache cache = new TestServicesCache(); 128 cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1)); 129 int u1uid = UserHandle.getUid(U1, 0); 130 cache.addServiceForQuerying(U1, r2, newServiceInfo(t2, u1uid)); 131 assertEquals(1, cache.getAllServicesSize(U0)); 132 assertEquals(1, cache.getPersistentServicesSize(U0)); 133 assertEquals(1, cache.getAllServicesSize(U1)); 134 assertEquals(1, cache.getPersistentServicesSize(U1)); 135 assertEquals("No services should be available for user 3", 0, cache.getAllServicesSize(3)); 136 // Re-read data from disk and verify services were saved 137 cache = new TestServicesCache(); 138 assertEquals(1, cache.getPersistentServicesSize(U0)); 139 assertEquals(1, cache.getPersistentServicesSize(U1)); 140 assertNotEmptyFileCreated(cache, U0); 141 assertNotEmptyFileCreated(cache, U1); 142 } 143 144 public void testOnRemove() { 145 TestServicesCache cache = new TestServicesCache(); 146 cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1)); 147 int u1uid = UserHandle.getUid(U1, 0); 148 cache.addServiceForQuerying(U1, r2, newServiceInfo(t2, u1uid)); 149 assertEquals(1, cache.getAllServicesSize(U0)); 150 assertEquals(1, cache.getAllServicesSize(U1)); 151 // Simulate ACTION_USER_REMOVED 152 cache.onUserRemoved(U1); 153 // Make queryIntentServices(u1) return no results for U1 154 cache.clearServicesForQuerying(); 155 assertEquals(1, cache.getAllServicesSize(U0)); 156 assertEquals(0, cache.getAllServicesSize(U1)); 157 } 158 159 public void testMigration() { 160 // Prepare "old" file for testing 161 String oldFile = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" 162 + "<services>\n" 163 + " <service uid=\"1\" type=\"type1\" value=\"value1\" />\n" 164 + " <service uid=\"100002\" type=\"type2\" value=\"value2\" />\n" 165 + "<services>\n"; 166 167 File file = new File(mSyncDir, TestServicesCache.SERVICE_INTERFACE + ".xml"); 168 FileUtils.copyToFile(new ByteArrayInputStream(oldFile.getBytes()), file); 169 170 int u0 = 0; 171 int u1 = 1; 172 TestServicesCache cache = new TestServicesCache(); 173 assertEquals(1, cache.getPersistentServicesSize(u0)); 174 assertEquals(1, cache.getPersistentServicesSize(u1)); 175 assertNotEmptyFileCreated(cache, u0); 176 assertNotEmptyFileCreated(cache, u1); 177 // Check that marker was created 178 File markerFile = new File(mSyncDir, TestServicesCache.SERVICE_INTERFACE + ".xml.migrated"); 179 assertTrue("Marker file should be created at " + markerFile, markerFile.exists()); 180 // Now introduce 2 service types for u0: t1, t2. type1 will be removed 181 cache.addServiceForQuerying(0, r1, newServiceInfo(t1, 1)); 182 cache.addServiceForQuerying(0, r2, newServiceInfo(t2, 2)); 183 assertEquals(2, cache.getAllServicesSize(u0)); 184 assertEquals(0, cache.getAllServicesSize(u1)); 185 // Re-read data from disk. Verify that services were saved and old file was ignored 186 cache = new TestServicesCache(); 187 assertEquals(2, cache.getPersistentServicesSize(u0)); 188 assertEquals(0, cache.getPersistentServicesSize(u1)); 189 } 190 191 private static RegisteredServicesCache.ServiceInfo<TestServiceType> newServiceInfo( 192 TestServiceType type, int uid) { 193 final ComponentInfo info = new ComponentInfo(); 194 info.applicationInfo = new ApplicationInfo(); 195 info.applicationInfo.uid = uid; 196 return new RegisteredServicesCache.ServiceInfo<>(type, info, null); 197 } 198 199 private void assertNotEmptyFileCreated(TestServicesCache cache, int userId) { 200 File dir = new File(cache.getUserSystemDirectory(userId), 201 RegisteredServicesCache.REGISTERED_SERVICES_DIR); 202 File file = new File(dir, TestServicesCache.SERVICE_INTERFACE+".xml"); 203 assertTrue("File should be created at " + file, file.length() > 0); 204 } 205 206 /** 207 * Mock implementation of {@link android.content.pm.RegisteredServicesCache} for testing 208 */ 209 private class TestServicesCache extends RegisteredServicesCache<TestServiceType> { 210 static final String SERVICE_INTERFACE = "RegisteredServicesCacheTest"; 211 static final String SERVICE_META_DATA = "RegisteredServicesCacheTest"; 212 static final String ATTRIBUTES_NAME = "test"; 213 private SparseArray<Map<ResolveInfo, ServiceInfo<TestServiceType>>> mServices 214 = new SparseArray<>(); 215 216 public TestServicesCache() { 217 super(RegisteredServicesCacheTest.this.mContext, 218 SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, new TestSerializer()); 219 } 220 221 @Override 222 public TestServiceType parseServiceAttributes(Resources res, String packageName, 223 AttributeSet attrs) { 224 return null; 225 } 226 227 @Override 228 protected List<ResolveInfo> queryIntentServices(int userId) { 229 Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices 230 .get(userId, new HashMap<ResolveInfo, ServiceInfo<TestServiceType>>()); 231 return new ArrayList<>(map.keySet()); 232 } 233 234 @Override 235 protected File getUserSystemDirectory(int userId) { 236 File dir = new File(mDataDir, "users/" + userId); 237 dir.mkdirs(); 238 return dir; 239 } 240 241 @Override 242 protected List<UserInfo> getUsers() { 243 return mUsers; 244 } 245 246 @Override 247 protected UserInfo getUser(int userId) { 248 for (UserInfo user : getUsers()) { 249 if (user.id == userId) { 250 return user; 251 } 252 } 253 return null; 254 } 255 256 @Override 257 protected File getDataDirectory() { 258 return mDataDir; 259 } 260 261 void addServiceForQuerying(int userId, ResolveInfo resolveInfo, 262 ServiceInfo<TestServiceType> serviceInfo) { 263 Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.get(userId); 264 if (map == null) { 265 map = new HashMap<>(); 266 mServices.put(userId, map); 267 } 268 map.put(resolveInfo, serviceInfo); 269 } 270 271 void clearServicesForQuerying() { 272 mServices.clear(); 273 } 274 275 int getPersistentServicesSize(int user) { 276 return getPersistentServices(user).size(); 277 } 278 279 int getAllServicesSize(int user) { 280 return getAllServices(user).size(); 281 } 282 283 @Override 284 protected boolean inSystemImage(int callerUid) { 285 return callerUid == SYSTEM_IMAGE_UID; 286 } 287 288 @Override 289 protected ServiceInfo<TestServiceType> parseServiceInfo( 290 ResolveInfo resolveInfo) throws XmlPullParserException, IOException { 291 int size = mServices.size(); 292 for (int i = 0; i < size; i++) { 293 Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i); 294 ServiceInfo<TestServiceType> serviceInfo = map.get(resolveInfo); 295 if (serviceInfo != null) { 296 return serviceInfo; 297 } 298 } 299 throw new IllegalArgumentException("Unexpected service " + resolveInfo); 300 } 301 302 @Override 303 public void onUserRemoved(int userId) { 304 super.onUserRemoved(userId); 305 } 306 } 307 308 static class TestSerializer implements XmlSerializerAndParser<TestServiceType> { 309 310 public void writeAsXml(TestServiceType item, XmlSerializer out) throws IOException { 311 out.attribute(null, "type", item.type); 312 out.attribute(null, "value", item.value); 313 } 314 315 public TestServiceType createFromXml(XmlPullParser parser) 316 throws IOException, XmlPullParserException { 317 final String type = parser.getAttributeValue(null, "type"); 318 final String value = parser.getAttributeValue(null, "value"); 319 return new TestServiceType(type, value); 320 } 321 } 322 323 static class TestServiceType implements Parcelable { 324 final String type; 325 final String value; 326 327 public TestServiceType(String type, String value) { 328 this.type = type; 329 this.value = value; 330 } 331 332 @Override 333 public boolean equals(Object o) { 334 if (this == o) { 335 return true; 336 } 337 if (o == null || getClass() != o.getClass()) { 338 return false; 339 } 340 341 TestServiceType that = (TestServiceType) o; 342 343 return type.equals(that.type) && value.equals(that.value); 344 } 345 346 @Override 347 public int hashCode() { 348 return 31 * type.hashCode() + value.hashCode(); 349 } 350 351 @Override 352 public String toString() { 353 return "TestServiceType{" + 354 "type='" + type + '\'' + 355 ", value='" + value + '\'' + 356 '}'; 357 } 358 359 public int describeContents() { 360 return 0; 361 } 362 363 public void writeToParcel(Parcel dest, int flags) { 364 dest.writeString(type); 365 dest.writeString(value); 366 } 367 368 public TestServiceType(Parcel source) { 369 this(source.readString(), source.readString()); 370 } 371 372 public static final Creator<TestServiceType> CREATOR = new Creator<TestServiceType>() { 373 public TestServiceType createFromParcel(Parcel source) { 374 return new TestServiceType(source); 375 } 376 377 public TestServiceType[] newArray(int size) { 378 return new TestServiceType[size]; 379 } 380 }; 381 } 382 } 383