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