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.compat.annotation.UnsupportedAppUsage; 21 import android.os.Build; 22 import android.os.Bundle; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 26 /** 27 * Convenience class to construct sync requests. See {@link android.content.SyncRequest.Builder} 28 * for an explanation of the various functions. The resulting object is passed through to the 29 * framework via {@link android.content.ContentResolver#requestSync(SyncRequest)}. 30 */ 31 public class SyncRequest implements Parcelable { 32 private static final String TAG = "SyncRequest"; 33 /** Account to pass to the sync adapter. Can be null. */ 34 @UnsupportedAppUsage 35 private final Account mAccountToSync; 36 /** Authority string that corresponds to a ContentProvider. */ 37 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 38 private final String mAuthority; 39 /** Bundle containing user info as well as sync settings. */ 40 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 41 private final Bundle mExtras; 42 /** Don't allow this sync request on metered networks. */ 43 private final boolean mDisallowMetered; 44 /** 45 * Amount of time before {@link #mSyncRunTimeSecs} from which the sync may optionally be 46 * started. 47 */ 48 private final long mSyncFlexTimeSecs; 49 /** 50 * Specifies a point in the future at which the sync must have been scheduled to run. 51 */ 52 @UnsupportedAppUsage 53 private final long mSyncRunTimeSecs; 54 /** Periodic versus one-off. */ 55 @UnsupportedAppUsage 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 */ isPeriodic()68 public boolean isPeriodic() { 69 return mIsPeriodic; 70 } 71 72 /** 73 * {@hide} 74 * @return whether this sync is expedited. 75 */ isExpedited()76 public boolean isExpedited() { 77 return mIsExpedited; 78 } 79 80 /** 81 * {@hide} 82 * 83 * @return account object for this sync. 84 * @throws IllegalArgumentException if this function is called for a request that targets a 85 * sync service. 86 */ getAccount()87 public Account getAccount() { 88 return mAccountToSync; 89 } 90 91 /** 92 * {@hide} 93 * 94 * @return provider for this sync. 95 * @throws IllegalArgumentException if this function is called for a request that targets a 96 * sync service. 97 */ getProvider()98 public String getProvider() { 99 return mAuthority; 100 } 101 102 /** 103 * {@hide} 104 * Retrieve bundle for this SyncRequest. Will not be null. 105 */ getBundle()106 public Bundle getBundle() { 107 return mExtras; 108 } 109 110 /** 111 * {@hide} 112 * @return the earliest point in time that this sync can be scheduled. 113 */ getSyncFlexTime()114 public long getSyncFlexTime() { 115 return mSyncFlexTimeSecs; 116 } 117 /** 118 * {@hide} 119 * @return the last point in time at which this sync must scheduled. 120 */ getSyncRunTime()121 public long getSyncRunTime() { 122 return mSyncRunTimeSecs; 123 } 124 125 public static final @android.annotation.NonNull Creator<SyncRequest> CREATOR = new Creator<SyncRequest>() { 126 127 @Override 128 public SyncRequest createFromParcel(Parcel in) { 129 return new SyncRequest(in); 130 } 131 132 @Override 133 public SyncRequest[] newArray(int size) { 134 return new SyncRequest[size]; 135 } 136 }; 137 138 @Override describeContents()139 public int describeContents() { 140 return 0; 141 } 142 143 @Override writeToParcel(Parcel parcel, int flags)144 public void writeToParcel(Parcel parcel, int flags) { 145 parcel.writeBundle(mExtras); 146 parcel.writeLong(mSyncFlexTimeSecs); 147 parcel.writeLong(mSyncRunTimeSecs); 148 parcel.writeInt((mIsPeriodic ? 1 : 0)); 149 parcel.writeInt((mDisallowMetered ? 1 : 0)); 150 parcel.writeInt((mIsAuthority ? 1 : 0)); 151 parcel.writeInt((mIsExpedited? 1 : 0)); 152 parcel.writeParcelable(mAccountToSync, flags); 153 parcel.writeString(mAuthority); 154 } 155 SyncRequest(Parcel in)156 private SyncRequest(Parcel in) { 157 mExtras = Bundle.setDefusable(in.readBundle(), true); 158 mSyncFlexTimeSecs = in.readLong(); 159 mSyncRunTimeSecs = in.readLong(); 160 mIsPeriodic = (in.readInt() != 0); 161 mDisallowMetered = (in.readInt() != 0); 162 mIsAuthority = (in.readInt() != 0); 163 mIsExpedited = (in.readInt() != 0); 164 mAccountToSync = in.readParcelable(null); 165 mAuthority = in.readString(); 166 } 167 168 /** {@hide} Protected ctor to instantiate anonymous SyncRequest. */ SyncRequest(SyncRequest.Builder b)169 protected SyncRequest(SyncRequest.Builder b) { 170 mSyncFlexTimeSecs = b.mSyncFlexTimeSecs; 171 mSyncRunTimeSecs = b.mSyncRunTimeSecs; 172 mAccountToSync = b.mAccount; 173 mAuthority = b.mAuthority; 174 mIsPeriodic = (b.mSyncType == Builder.SYNC_TYPE_PERIODIC); 175 mIsAuthority = (b.mSyncTarget == Builder.SYNC_TARGET_ADAPTER); 176 mIsExpedited = b.mExpedited; 177 mExtras = new Bundle(b.mCustomExtras); 178 // For now we merge the sync config extras & the custom extras into one bundle. 179 // TODO: pass the configuration extras through separately. 180 mExtras.putAll(b.mSyncConfigExtras); 181 mDisallowMetered = b.mDisallowMetered; 182 } 183 184 /** 185 * Builder class for a {@link SyncRequest}. As you build your SyncRequest this class will also 186 * perform validation. 187 */ 188 public static class Builder { 189 /** Unknown sync type. */ 190 private static final int SYNC_TYPE_UNKNOWN = 0; 191 /** Specify that this is a periodic sync. */ 192 private static final int SYNC_TYPE_PERIODIC = 1; 193 /** Specify that this is a one-time sync. */ 194 private static final int SYNC_TYPE_ONCE = 2; 195 /** Unknown sync target. */ 196 private static final int SYNC_TARGET_UNKNOWN = 0; 197 /** Specify that this is a sync with a provider. */ 198 private static final int SYNC_TARGET_ADAPTER = 2; 199 /** 200 * Earliest point of displacement into the future at which this sync can 201 * occur. 202 */ 203 private long mSyncFlexTimeSecs; 204 /** Displacement into the future at which this sync must occur. */ 205 private long mSyncRunTimeSecs; 206 /** 207 * Sync configuration information - custom user data explicitly provided by the developer. 208 * This data is handed over to the sync operation. 209 */ 210 private Bundle mCustomExtras; 211 /** 212 * Sync system configuration - used to store system sync configuration. Corresponds to 213 * ContentResolver.SYNC_EXTRAS_* flags. 214 * TODO: Use this instead of dumping into one bundle. Need to decide if these flags should 215 * discriminate between equivalent syncs. 216 */ 217 private Bundle mSyncConfigExtras; 218 /** Whether or not this sync can occur on metered networks. Default false. */ 219 private boolean mDisallowMetered; 220 /** 221 * Whether this builder is building a periodic sync, or a one-time sync. 222 */ 223 private int mSyncType = SYNC_TYPE_UNKNOWN; 224 /** Whether this will go to a sync adapter. */ 225 private int mSyncTarget = SYNC_TARGET_UNKNOWN; 226 /** Whether this is a user-activated sync. */ 227 private boolean mIsManual; 228 /** 229 * Whether to retry this one-time sync if the sync fails. Not valid for 230 * periodic syncs. See {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY}. 231 */ 232 private boolean mNoRetry; 233 /** 234 * Whether to respect back-off for this one-time sync. Not valid for 235 * periodic syncs. See 236 * {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}; 237 */ 238 private boolean mIgnoreBackoff; 239 240 /** Ignore sync system settings and perform sync anyway. */ 241 private boolean mIgnoreSettings; 242 243 /** This sync will run in preference to other non-expedited syncs. */ 244 private boolean mExpedited; 245 246 /** 247 * The Account object that together with an Authority name define the SyncAdapter (if 248 * this sync is bound to a provider), otherwise null. 249 */ 250 private Account mAccount; 251 /** 252 * The Authority name that together with an Account define the SyncAdapter (if 253 * this sync is bound to a provider), otherwise null. 254 */ 255 private String mAuthority; 256 /** 257 * Whether the sync requires the phone to be plugged in. 258 */ 259 private boolean mRequiresCharging; 260 Builder()261 public Builder() { 262 } 263 264 /** 265 * Request that a sync occur immediately. 266 * 267 * Example 268 * <pre> 269 * SyncRequest.Builder builder = (new SyncRequest.Builder()).syncOnce(); 270 * </pre> 271 */ syncOnce()272 public Builder syncOnce() { 273 if (mSyncType != SYNC_TYPE_UNKNOWN) { 274 throw new IllegalArgumentException("Sync type has already been defined."); 275 } 276 mSyncType = SYNC_TYPE_ONCE; 277 setupInterval(0, 0); 278 return this; 279 } 280 281 /** 282 * Build a periodic sync. Either this or syncOnce() <b>must</b> be called for this builder. 283 * Syncs are identified by target {@link android.provider} and by the 284 * contents of the extras bundle. 285 * You cannot reuse the same builder for one-time syncs after having specified a periodic 286 * sync (by calling this function). If you do, an <code>IllegalArgumentException</code> 287 * will be thrown. 288 * <p>The bundle for a periodic sync can be queried by applications with the correct 289 * permissions using 290 * {@link ContentResolver#getPeriodicSyncs(Account account, String provider)}, so no 291 * sensitive data should be transferred here. 292 * 293 * Example usage. 294 * 295 * <pre> 296 * Request a periodic sync every 5 hours with 20 minutes of flex. 297 * SyncRequest.Builder builder = 298 * (new SyncRequest.Builder()).syncPeriodic(5 * HOUR_IN_SECS, 20 * MIN_IN_SECS); 299 * 300 * Schedule a periodic sync every hour at any point in time during that hour. 301 * SyncRequest.Builder builder = 302 * (new SyncRequest.Builder()).syncPeriodic(1 * HOUR_IN_SECS, 1 * HOUR_IN_SECS); 303 * </pre> 304 * 305 * N.B.: Periodic syncs are not allowed to have any of 306 * {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY}, 307 * {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}, 308 * {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS}, 309 * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE}, 310 * {@link ContentResolver#SYNC_EXTRAS_FORCE}, 311 * {@link ContentResolver#SYNC_EXTRAS_EXPEDITED}, 312 * {@link ContentResolver#SYNC_EXTRAS_MANUAL} 313 * set to true. If any are supplied then an <code>IllegalArgumentException</code> will 314 * be thrown. 315 * 316 * @param pollFrequency the amount of time in seconds that you wish 317 * to elapse between periodic syncs. A minimum period of 1 hour is enforced. 318 * @param beforeSeconds the amount of flex time in seconds before 319 * {@code pollFrequency} that you permit for the sync to take 320 * place. Must be less than {@code pollFrequency} and greater than 321 * MAX(5% of {@code pollFrequency}, 5 minutes) 322 */ syncPeriodic(long pollFrequency, long beforeSeconds)323 public Builder syncPeriodic(long pollFrequency, long beforeSeconds) { 324 if (mSyncType != SYNC_TYPE_UNKNOWN) { 325 throw new IllegalArgumentException("Sync type has already been defined."); 326 } 327 mSyncType = SYNC_TYPE_PERIODIC; 328 setupInterval(pollFrequency, beforeSeconds); 329 return this; 330 } 331 setupInterval(long at, long before)332 private void setupInterval(long at, long before) { 333 if (before > at) { 334 throw new IllegalArgumentException("Specified run time for the sync must be" + 335 " after the specified flex time."); 336 } 337 mSyncRunTimeSecs = at; 338 mSyncFlexTimeSecs = before; 339 } 340 341 /** 342 * Will throw an <code>IllegalArgumentException</code> if called and 343 * {@link #setIgnoreSettings(boolean ignoreSettings)} has already been called. 344 * @param disallow true to allow this transfer on metered networks. Default false. 345 * 346 */ setDisallowMetered(boolean disallow)347 public Builder setDisallowMetered(boolean disallow) { 348 if (mIgnoreSettings && disallow) { 349 throw new IllegalArgumentException("setDisallowMetered(true) after having" 350 + " specified that settings are ignored."); 351 } 352 mDisallowMetered = disallow; 353 return this; 354 } 355 356 /** 357 * Specify whether the sync requires the phone to be plugged in. 358 * @param requiresCharging true if sync requires the phone to be plugged in. Default false. 359 */ setRequiresCharging(boolean requiresCharging)360 public Builder setRequiresCharging(boolean requiresCharging) { 361 mRequiresCharging = requiresCharging; 362 return this; 363 } 364 365 /** 366 * Specify an authority and account for this transfer. 367 * 368 * @param authority A String identifying the content provider to be synced. 369 * @param account Account to sync. Can be null unless this is a periodic 370 * sync, for which verification by the ContentResolver will 371 * fail. If a sync is performed without an account, the 372 */ setSyncAdapter(Account account, String authority)373 public Builder setSyncAdapter(Account account, String authority) { 374 if (mSyncTarget != SYNC_TARGET_UNKNOWN) { 375 throw new IllegalArgumentException("Sync target has already been defined."); 376 } 377 if (authority != null && authority.length() == 0) { 378 throw new IllegalArgumentException("Authority must be non-empty"); 379 } 380 mSyncTarget = SYNC_TARGET_ADAPTER; 381 mAccount = account; 382 mAuthority = authority; 383 return this; 384 } 385 386 /** 387 * Developer-provided extras handed back when sync actually occurs. This bundle is copied 388 * into the SyncRequest returned by {@link #build()}. 389 * 390 * Example: 391 * <pre> 392 * String[] syncItems = {"dog", "cat", "frog", "child"}; 393 * SyncRequest.Builder builder = 394 * new SyncRequest.Builder() 395 * .setSyncAdapter(dummyAccount, dummyProvider) 396 * .syncOnce(); 397 * 398 * for (String syncData : syncItems) { 399 * Bundle extras = new Bundle(); 400 * extras.setString("data", syncData); 401 * builder.setExtras(extras); 402 * ContentResolver.sync(builder.build()); // Each sync() request creates a unique sync. 403 * } 404 * </pre> 405 * Only values of the following types may be used in the extras bundle: 406 * <ul> 407 * <li>Integer</li> 408 * <li>Long</li> 409 * <li>Boolean</li> 410 * <li>Float</li> 411 * <li>Double</li> 412 * <li>String</li> 413 * <li>Account</li> 414 * <li>null</li> 415 * </ul> 416 * If any data is present in the bundle not of this type, build() will 417 * throw a runtime exception. 418 * 419 * @param bundle extras bundle to set. 420 */ setExtras(Bundle bundle)421 public Builder setExtras(Bundle bundle) { 422 mCustomExtras = bundle; 423 return this; 424 } 425 426 /** 427 * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY}. 428 * 429 * A one-off sync operation that fails will be retried with exponential back-off unless 430 * this is set to false. Not valid for periodic sync and will throw an 431 * <code>IllegalArgumentException</code> in build(). 432 * 433 * @param noRetry true to not retry a failed sync. Default false. 434 */ setNoRetry(boolean noRetry)435 public Builder setNoRetry(boolean noRetry) { 436 mNoRetry = noRetry; 437 return this; 438 } 439 440 /** 441 * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS}. 442 * 443 * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in 444 * {@link #build()}. 445 * <p>Throws <code>IllegalArgumentException</code> if called and 446 * {@link #setDisallowMetered(boolean)} has been set. 447 * 448 * 449 * @param ignoreSettings true to ignore the sync automatically settings. Default false. 450 */ setIgnoreSettings(boolean ignoreSettings)451 public Builder setIgnoreSettings(boolean ignoreSettings) { 452 if (mDisallowMetered && ignoreSettings) { 453 throw new IllegalArgumentException("setIgnoreSettings(true) after having specified" 454 + " sync settings with this builder."); 455 } 456 mIgnoreSettings = ignoreSettings; 457 return this; 458 } 459 460 /** 461 * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}. 462 * 463 * Ignoring back-off will force the sync scheduling process to ignore any back-off that was 464 * the result of a failed sync, as well as to invalidate any {@link SyncResult#delayUntil} 465 * value that may have been set by the adapter. Successive failures will not honor this 466 * flag. Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> 467 * in {@link #build()}. 468 * 469 * @param ignoreBackoff ignore back off settings. Default false. 470 */ setIgnoreBackoff(boolean ignoreBackoff)471 public Builder setIgnoreBackoff(boolean ignoreBackoff) { 472 mIgnoreBackoff = ignoreBackoff; 473 return this; 474 } 475 476 /** 477 * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_MANUAL}. 478 * 479 * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in 480 * {@link #build()}. 481 * 482 * @param isManual User-initiated sync or not. Default false. 483 */ setManual(boolean isManual)484 public Builder setManual(boolean isManual) { 485 mIsManual = isManual; 486 return this; 487 } 488 489 /** 490 * An expedited sync runs immediately and can preempt other non-expedited running syncs. 491 * 492 * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in 493 * {@link #build()}. 494 * 495 * @param expedited whether to run expedited. Default false. 496 */ setExpedited(boolean expedited)497 public Builder setExpedited(boolean expedited) { 498 mExpedited = expedited; 499 return this; 500 } 501 502 /** 503 * Performs validation over the request and throws the runtime exception 504 * <code>IllegalArgumentException</code> if this validation fails. 505 * 506 * @return a SyncRequest with the information contained within this 507 * builder. 508 */ build()509 public SyncRequest build() { 510 // Validate the extras bundle 511 ContentResolver.validateSyncExtrasBundle(mCustomExtras); 512 if (mCustomExtras == null) { 513 mCustomExtras = new Bundle(); 514 } 515 // Combine builder extra flags into the config bundle. 516 mSyncConfigExtras = new Bundle(); 517 if (mIgnoreBackoff) { 518 mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true); 519 } 520 if (mDisallowMetered) { 521 mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, true); 522 } 523 if (mRequiresCharging) { 524 mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_REQUIRE_CHARGING, true); 525 } 526 if (mIgnoreSettings) { 527 mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true); 528 } 529 if (mNoRetry) { 530 mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true); 531 } 532 if (mExpedited) { 533 mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); 534 } 535 if (mIsManual) { 536 mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true); 537 mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true); 538 } 539 if (mSyncType == SYNC_TYPE_PERIODIC) { 540 // If this is a periodic sync ensure than invalid extras were not set. 541 if (ContentResolver.invalidPeriodicExtras(mCustomExtras) || 542 ContentResolver.invalidPeriodicExtras(mSyncConfigExtras)) { 543 throw new IllegalArgumentException("Illegal extras were set"); 544 } 545 } 546 // Ensure that a target for the sync has been set. 547 if (mSyncTarget == SYNC_TARGET_UNKNOWN) { 548 throw new IllegalArgumentException("Must specify an adapter with" + 549 " setSyncAdapter(Account, String"); 550 } 551 return new SyncRequest(this); 552 } 553 } 554 } 555