Home | History | Annotate | Download | only in pm
      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