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