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
17package android.content.pm;
18
19import android.content.res.Resources;
20import android.os.FileUtils;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.os.UserHandle;
24import android.test.AndroidTestCase;
25import android.util.AttributeSet;
26import android.util.SparseArray;
27
28import org.xmlpull.v1.XmlPullParser;
29import org.xmlpull.v1.XmlPullParserException;
30import org.xmlpull.v1.XmlSerializer;
31
32import java.io.ByteArrayInputStream;
33import java.io.File;
34import java.io.IOException;
35import java.util.ArrayList;
36import java.util.Collection;
37import java.util.HashMap;
38import java.util.HashSet;
39import java.util.List;
40import java.util.Map;
41import java.util.Set;
42
43/**
44 * Tests for {@link android.content.pm.RegisteredServicesCache}
45 */
46public 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