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