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