1 /*
2  * Copyright (C) 2019 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 com.android.server.om;
18 
19 import static android.content.om.OverlayInfo.STATE_DISABLED;
20 import static android.content.om.OverlayInfo.STATE_ENABLED;
21 
22 import static org.junit.Assert.assertArrayEquals;
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertNotNull;
26 import static org.junit.Assert.assertTrue;
27 import static org.junit.Assert.fail;
28 
29 import android.content.om.OverlayIdentifier;
30 import android.content.om.OverlayInfo;
31 import android.text.TextUtils;
32 import android.util.Xml;
33 
34 import androidx.annotation.NonNull;
35 import androidx.test.runner.AndroidJUnit4;
36 
37 import com.android.modules.utils.TypedXmlPullParser;
38 
39 import org.junit.Before;
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 import org.xmlpull.v1.XmlPullParser;
43 
44 import java.io.ByteArrayInputStream;
45 import java.io.ByteArrayOutputStream;
46 import java.io.InputStream;
47 import java.util.ArrayList;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Set;
51 import java.util.stream.IntStream;
52 
53 import javax.annotation.Nullable;
54 
55 @RunWith(AndroidJUnit4.class)
56 public class OverlayManagerSettingsTests {
57     private OverlayManagerSettings mSettings;
58     private static int USER_0 = 0;
59     private static int USER_1 = 1;
60 
61     private static OverlayIdentifier OVERLAY_A = new OverlayIdentifier("com.test.overlay_a",
62             null /* overlayName */);
63     private static OverlayIdentifier OVERLAY_B = new OverlayIdentifier("com.test.overlay_b",
64             null /* overlayName */);
65     private static OverlayIdentifier OVERLAY_C = new OverlayIdentifier("com.test.overlay_c",
66             null /* overlayName */);
67 
68     private static final OverlayInfo OVERLAY_A_USER0 = createInfo(OVERLAY_A, USER_0);
69     private static final OverlayInfo OVERLAY_B_USER0 = createInfo(OVERLAY_B, USER_0);
70     private static final OverlayInfo OVERLAY_C_USER0 = createInfo(OVERLAY_C, USER_0);
71 
72     private static final OverlayInfo OVERLAY_A_USER1 = createInfo(OVERLAY_A, USER_1);
73     private static final OverlayInfo OVERLAY_B_USER1 = createInfo(OVERLAY_B, USER_1);
74 
75     private static final String TARGET_PACKAGE = "com.test.target";
76 
77     @Before
setUp()78     public void setUp() throws Exception {
79         mSettings = new OverlayManagerSettings();
80     }
81 
82     // tests: generic functionality
83 
84     @Test
testSettingsInitiallyEmpty()85     public void testSettingsInitiallyEmpty() throws Exception {
86         final Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(0 /* userId */);
87         assertEquals(0, map.size());
88     }
89 
90     @Test
testBasicSetAndGet()91     public void testBasicSetAndGet() throws Exception {
92         assertDoesNotContain(mSettings, OVERLAY_A_USER0);
93 
94         insertSetting(OVERLAY_A_USER0);
95         assertContains(mSettings, OVERLAY_A_USER0);
96         final OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_A, USER_0);
97         assertEquals(OVERLAY_A_USER0, oi);
98 
99         assertTrue(mSettings.remove(OVERLAY_A, USER_0));
100         assertDoesNotContain(mSettings, OVERLAY_A, USER_0);
101     }
102 
103     @Test
testGetUsers()104     public void testGetUsers() throws Exception {
105         assertArrayEquals(new int[]{}, mSettings.getUsers());
106 
107         insertSetting(OVERLAY_A_USER0);
108         assertArrayEquals(new int[]{USER_0}, mSettings.getUsers());
109 
110         insertSetting(OVERLAY_A_USER1);
111         insertSetting(OVERLAY_B_USER1);
112         assertArrayEquals(new int[]{USER_0, USER_1}, mSettings.getUsers());
113     }
114 
115     @Test
testGetOverlaysForUser()116     public void testGetOverlaysForUser() throws Exception {
117         insertSetting(OVERLAY_A_USER0);
118         insertSetting(OVERLAY_B_USER0);
119         insertSetting(OVERLAY_A_USER1);
120         insertSetting(OVERLAY_B_USER0);
121 
122         final Map<String, List<OverlayInfo>> map = mSettings.getOverlaysForUser(USER_0);
123         assertEquals(Set.of(TARGET_PACKAGE), map.keySet());
124 
125         // Two overlays in user 0 target the same package
126         final List<OverlayInfo> list = map.get(TARGET_PACKAGE);
127         assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0), list);
128 
129         // No users installed for user 3
130         assertEquals(Map.<String, List<OverlayInfo>>of(), mSettings.getOverlaysForUser(3));
131     }
132 
133     @Test
testRemoveUser()134     public void testRemoveUser() throws Exception {
135         insertSetting(OVERLAY_A_USER0);
136         insertSetting(OVERLAY_B_USER0);
137         insertSetting(OVERLAY_A_USER1);
138 
139         assertContains(mSettings, OVERLAY_A_USER0);
140         assertContains(mSettings, OVERLAY_B_USER0);
141         assertContains(mSettings, OVERLAY_A_USER1);
142 
143         mSettings.removeUser(USER_0);
144 
145         assertDoesNotContain(mSettings, OVERLAY_A_USER0);
146         assertDoesNotContain(mSettings, OVERLAY_B_USER0);
147         assertContains(mSettings, OVERLAY_A_USER1);
148     }
149 
150     @Test
testOrderOfNewlyAddedItems()151     public void testOrderOfNewlyAddedItems() throws Exception {
152         // new items are appended to the list
153         insertSetting(OVERLAY_A_USER0);
154         insertSetting(OVERLAY_B_USER0);
155         insertSetting(OVERLAY_C_USER0);
156 
157         assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
158                 mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
159 
160         // overlays keep their positions when updated
161         mSettings.setState(OVERLAY_B, USER_0, STATE_ENABLED);
162         final OverlayInfo oi = mSettings.getOverlayInfo(OVERLAY_B, USER_0);
163         assertNotNull(oi);
164 
165         assertListsAreEqual(List.of(OVERLAY_A_USER0, oi, OVERLAY_C_USER0),
166                 mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
167     }
168 
169     @Test
testSetPriority()170     public void testSetPriority() throws Exception {
171         insertSetting(OVERLAY_A_USER0);
172         insertSetting(OVERLAY_B_USER0);
173         insertSetting(OVERLAY_C_USER0);
174 
175         assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
176                 mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
177 
178         assertTrue(mSettings.setPriority(OVERLAY_B, OVERLAY_C, USER_0));
179         assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_C_USER0, OVERLAY_B_USER0),
180                 mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
181 
182         // Nothing happens if the parent package cannot be found
183         assertFalse(mSettings.setPriority(OVERLAY_B, new OverlayIdentifier("does.not.exist"),
184                 USER_0));
185         assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_C_USER0, OVERLAY_B_USER0),
186                 mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
187 
188         // An overlay should not affect the priority of overlays targeting a different package
189         final OverlayInfo otherTarget = new OverlayInfo(
190                 "com.test.overlay_other",
191                 null,
192                 "com.test.some.other.target",
193                 null,
194                 "some-category",
195                 "/data/app/com.test.overlay_other-1/base.apk",
196                 STATE_DISABLED,
197                 0,
198                 0,
199                 true,
200                 false);
201         insertSetting(otherTarget);
202         assertFalse(mSettings.setPriority(OVERLAY_A, otherTarget.getOverlayIdentifier(), USER_0));
203     }
204 
205     @Test
testSetLowestPriority()206     public void testSetLowestPriority() throws Exception {
207         insertSetting(OVERLAY_A_USER0);
208         insertSetting(OVERLAY_B_USER0);
209         insertSetting(OVERLAY_C_USER0);
210         assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
211                 mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
212 
213         assertTrue(mSettings.setLowestPriority(OVERLAY_B, USER_0));
214         assertListsAreEqual(List.of(OVERLAY_B_USER0, OVERLAY_A_USER0, OVERLAY_C_USER0),
215                 mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
216     }
217 
218     @Test
testSetHighestPriority()219     public void testSetHighestPriority() throws Exception {
220         insertSetting(OVERLAY_A_USER0);
221         insertSetting(OVERLAY_B_USER0);
222         insertSetting(OVERLAY_C_USER0);
223         assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_B_USER0, OVERLAY_C_USER0),
224                 mSettings.getOverlaysForTarget(TARGET_PACKAGE, USER_0));
225 
226         assertTrue(mSettings.setHighestPriority(OVERLAY_B, USER_0));
227         assertListsAreEqual(List.of(OVERLAY_A_USER0, OVERLAY_C_USER0, OVERLAY_B_USER0),
228                 mSettings.getOverlaysForTarget(OVERLAY_A_USER0.targetPackageName, USER_0));
229     }
230 
231     // tests: persist and restore
232 
233     @Test
testPersistEmpty()234     public void testPersistEmpty() throws Exception {
235         ByteArrayOutputStream os = new ByteArrayOutputStream();
236         mSettings.persist(os);
237         ByteArrayInputStream xml = new ByteArrayInputStream(os.toByteArray());
238 
239         assertEquals(1, countXmlTags(xml, "overlays"));
240         assertEquals(0, countXmlTags(xml, "item"));
241     }
242 
243     @Test
testPersistDifferentOverlaysSameUser()244     public void testPersistDifferentOverlaysSameUser() throws Exception {
245         insertSetting(OVERLAY_A_USER0);
246         insertSetting(OVERLAY_B_USER0);
247 
248         ByteArrayOutputStream os = new ByteArrayOutputStream();
249         mSettings.persist(os);
250         ByteArrayInputStream xml = new ByteArrayInputStream(os.toByteArray());
251 
252         assertEquals(1, countXmlTags(xml, "overlays"));
253         assertEquals(2, countXmlTags(xml, "item"));
254         assertEquals(1, countXmlAttributesWhere(xml, "item", "packageName",
255                 OVERLAY_A.getPackageName()));
256         assertEquals(1, countXmlAttributesWhere(xml, "item", "packageName",
257                 OVERLAY_B.getPackageName()));
258         assertEquals(2, countXmlAttributesWhere(xml, "item", "userId",
259                     Integer.toString(USER_0)));
260     }
261 
262     @Test
testPersistSameOverlayDifferentUsers()263     public void testPersistSameOverlayDifferentUsers() throws Exception {
264         insertSetting(OVERLAY_A_USER0);
265         insertSetting(OVERLAY_A_USER1);
266 
267         ByteArrayOutputStream os = new ByteArrayOutputStream();
268         mSettings.persist(os);
269         ByteArrayInputStream xml = new ByteArrayInputStream(os.toByteArray());
270 
271         assertEquals(1, countXmlTags(xml, "overlays"));
272         assertEquals(2, countXmlTags(xml, "item"));
273         assertEquals(2, countXmlAttributesWhere(xml, "item", "packageName",
274                 OVERLAY_A.getPackageName()));
275         assertEquals(1, countXmlAttributesWhere(xml, "item", "userId",
276                     Integer.toString(USER_0)));
277         assertEquals(1, countXmlAttributesWhere(xml, "item", "userId",
278                     Integer.toString(USER_1)));
279     }
280 
281     @Test
testPersistEnabled()282     public void testPersistEnabled() throws Exception {
283         insertSetting(OVERLAY_A_USER0);
284         mSettings.setEnabled(OVERLAY_A, USER_0, true);
285 
286         ByteArrayOutputStream os = new ByteArrayOutputStream();
287         mSettings.persist(os);
288         ByteArrayInputStream xml = new ByteArrayInputStream(os.toByteArray());
289 
290         assertEquals(1, countXmlAttributesWhere(xml, "item", "isEnabled", "true"));
291     }
292 
293     @Test
testRestoreEmpty()294     public void testRestoreEmpty() throws Exception {
295         final int version = OverlayManagerSettings.Serializer.CURRENT_VERSION;
296         final String xml =
297                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
298                 + "<overlays version=\"" + version + "\" />\n";
299         ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
300 
301         mSettings.restore(is);
302         assertDoesNotContain(mSettings, new OverlayIdentifier("com.test.overlay"), 0);
303     }
304 
305     @Test
testRestoreSingleUserSingleOverlay()306     public void testRestoreSingleUserSingleOverlay() throws Exception {
307         final int version = OverlayManagerSettings.Serializer.CURRENT_VERSION;
308         final String xml =
309                 "<?xml version='1.0' encoding='utf-8' standalone='yes'?>\n"
310                 + "<overlays version='" + version + "'>\n"
311                 + "<item packageName='com.test.overlay'\n"
312                 + "      overlayName='test'\n"
313                 + "      userId='1234'\n"
314                 + "      targetPackageName='com.test.target'\n"
315                 + "      baseCodePath='/data/app/com.test.overlay-1/base.apk'\n"
316                 + "      state='" + STATE_DISABLED + "'\n"
317                 + "      isEnabled='false'\n"
318                 + "      category='test-category'\n"
319                 + "      isStatic='false'\n"
320                 + "      priority='0' />\n"
321                 + "</overlays>\n";
322         ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
323 
324         mSettings.restore(is);
325         final OverlayIdentifier identifier = new OverlayIdentifier("com.test.overlay", "test");
326         OverlayInfo oi = mSettings.getOverlayInfo(identifier, 1234);
327         assertNotNull(oi);
328         assertEquals("com.test.overlay", oi.packageName);
329         assertEquals("test", oi.overlayName);
330         assertEquals("com.test.target", oi.targetPackageName);
331         assertEquals("/data/app/com.test.overlay-1/base.apk", oi.baseCodePath);
332         assertEquals(1234, oi.userId);
333         assertEquals(STATE_DISABLED, oi.state);
334         assertFalse(mSettings.getEnabled(identifier, 1234));
335     }
336 
337     @Test
testPersistAndRestore()338     public void testPersistAndRestore() throws Exception {
339         insertSetting(OVERLAY_A_USER0);
340         insertSetting(OVERLAY_B_USER1);
341 
342         ByteArrayOutputStream os = new ByteArrayOutputStream();
343         mSettings.persist(os);
344         ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
345         OverlayManagerSettings newSettings = new OverlayManagerSettings();
346         newSettings.restore(is);
347 
348         OverlayInfo a = newSettings.getOverlayInfo(OVERLAY_A, USER_0);
349         assertEquals(OVERLAY_A_USER0, a);
350 
351         OverlayInfo b = newSettings.getOverlayInfo(OVERLAY_B, USER_1);
352         assertEquals(OVERLAY_B_USER1, b);
353     }
354 
countXmlTags(InputStream in, String tagToLookFor)355     private int countXmlTags(InputStream in, String tagToLookFor) throws Exception {
356         in.reset();
357         int count = 0;
358         TypedXmlPullParser parser = Xml.resolvePullParser(in);
359         int event = parser.getEventType();
360         while (event != XmlPullParser.END_DOCUMENT) {
361             if (event == XmlPullParser.START_TAG && tagToLookFor.equals(parser.getName())) {
362                 count++;
363             }
364             event = parser.next();
365         }
366         return count;
367     }
368 
countXmlAttributesWhere(InputStream in, String tag, String attr, String value)369     private int countXmlAttributesWhere(InputStream in, String tag, String attr, String value)
370             throws Exception {
371         in.reset();
372         int count = 0;
373         TypedXmlPullParser parser = Xml.resolvePullParser(in);
374         int event = parser.getEventType();
375         while (event != XmlPullParser.END_DOCUMENT) {
376             if (event == XmlPullParser.START_TAG && tag.equals(parser.getName())) {
377                 String v = parser.getAttributeValue(null, attr);
378                 if (value.equals(v)) {
379                     count++;
380                 }
381             }
382             event = parser.next();
383         }
384         return count;
385     }
386 
insertSetting(OverlayInfo oi)387     private void insertSetting(OverlayInfo oi) throws Exception {
388         mSettings.init(oi.getOverlayIdentifier(), oi.userId, oi.targetPackageName, null,
389                 oi.baseCodePath, true, false,0, oi.category, oi.isFabricated);
390         mSettings.setState(oi.getOverlayIdentifier(), oi.userId, oi.state);
391         mSettings.setEnabled(oi.getOverlayIdentifier(), oi.userId, false);
392     }
393 
assertContains(final OverlayManagerSettings settings, final OverlayInfo oi)394     private static void assertContains(final OverlayManagerSettings settings,
395             final OverlayInfo oi) {
396         try {
397             settings.getOverlayInfo(oi.getOverlayIdentifier(), oi.userId);
398         } catch (OverlayManagerSettings.BadKeyException e) {
399             fail(String.format("settings does not contain overlay=%s userId=%d",
400                     oi.getOverlayIdentifier(), oi.userId));
401         }
402     }
403 
assertDoesNotContain(final OverlayManagerSettings settings, final OverlayInfo oi)404     private static void assertDoesNotContain(final OverlayManagerSettings settings,
405             final OverlayInfo oi) {
406         assertDoesNotContain(settings, oi.getOverlayIdentifier(), oi.userId);
407     }
408 
assertDoesNotContain(final OverlayManagerSettings settings, final OverlayIdentifier overlay, int userId)409     private static void assertDoesNotContain(final OverlayManagerSettings settings,
410             final OverlayIdentifier overlay, int userId) {
411         try {
412             settings.getOverlayInfo(overlay, userId);
413             fail(String.format("settings contains overlay=%s userId=%d", overlay, userId));
414         } catch (OverlayManagerSettings.BadKeyException e) {
415             // do nothing: we expect to end up here
416         }
417     }
418 
createInfo(@onNull OverlayIdentifier identifier, int userId)419     private static OverlayInfo createInfo(@NonNull OverlayIdentifier identifier, int userId) {
420         return new OverlayInfo(
421                 identifier.getPackageName(),
422                 identifier.getOverlayName(),
423                 "com.test.target",
424                 null,
425                 "some-category",
426                 "/data/app/" + identifier + "/base.apk",
427                 STATE_DISABLED,
428                 userId,
429                 0,
430                 true,
431                 false);
432     }
433 
assertContains(int[] haystack, int needle)434     private static void assertContains(int[] haystack, int needle) {
435         List<Integer> list = IntStream.of(haystack)
436                 .boxed()
437                 .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
438         if (!list.contains(needle)) {
439             fail(String.format("integer array [%s] does not contain value %s",
440                         TextUtils.join(",", list), needle));
441         }
442     }
443 
assertDoesNotContain(int[] haystack, int needle)444     private static void assertDoesNotContain(int[] haystack, int needle) {
445         List<Integer> list = IntStream.of(haystack)
446                 .boxed()
447                 .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
448         if (list.contains(needle)) {
449             fail(String.format("integer array [%s] contains value %s",
450                         TextUtils.join(",", list), needle));
451         }
452     }
453 
assertListsAreEqual( @onNull List<OverlayInfo> expected, @Nullable List<OverlayInfo> actual)454     private static void assertListsAreEqual(
455             @NonNull List<OverlayInfo> expected, @Nullable List<OverlayInfo> actual) {
456         if (!expected.equals(actual)) {
457             fail(String.format("lists [%s] and [%s] differ",
458                         TextUtils.join(",", expected), TextUtils.join(",", actual)));
459         }
460     }
461 }
462