1 /*
2  * Copyright (C) 2022 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.os;
18 
19 import static android.os.BundleMerger.STRATEGY_ARRAY_APPEND;
20 import static android.os.BundleMerger.STRATEGY_ARRAY_LIST_APPEND;
21 import static android.os.BundleMerger.STRATEGY_BOOLEAN_AND;
22 import static android.os.BundleMerger.STRATEGY_BOOLEAN_OR;
23 import static android.os.BundleMerger.STRATEGY_COMPARABLE_MAX;
24 import static android.os.BundleMerger.STRATEGY_COMPARABLE_MIN;
25 import static android.os.BundleMerger.STRATEGY_FIRST;
26 import static android.os.BundleMerger.STRATEGY_LAST;
27 import static android.os.BundleMerger.STRATEGY_NUMBER_ADD;
28 import static android.os.BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST;
29 import static android.os.BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD;
30 import static android.os.BundleMerger.STRATEGY_REJECT;
31 import static android.os.BundleMerger.merge;
32 
33 import static org.junit.Assert.assertArrayEquals;
34 import static org.junit.Assert.assertEquals;
35 import static org.junit.Assert.assertThrows;
36 
37 import android.content.Intent;
38 import android.net.Uri;
39 
40 import androidx.test.filters.SmallTest;
41 
42 import org.junit.Test;
43 import org.junit.runner.RunWith;
44 import org.junit.runners.JUnit4;
45 
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.Set;
49 
50 @SmallTest
51 @RunWith(JUnit4.class)
52 public class BundleMergerTest {
53     /**
54      * Strategies are only applied when there is an actual conflict; in the
55      * absence of conflict we pick whichever value is defined.
56      */
57     @Test
testNoConflict()58     public void testNoConflict() throws Exception {
59         for (int strategy = Byte.MIN_VALUE; strategy < Byte.MAX_VALUE; strategy++) {
60             assertEquals(null, merge(strategy, null, null));
61             assertEquals(10, merge(strategy, 10, null));
62             assertEquals(20, merge(strategy, null, 20));
63         }
64     }
65 
66     /**
67      * Strategies are only applied to identical data types; if there are mixed
68      * types we always reject the two conflicting values.
69      */
70     @Test
testMixedTypes()71     public void testMixedTypes() throws Exception {
72         for (int strategy = Byte.MIN_VALUE; strategy < Byte.MAX_VALUE; strategy++) {
73             final int finalStrategy = strategy;
74             assertThrows(Exception.class, () -> {
75                 merge(finalStrategy, 10, "foo");
76             });
77             assertThrows(Exception.class, () -> {
78                 merge(finalStrategy, List.of("foo"), "bar");
79             });
80             assertThrows(Exception.class, () -> {
81                 merge(finalStrategy, new String[] { "foo" }, "bar");
82             });
83             assertThrows(Exception.class, () -> {
84                 merge(finalStrategy, Integer.valueOf(10), Long.valueOf(10));
85             });
86         }
87     }
88 
89     @Test
testStrategyReject()90     public void testStrategyReject() throws Exception {
91         assertEquals(null, merge(STRATEGY_REJECT, 10, 20));
92 
93         // Identical values aren't technically a conflict, so they're passed
94         // through without being rejected
95         assertEquals(10, merge(STRATEGY_REJECT, 10, 10));
96         assertArrayEquals(new int[] {10},
97                 (int[]) merge(STRATEGY_REJECT, new int[] {10}, new int[] {10}));
98     }
99 
100     @Test
testStrategyFirst()101     public void testStrategyFirst() throws Exception {
102         assertEquals(10, merge(STRATEGY_FIRST, 10, 20));
103     }
104 
105     @Test
testStrategyLast()106     public void testStrategyLast() throws Exception {
107         assertEquals(20, merge(STRATEGY_LAST, 10, 20));
108     }
109 
110     @Test
testStrategyComparableMin()111     public void testStrategyComparableMin() throws Exception {
112         assertEquals(10, merge(STRATEGY_COMPARABLE_MIN, 10, 20));
113         assertEquals(10, merge(STRATEGY_COMPARABLE_MIN, 20, 10));
114         assertEquals("a", merge(STRATEGY_COMPARABLE_MIN, "a", "z"));
115         assertEquals("a", merge(STRATEGY_COMPARABLE_MIN, "z", "a"));
116 
117         assertThrows(Exception.class, () -> {
118             merge(STRATEGY_COMPARABLE_MIN, new Binder(), new Binder());
119         });
120     }
121 
122     @Test
testStrategyComparableMax()123     public void testStrategyComparableMax() throws Exception {
124         assertEquals(20, merge(STRATEGY_COMPARABLE_MAX, 10, 20));
125         assertEquals(20, merge(STRATEGY_COMPARABLE_MAX, 20, 10));
126         assertEquals("z", merge(STRATEGY_COMPARABLE_MAX, "a", "z"));
127         assertEquals("z", merge(STRATEGY_COMPARABLE_MAX, "z", "a"));
128 
129         assertThrows(Exception.class, () -> {
130             merge(STRATEGY_COMPARABLE_MAX, new Binder(), new Binder());
131         });
132     }
133 
134     @Test
testStrategyNumberAdd()135     public void testStrategyNumberAdd() throws Exception {
136         assertEquals(30, merge(STRATEGY_NUMBER_ADD, 10, 20));
137         assertEquals(30, merge(STRATEGY_NUMBER_ADD, 20, 10));
138         assertEquals(30L, merge(STRATEGY_NUMBER_ADD, 10L, 20L));
139         assertEquals(30L, merge(STRATEGY_NUMBER_ADD, 20L, 10L));
140 
141         assertThrows(Exception.class, () -> {
142             merge(STRATEGY_NUMBER_ADD, new Binder(), new Binder());
143         });
144     }
145 
146     @Test
testStrategyNumberIncrementFirst()147     public void testStrategyNumberIncrementFirst() throws Exception {
148         assertEquals(11, merge(STRATEGY_NUMBER_INCREMENT_FIRST, 10, 20));
149         assertEquals(21, merge(STRATEGY_NUMBER_INCREMENT_FIRST, 20, 10));
150         assertEquals(11L, merge(STRATEGY_NUMBER_INCREMENT_FIRST, 10L, 20L));
151         assertEquals(21L, merge(STRATEGY_NUMBER_INCREMENT_FIRST, 20L, 10L));
152     }
153 
154     @Test
testStrategyNumberIncrementFirstAndAdd()155     public void testStrategyNumberIncrementFirstAndAdd() throws Exception {
156         assertEquals(31, merge(STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD, 10, 20));
157         assertEquals(31, merge(STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD, 20, 10));
158         assertEquals(31L, merge(STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD, 10L, 20L));
159         assertEquals(31L, merge(STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD, 20L, 10L));
160     }
161 
162     @Test
testStrategyBooleanAnd()163     public void testStrategyBooleanAnd() throws Exception {
164         assertEquals(false, merge(STRATEGY_BOOLEAN_AND, false, false));
165         assertEquals(false, merge(STRATEGY_BOOLEAN_AND, true, false));
166         assertEquals(false, merge(STRATEGY_BOOLEAN_AND, false, true));
167         assertEquals(true, merge(STRATEGY_BOOLEAN_AND, true, true));
168 
169         assertThrows(Exception.class, () -> {
170             merge(STRATEGY_BOOLEAN_AND, "True!", "False?");
171         });
172     }
173 
174     @Test
testStrategyBooleanOr()175     public void testStrategyBooleanOr() throws Exception {
176         assertEquals(false, merge(STRATEGY_BOOLEAN_OR, false, false));
177         assertEquals(true, merge(STRATEGY_BOOLEAN_OR, true, false));
178         assertEquals(true, merge(STRATEGY_BOOLEAN_OR, false, true));
179         assertEquals(true, merge(STRATEGY_BOOLEAN_OR, true, true));
180 
181         assertThrows(Exception.class, () -> {
182             merge(STRATEGY_BOOLEAN_OR, "True!", "False?");
183         });
184     }
185 
186     @Test
testStrategyArrayAppend()187     public void testStrategyArrayAppend() throws Exception {
188         assertArrayEquals(new int[] {},
189                 (int[]) merge(STRATEGY_ARRAY_APPEND, new int[] {}, new int[] {}));
190         assertArrayEquals(new int[] {10},
191                 (int[]) merge(STRATEGY_ARRAY_APPEND, new int[] {10}, new int[] {}));
192         assertArrayEquals(new int[] {20},
193                 (int[]) merge(STRATEGY_ARRAY_APPEND, new int[] {}, new int[] {20}));
194         assertArrayEquals(new int[] {10, 20},
195                 (int[]) merge(STRATEGY_ARRAY_APPEND, new int[] {10}, new int[] {20}));
196         assertArrayEquals(new int[] {10, 30, 20, 40},
197                 (int[]) merge(STRATEGY_ARRAY_APPEND, new int[] {10, 30}, new int[] {20, 40}));
198         assertArrayEquals(new String[] {"a", "b"},
199                 (String[]) merge(STRATEGY_ARRAY_APPEND, new String[] {"a"}, new String[] {"b"}));
200 
201         assertThrows(Exception.class, () -> {
202             merge(STRATEGY_ARRAY_APPEND, 10, 20);
203         });
204     }
205 
206     @Test
testStrategyArrayListAppend()207     public void testStrategyArrayListAppend() throws Exception {
208         assertEquals(arrayListOf(),
209                 merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(), arrayListOf()));
210         assertEquals(arrayListOf(10),
211                 merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(10), arrayListOf()));
212         assertEquals(arrayListOf(20),
213                 merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(), arrayListOf(20)));
214         assertEquals(arrayListOf(10, 20),
215                 merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(10), arrayListOf(20)));
216         assertEquals(arrayListOf(10, 30, 20, 40),
217                 merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(10, 30), arrayListOf(20, 40)));
218         assertEquals(arrayListOf("a", "b"),
219                 merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf("a"), arrayListOf("b")));
220 
221         assertThrows(Exception.class, () -> {
222             merge(STRATEGY_ARRAY_LIST_APPEND, 10, 20);
223         });
224     }
225 
226     @Test
testSetDefaultMergeStrategy()227     public void testSetDefaultMergeStrategy() throws Exception {
228         final BundleMerger merger = new BundleMerger();
229         merger.setDefaultMergeStrategy(STRATEGY_FIRST);
230         merger.setMergeStrategy(Intent.EXTRA_INDEX, STRATEGY_COMPARABLE_MAX);
231 
232         Bundle a = new Bundle();
233         a.putString(Intent.EXTRA_SUBJECT, "SubjectA");
234         a.putInt(Intent.EXTRA_INDEX, 10);
235 
236         Bundle b = new Bundle();
237         b.putString(Intent.EXTRA_SUBJECT, "SubjectB");
238         b.putInt(Intent.EXTRA_INDEX, 20);
239 
240         Bundle ab = merger.merge(a, b);
241         assertEquals("SubjectA", ab.getString(Intent.EXTRA_SUBJECT));
242         assertEquals(20, ab.getInt(Intent.EXTRA_INDEX));
243 
244         Bundle ba = merger.merge(b, a);
245         assertEquals("SubjectB", ba.getString(Intent.EXTRA_SUBJECT));
246         assertEquals(20, ba.getInt(Intent.EXTRA_INDEX));
247     }
248 
249     @Test
testMerge_Simple()250     public void testMerge_Simple() throws Exception {
251         final BundleMerger merger = new BundleMerger();
252         final Bundle probe = new Bundle();
253         probe.putInt(Intent.EXTRA_INDEX, 42);
254 
255         assertEquals(null, merger.merge(null, null));
256         assertEquals(probe.keySet(), merger.merge(probe, null).keySet());
257         assertEquals(probe.keySet(), merger.merge(null, probe).keySet());
258         assertEquals(probe.keySet(), merger.merge(probe, probe).keySet());
259     }
260 
261     /**
262      * Verify that we can merge parcelables present in the base classpath, since
263      * everyone on the device will be able to unpack them.
264      */
265     @Test
testMerge_Parcelable_BCP()266     public void testMerge_Parcelable_BCP() throws Exception {
267         final BundleMerger merger = new BundleMerger();
268         merger.setMergeStrategy(Intent.EXTRA_STREAM, STRATEGY_COMPARABLE_MIN);
269 
270         Bundle a = new Bundle();
271         a.putParcelable(Intent.EXTRA_STREAM, Uri.parse("http://example.com"));
272         a = parcelAndUnparcel(a);
273 
274         Bundle b = new Bundle();
275         b.putParcelable(Intent.EXTRA_STREAM, Uri.parse("http://example.net"));
276         b = parcelAndUnparcel(b);
277 
278         assertEquals(Uri.parse("http://example.com"),
279                 merger.merge(a, b).getParcelable(Intent.EXTRA_STREAM, Uri.class));
280         assertEquals(Uri.parse("http://example.com"),
281                 merger.merge(b, a).getParcelable(Intent.EXTRA_STREAM, Uri.class));
282     }
283 
284     /**
285      * Verify that we tiptoe around custom parcelables while still merging other
286      * known data types. Custom parcelables aren't in the base classpath, so not
287      * everyone on the device will be able to unpack them.
288      */
289     @Test
testMerge_Parcelable_Custom()290     public void testMerge_Parcelable_Custom() throws Exception {
291         final BundleMerger merger = new BundleMerger();
292         merger.setMergeStrategy(Intent.EXTRA_INDEX, STRATEGY_NUMBER_ADD);
293 
294         Bundle a = new Bundle();
295         a.putInt(Intent.EXTRA_INDEX, 10);
296         a.putString(Intent.EXTRA_CC, "foo@bar.com");
297         a.putParcelable(Intent.EXTRA_SUBJECT, new ExplodingParcelable());
298         a = parcelAndUnparcel(a);
299 
300         Bundle b = new Bundle();
301         b.putInt(Intent.EXTRA_INDEX, 20);
302         a.putString(Intent.EXTRA_BCC, "foo@baz.com");
303         b.putParcelable(Intent.EXTRA_STREAM, new ExplodingParcelable());
304         b = parcelAndUnparcel(b);
305 
306         Bundle ab = merger.merge(a, b);
307         assertEquals(Set.of(Intent.EXTRA_INDEX, Intent.EXTRA_CC, Intent.EXTRA_BCC,
308                 Intent.EXTRA_SUBJECT, Intent.EXTRA_STREAM), ab.keySet());
309         assertEquals(30, ab.getInt(Intent.EXTRA_INDEX));
310         assertEquals("foo@bar.com", ab.getString(Intent.EXTRA_CC));
311         assertEquals("foo@baz.com", ab.getString(Intent.EXTRA_BCC));
312 
313         // And finally, make sure that if we try unpacking one of our custom
314         // values that we actually explode
315         assertThrows(BadParcelableException.class, () -> {
316             ab.getParcelable(Intent.EXTRA_SUBJECT, ExplodingParcelable.class);
317         });
318         assertThrows(BadParcelableException.class, () -> {
319             ab.getParcelable(Intent.EXTRA_STREAM, ExplodingParcelable.class);
320         });
321     }
322 
323     @Test
testMerge_PackageChanged()324     public void testMerge_PackageChanged() throws Exception {
325         final BundleMerger merger = new BundleMerger();
326         merger.setMergeStrategy(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, STRATEGY_ARRAY_APPEND);
327 
328         final Bundle first = new Bundle();
329         first.putInt(Intent.EXTRA_UID, 10001);
330         first.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, new String[] {
331                 "com.example.Foo",
332         });
333 
334         final Bundle second = new Bundle();
335         second.putInt(Intent.EXTRA_UID, 10001);
336         second.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, new String[] {
337                 "com.example.Bar",
338                 "com.example.Baz",
339         });
340 
341         final Bundle res = merger.merge(first, second);
342         assertEquals(10001, res.getInt(Intent.EXTRA_UID));
343         assertArrayEquals(new String[] {
344                 "com.example.Foo", "com.example.Bar", "com.example.Baz",
345         }, res.getStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST));
346     }
347 
348     /**
349      * Each event in isolation reports "zero events dropped", but if we need to
350      * merge them together, then we start incrementing.
351      */
352     @Test
testMerge_DropBox()353     public void testMerge_DropBox() throws Exception {
354         final BundleMerger merger = new BundleMerger();
355         merger.setMergeStrategy(DropBoxManager.EXTRA_TIME,
356                 STRATEGY_COMPARABLE_MAX);
357         merger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT,
358                 STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD);
359 
360         final long now = System.currentTimeMillis();
361         final Bundle a = new Bundle();
362         a.putString(DropBoxManager.EXTRA_TAG, "system_server_strictmode");
363         a.putLong(DropBoxManager.EXTRA_TIME, now);
364         a.putInt(DropBoxManager.EXTRA_DROPPED_COUNT, 0);
365 
366         final Bundle b = new Bundle();
367         b.putString(DropBoxManager.EXTRA_TAG, "system_server_strictmode");
368         b.putLong(DropBoxManager.EXTRA_TIME, now + 1000);
369         b.putInt(DropBoxManager.EXTRA_DROPPED_COUNT, 0);
370 
371         final Bundle c = new Bundle();
372         c.putString(DropBoxManager.EXTRA_TAG, "system_server_strictmode");
373         c.putLong(DropBoxManager.EXTRA_TIME, now + 2000);
374         c.putInt(DropBoxManager.EXTRA_DROPPED_COUNT, 0);
375 
376         final Bundle d = new Bundle();
377         d.putString(DropBoxManager.EXTRA_TAG, "system_server_strictmode");
378         d.putLong(DropBoxManager.EXTRA_TIME, now + 3000);
379         d.putInt(DropBoxManager.EXTRA_DROPPED_COUNT, 5);
380 
381         final Bundle ab = merger.merge(a, b);
382         assertEquals("system_server_strictmode", ab.getString(DropBoxManager.EXTRA_TAG));
383         assertEquals(now + 1000, ab.getLong(DropBoxManager.EXTRA_TIME));
384         assertEquals(1, ab.getInt(DropBoxManager.EXTRA_DROPPED_COUNT));
385 
386         final Bundle abc = merger.merge(ab, c);
387         assertEquals("system_server_strictmode", abc.getString(DropBoxManager.EXTRA_TAG));
388         assertEquals(now + 2000, abc.getLong(DropBoxManager.EXTRA_TIME));
389         assertEquals(2, abc.getInt(DropBoxManager.EXTRA_DROPPED_COUNT));
390 
391         final Bundle abcd = merger.merge(abc, d);
392         assertEquals("system_server_strictmode", abcd.getString(DropBoxManager.EXTRA_TAG));
393         assertEquals(now + 3000, abcd.getLong(DropBoxManager.EXTRA_TIME));
394         assertEquals(8, abcd.getInt(DropBoxManager.EXTRA_DROPPED_COUNT));
395     }
396 
arrayListOf(Object... values)397     private static ArrayList<Object> arrayListOf(Object... values) {
398         final ArrayList<Object> res = new ArrayList<>(values.length);
399         for (Object value : values) {
400             res.add(value);
401         }
402         return res;
403     }
404 
parcelAndUnparcel(Bundle input)405     private static Bundle parcelAndUnparcel(Bundle input) {
406         final Parcel parcel = Parcel.obtain();
407         try {
408             input.writeToParcel(parcel, 0);
409             parcel.setDataPosition(0);
410             return Bundle.CREATOR.createFromParcel(parcel);
411         } finally {
412             parcel.recycle();
413         }
414     }
415 
416     /**
417      * Object that only offers to parcel itself; if something tries unparceling
418      * it, it will "explode" by throwing an exception.
419      * <p>
420      * Useful for verifying interactions that must leave unknown data in a
421      * parceled state.
422      */
423     public static class ExplodingParcelable implements Parcelable {
ExplodingParcelable()424         public ExplodingParcelable() {
425         }
426 
427         @Override
describeContents()428         public int describeContents() {
429             return 0;
430         }
431 
432         @Override
writeToParcel(Parcel out, int flags)433         public void writeToParcel(Parcel out, int flags) {
434             out.writeInt(42);
435         }
436 
437         public static final Creator<ExplodingParcelable> CREATOR =
438                 new Creator<ExplodingParcelable>() {
439                     @Override
440                     public ExplodingParcelable createFromParcel(Parcel in) {
441                         throw new BadParcelableException("exploding!");
442                     }
443 
444                     @Override
445                     public ExplodingParcelable[] newArray(int size) {
446                         throw new BadParcelableException("exploding!");
447                     }
448                 };
449     }
450 }
451