SyncRequest.java revision c81891c1257895220c00e9ee99968ce305cfdd3b
1/* 2 * Copyright (C) 2013 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 17package android.content; 18 19import android.accounts.Account; 20import android.os.Bundle; 21import android.os.Parcel; 22import android.os.Parcelable; 23import android.util.Pair; 24 25public class SyncRequest implements Parcelable { 26 private static final String TAG = "SyncRequest"; 27 /** Account to pass to the sync adapter. Can be null. */ 28 private final Account mAccountToSync; 29 /** Authority string that corresponds to a ContentProvider. */ 30 private final String mAuthority; 31 /** {@link SyncService} identifier. */ 32 private final ComponentName mComponentInfo; 33 /** Bundle containing user info as well as sync settings. */ 34 private final Bundle mExtras; 35 /** Allow this sync request on metered networks. */ 36 private final boolean mAllowMetered; 37 /** 38 * Anticipated upload size in bytes. 39 * TODO: Not yet used - we put this information into the bundle for simplicity. 40 */ 41 private final long mTxBytes; 42 /** 43 * Anticipated download size in bytes. 44 * TODO: Not yet used - we put this information into the bundle. 45 */ 46 private final long mRxBytes; 47 /** 48 * Amount of time before {@link #mSyncRunTimeSecs} from which the sync may optionally be 49 * started. 50 */ 51 private final long mSyncFlexTimeSecs; 52 /** 53 * Specifies a point in the future at which the sync must have been scheduled to run. 54 */ 55 private final long mSyncRunTimeSecs; 56 /** Periodic versus one-off. */ 57 private final boolean mIsPeriodic; 58 /** Service versus provider. */ 59 private final boolean mIsAuthority; 60 /** Sync should be run in lieu of other syncs. */ 61 private final boolean mIsExpedited; 62 63 /** 64 * {@hide} 65 * @return whether this sync is periodic or one-time. A Sync Request must be 66 * either one of these or an InvalidStateException will be thrown in 67 * Builder.build(). 68 */ 69 public boolean isPeriodic() { 70 return mIsPeriodic; 71 } 72 73 public boolean isExpedited() { 74 return mIsExpedited; 75 } 76 77 /** 78 * {@hide} 79 * @return true if this sync uses an account/authority pair, or false if 80 * this is an anonymous sync bound to an @link AnonymousSyncService. 81 */ 82 public boolean hasAuthority() { 83 return mIsAuthority; 84 } 85 86 /** 87 * {@hide} 88 * Throws a runtime IllegalArgumentException if this function is called for an 89 * anonymous sync. 90 * 91 * @return (Account, Provider) for this SyncRequest. 92 */ 93 public Pair<Account, String> getProviderInfo() { 94 if (!hasAuthority()) { 95 throw new IllegalArgumentException("Cannot getProviderInfo() for an anonymous sync."); 96 } 97 return Pair.create(mAccountToSync, mAuthority); 98 } 99 100 /** 101 * {@hide} 102 * Throws a runtime IllegalArgumentException if this function is called for a 103 * SyncRequest that is bound to an account/provider. 104 * 105 * @return ComponentName for the service that this sync will bind to. 106 */ 107 public ComponentName getService() { 108 if (hasAuthority()) { 109 throw new IllegalArgumentException( 110 "Cannot getAnonymousService() for a sync that has specified a provider."); 111 } 112 return mComponentInfo; 113 } 114 115 /** 116 * {@hide} 117 * Retrieve bundle for this SyncRequest. Will not be null. 118 */ 119 public Bundle getBundle() { 120 return mExtras; 121 } 122 123 /** 124 * {@hide} 125 * @return the earliest point in time that this sync can be scheduled. 126 */ 127 public long getSyncFlexTime() { 128 return mSyncFlexTimeSecs; 129 } 130 /** 131 * {@hide} 132 * @return the last point in time at which this sync must scheduled. 133 */ 134 public long getSyncRunTime() { 135 return mSyncRunTimeSecs; 136 } 137 138 public static final Creator<SyncRequest> CREATOR = new Creator<SyncRequest>() { 139 140 @Override 141 public SyncRequest createFromParcel(Parcel in) { 142 return new SyncRequest(in); 143 } 144 145 @Override 146 public SyncRequest[] newArray(int size) { 147 return new SyncRequest[size]; 148 } 149 }; 150 151 @Override 152 public int describeContents() { 153 return 0; 154 } 155 156 @Override 157 public void writeToParcel(Parcel parcel, int flags) { 158 parcel.writeBundle(mExtras); 159 parcel.writeLong(mSyncFlexTimeSecs); 160 parcel.writeLong(mSyncRunTimeSecs); 161 parcel.writeInt((mIsPeriodic ? 1 : 0)); 162 parcel.writeInt((mAllowMetered ? 1 : 0)); 163 parcel.writeLong(mTxBytes); 164 parcel.writeLong(mRxBytes); 165 parcel.writeInt((mIsAuthority ? 1 : 0)); 166 parcel.writeInt((mIsExpedited? 1 : 0)); 167 if (mIsAuthority) { 168 parcel.writeParcelable(mAccountToSync, flags); 169 parcel.writeString(mAuthority); 170 } else { 171 parcel.writeParcelable(mComponentInfo, flags); 172 } 173 } 174 175 private SyncRequest(Parcel in) { 176 mExtras = in.readBundle(); 177 mSyncFlexTimeSecs = in.readLong(); 178 mSyncRunTimeSecs = in.readLong(); 179 mIsPeriodic = (in.readInt() != 0); 180 mAllowMetered = (in.readInt() != 0); 181 mTxBytes = in.readLong(); 182 mRxBytes = in.readLong(); 183 mIsAuthority = (in.readInt() != 0); 184 mIsExpedited = (in.readInt() != 0); 185 if (mIsAuthority) { 186 mComponentInfo = null; 187 mAccountToSync = in.readParcelable(null); 188 mAuthority = in.readString(); 189 } else { 190 mComponentInfo = in.readParcelable(null); 191 mAccountToSync = null; 192 mAuthority = null; 193 } 194 } 195 196 /** {@hide} Protected ctor to instantiate anonymous SyncRequest. */ 197 protected SyncRequest(SyncRequest.Builder b) { 198 mSyncFlexTimeSecs = b.mSyncFlexTimeSecs; 199 mSyncRunTimeSecs = b.mSyncRunTimeSecs; 200 mAccountToSync = b.mAccount; 201 mAuthority = b.mAuthority; 202 mComponentInfo = b.mComponentName; 203 mIsPeriodic = (b.mSyncType == Builder.SYNC_TYPE_PERIODIC); 204 mIsAuthority = (b.mSyncTarget == Builder.SYNC_TARGET_ADAPTER); 205 mIsExpedited = b.mExpedited; 206 mExtras = new Bundle(b.mCustomExtras); 207 mAllowMetered = b.mAllowMetered; 208 mTxBytes = b.mTxBytes; 209 mRxBytes = b.mRxBytes; 210 } 211 212 /** 213 * Builder class for a @link SyncRequest. As you build your SyncRequest this class will also 214 * perform validation. 215 */ 216 public static class Builder { 217 /** Unknown sync type. */ 218 private static final int SYNC_TYPE_UNKNOWN = 0; 219 /** Specify that this is a periodic sync. */ 220 private static final int SYNC_TYPE_PERIODIC = 1; 221 /** Specify that this is a one-time sync. */ 222 private static final int SYNC_TYPE_ONCE = 2; 223 /** Unknown sync target. */ 224 private static final int SYNC_TARGET_UNKNOWN = 0; 225 /** Specify that this is an anonymous sync. */ 226 private static final int SYNC_TARGET_SERVICE = 1; 227 /** Specify that this is a sync with a provider. */ 228 private static final int SYNC_TARGET_ADAPTER = 2; 229 /** 230 * Earliest point of displacement into the future at which this sync can 231 * occur. 232 */ 233 private long mSyncFlexTimeSecs; 234 /** Displacement into the future at which this sync must occur. */ 235 private long mSyncRunTimeSecs; 236 /** 237 * Sync configuration information - custom user data explicitly provided by the developer. 238 * This data is handed over to the sync operation. 239 */ 240 private Bundle mCustomExtras; 241 /** 242 * Sync system configuration - used to store system sync configuration. Corresponds to 243 * ContentResolver.SYNC_EXTRAS_* flags. 244 * TODO: Use this instead of dumping into one bundle. Need to decide if these flags should 245 * discriminate between equivalent syncs. 246 */ 247 private Bundle mSyncConfigExtras; 248 /** Expected upload transfer in bytes. */ 249 private long mTxBytes = -1L; 250 /** Expected download transfer in bytes. */ 251 private long mRxBytes = -1L; 252 /** Whether or not this sync can occur on metered networks. Default false. */ 253 private boolean mAllowMetered; 254 /** Priority of this sync relative to others from calling app [-2, 2]. Default 0. */ 255 private int mPriority = 0; 256 /** 257 * Whether this builder is building a periodic sync, or a one-time sync. 258 */ 259 private int mSyncType = SYNC_TYPE_UNKNOWN; 260 /** Whether this will go to a sync adapter or to a sync service. */ 261 private int mSyncTarget = SYNC_TARGET_UNKNOWN; 262 /** Whether this is a user-activated sync. */ 263 private boolean mIsManual; 264 /** 265 * Whether to retry this one-time sync if the sync fails. Not valid for 266 * periodic syncs. See {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY}. 267 */ 268 private boolean mNoRetry; 269 /** 270 * Whether to respect back-off for this one-time sync. Not valid for 271 * periodic syncs. See 272 * {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}; 273 */ 274 private boolean mIgnoreBackoff; 275 276 /** Ignore sync system settings and perform sync anyway. */ 277 private boolean mIgnoreSettings; 278 279 /** This sync will run in preference to other non-expedited syncs. */ 280 private boolean mExpedited; 281 282 /** 283 * The {@link SyncService} component that 284 * contains the sync logic if this is a provider-less sync, otherwise 285 * null. 286 */ 287 private ComponentName mComponentName; 288 /** 289 * The Account object that together with an Authority name define the SyncAdapter (if 290 * this sync is bound to a provider), otherwise null. 291 */ 292 private Account mAccount; 293 /** 294 * The Authority name that together with an Account define the SyncAdapter (if 295 * this sync is bound to a provider), otherwise null. 296 */ 297 private String mAuthority; 298 299 public Builder() { 300 } 301 302 /** 303 * Developer can define timing constraints for this one-shot request. 304 * These values are elapsed real-time. 305 * 306 * @param whenSeconds The time in seconds at which you want this 307 * sync to occur. 308 * @param beforeSeconds The amount of time in advance of whenSeconds that this 309 * sync may be permitted to occur. This is rounded up to a minimum of 5 310 * seconds, for any sync for which whenSeconds > 5. 311 * 312 * Example 313 * <pre> 314 * Perform an immediate sync. 315 * SyncRequest.Builder builder = (new SyncRequest.Builder()).syncOnce(0, 0); 316 * That is, a sync 0 seconds from now with 0 seconds of flex. 317 * 318 * Perform a sync in exactly 5 minutes. 319 * SyncRequest.Builder builder = 320 * new SyncRequest.Builder().syncOnce(5 * MIN_IN_SECS, 0); 321 * 322 * Perform a sync in 5 minutes, with one minute of leeway (between 4 and 5 minutes from 323 * now). 324 * SyncRequest.Builder builder = 325 * new SyncRequest.Builder().syncOnce(5 * MIN_IN_SECS, 1 * MIN_IN_SECS); 326 * </pre> 327 */ 328 public Builder syncOnce(long whenSeconds, long beforeSeconds) { 329 if (mSyncType != SYNC_TYPE_UNKNOWN) { 330 throw new IllegalArgumentException("Sync type has already been defined."); 331 } 332 mSyncType = SYNC_TYPE_ONCE; 333 setupInterval(whenSeconds, beforeSeconds); 334 return this; 335 } 336 337 /** 338 * Build a periodic sync. Either this or syncOnce() <b>must</b> be called for this builder. 339 * Syncs are identified by target {@link SyncService}/{@link android.provider} and by the 340 * contents of the extras bundle. 341 * You cannot reuse the same builder for one-time syncs after having specified a periodic 342 * sync (by calling this function). If you do, an <code>IllegalArgumentException</code> 343 * will be thrown. 344 * 345 * Example usage. 346 * 347 * <pre> 348 * Request a periodic sync every 5 hours with 20 minutes of flex. 349 * SyncRequest.Builder builder = 350 * (new SyncRequest.Builder()).syncPeriodic(5 * HOUR_IN_SECS, 20 * MIN_IN_SECS); 351 * 352 * Schedule a periodic sync every hour at any point in time during that hour. 353 * SyncRequest.Builder builder = 354 * (new SyncRequest.Builder()).syncPeriodic(1 * HOUR_IN_SECS, 1 * HOUR_IN_SECS); 355 * </pre> 356 * 357 * N.B.: Periodic syncs are not allowed to have any of 358 * {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY}, 359 * {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}, 360 * {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS}, 361 * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE}, 362 * {@link ContentResolver#SYNC_EXTRAS_FORCE}, 363 * {@link ContentResolver#SYNC_EXTRAS_EXPEDITED}, 364 * {@link ContentResolver#SYNC_EXTRAS_MANUAL} 365 * set to true. If any are supplied then an <code>IllegalArgumentException</code> will 366 * be thrown. 367 * 368 * @param pollFrequency the amount of time in seconds that you wish 369 * to elapse between periodic syncs. 370 * @param beforeSeconds the amount of flex time in seconds before 371 * {@code pollFrequency} that you permit for the sync to take 372 * place. Must be less than {@code pollFrequency}. 373 */ 374 public Builder syncPeriodic(long pollFrequency, long beforeSeconds) { 375 if (mSyncType != SYNC_TYPE_UNKNOWN) { 376 throw new IllegalArgumentException("Sync type has already been defined."); 377 } 378 mSyncType = SYNC_TYPE_PERIODIC; 379 setupInterval(pollFrequency, beforeSeconds); 380 return this; 381 } 382 383 /** {@hide} */ 384 private void setupInterval(long at, long before) { 385 if (before > at) { 386 throw new IllegalArgumentException("Specified run time for the sync must be" + 387 " after the specified flex time."); 388 } 389 mSyncRunTimeSecs = at; 390 mSyncFlexTimeSecs = before; 391 } 392 393 /** 394 * Developer can provide insight into their payload size; optional. -1 specifies unknown, 395 * so that you are not restricted to defining both fields. 396 * 397 * @param rxBytes Bytes expected to be downloaded. 398 * @param txBytes Bytes expected to be uploaded. 399 */ 400 public Builder setTransferSize(long rxBytes, long txBytes) { 401 mRxBytes = rxBytes; 402 mTxBytes = txBytes; 403 return this; 404 } 405 406 /** 407 * @param allow false to allow this transfer on metered networks. Default true. 408 */ 409 public Builder setAllowMetered(boolean allow) { 410 mAllowMetered = true; 411 return this; 412 } 413 414 /** 415 * Specify an authority and account for this transfer. Cannot be used with 416 * {@link #setSyncAdapter(ComponentName cname)}. 417 * 418 * @param authority 419 * @param account Account to sync. Can be null unless this is a periodic 420 * sync, for which verification by the ContentResolver will 421 * fail. If a sync is performed without an account, the 422 */ 423 public Builder setSyncAdapter(Account account, String authority) { 424 if (mSyncTarget != SYNC_TARGET_UNKNOWN) { 425 throw new IllegalArgumentException("Sync target has already been defined."); 426 } 427 mSyncTarget = SYNC_TARGET_ADAPTER; 428 mAccount = account; 429 mAuthority = authority; 430 mComponentName = null; 431 return this; 432 } 433 434 /** 435 * Specify the {@link SyncService} component for this sync. This is not validated until 436 * sync time so providing an incorrect component name here will not fail. Cannot be used 437 * with {@link #setSyncAdapter(Account account, String authority)}. 438 * 439 * @param cname ComponentName to identify your Anonymous service 440 */ 441 public Builder setSyncAdapter(ComponentName cname) { 442 if (mSyncTarget != SYNC_TARGET_UNKNOWN) { 443 throw new IllegalArgumentException("Sync target has already been defined."); 444 } 445 mSyncTarget = SYNC_TARGET_SERVICE; 446 mComponentName = cname; 447 mAccount = null; 448 mAuthority = null; 449 return this; 450 } 451 452 /** 453 * Developer-provided extras handed back when sync actually occurs. This bundle is copied 454 * into the SyncRequest returned by {@link #build()}. 455 * 456 * Example: 457 * <pre> 458 * String[] syncItems = {"dog", "cat", "frog", "child"}; 459 * SyncRequest.Builder builder = 460 * new SyncRequest.Builder() 461 * .setSyncAdapter(dummyAccount, dummyProvider) 462 * .syncOnce(5 * MINUTES_IN_SECS); 463 * 464 * for (String syncData : syncItems) { 465 * Bundle extras = new Bundle(); 466 * extras.setString("data", syncData); 467 * builder.setExtras(extras); 468 * ContentResolver.sync(builder.build()); // Each sync() request creates a unique sync. 469 * } 470 * </pre> 471 * Only values of the following types may be used in the extras bundle: 472 * <ul> 473 * <li>Integer</li> 474 * <li>Long</li> 475 * <li>Boolean</li> 476 * <li>Float</li> 477 * <li>Double</li> 478 * <li>String</li> 479 * <li>Account</li> 480 * <li>null</li> 481 * </ul> 482 * If any data is present in the bundle not of this type, build() will 483 * throw a runtime exception. 484 * 485 * @param bundle extras bundle to set. 486 */ 487 public Builder setExtras(Bundle bundle) { 488 mCustomExtras = bundle; 489 return this; 490 } 491 492 /** 493 * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY}. 494 * 495 * A one-off sync operation that fails will be retried with exponential back-off unless 496 * this is set to false. Not valid for periodic sync and will throw an 497 * <code>IllegalArgumentException</code> in build(). 498 * 499 * @param noRetry true to not retry a failed sync. Default false. 500 */ 501 public Builder setNoRetry(boolean noRetry) { 502 mNoRetry = noRetry; 503 return this; 504 } 505 506 /** 507 * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS}. 508 * 509 * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in 510 * {@link #build()}. 511 * 512 * @param ignoreSettings true to ignore the sync automatically settings. Default false. 513 */ 514 public Builder setIgnoreSettings(boolean ignoreSettings) { 515 mIgnoreSettings = ignoreSettings; 516 return this; 517 } 518 519 /** 520 * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}. 521 * 522 * Ignoring back-off will force the sync scheduling process to ignore any back-off that was 523 * the result of a failed sync, as well as to invalidate any {@link SyncResult#delayUntil} 524 * value that may have been set by the adapter. Successive failures will not honor this 525 * flag. Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> 526 * in {@link #build()}. 527 * 528 * @param ignoreBackoff ignore back off settings. Default false. 529 */ 530 public Builder setIgnoreBackoff(boolean ignoreBackoff) { 531 mIgnoreBackoff = ignoreBackoff; 532 return this; 533 } 534 535 /** 536 * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_MANUAL}. 537 * 538 * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in 539 * {@link #build()}. 540 * 541 * @param isManual User-initiated sync or not. Default false. 542 */ 543 public Builder setManual(boolean isManual) { 544 mIsManual = isManual; 545 return this; 546 } 547 548 /** 549 * An expedited sync runs immediately and can preempt other non-expedited running syncs. 550 * 551 * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in 552 * {@link #build()}. 553 * 554 * @param expedited whether to run expedited. Default false. 555 */ 556 public Builder setExpedited(boolean expedited) { 557 mExpedited = expedited; 558 return this; 559 } 560 561 /** 562 * @param priority the priority of this request among all requests from the calling app. 563 * Range of [-2,2] similar to how this is done with notifications. 564 */ 565 public Builder setPriority(int priority) { 566 if (priority < -2 || priority > 2) { 567 throw new IllegalArgumentException("Priority must be within range [-2, 2]"); 568 } 569 mPriority = priority; 570 return this; 571 } 572 573 /** 574 * Performs validation over the request and throws the runtime exception 575 * <code>IllegalArgumentException</code> if this validation fails. 576 * 577 * @return a SyncRequest with the information contained within this 578 * builder. 579 */ 580 public SyncRequest build() { 581 // Validate the extras bundle 582 try { 583 ContentResolver.validateSyncExtrasBundle(mCustomExtras); 584 } catch (IllegalArgumentException e) { 585 throw new IllegalArgumentException(e.getMessage()); 586 } 587 if (mCustomExtras == null) { 588 mCustomExtras = new Bundle(); 589 } 590 // Combine the builder extra flags into the copy of the bundle. 591 if (mIgnoreBackoff) { 592 mCustomExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true); 593 } 594 if (mAllowMetered) { 595 mCustomExtras.putBoolean(ContentResolver.SYNC_EXTRAS_ALLOW_METERED, true); 596 } 597 if (mIgnoreSettings) { 598 mCustomExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true); 599 } 600 if (mNoRetry) { 601 mCustomExtras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true); 602 } 603 if (mExpedited) { 604 mCustomExtras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); 605 } 606 if (mIsManual) { 607 mCustomExtras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); 608 } 609 // Upload/download expectations. 610 mCustomExtras.putLong(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD, mTxBytes); 611 mCustomExtras.putLong(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD, mRxBytes); 612 // Priority. 613 mCustomExtras.putInt(ContentResolver.SYNC_EXTRAS_PRIORITY, mPriority); 614 if (mSyncType == SYNC_TYPE_PERIODIC) { 615 // If this is a periodic sync ensure than invalid extras were 616 // not set. 617 if (mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false) 618 || mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false) 619 || mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false) 620 || mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false) 621 || mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false) 622 || mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false) 623 || mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) { 624 throw new IllegalArgumentException("Illegal extras were set"); 625 } 626 } else if (mSyncType == SYNC_TYPE_UNKNOWN) { 627 throw new IllegalArgumentException("Must call either syncOnce() or syncPeriodic()"); 628 } 629 // Ensure that a target for the sync has been set. 630 if (mSyncTarget == SYNC_TARGET_UNKNOWN) { 631 throw new IllegalArgumentException("Must specify an adapter with one of" 632 + "setSyncAdapter(ComponentName) or setSyncAdapter(Account, String"); 633 } 634 return new SyncRequest(this); 635 } 636 } 637} 638