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;
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 */
29public 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     */
61    public boolean isPeriodic() {
62        return mIsPeriodic;
63    }
64
65    /**
66     * {@hide}
67     * @return whether this sync is expedited.
68     */
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     */
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     */
91    public String getProvider() {
92        return mAuthority;
93    }
94
95    /**
96     * {@hide}
97     * Retrieve bundle for this SyncRequest. Will not be null.
98     */
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     */
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     */
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
132    public int describeContents() {
133        return 0;
134    }
135
136    @Override
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
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. */
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
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         */
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         */
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
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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