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