1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.sync.notifier; 6 7 import android.accounts.Account; 8 import android.content.ComponentName; 9 import android.content.Intent; 10 import android.os.Bundle; 11 import android.test.ServiceTestCase; 12 import android.test.suitebuilder.annotation.SmallTest; 13 14 import com.google.ipc.invalidation.external.client.InvalidationListener.RegistrationState; 15 import com.google.ipc.invalidation.external.client.contrib.AndroidListener; 16 import com.google.ipc.invalidation.external.client.types.ErrorInfo; 17 import com.google.ipc.invalidation.external.client.types.Invalidation; 18 import com.google.ipc.invalidation.external.client.types.ObjectId; 19 20 import org.chromium.base.CollectionUtil; 21 import org.chromium.base.test.util.AdvancedMockContext; 22 import org.chromium.base.test.util.Feature; 23 import org.chromium.sync.internal_api.pub.base.ModelType; 24 import org.chromium.sync.notifier.InvalidationPreferences.EditContext; 25 import org.chromium.sync.signin.AccountManagerHelper; 26 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.EnumSet; 30 import java.util.HashSet; 31 import java.util.List; 32 import java.util.Set; 33 34 /** 35 * Tests for the {@link InvalidationService}. 36 * 37 * @author dsmyers (at) google.com (Daniel Myers) 38 */ 39 public class InvalidationServiceTest extends ServiceTestCase<TestableInvalidationService> { 40 /** Id used when creating clients. */ 41 private static final byte[] CLIENT_ID = new byte[]{0, 4, 7}; 42 43 /** Intents provided to {@link #startService}. */ 44 private List<Intent> mStartServiceIntents; 45 46 public InvalidationServiceTest() { 47 super(TestableInvalidationService.class); 48 } 49 50 @Override 51 public void setUp() throws Exception { 52 super.setUp(); 53 mStartServiceIntents = new ArrayList<Intent>(); 54 setContext(new AdvancedMockContext(getContext()) { 55 @Override 56 public ComponentName startService(Intent intent) { 57 mStartServiceIntents.add(intent); 58 return new ComponentName(this, InvalidationServiceTest.class); 59 } 60 }); 61 setupService(); 62 } 63 64 @Override 65 public void tearDown() throws Exception { 66 if (InvalidationService.getIsClientStartedForTest()) { 67 Intent stopIntent = createStopIntent(); 68 getService().onHandleIntent(stopIntent); 69 } 70 assertFalse(InvalidationService.getIsClientStartedForTest()); 71 super.tearDown(); 72 } 73 74 @SmallTest 75 @Feature({"Sync"}) 76 public void testComputeRegistrationOps() { 77 /* 78 * Test plan: compute the set of registration operations resulting from various combinations 79 * of existing and desired registrations. Verifying that they are correct. 80 */ 81 Set<ObjectId> regAccumulator = new HashSet<ObjectId>(); 82 Set<ObjectId> unregAccumulator = new HashSet<ObjectId>(); 83 84 // Empty existing and desired registrations should yield empty operation sets. 85 InvalidationService.computeRegistrationOps( 86 ModelType.modelTypesToObjectIds( 87 CollectionUtil.newHashSet(ModelType.BOOKMARK, ModelType.SESSION)), 88 ModelType.modelTypesToObjectIds( 89 CollectionUtil.newHashSet(ModelType.BOOKMARK, ModelType.SESSION)), 90 regAccumulator, unregAccumulator); 91 assertEquals(0, regAccumulator.size()); 92 assertEquals(0, unregAccumulator.size()); 93 94 // Equal existing and desired registrations should yield empty operation sets. 95 InvalidationService.computeRegistrationOps(new HashSet<ObjectId>(), 96 new HashSet<ObjectId>(), regAccumulator, unregAccumulator); 97 assertEquals(0, regAccumulator.size()); 98 assertEquals(0, unregAccumulator.size()); 99 100 // Empty existing and non-empty desired registrations should yield desired registrations 101 // as the registration operations to do and no unregistrations. 102 Set<ObjectId> desiredTypes = 103 CollectionUtil.newHashSet( 104 ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId()); 105 InvalidationService.computeRegistrationOps( 106 new HashSet<ObjectId>(), 107 desiredTypes, 108 regAccumulator, unregAccumulator); 109 assertEquals( 110 CollectionUtil.newHashSet( 111 ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId()), 112 new HashSet<ObjectId>(regAccumulator)); 113 assertEquals(0, unregAccumulator.size()); 114 regAccumulator.clear(); 115 116 // Unequal existing and desired registrations should yield both registrations and 117 // unregistrations. We should unregister TYPED_URL and register BOOKMARK, keeping SESSION. 118 InvalidationService.computeRegistrationOps( 119 CollectionUtil.newHashSet( 120 ModelType.SESSION.toObjectId(), ModelType.TYPED_URL.toObjectId()), 121 CollectionUtil.newHashSet( 122 ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId()), 123 regAccumulator, unregAccumulator); 124 assertEquals(CollectionUtil.newHashSet(ModelType.BOOKMARK.toObjectId()), regAccumulator); 125 assertEquals(CollectionUtil.newHashSet(ModelType.TYPED_URL.toObjectId()), 126 unregAccumulator); 127 regAccumulator.clear(); 128 unregAccumulator.clear(); 129 } 130 131 @SmallTest 132 @Feature({"Sync"}) 133 public void testReady() { 134 /** 135 * Test plan: call ready. Verify that the service sets the client id correctly and reissues 136 * pending registrations. 137 */ 138 139 // Persist some registrations. 140 InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); 141 EditContext editContext = invPrefs.edit(); 142 invPrefs.setSyncTypes(editContext, CollectionUtil.newArrayList("BOOKMARK", "SESSION")); 143 ObjectId objectId = ObjectId.newInstance(1, "obj".getBytes()); 144 invPrefs.setObjectIds(editContext, CollectionUtil.newArrayList(objectId)); 145 assertTrue(invPrefs.commit(editContext)); 146 147 // Issue ready. 148 getService().ready(CLIENT_ID); 149 assertTrue(Arrays.equals(CLIENT_ID, InvalidationService.getClientIdForTest())); 150 byte[] otherCid = "otherCid".getBytes(); 151 getService().ready(otherCid); 152 assertTrue(Arrays.equals(otherCid, InvalidationService.getClientIdForTest())); 153 154 // Verify registrations issued. 155 assertEquals(CollectionUtil.newHashSet( 156 ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId(), objectId), 157 new HashSet<ObjectId>(getService().mRegistrations.get(0))); 158 } 159 160 @SmallTest 161 @Feature({"Sync"}) 162 public void testReissueRegistrations() { 163 /* 164 * Test plan: call the reissueRegistrations method of the listener with both empty and 165 * non-empty sets of desired registrations stored in preferences. Verify that no register 166 * intent is set in the first case and that the appropriate register intent is sent in 167 * the second. 168 */ 169 170 // No persisted registrations. 171 getService().reissueRegistrations(CLIENT_ID); 172 assertTrue(getService().mRegistrations.isEmpty()); 173 174 // Persist some registrations. 175 InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); 176 EditContext editContext = invPrefs.edit(); 177 invPrefs.setSyncTypes(editContext, CollectionUtil.newArrayList("BOOKMARK", "SESSION")); 178 ObjectId objectId = ObjectId.newInstance(1, "obj".getBytes()); 179 invPrefs.setObjectIds(editContext, CollectionUtil.newArrayList(objectId)); 180 assertTrue(invPrefs.commit(editContext)); 181 182 // Reissue registrations and verify that the appropriate registrations are issued. 183 getService().reissueRegistrations(CLIENT_ID); 184 assertEquals(1, getService().mRegistrations.size()); 185 assertEquals(CollectionUtil.newHashSet( 186 ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId(), objectId), 187 new HashSet<ObjectId>(getService().mRegistrations.get(0))); 188 } 189 190 @SmallTest 191 @Feature({"Sync"}) 192 public void testInformRegistrationStatus() { 193 /* 194 * Test plan: call inform registration status under a variety of circumstances and verify 195 * that the appropriate (un)register calls are issued. 196 * 197 * 1. Registration of desired object. No calls issued. 198 * 2. Unregistration of undesired object. No calls issued. 199 * 3. Registration of undesired object. Unregistration issued. 200 * 4. Unregistration of desired object. Registration issued. 201 */ 202 // Initial test setup: persist a single registration into preferences. 203 InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); 204 EditContext editContext = invPrefs.edit(); 205 invPrefs.setSyncTypes(editContext, CollectionUtil.newArrayList("SESSION")); 206 ObjectId desiredObjectId = ObjectId.newInstance(1, "obj1".getBytes()); 207 ObjectId undesiredObjectId = ObjectId.newInstance(1, "obj2".getBytes()); 208 invPrefs.setObjectIds(editContext, CollectionUtil.newArrayList(desiredObjectId)); 209 assertTrue(invPrefs.commit(editContext)); 210 211 // Cases 1 and 2: calls matching desired state cause no actions. 212 getService().informRegistrationStatus(CLIENT_ID, ModelType.SESSION.toObjectId(), 213 RegistrationState.REGISTERED); 214 getService().informRegistrationStatus(CLIENT_ID, desiredObjectId, 215 RegistrationState.REGISTERED); 216 getService().informRegistrationStatus(CLIENT_ID, ModelType.BOOKMARK.toObjectId(), 217 RegistrationState.UNREGISTERED); 218 getService().informRegistrationStatus(CLIENT_ID, undesiredObjectId, 219 RegistrationState.UNREGISTERED); 220 assertTrue(getService().mRegistrations.isEmpty()); 221 assertTrue(getService().mUnregistrations.isEmpty()); 222 223 // Case 3: registration of undesired object triggers an unregistration. 224 getService().informRegistrationStatus(CLIENT_ID, ModelType.BOOKMARK.toObjectId(), 225 RegistrationState.REGISTERED); 226 getService().informRegistrationStatus(CLIENT_ID, undesiredObjectId, 227 RegistrationState.REGISTERED); 228 assertEquals(2, getService().mUnregistrations.size()); 229 assertEquals(0, getService().mRegistrations.size()); 230 assertEquals(CollectionUtil.newArrayList(ModelType.BOOKMARK.toObjectId()), 231 getService().mUnregistrations.get(0)); 232 assertEquals(CollectionUtil.newArrayList(undesiredObjectId), 233 getService().mUnregistrations.get(1)); 234 235 // Case 4: unregistration of a desired object triggers a registration. 236 getService().informRegistrationStatus(CLIENT_ID, ModelType.SESSION.toObjectId(), 237 RegistrationState.UNREGISTERED); 238 getService().informRegistrationStatus(CLIENT_ID, desiredObjectId, 239 RegistrationState.UNREGISTERED); 240 assertEquals(2, getService().mUnregistrations.size()); 241 assertEquals(2, getService().mRegistrations.size()); 242 assertEquals(CollectionUtil.newArrayList(ModelType.SESSION.toObjectId()), 243 getService().mRegistrations.get(0)); 244 assertEquals(CollectionUtil.newArrayList(desiredObjectId), 245 getService().mRegistrations.get(1)); 246 } 247 248 @SmallTest 249 @Feature({"Sync"}) 250 public void testInformRegistrationFailure() { 251 /* 252 * Test plan: call inform registration failure under a variety of circumstances and verify 253 * that the appropriate (un)register calls are issued. 254 * 255 * 1. Transient registration failure for an object that should be registered. Register 256 * should be called. 257 * 2. Permanent registration failure for an object that should be registered. No calls. 258 * 3. Transient registration failure for an object that should not be registered. Unregister 259 * should be called. 260 * 4. Permanent registration failure for an object should not be registered. No calls. 261 */ 262 263 // Initial test setup: persist a single registration into preferences. 264 InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); 265 EditContext editContext = invPrefs.edit(); 266 invPrefs.setSyncTypes(editContext, CollectionUtil.newArrayList("SESSION")); 267 ObjectId desiredObjectId = ObjectId.newInstance(1, "obj1".getBytes()); 268 ObjectId undesiredObjectId = ObjectId.newInstance(1, "obj2".getBytes()); 269 invPrefs.setObjectIds(editContext, CollectionUtil.newArrayList(desiredObjectId)); 270 assertTrue(invPrefs.commit(editContext)); 271 272 // Cases 2 and 4: permanent registration failures never cause calls to be made. 273 getService().informRegistrationFailure(CLIENT_ID, ModelType.SESSION.toObjectId(), false, 274 ""); 275 getService().informRegistrationFailure(CLIENT_ID, ModelType.BOOKMARK.toObjectId(), false, 276 ""); 277 getService().informRegistrationFailure(CLIENT_ID, desiredObjectId, false, ""); 278 getService().informRegistrationFailure(CLIENT_ID, undesiredObjectId, false, ""); 279 assertTrue(getService().mRegistrations.isEmpty()); 280 assertTrue(getService().mUnregistrations.isEmpty()); 281 282 // Case 1: transient failure of a desired registration results in re-registration. 283 getService().informRegistrationFailure(CLIENT_ID, ModelType.SESSION.toObjectId(), true, ""); 284 getService().informRegistrationFailure(CLIENT_ID, desiredObjectId, true, ""); 285 assertEquals(2, getService().mRegistrations.size()); 286 assertTrue(getService().mUnregistrations.isEmpty()); 287 assertEquals(CollectionUtil.newArrayList(ModelType.SESSION.toObjectId()), 288 getService().mRegistrations.get(0)); 289 assertEquals(CollectionUtil.newArrayList(desiredObjectId), 290 getService().mRegistrations.get(1)); 291 292 // Case 3: transient failure of an undesired registration results in unregistration. 293 getService().informRegistrationFailure(CLIENT_ID, ModelType.BOOKMARK.toObjectId(), true, 294 ""); 295 getService().informRegistrationFailure(CLIENT_ID, undesiredObjectId, true, ""); 296 assertEquals(2, getService().mRegistrations.size()); 297 assertEquals(2, getService().mUnregistrations.size()); 298 assertEquals(CollectionUtil.newArrayList(ModelType.BOOKMARK.toObjectId()), 299 getService().mUnregistrations.get(0)); 300 assertEquals(CollectionUtil.newArrayList(undesiredObjectId), 301 getService().mUnregistrations.get(1)); 302 } 303 304 @SmallTest 305 @Feature({"Sync"}) 306 public void testInformError() { 307 /* 308 * Test plan: call informError with both permanent and transient errors. Verify that 309 * the transient error causes no action to be taken and that the permanent error causes 310 * the client to be stopped. 311 */ 312 313 // Client needs to be started for the permament error to trigger and stop. 314 getService().setShouldRunStates(true, true); 315 getService().onCreate(); 316 getService().onHandleIntent(createStartIntent()); 317 getService().mStartedServices.clear(); // Discard start intent. 318 319 // Transient error. 320 getService().informError(ErrorInfo.newInstance(0, true, "transient", null)); 321 assertTrue(getService().mStartedServices.isEmpty()); 322 323 // Permanent error. 324 getService().informError(ErrorInfo.newInstance(0, false, "permanent", null)); 325 assertEquals(1, getService().mStartedServices.size()); 326 Intent sentIntent = getService().mStartedServices.get(0); 327 Intent stopIntent = AndroidListener.createStopIntent(getContext()); 328 assertTrue(stopIntent.filterEquals(sentIntent)); 329 assertEquals(stopIntent.getExtras().keySet(), sentIntent.getExtras().keySet()); 330 } 331 332 @SmallTest 333 @Feature({"Sync"}) 334 public void testReadWriteState() { 335 /* 336 * Test plan: read, write, and read the internal notification client persistent state. 337 * Verify appropriate return values. 338 */ 339 assertNull(getService().readState()); 340 byte[] writtenState = new byte[]{7, 4, 0}; 341 getService().writeState(writtenState); 342 assertTrue(Arrays.equals(writtenState, getService().readState())); 343 } 344 345 @SmallTest 346 @Feature({"Sync"}) 347 public void testInvalidateWithPayload() { 348 doTestInvalidate(true); 349 } 350 351 @SmallTest 352 @Feature({"Sync"}) 353 public void testInvalidateWithoutPayload() { 354 doTestInvalidate(false); 355 } 356 357 private void doTestInvalidate(boolean hasPayload) { 358 /* 359 * Test plan: call invalidate() with an invalidation that may or may not have a payload. 360 * Verify the produced bundle has the correct fields. 361 */ 362 // Call invalidate. 363 int version = 4747; 364 ObjectId objectId = ObjectId.newInstance(55, "BOOKMARK".getBytes()); 365 final String payload = "testInvalidate-" + hasPayload; 366 Invalidation invalidation = hasPayload ? 367 Invalidation.newInstance(objectId, version, payload.getBytes()) : 368 Invalidation.newInstance(objectId, version); 369 byte[] ackHandle = ("testInvalidate-" + hasPayload).getBytes(); 370 getService().invalidate(invalidation, ackHandle); 371 372 // Validate bundle. 373 assertEquals(1, getService().mRequestedSyncs.size()); 374 Bundle syncBundle = getService().mRequestedSyncs.get(0); 375 assertEquals(55, syncBundle.getInt("objectSource")); 376 assertEquals("BOOKMARK", syncBundle.getString("objectId")); 377 assertEquals(version, syncBundle.getLong("version")); 378 assertEquals(hasPayload ? payload : "", syncBundle.getString("payload")); 379 380 // Ensure acknowledged. 381 assertSingleAcknowledgement(ackHandle); 382 } 383 384 @SmallTest 385 @Feature({"Sync"}) 386 public void testInvalidateUnknownVersion() { 387 /* 388 * Test plan: call invalidateUnknownVersion(). Verify the produced bundle has the correct 389 * fields. 390 */ 391 ObjectId objectId = ObjectId.newInstance(55, "BOOKMARK".getBytes()); 392 byte[] ackHandle = "testInvalidateUV".getBytes(); 393 getService().invalidateUnknownVersion(objectId, ackHandle); 394 395 // Validate bundle. 396 assertEquals(1, getService().mRequestedSyncs.size()); 397 Bundle syncBundle = getService().mRequestedSyncs.get(0); 398 assertEquals(55, syncBundle.getInt("objectSource")); 399 assertEquals("BOOKMARK", syncBundle.getString("objectId")); 400 assertEquals(0, syncBundle.getLong("version")); 401 assertEquals("", syncBundle.getString("payload")); 402 403 // Ensure acknowledged. 404 assertSingleAcknowledgement(ackHandle); 405 } 406 407 @SmallTest 408 @Feature({"Sync"}) 409 public void testInvalidateAll() { 410 /* 411 * Test plan: call invalidateAll(). Verify the produced bundle has the correct fields. 412 */ 413 byte[] ackHandle = "testInvalidateAll".getBytes(); 414 getService().invalidateAll(ackHandle); 415 416 // Validate bundle. 417 assertEquals(1, getService().mRequestedSyncs.size()); 418 Bundle syncBundle = getService().mRequestedSyncs.get(0); 419 assertEquals(0, syncBundle.keySet().size()); 420 421 // Ensure acknowledged. 422 assertSingleAcknowledgement(ackHandle); 423 } 424 425 /** Asserts that the service received a single acknowledgement with handle {@code ackHandle}. */ 426 private void assertSingleAcknowledgement(byte[] ackHandle) { 427 assertEquals(1, getService().mAcknowledgements.size()); 428 assertTrue(Arrays.equals(ackHandle, getService().mAcknowledgements.get(0))); 429 } 430 431 @SmallTest 432 @Feature({"Sync"}) 433 public void testShouldClientBeRunning() { 434 /* 435 * Test plan: call shouldClientBeRunning with various combinations of 436 * in-foreground/sync-enabled. Verify appropriate return values. 437 */ 438 getService().setShouldRunStates(false, false); 439 assertFalse(getService().shouldClientBeRunning()); 440 441 getService().setShouldRunStates(false, true); 442 assertFalse(getService().shouldClientBeRunning()); 443 444 getService().setShouldRunStates(true, false); 445 assertFalse(getService().shouldClientBeRunning()); 446 447 // Should only be running if both in the foreground and sync is enabled. 448 getService().setShouldRunStates(true, true); 449 assertTrue(getService().shouldClientBeRunning()); 450 } 451 452 @SmallTest 453 @Feature({"Sync"}) 454 public void testStartAndStopClient() { 455 /* 456 * Test plan: with Chrome configured so that the client should run, send it an empty 457 * intent. Even though no owning account is known, the client should still start. Send 458 * it a stop intent and verify that it stops. 459 */ 460 461 // Note: we are manipulating the service object directly, rather than through startService, 462 // because otherwise we would need to handle the asynchronous execution model of the 463 // underlying IntentService. 464 getService().setShouldRunStates(true, true); 465 getService().onCreate(); 466 467 Intent startIntent = createStartIntent(); 468 getService().onHandleIntent(startIntent); 469 assertTrue(InvalidationService.getIsClientStartedForTest()); 470 471 Intent stopIntent = createStopIntent(); 472 getService().onHandleIntent(stopIntent); 473 assertFalse(InvalidationService.getIsClientStartedForTest()); 474 475 // The issued intents should have been an AndroidListener start intent followed by an 476 // AndroidListener stop intent. 477 assertEquals(2, mStartServiceIntents.size()); 478 assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); 479 assertTrue(isAndroidListenerStopIntent(mStartServiceIntents.get(1))); 480 } 481 482 @SmallTest 483 @Feature({"Sync"}) 484 public void testClientStopsWhenShouldNotBeRunning() { 485 /* 486 * Test plan: start the client. Then, change the configuration so that Chrome should not 487 * be running. Send an intent to the service and verify that it stops. 488 */ 489 getService().setShouldRunStates(true, true); 490 getService().onCreate(); 491 492 // Start the service. 493 Intent startIntent = createStartIntent(); 494 getService().onHandleIntent(startIntent); 495 assertTrue(InvalidationService.getIsClientStartedForTest()); 496 497 // Change configuration. 498 getService().setShouldRunStates(false, false); 499 500 // Send an Intent and verify that the service stops. 501 getService().onHandleIntent(startIntent); 502 assertFalse(InvalidationService.getIsClientStartedForTest()); 503 504 // The issued intents should have been an AndroidListener start intent followed by an 505 // AndroidListener stop intent. 506 assertEquals(2, mStartServiceIntents.size()); 507 assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); 508 assertTrue(isAndroidListenerStopIntent(mStartServiceIntents.get(1))); 509 } 510 511 @SmallTest 512 @Feature({"Sync"}) 513 public void testRegistrationIntent() { 514 /* 515 * Test plan: send a registration-change intent. Verify that it starts the client and 516 * sets both the account and registrations in shared preferences. 517 */ 518 getService().setShouldRunStates(true, true); 519 getService().onCreate(); 520 521 // Send register Intent. 522 Set<ModelType> desiredRegistrations = CollectionUtil.newHashSet( 523 ModelType.BOOKMARK, ModelType.SESSION); 524 Account account = AccountManagerHelper.createAccountFromName("test (at) example.com"); 525 Intent registrationIntent = createRegisterIntent(account, false, desiredRegistrations); 526 getService().onHandleIntent(registrationIntent); 527 528 // Verify client started and state written. 529 assertTrue(InvalidationService.getIsClientStartedForTest()); 530 InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); 531 assertEquals(account, invPrefs.getSavedSyncedAccount()); 532 assertEquals(ModelType.modelTypesToSyncTypes(desiredRegistrations), 533 invPrefs.getSavedSyncedTypes()); 534 assertNull(invPrefs.getSavedObjectIds()); 535 assertEquals(1, mStartServiceIntents.size()); 536 assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); 537 538 // Send another registration-change intent, this type with all-types set to true, and 539 // verify that the on-disk state is updated and that no addition Intents are issued. 540 getService().onHandleIntent(createRegisterIntent(account, true, null)); 541 assertEquals(account, invPrefs.getSavedSyncedAccount()); 542 assertEquals(CollectionUtil.newHashSet(ModelType.ALL_TYPES_TYPE), 543 invPrefs.getSavedSyncedTypes()); 544 assertEquals(1, mStartServiceIntents.size()); 545 546 // Finally, send one more registration-change intent, this time with a different account, 547 // and verify that it both updates the account, stops thye existing client, and 548 // starts a new client. 549 Account account2 = AccountManagerHelper.createAccountFromName("test2 (at) example.com"); 550 getService().onHandleIntent(createRegisterIntent(account2, true, null)); 551 assertEquals(account2, invPrefs.getSavedSyncedAccount()); 552 assertEquals(3, mStartServiceIntents.size()); 553 assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); 554 assertTrue(isAndroidListenerStopIntent(mStartServiceIntents.get(1))); 555 assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(2))); 556 } 557 558 /** 559 * Determines if the correct object ids have been written to preferences and registered with the 560 * invalidation client. 561 * 562 * @param expectedTypes The Sync types expected to be registered. 563 * @param expectedObjectIds The additional object ids expected to be registered. 564 * @param isReady Whether the client is ready to register/unregister. 565 */ 566 private boolean expectedObjectIdsRegistered(Set<ModelType> expectedTypes, 567 Set<ObjectId> expectedObjectIds, boolean isReady) { 568 // Get synced types saved to preferences. 569 Set<String> expectedSyncTypes = ModelType.modelTypesToSyncTypes(expectedTypes); 570 InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); 571 Set<String> actualSyncTypes = invPrefs.getSavedSyncedTypes(); 572 if (actualSyncTypes == null) { 573 actualSyncTypes = new HashSet<String>(); 574 } 575 576 // Get object ids saved to preferences. 577 Set<ObjectId> actualObjectIds = invPrefs.getSavedObjectIds(); 578 if (actualObjectIds == null) { 579 actualObjectIds = new HashSet<ObjectId>(); 580 } 581 582 // Get expected registered object ids. 583 Set<ObjectId> expectedRegisteredIds = new HashSet<ObjectId>(); 584 if (isReady) { 585 expectedRegisteredIds.addAll(ModelType.modelTypesToObjectIds(expectedTypes)); 586 expectedRegisteredIds.addAll(expectedObjectIds); 587 } 588 589 return actualSyncTypes.equals(expectedSyncTypes) && 590 actualObjectIds.equals(expectedObjectIds) && 591 getService().mCurrentRegistrations.equals(expectedRegisteredIds); 592 } 593 594 @SmallTest 595 @Feature({"Sync"}) 596 public void testRegistrationIntentWithTypesAndObjectIds() { 597 /* 598 * Test plan: send a mix of registration-change intents: some for Sync types and some for 599 * object ids. Verify that registering for Sync types does not interfere with object id 600 * registration and vice-versa. 601 */ 602 getService().setShouldRunStates(true, true); 603 getService().onCreate(); 604 605 Account account = AccountManagerHelper.createAccountFromName("test (at) example.com"); 606 Set<ObjectId> objectIds = new HashSet<ObjectId>(); 607 Set<ModelType> types = new HashSet<ModelType>(); 608 609 // Register for some object ids. 610 objectIds.add(ObjectId.newInstance(1, "obj1".getBytes())); 611 objectIds.add(ObjectId.newInstance(2, "obj2".getBytes())); 612 Intent registrationIntent = 613 createRegisterIntent(account, new int[] {1, 2}, new String[] {"obj1", "obj2"}); 614 getService().onHandleIntent(registrationIntent); 615 assertTrue(expectedObjectIdsRegistered(types, objectIds, false /* isReady */)); 616 617 // Register for some types. 618 types.add(ModelType.BOOKMARK); 619 types.add(ModelType.SESSION); 620 registrationIntent = createRegisterIntent(account, false, types); 621 getService().onHandleIntent(registrationIntent); 622 assertTrue(expectedObjectIdsRegistered(types, objectIds, false /* isReady */)); 623 624 // Set client to be ready and verify registrations. 625 getService().ready(CLIENT_ID); 626 assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */)); 627 628 // Change object id registration with types registered. 629 objectIds.add(ObjectId.newInstance(3, "obj3".getBytes())); 630 registrationIntent = createRegisterIntent( 631 account, new int[] {1, 2, 3}, new String[] {"obj1", "obj2", "obj3"}); 632 getService().onHandleIntent(registrationIntent); 633 assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */)); 634 635 // Change type registration with object ids registered. 636 types.remove(ModelType.BOOKMARK); 637 registrationIntent = createRegisterIntent(account, false, types); 638 getService().onHandleIntent(registrationIntent); 639 assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */)); 640 641 // Unregister all types. 642 types.clear(); 643 registrationIntent = createRegisterIntent(account, false, types); 644 getService().onHandleIntent(registrationIntent); 645 assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */)); 646 647 // Change object id registration with no types registered. 648 objectIds.remove(ObjectId.newInstance(2, "obj2".getBytes())); 649 registrationIntent = createRegisterIntent( 650 account, new int[] {1, 3}, new String[] {"obj1", "obj3"}); 651 getService().onHandleIntent(registrationIntent); 652 assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */)); 653 654 // Unregister all object ids. 655 objectIds.clear(); 656 registrationIntent = createRegisterIntent(account, new int[0], new String[0]); 657 getService().onHandleIntent(registrationIntent); 658 assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */)); 659 660 // Change type registration with no object ids registered. 661 types.add(ModelType.BOOKMARK); 662 types.add(ModelType.PASSWORD); 663 registrationIntent = createRegisterIntent(account, false, types); 664 getService().onHandleIntent(registrationIntent); 665 assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */)); 666 } 667 668 @SmallTest 669 @Feature({"Sync"}) 670 public void testRegistrationIntentNoProxyTabsUsingReady() { 671 getService().setShouldRunStates(true, true); 672 getService().onCreate(); 673 674 // Send register Intent. 675 Account account = AccountManagerHelper.createAccountFromName("test (at) example.com"); 676 Intent registrationIntent = createRegisterIntent(account, true, null); 677 getService().onHandleIntent(registrationIntent); 678 679 // Verify client started and state written. 680 assertTrue(InvalidationService.getIsClientStartedForTest()); 681 InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); 682 assertEquals(account, invPrefs.getSavedSyncedAccount()); 683 assertEquals(CollectionUtil.newHashSet(ModelType.ALL_TYPES_TYPE), 684 invPrefs.getSavedSyncedTypes()); 685 assertEquals(1, mStartServiceIntents.size()); 686 assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); 687 688 // Set client to be ready. This triggers registrations. 689 getService().ready(CLIENT_ID); 690 assertTrue(Arrays.equals(CLIENT_ID, InvalidationService.getClientIdForTest())); 691 692 // Ensure registrations are correct. 693 Set<ObjectId> expectedTypes = 694 ModelType.modelTypesToObjectIds(EnumSet.allOf(ModelType.class)); 695 assertEquals(expectedTypes, new HashSet<ObjectId>(getService().mRegistrations.get(0))); 696 } 697 698 @SmallTest 699 @Feature({"Sync"}) 700 public void testRegistrationIntentNoProxyTabsAlreadyWithClientId() { 701 getService().setShouldRunStates(true, true); 702 getService().onCreate(); 703 704 // Send register Intent with no desired types. 705 Account account = AccountManagerHelper.createAccountFromName("test (at) example.com"); 706 Intent registrationIntent = createRegisterIntent(account, false, new HashSet<ModelType>()); 707 getService().onHandleIntent(registrationIntent); 708 709 // Verify client started and state written. 710 assertTrue(InvalidationService.getIsClientStartedForTest()); 711 InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); 712 assertEquals(account, invPrefs.getSavedSyncedAccount()); 713 assertEquals(new HashSet<String>(), invPrefs.getSavedSyncedTypes()); 714 assertEquals(1, mStartServiceIntents.size()); 715 assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); 716 717 // Make sure client is ready. 718 getService().ready(CLIENT_ID); 719 assertTrue(Arrays.equals(CLIENT_ID, InvalidationService.getClientIdForTest())); 720 721 // Choose to register for all types in an already ready client. 722 registrationIntent = createRegisterIntent(account, true, null); 723 getService().onHandleIntent(registrationIntent); 724 725 // Ensure registrations are correct. 726 assertEquals(1, getService().mRegistrations.size()); 727 Set<ObjectId> expectedTypes = 728 ModelType.modelTypesToObjectIds(EnumSet.allOf(ModelType.class)); 729 assertEquals(expectedTypes, new HashSet<ObjectId>(getService().mRegistrations.get(0))); 730 } 731 732 @SmallTest 733 @Feature({"Sync"}) 734 public void testRegistrationIntentWhenClientShouldNotBeRunning() { 735 /* 736 * Test plan: send a registration change event when the client should not be running. 737 * Verify that the service updates the on-disk state but does not start the client. 738 */ 739 getService().onCreate(); 740 741 // Send register Intent. 742 Account account = AccountManagerHelper.createAccountFromName("test (at) example.com"); 743 Set<ModelType> desiredRegistrations = CollectionUtil.newHashSet( 744 ModelType.BOOKMARK, ModelType.SESSION); 745 Intent registrationIntent = createRegisterIntent(account, false, desiredRegistrations); 746 getService().onHandleIntent(registrationIntent); 747 748 // Verify state written but client not started. 749 assertFalse(InvalidationService.getIsClientStartedForTest()); 750 InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); 751 assertEquals(account, invPrefs.getSavedSyncedAccount()); 752 assertEquals(ModelType.modelTypesToSyncTypes(desiredRegistrations), 753 invPrefs.getSavedSyncedTypes()); 754 assertEquals(0, mStartServiceIntents.size()); 755 } 756 757 @SmallTest 758 @Feature({"Sync"}) 759 public void testDeferredRegistrationsIssued() { 760 /* 761 * Test plan: send a registration-change intent. Verify that the client issues a start 762 * intent but makes no registration calls. Issue a reissueRegistrations call and verify 763 * that the client does issue the appropriate registrations. 764 */ 765 getService().setShouldRunStates(true, true); 766 getService().onCreate(); 767 768 // Send register Intent. Verify client started but no registrations issued. 769 Account account = AccountManagerHelper.createAccountFromName("test (at) example.com"); 770 Set<ModelType> desiredRegistrations = CollectionUtil.newHashSet( 771 ModelType.BOOKMARK, ModelType.SESSION); 772 Set<ObjectId> desiredObjectIds = ModelType.modelTypesToObjectIds(desiredRegistrations); 773 774 Intent registrationIntent = createRegisterIntent(account, false, desiredRegistrations); 775 getService().onHandleIntent(registrationIntent); 776 assertTrue(InvalidationService.getIsClientStartedForTest()); 777 assertEquals(1, mStartServiceIntents.size()); 778 assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); 779 InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); 780 assertEquals(ModelType.modelTypesToSyncTypes(desiredRegistrations), 781 invPrefs.getSavedSyncedTypes()); 782 assertEquals(desiredObjectIds, getService().readRegistrationsFromPrefs()); 783 784 // Issue reissueRegistrations; verify registration intent issues. 785 getService().reissueRegistrations(CLIENT_ID); 786 assertEquals(2, mStartServiceIntents.size()); 787 Intent expectedRegisterIntent = AndroidListener.createRegisterIntent( 788 getContext(), 789 CLIENT_ID, 790 desiredObjectIds); 791 Intent actualRegisterIntent = mStartServiceIntents.get(1); 792 assertTrue(expectedRegisterIntent.filterEquals(actualRegisterIntent)); 793 assertEquals(expectedRegisterIntent.getExtras().keySet(), 794 actualRegisterIntent.getExtras().keySet()); 795 assertEquals( 796 desiredObjectIds, 797 new HashSet<ObjectId>(getService().mRegistrations.get(0))); 798 } 799 800 @SmallTest 801 @Feature({"Sync"}) 802 public void testRegistrationRetries() { 803 /* 804 * Test plan: validate that the alarm receiver used by the AndroidListener underlying 805 * InvalidationService is correctly configured in the manifest and retries registrations 806 * with exponential backoff. May need to be implemented as a downstream Chrome for Android 807 * test. 808 */ 809 // TODO(dsmyers): implement. 810 // Bug: https://code.google.com/p/chromium/issues/detail?id=172398 811 } 812 813 /** Creates an intent to start the InvalidationService. */ 814 private Intent createStartIntent() { 815 Intent intent = new Intent(); 816 return intent; 817 } 818 819 /** Creates an intent to stop the InvalidationService. */ 820 private Intent createStopIntent() { 821 Intent intent = new Intent(); 822 intent.putExtra(InvalidationIntentProtocol.EXTRA_STOP, true); 823 return intent; 824 } 825 826 /** Creates an intent to register some types with the InvalidationService. */ 827 private Intent createRegisterIntent(Account account, boolean allTypes, Set<ModelType> types) { 828 Intent intent = InvalidationIntentProtocol.createRegisterIntent(account, allTypes, types); 829 return intent; 830 } 831 832 /** Creates an intent to register some types with the InvalidationService. */ 833 private Intent createRegisterIntent( 834 Account account, int[] objectSources, String[] objectNames) { 835 Intent intent = InvalidationIntentProtocol.createRegisterIntent( 836 account, objectSources, objectNames); 837 return intent; 838 } 839 840 /** Returns whether {@code intent} is an {@link AndroidListener} start intent. */ 841 private boolean isAndroidListenerStartIntent(Intent intent) { 842 Intent startIntent = AndroidListener.createStartIntent(getContext(), 843 InvalidationService.CLIENT_TYPE, "unused".getBytes()); 844 return intent.getExtras().keySet().equals(startIntent.getExtras().keySet()); 845 } 846 847 /** Returns whether {@code intent} is an {@link AndroidListener} stop intent. */ 848 private boolean isAndroidListenerStopIntent(Intent intent) { 849 Intent stopIntent = AndroidListener.createStopIntent(getContext()); 850 return intent.getExtras().keySet().equals(stopIntent.getExtras().keySet()); 851 } 852 } 853