1 /* 2 * Copyright (C) 2008 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.provider.cts.settings; 18 19 import static android.provider.DeviceConfig.SYNC_DISABLED_MODE_NONE; 20 import static android.provider.Settings.RESET_MODE_PACKAGE_DEFAULTS; 21 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertFalse; 24 import static org.junit.Assert.assertNull; 25 import static org.junit.Assert.assertTrue; 26 import static org.junit.Assert.fail; 27 28 import android.annotation.NonNull; 29 import android.annotation.UserIdInt; 30 import android.content.ContentResolver; 31 import android.content.Context; 32 import android.database.ContentObserver; 33 import android.net.Uri; 34 import android.os.UserHandle; 35 import android.provider.DeviceConfig; 36 import android.provider.Settings; 37 import android.util.Log; 38 39 import androidx.test.InstrumentationRegistry; 40 import androidx.test.runner.AndroidJUnit4; 41 42 import com.android.compatibility.common.util.PollingCheck; 43 import com.android.internal.annotations.GuardedBy; 44 45 import org.junit.After; 46 import org.junit.Before; 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 50 import java.util.ArrayList; 51 import java.util.Arrays; 52 import java.util.Collection; 53 import java.util.HashMap; 54 import java.util.HashSet; 55 import java.util.List; 56 import java.util.Map; 57 import java.util.Objects; 58 import java.util.Set; 59 import java.util.concurrent.CountDownLatch; 60 import java.util.concurrent.Executors; 61 import java.util.concurrent.TimeUnit; 62 63 @RunWith(AndroidJUnit4.class) 64 public class Settings_ConfigTest { 65 66 private static final String NAMESPACE1 = "namespace1"; 67 private static final String NAMESPACE2 = "namespace2"; 68 private static final String EMPTY_NAMESPACE = "empty_namespace"; 69 private static final String KEY1 = "key1"; 70 private static final String KEY2 = "key2"; 71 private static final String VALUE1 = "value1"; 72 private static final String VALUE2 = "value2"; 73 private static final String VALUE3 = "value3"; 74 private static final String VALUE4 = "value4"; 75 private static final String DEFAULT_VALUE = "default_value"; 76 77 78 private static final String TAG = "ContentResolverTest"; 79 80 private static final Uri TABLE1_URI = Uri.parse("content://" 81 + Settings.AUTHORITY + "/config"); 82 83 private static final String TEST_PACKAGE_NAME = "android.content.cts"; 84 85 private static final long OPERATION_TIMEOUT_MS = 5000; 86 87 private static final Context CONTEXT = InstrumentationRegistry.getContext(); 88 89 90 private static final long WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec 91 private final Object mLock = new Object(); 92 93 94 private static final String WRITE_DEVICE_CONFIG_PERMISSION = 95 "android.permission.WRITE_DEVICE_CONFIG"; 96 97 private static final String READ_DEVICE_CONFIG_PERMISSION = 98 "android.permission.READ_DEVICE_CONFIG"; 99 100 private static final String MONITOR_DEVICE_CONFIG_ACCESS = 101 "android.permission.MONITOR_DEVICE_CONFIG_ACCESS"; 102 103 private static ContentResolver sContentResolver; 104 private static Context sContext; 105 106 /** 107 * Get necessary permissions to access Setting.Config API and set up context 108 */ 109 @Before setUpContext()110 public void setUpContext() throws Exception { 111 InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( 112 WRITE_DEVICE_CONFIG_PERMISSION, READ_DEVICE_CONFIG_PERMISSION, 113 MONITOR_DEVICE_CONFIG_ACCESS); 114 sContext = InstrumentationRegistry.getContext(); 115 sContentResolver = sContext.getContentResolver(); 116 } 117 118 /** 119 * Clean up the namespaces, sync mode and permissions. 120 */ 121 @After cleanUp()122 public void cleanUp() throws Exception { 123 Settings.Config.setSyncDisabledMode(SYNC_DISABLED_MODE_NONE); 124 deleteProperties(NAMESPACE1, Arrays.asList(KEY1, KEY2)); 125 deleteProperties(NAMESPACE2, Arrays.asList(KEY1, KEY2)); 126 InstrumentationRegistry.getInstrumentation().getUiAutomation() 127 .dropShellPermissionIdentity(); 128 } 129 130 /** 131 * Checks that getting string which does not exist returns null. 132 */ 133 @Test testGetString_empty()134 public void testGetString_empty() { 135 String result = Settings.Config.getString(KEY1); 136 assertNull("Request for non existent flag name in Settings.Config API should return null " 137 + "while " + result + " was returned", result); 138 } 139 140 /** 141 * Checks that getting strings which does not exist returns empty map. 142 */ 143 @Test testGetStrings_empty()144 public void testGetStrings_empty() { 145 Map<String, String> result = Settings.Config 146 .getStrings(EMPTY_NAMESPACE, Arrays.asList(KEY1)); 147 assertTrue("Request for non existent flag name in Settings.Config API should return " 148 + "empty map while " + result.toString() + " was returned", result.isEmpty()); 149 } 150 151 /** 152 * Checks that setting and getting string from the same namespace return correct value. 153 */ 154 @Test testSetAndGetString_sameNamespace()155 public void testSetAndGetString_sameNamespace() { 156 Settings.Config.putString(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/false); 157 String result = Settings.Config.getStrings(NAMESPACE1, Arrays.asList(KEY1)).get(KEY1); 158 assertEquals("Value read from Settings.Config API does not match written value.", VALUE1, 159 result); 160 } 161 162 /** 163 * Checks that setting a string in one namespace does not set the same string in a different 164 * namespace. 165 */ 166 @Test testSetAndGetString_differentNamespace()167 public void testSetAndGetString_differentNamespace() { 168 Settings.Config.putString(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/false); 169 String result = Settings.Config.getStrings(NAMESPACE2, Arrays.asList(KEY1)).get(KEY1); 170 assertNull("Value for same keys written to different namespaces must not clash", result); 171 } 172 173 /** 174 * Checks that different namespaces can keep different values for the same key. 175 */ 176 @Test testSetAndGetString_multipleNamespaces()177 public void testSetAndGetString_multipleNamespaces() { 178 Settings.Config.putString(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/false); 179 Settings.Config.putString(NAMESPACE2, KEY1, VALUE2, /*makeDefault=*/false); 180 String result = Settings.Config.getStrings(NAMESPACE1, Arrays.asList(KEY1)).get(KEY1); 181 assertEquals("Value read from Settings.Config API does not match written value.", VALUE1, 182 result); 183 result = Settings.Config.getStrings(NAMESPACE2, Arrays.asList(KEY1)).get(KEY1); 184 assertEquals("Value read from Settings.Config API does not match written value.", VALUE2, 185 result); 186 } 187 188 /** 189 * Checks that saving value twice keeps the last value. 190 */ 191 @Test testSetAndGetString_overrideValue()192 public void testSetAndGetString_overrideValue() { 193 Settings.Config.putString(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/false); 194 Settings.Config.putString(NAMESPACE1, KEY1, VALUE2, /*makeDefault=*/false); 195 String result = Settings.Config.getStrings(NAMESPACE1, Arrays.asList(KEY1)).get(KEY1); 196 assertEquals("New value written to the same namespace/key did not override previous" 197 + " value.", VALUE2, result); 198 } 199 200 /** 201 * Checks that putString() fails with NullPointerException when called with null namespace. 202 */ 203 @Test testPutString_nullNamespace()204 public void testPutString_nullNamespace() { 205 try { 206 Settings.Config.putString(null, KEY1, DEFAULT_VALUE, /*makeDefault=*/false); 207 fail("Settings.Config.putString() with null namespace must result in " 208 + "NullPointerException"); 209 } catch (NullPointerException e) { 210 // expected 211 } 212 } 213 214 /** 215 * Checks that putString() fails with NullPointerException when called with null name. 216 */ 217 @Test testPutString_nullName()218 public void testPutString_nullName() { 219 try { 220 Settings.Config.putString(NAMESPACE1, null, DEFAULT_VALUE, /*makeDefault=*/false); 221 fail("Settings.Config.putString() with null name must result in NullPointerException"); 222 } catch (NullPointerException e) { 223 // expected 224 } 225 } 226 227 /** 228 * Checks that setting and getting strings from the same namespace return correct values. 229 */ 230 @Test testSetAndGetStrings_sameNamespace()231 public void testSetAndGetStrings_sameNamespace() throws Exception { 232 assertNull(Settings.Config.getStrings(NAMESPACE1, Arrays.asList(KEY1)).get(KEY1)); 233 assertNull(Settings.Config.getStrings(NAMESPACE1, Arrays.asList(KEY1)).get(KEY2)); 234 Settings.Config.setStrings(NAMESPACE1, new HashMap<String, String>() {{ 235 put(KEY1, VALUE1); 236 put(KEY2, VALUE2); 237 }}); 238 239 assertEquals(VALUE1, Settings.Config.getStrings(NAMESPACE1, Arrays.asList(KEY1)).get(KEY1)); 240 assertEquals(VALUE2, Settings.Config.getStrings(NAMESPACE1, Arrays.asList(KEY2)).get(KEY2)); 241 } 242 243 /** 244 * Checks that setting strings in one namespace does not set the same strings in a 245 * different namespace. 246 */ 247 @Test testSetAndGetStrings_differentNamespace()248 public void testSetAndGetStrings_differentNamespace() throws Exception { 249 Settings.Config.setStrings(NAMESPACE1, new HashMap<String, String>() {{ 250 put(KEY1, VALUE1); 251 put(KEY2, VALUE2); 252 }}); 253 254 assertNull(Settings.Config.getStrings(NAMESPACE2, Arrays.asList(KEY1)).get(KEY1)); 255 assertNull(Settings.Config.getStrings(NAMESPACE2, Arrays.asList(KEY2)).get(KEY2)); 256 } 257 258 /** 259 * Checks that different namespaces can keep different values for the same keys. 260 */ 261 @Test testSetAndGetStrings_multipleNamespaces()262 public void testSetAndGetStrings_multipleNamespaces() throws Exception { 263 Settings.Config.setStrings(NAMESPACE1, new HashMap<String, String>() {{ 264 put(KEY1, VALUE1); 265 put(KEY2, VALUE2); 266 }}); 267 Settings.Config.setStrings(NAMESPACE2, new HashMap<String, String>() {{ 268 put(KEY1, VALUE3); 269 put(KEY2, VALUE4); 270 }}); 271 272 Map<String, String> namespace1Values = Settings.Config 273 .getStrings(NAMESPACE1, Arrays.asList(KEY1, KEY2)); 274 Map<String, String> namespace2Values = Settings.Config 275 .getStrings(NAMESPACE2, Arrays.asList(KEY1, KEY2)); 276 277 assertEquals(namespace1Values.toString(), VALUE1, namespace1Values.get(KEY1)); 278 assertEquals(namespace1Values.toString(), VALUE2, namespace1Values.get(KEY2)); 279 assertEquals(namespace2Values.toString(), VALUE3, namespace2Values.get(KEY1)); 280 assertEquals(namespace2Values.toString(), VALUE4, namespace2Values.get(KEY2)); 281 } 282 283 284 /** 285 * Checks that saving values twice keeps the last values. 286 */ 287 @Test testSetAndGetStrings_overrideValue()288 public void testSetAndGetStrings_overrideValue() throws Exception { 289 Settings.Config.setStrings(NAMESPACE1, new HashMap<String, String>() {{ 290 put(KEY1, VALUE1); 291 put(KEY2, VALUE2); 292 }}); 293 294 Settings.Config.setStrings(NAMESPACE1, new HashMap<String, String>() {{ 295 put(KEY1, VALUE3); 296 put(KEY2, VALUE4); 297 }}); 298 299 assertEquals(VALUE3, Settings.Config.getStrings(NAMESPACE1, Arrays.asList(KEY1)).get(KEY1)); 300 assertEquals(VALUE4, Settings.Config.getStrings(NAMESPACE1, Arrays.asList(KEY2)).get(KEY2)); 301 } 302 303 304 /** 305 * Checks that deleteString() fails with NullPointerException when called with null namespace. 306 */ 307 @Test testDeleteString_nullKey()308 public void testDeleteString_nullKey() { 309 try { 310 Settings.Config.deleteString(null, KEY1); 311 fail("Settings.Config.deleteString() with null namespace must result in " 312 + "NullPointerException"); 313 } catch (NullPointerException e) { 314 // expected 315 } 316 } 317 318 /** 319 * Checks that deleteString() fails with NullPointerException when called with null key. 320 */ 321 @Test testDeleteString_nullNamespace()322 public void testDeleteString_nullNamespace() { 323 try { 324 Settings.Config.deleteString(NAMESPACE1, null); 325 fail("Settings.Config.deleteString() with null key must result in " 326 + "NullPointerException"); 327 } catch (NullPointerException e) { 328 // expected 329 } 330 } 331 332 /** 333 * Checks delete string. 334 */ 335 @Test testDeleteString()336 public void testDeleteString() { 337 Settings.Config.putString(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/false); 338 assertEquals(VALUE1, Settings.Config.getStrings(NAMESPACE1, Arrays.asList(KEY1)).get(KEY1)); 339 340 Settings.Config.deleteString(NAMESPACE1, KEY1); 341 assertNull(Settings.Config.getStrings(NAMESPACE1, Arrays.asList(KEY1)).get(KEY1)); 342 } 343 344 345 /** 346 * Test that reset to package default successfully resets values. 347 */ 348 @Test testResetToPackageDefaults()349 public void testResetToPackageDefaults() { 350 Settings.Config.putString(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/true); 351 Settings.Config.putString(NAMESPACE1, KEY1, VALUE2, /*makeDefault=*/false); 352 353 assertEquals(VALUE2, Settings.Config.getStrings(NAMESPACE1, Arrays.asList(KEY1)).get(KEY1)); 354 355 Settings.Config.resetToDefaults(RESET_MODE_PACKAGE_DEFAULTS, NAMESPACE1); 356 357 assertEquals(VALUE1, Settings.Config.getStrings(NAMESPACE1, Arrays.asList(KEY1)).get(KEY1)); 358 } 359 360 /** 361 * Test updating syncDisabledMode. 362 */ 363 @Test testSetSyncDisabledMode()364 public void testSetSyncDisabledMode() { 365 Settings.Config.setSyncDisabledMode(SYNC_DISABLED_MODE_NONE); 366 assertEquals(SYNC_DISABLED_MODE_NONE, Settings.Config.getSyncDisabledMode()); 367 Settings.Config.setSyncDisabledMode(RESET_MODE_PACKAGE_DEFAULTS); 368 assertEquals(RESET_MODE_PACKAGE_DEFAULTS, Settings.Config.getSyncDisabledMode()); 369 } 370 371 /** 372 * Test register content observer. 373 */ 374 @Test testRegisterContentObserver()375 public void testRegisterContentObserver() { 376 final MockContentObserver mco = new MockContentObserver(); 377 378 Settings.Config.registerContentObserver(NAMESPACE1, true, mco); 379 assertFalse(mco.hadOnChanged()); 380 381 Settings.Config.putString(NAMESPACE1, KEY1, VALUE2, /*makeDefault=*/false); 382 new PollingCheck() { 383 @Override 384 protected boolean check() { 385 return mco.hadOnChanged(); 386 } 387 }.run(); 388 389 mco.reset(); 390 Settings.Config.unregisterContentObserver(mco); 391 assertFalse(mco.hadOnChanged()); 392 Settings.Config.putString(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/false); 393 394 assertFalse(mco.hadOnChanged()); 395 396 try { 397 Settings.Config.registerContentObserver(null, false, mco); 398 fail("did not throw Exceptionwhen uri is null."); 399 } catch (NullPointerException e) { 400 //expected. 401 } catch (IllegalArgumentException e) { 402 // also expected 403 } 404 405 try { 406 Settings.Config.registerContentObserver(NAMESPACE1, false, null); 407 fail("did not throw Exception when register null content observer."); 408 } catch (NullPointerException e) { 409 //expected. 410 } 411 412 try { 413 sContentResolver.unregisterContentObserver(null); 414 fail("did not throw NullPointerException when unregister null content observer."); 415 } catch (NullPointerException e) { 416 //expected. 417 } 418 } 419 420 /** 421 * Test set monitor callback. 422 */ 423 @Test testSetMonitorCallback()424 public void testSetMonitorCallback() { 425 final CountDownLatch latch = new CountDownLatch(2); 426 final TestMonitorCallback callback = new TestMonitorCallback(latch); 427 428 Settings.Config.setMonitorCallback(sContentResolver, 429 Executors.newSingleThreadExecutor(), callback); 430 try { 431 Settings.Config.setStrings(NAMESPACE1, new HashMap<String, String>() {{ 432 put(KEY1, VALUE1); 433 put(KEY2, VALUE2); 434 }}); 435 } catch (DeviceConfig.BadConfigException e) { 436 fail("Callback set strings" + e.toString()); 437 } 438 // Reading properties triggers the monitor callback function. 439 Settings.Config.getStrings(NAMESPACE1, Arrays.asList(KEY1)); 440 441 try { 442 if (!latch.await(OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { 443 fail("Callback function was not called"); 444 } 445 } catch (InterruptedException e) { 446 // this part is executed when an exception (in this example InterruptedException) occurs 447 fail("Callback function was not called due to interruption" + e.toString()); 448 } 449 assertEquals(callback.onNamespaceUpdateCalls, 1); 450 assertEquals(callback.onDeviceConfigAccessCalls, 1); 451 } 452 453 /** 454 * Test clear monitor callback. 455 */ 456 @Test testClearMonitorCallback()457 public void testClearMonitorCallback() { 458 final CountDownLatch latch = new CountDownLatch(2); 459 final TestMonitorCallback callback = new TestMonitorCallback(latch); 460 461 Settings.Config.setMonitorCallback(sContentResolver, 462 Executors.newSingleThreadExecutor(), callback); 463 Settings.Config.clearMonitorCallback(sContentResolver); 464 // Reading properties triggers the monitor callback function. 465 Settings.Config.getStrings(NAMESPACE1, Arrays.asList(KEY1)); 466 try { 467 Settings.Config.setStrings(NAMESPACE1, new HashMap<String, String>() {{ 468 put(KEY1, VALUE1); 469 put(KEY2, VALUE2); 470 }}); 471 } catch (DeviceConfig.BadConfigException e) { 472 fail("Callback set strings" + e.toString()); 473 } 474 475 try { 476 if (latch.await(OPERATION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { 477 fail("Callback function was called while it has been cleared"); 478 } 479 } catch (InterruptedException e) { 480 // this part is executed when an exception (in this example InterruptedException) occurs 481 fail("un expected interruption occur" + e.toString()); 482 } 483 assertEquals(callback.onNamespaceUpdateCalls, 0); 484 assertEquals(callback.onDeviceConfigAccessCalls, 0); 485 } 486 487 private class TestMonitorCallback implements DeviceConfig.MonitorCallback { 488 public int onNamespaceUpdateCalls = 0; 489 public int onDeviceConfigAccessCalls = 0; 490 public CountDownLatch latch; 491 TestMonitorCallback(CountDownLatch latch)492 TestMonitorCallback(CountDownLatch latch) { 493 this.latch = latch; 494 } 495 onNamespaceUpdate(@onNull String updatedNamespace)496 public void onNamespaceUpdate(@NonNull String updatedNamespace) { 497 onNamespaceUpdateCalls++; 498 latch.countDown(); 499 } 500 onDeviceConfigAccess(@onNull String callingPackage, @NonNull String namespace)501 public void onDeviceConfigAccess(@NonNull String callingPackage, 502 @NonNull String namespace) { 503 onDeviceConfigAccessCalls++; 504 latch.countDown(); 505 } 506 } 507 deleteProperty(String namespace, String key)508 private static void deleteProperty(String namespace, String key) { 509 Settings.Config.deleteString(namespace, key); 510 } 511 deleteProperties(String namespace, List<String> keys)512 private static void deleteProperties(String namespace, List<String> keys) { 513 HashMap<String, String> deletedKeys = new HashMap<String, String>(); 514 for (String key : keys) { 515 deletedKeys.put(key, null); 516 } 517 518 try { 519 Settings.Config.setStrings(namespace, deletedKeys); 520 } catch (DeviceConfig.BadConfigException e) { 521 fail("Failed to delete the properties " + e.toString()); 522 } 523 } 524 525 private static class MockContentObserver extends ContentObserver { 526 private boolean mHadOnChanged = false; 527 private List<Change> mChanges = new ArrayList<>(); 528 MockContentObserver()529 MockContentObserver() { 530 super(null); 531 } 532 533 @Override deliverSelfNotifications()534 public boolean deliverSelfNotifications() { 535 return true; 536 } 537 538 @Override onChange(boolean selfChange, Collection<Uri> uris, int flags)539 public synchronized void onChange(boolean selfChange, Collection<Uri> uris, int flags) { 540 doOnChangeLocked(selfChange, uris, flags, /*userId=*/ -1); 541 } 542 543 @Override onChange(boolean selfChange, @NonNull Collection<Uri> uris, @ContentResolver.NotifyFlags int flags, UserHandle user)544 public synchronized void onChange(boolean selfChange, @NonNull Collection<Uri> uris, 545 @ContentResolver.NotifyFlags int flags, UserHandle user) { 546 doOnChangeLocked(selfChange, uris, flags, user.getIdentifier()); 547 } 548 hadOnChanged()549 public synchronized boolean hadOnChanged() { 550 return mHadOnChanged; 551 } 552 reset()553 public synchronized void reset() { 554 mHadOnChanged = false; 555 } 556 hadChanges(Collection<Change> changes)557 public synchronized boolean hadChanges(Collection<Change> changes) { 558 return mChanges.containsAll(changes); 559 } 560 561 @GuardedBy("this") doOnChangeLocked(boolean selfChange, @NonNull Collection<Uri> uris, @ContentResolver.NotifyFlags int flags, @UserIdInt int userId)562 private void doOnChangeLocked(boolean selfChange, @NonNull Collection<Uri> uris, 563 @ContentResolver.NotifyFlags int flags, @UserIdInt int userId) { 564 final Change change = new Change(selfChange, uris, flags, userId); 565 Log.v(TAG, change.toString()); 566 567 mHadOnChanged = true; 568 mChanges.add(change); 569 } 570 } 571 572 public static class Change { 573 public final boolean selfChange; 574 public final Iterable<Uri> uris; 575 public final int flags; 576 @UserIdInt 577 public final int userId; 578 Change(boolean selfChange, Iterable<Uri> uris, int flags)579 public Change(boolean selfChange, Iterable<Uri> uris, int flags) { 580 this.selfChange = selfChange; 581 this.uris = uris; 582 this.flags = flags; 583 this.userId = -1; 584 } 585 Change(boolean selfChange, Iterable<Uri> uris, int flags, @UserIdInt int userId)586 public Change(boolean selfChange, Iterable<Uri> uris, int flags, @UserIdInt int userId) { 587 this.selfChange = selfChange; 588 this.uris = uris; 589 this.flags = flags; 590 this.userId = userId; 591 } 592 593 @Override toString()594 public String toString() { 595 return String.format("onChange(%b, %s, %d, %d)", 596 selfChange, asSet(uris).toString(), flags, userId); 597 } 598 599 @Override equals(Object other)600 public boolean equals(Object other) { 601 if (other instanceof Change) { 602 final Change change = (Change) other; 603 return change.selfChange == selfChange 604 && Objects.equals(asSet(change.uris), asSet(uris)) 605 && change.flags == flags 606 && change.userId == userId; 607 } else { 608 return false; 609 } 610 } 611 asSet(Iterable<Uri> uris)612 private static Set<Uri> asSet(Iterable<Uri> uris) { 613 final Set<Uri> asSet = new HashSet<>(); 614 uris.forEach(asSet::add); 615 return asSet; 616 } 617 } 618 } 619