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