• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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