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