SyncRequest.java revision 632515b9d0960749ddb1636677d7f12f196d73f7
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
24public class SyncRequest implements Parcelable {
25    private static final String TAG = "SyncRequest";
26    /** Account to pass to the sync adapter. May be null. */
27    private final Account mAccountToSync;
28    /** Authority string that corresponds to a ContentProvider. */
29    private final String mAuthority;
30    /** Sync service identifier. May be null.*/
31    private final ComponentName mComponentInfo;
32    /** Bundle containing user info as well as sync settings. */
33    private final Bundle mExtras;
34    /** Disallow this sync request on metered networks. */
35    private final boolean mDisallowMetered;
36    /**
37     * Anticipated upload size in bytes.
38     * TODO: Not yet used - we put this information into the bundle for simplicity.
39     */
40    private final long mTxBytes;
41    /**
42     * Anticipated download size in bytes.
43     * TODO: Not yet used - we put this information into the bundle.
44     */
45    private final long mRxBytes;
46    /**
47     * Amount of time before {@link #mSyncRunTimeSecs} from which the sync may optionally be
48     * started.
49     */
50    private final long mSyncFlexTimeSecs;
51    /**
52     * Specifies a point in the future at which the sync must have been scheduled to run.
53     */
54    private final long mSyncRunTimeSecs;
55    /** Periodic versus one-off. */
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     */
68    public boolean isPeriodic() {
69        return mIsPeriodic;
70    }
71
72    /**
73     * {@hide}
74     * @return whether this is an expedited sync.
75     */
76    public boolean isExpedited() {
77        return mIsExpedited;
78    }
79
80    /**
81     * {@hide}
82     * @return true if this sync uses an account/authority pair, or false if this sync is bound to
83     * a Sync Service.
84     */
85    public boolean hasAuthority() {
86        return mIsAuthority;
87    }
88
89    /**
90     * {@hide}
91     * @return account object for this sync.
92     * @throws IllegalArgumentException if this function is called for a request that does not
93     * specify an account/provider authority.
94     */
95    public Account getAccount() {
96        if (!hasAuthority()) {
97            throw new IllegalArgumentException("Cannot getAccount() for a sync that does not"
98                    + "specify an authority.");
99        }
100        return mAccountToSync;
101    }
102
103    /**
104     * {@hide}
105     * @return provider for this sync.
106     * @throws IllegalArgumentException if this function is called for a request that does not
107     * specify an account/provider authority.
108     */
109    public String getProvider() {
110        if (!hasAuthority()) {
111            throw new IllegalArgumentException("Cannot getProvider() for a sync that does not"
112                    + "specify a provider.");
113        }
114        return mAuthority;
115    }
116
117    /**
118     * {@hide}
119     * Retrieve bundle for this SyncRequest. Will not be null.
120     */
121    public Bundle getBundle() {
122        return mExtras;
123    }
124
125    /**
126     * {@hide}
127     * @return the earliest point in time that this sync can be scheduled.
128     */
129    public long getSyncFlexTime() {
130        return mSyncFlexTimeSecs;
131    }
132
133    /**
134     * {@hide}
135     * @return the last point in time at which this sync must scheduled.
136     */
137    public long getSyncRunTime() {
138        return mSyncRunTimeSecs;
139    }
140
141    public static final Creator<SyncRequest> CREATOR = new Creator<SyncRequest>() {
142
143        @Override
144        public SyncRequest createFromParcel(Parcel in) {
145            return new SyncRequest(in);
146        }
147
148        @Override
149        public SyncRequest[] newArray(int size) {
150            return new SyncRequest[size];
151        }
152    };
153
154    @Override
155    public int describeContents() {
156        return 0;
157    }
158
159    @Override
160    public void writeToParcel(Parcel parcel, int flags) {
161        parcel.writeBundle(mExtras);
162        parcel.writeLong(mSyncFlexTimeSecs);
163        parcel.writeLong(mSyncRunTimeSecs);
164        parcel.writeInt((mIsPeriodic ? 1 : 0));
165        parcel.writeInt((mDisallowMetered ? 1 : 0));
166        parcel.writeLong(mTxBytes);
167        parcel.writeLong(mRxBytes);
168        parcel.writeInt((mIsAuthority ? 1 : 0));
169        parcel.writeInt((mIsExpedited? 1 : 0));
170        if (mIsAuthority) {
171            parcel.writeParcelable(mAccountToSync, flags);
172            parcel.writeString(mAuthority);
173        } else {
174            parcel.writeParcelable(mComponentInfo, flags);
175        }
176    }
177
178    private SyncRequest(Parcel in) {
179        mExtras = in.readBundle();
180        mSyncFlexTimeSecs = in.readLong();
181        mSyncRunTimeSecs = in.readLong();
182        mIsPeriodic = (in.readInt() != 0);
183        mDisallowMetered = (in.readInt() != 0);
184        mTxBytes = in.readLong();
185        mRxBytes = in.readLong();
186        mIsAuthority = (in.readInt() != 0);
187        mIsExpedited = (in.readInt() != 0);
188        if (mIsAuthority) {
189            mComponentInfo = null;
190            mAccountToSync = in.readParcelable(null);
191            mAuthority = in.readString();
192        } else {
193            mComponentInfo = in.readParcelable(null);
194            mAccountToSync = null;
195            mAuthority = null;
196        }
197    }
198
199    /** {@hide} Protected ctor to instantiate anonymous SyncRequest. */
200    protected SyncRequest(SyncRequest.Builder b) {
201        mSyncFlexTimeSecs = b.mSyncFlexTimeSecs;
202        mSyncRunTimeSecs = b.mSyncRunTimeSecs;
203        mAccountToSync = b.mAccount;
204        mAuthority = b.mAuthority;
205        mComponentInfo = b.mComponentName;
206        mIsPeriodic = (b.mSyncType == Builder.SYNC_TYPE_PERIODIC);
207        mIsAuthority = (b.mSyncTarget == Builder.SYNC_TARGET_ADAPTER);
208        mIsExpedited = b.mExpedited;
209        mExtras = new Bundle(b.mCustomExtras);
210        // For now we merge the sync config extras & the custom extras into one bundle.
211        // TODO: pass the configuration extras through separately.
212        mExtras.putAll(b.mSyncConfigExtras);
213        mDisallowMetered = b.mDisallowMetered;
214        mTxBytes = b.mTxBytes;
215        mRxBytes = b.mRxBytes;
216    }
217
218    /**
219     * Builder class for a {@link SyncRequest}. As you build your SyncRequest this class will also
220     * perform validation.
221     */
222    public static class Builder {
223        /** Unknown sync type. */
224        private static final int SYNC_TYPE_UNKNOWN = 0;
225        /** Specify that this is a periodic sync. */
226        private static final int SYNC_TYPE_PERIODIC = 1;
227        /** Specify that this is a one-time sync. */
228        private static final int SYNC_TYPE_ONCE = 2;
229        /** Unknown sync target. */
230        private static final int SYNC_TARGET_UNKNOWN = 0;
231        /** Specify that this is an anonymous sync. */
232        private static final int SYNC_TARGET_SERVICE = 1;
233        /** Specify that this is a sync with a provider. */
234        private static final int SYNC_TARGET_ADAPTER = 2;
235        /** Earliest point of displacement into the future at which this sync can occur. */
236        private long mSyncFlexTimeSecs;
237        /** Latest point of displacement into the future at which this sync must occur. */
238        private long mSyncRunTimeSecs;
239        /**
240         * Sync configuration information - custom user data explicitly provided by the developer.
241         * This data is handed over to the sync operation.
242         */
243        private Bundle mCustomExtras;
244        /**
245         * Sync system configuration -  used to store system sync configuration. Corresponds to
246         * ContentResolver.SYNC_EXTRAS_* flags.
247         * TODO: Use this instead of dumping into one bundle. Need to decide if these flags should
248         * discriminate between equivalent syncs.
249         */
250        private Bundle mSyncConfigExtras;
251        /** Expected upload transfer in bytes. */
252        private long mTxBytes = -1L;
253        /** Expected download transfer in bytes. */
254        private long mRxBytes = -1L;
255        /** Whether or not this sync can occur on metered networks. Default false. */
256        private boolean mDisallowMetered;
257        /** Priority of this sync relative to others from calling app [-2, 2]. Default 0. */
258        private int mPriority = 0;
259        /**
260         * Whether this builder is building a periodic sync, or a one-time sync.
261         */
262        private int mSyncType = SYNC_TYPE_UNKNOWN;
263        /** Whether this will go to a sync adapter or to a sync service. */
264        private int mSyncTarget = SYNC_TARGET_UNKNOWN;
265        /** Whether this is a user-activated sync. */
266        private boolean mIsManual;
267        /**
268         * Whether to retry this one-time sync if the sync fails. Not valid for
269         * periodic syncs. See {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY}.
270         */
271        private boolean mNoRetry;
272        /**
273         * Whether to respect back-off for this one-time sync. Not valid for
274         * periodic syncs. See
275         * {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF};
276         */
277        private boolean mIgnoreBackoff;
278
279        /** Ignore sync system settings and perform sync anyway. */
280        private boolean mIgnoreSettings;
281
282        /** This sync will run in preference to other non-expedited syncs. */
283        private boolean mExpedited;
284
285        /**
286         * The sync component that contains the sync logic if this is a provider-less sync,
287         * otherwise null.
288         */
289        private ComponentName mComponentName;
290        /**
291         * The Account object that together with an Authority name define the SyncAdapter (if
292         * this sync is bound to a provider), otherwise null.
293         */
294        private Account mAccount;
295        /**
296         * The Authority name that together with an Account define the SyncAdapter (if
297         * this sync is bound to a provider), otherwise null.
298         */
299        private String mAuthority;
300
301        public Builder() {
302        }
303
304        /**
305         * Request that a sync occur immediately.
306         *
307         * Example
308         * <pre>
309         *     SyncRequest.Builder builder = (new SyncRequest.Builder()).syncOnce();
310         * </pre>
311         */
312        public Builder syncOnce() {
313            if (mSyncType != SYNC_TYPE_UNKNOWN) {
314                throw new IllegalArgumentException("Sync type has already been defined.");
315            }
316            mSyncType = SYNC_TYPE_ONCE;
317            setupInterval(0, 0);
318            return this;
319        }
320
321        /**
322         * Build a periodic sync. Either this or syncOnce() <b>must</b> be called for this builder.
323         * Syncs are identified by target {@link android.provider}/{@link android.accounts.Account}
324         * and by the contents of the extras bundle.
325         * You cannot reuse the same builder for one-time syncs (by calling this function) after
326         * having specified a periodic sync. If you do, an <code>IllegalArgumentException</code>
327         * will be thrown.
328         *
329         * Example usage.
330         *
331         * <pre>
332         *     Request a periodic sync every 5 hours with 20 minutes of flex.
333         *     SyncRequest.Builder builder =
334         *         (new SyncRequest.Builder()).syncPeriodic(5 * HOUR_IN_SECS, 20 * MIN_IN_SECS);
335         *
336         *     Schedule a periodic sync every hour at any point in time during that hour.
337         *     SyncRequest.Builder builder =
338         *         (new SyncRequest.Builder()).syncPeriodic(1 * HOUR_IN_SECS, 1 * HOUR_IN_SECS);
339         * </pre>
340         *
341         * N.B.: Periodic syncs are not allowed to have any of
342         * {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY},
343         * {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF},
344         * {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS},
345         * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE},
346         * {@link ContentResolver#SYNC_EXTRAS_FORCE},
347         * {@link ContentResolver#SYNC_EXTRAS_EXPEDITED},
348         * {@link ContentResolver#SYNC_EXTRAS_MANUAL}
349         * set to true. If any are supplied then an <code>IllegalArgumentException</code> will
350         * be thrown.
351         *
352         * @param pollFrequency the amount of time in seconds that you wish
353         *            to elapse between periodic syncs.
354         * @param beforeSeconds the amount of flex time in seconds before
355         *            {@code pollFrequency} that you permit for the sync to take
356         *            place. Must be less than {@code pollFrequency}.
357         */
358        public Builder syncPeriodic(long pollFrequency, long beforeSeconds) {
359            if (mSyncType != SYNC_TYPE_UNKNOWN) {
360                throw new IllegalArgumentException("Sync type has already been defined.");
361            }
362            mSyncType = SYNC_TYPE_PERIODIC;
363            setupInterval(pollFrequency, beforeSeconds);
364            return this;
365        }
366
367        /** {@hide} */
368        private void setupInterval(long at, long before) {
369            if (before > at) {
370                throw new IllegalArgumentException("Specified run time for the sync must be" +
371                    " after the specified flex time.");
372            }
373            mSyncRunTimeSecs = at;
374            mSyncFlexTimeSecs = before;
375        }
376
377        /**
378         * {@hide}
379         * Developer can provide insight into their payload size; optional. -1 specifies unknown,
380         * so that you are not restricted to defining both fields.
381         *
382         * @param rxBytes Bytes expected to be downloaded.
383         * @param txBytes Bytes expected to be uploaded.
384         */
385        public Builder setTransferSize(long rxBytes, long txBytes) {
386            mRxBytes = rxBytes;
387            mTxBytes = txBytes;
388            return this;
389        }
390
391        /**
392         * @see android.net.ConnectivityManager#isActiveNetworkMetered()
393         * @param disallow true to enforce that this transfer not occur on metered networks.
394         *                 Default false.
395         */
396        public Builder setDisallowMetered(boolean disallow) {
397            mDisallowMetered = disallow;
398            return this;
399        }
400
401        /**
402         * Specify an authority and account for this transfer.
403         *
404         * @param authority String identifying which content provider to sync.
405         * @param account Account to sync. Can be null unless this is a periodic sync.
406         */
407        public Builder setSyncAdapter(Account account, String authority) {
408            if (mSyncTarget != SYNC_TARGET_UNKNOWN) {
409                throw new IllegalArgumentException("Sync target has already been defined.");
410            }
411            if (authority != null && authority.length() == 0) {
412                throw new IllegalArgumentException("Authority must be non-empty");
413            }
414            mSyncTarget = SYNC_TARGET_ADAPTER;
415            mAccount = account;
416            mAuthority = authority;
417            mComponentName = null;
418            return this;
419        }
420
421        /**
422         * Optional developer-provided extras handed back in
423         * {@link AbstractThreadedSyncAdapter#onPerformSync(Account, Bundle, String,
424         * ContentProviderClient, SyncResult)} occurs. This bundle is copied into the SyncRequest
425         * returned by {@link #build()}.
426         *
427         * Example:
428         * <pre>
429         *   String[] syncItems = {"dog", "cat", "frog", "child"};
430         *   SyncRequest.Builder builder =
431         *     new SyncRequest.Builder()
432         *       .setSyncAdapter(dummyAccount, dummyProvider)
433         *       .syncOnce(5 * MINUTES_IN_SECS);
434         *
435         *   for (String syncData : syncItems) {
436         *     Bundle extras = new Bundle();
437         *     extras.setString("data", syncData);
438         *     builder.setExtras(extras);
439         *     ContentResolver.sync(builder.build()); // Each sync() request is for a unique sync.
440         *   }
441         * </pre>
442         * Only values of the following types may be used in the extras bundle:
443         * <ul>
444         * <li>Integer</li>
445         * <li>Long</li>
446         * <li>Boolean</li>
447         * <li>Float</li>
448         * <li>Double</li>
449         * <li>String</li>
450         * <li>Account</li>
451         * <li>null</li>
452         * </ul>
453         * If any data is present in the bundle not of this type, build() will
454         * throw a runtime exception.
455         *
456         * @param bundle extras bundle to set.
457         */
458        public Builder setExtras(Bundle bundle) {
459            mCustomExtras = bundle;
460            return this;
461        }
462
463        /**
464         * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY}.
465         *
466         * A one-off sync operation that fails will be retried with exponential back-off unless
467         * this is set to false. Not valid for periodic sync and will throw an
468         * <code>IllegalArgumentException</code> in build().
469         *
470         * @param noRetry true to not retry a failed sync. Default false.
471         */
472        public Builder setNoRetry(boolean noRetry) {
473            mNoRetry = noRetry;
474            return this;
475        }
476
477        /**
478         * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS}.
479         *
480         * A sync can specify that system sync settings be ignored (user has turned sync off). Not
481         * valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
482         * {@link #build()}.
483         *
484         * @param ignoreSettings true to ignore the sync automatically settings. Default false.
485         */
486        public Builder setIgnoreSettings(boolean ignoreSettings) {
487            mIgnoreSettings = ignoreSettings;
488            return this;
489        }
490
491        /**
492         * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}.
493         *
494         * Force the sync scheduling process to ignore any back-off that was the result of a failed
495         * sync, as well as to invalidate any {@link SyncResult#delayUntil} value that may have
496         * been set by the adapter. Successive failures will not honor this flag. Not valid for
497         * periodic sync and will throw an <code>IllegalArgumentException</code> in
498         * {@link #build()}.
499         *
500         * @param ignoreBackoff ignore back-off settings. Default false.
501         */
502        public Builder setIgnoreBackoff(boolean ignoreBackoff) {
503            mIgnoreBackoff = ignoreBackoff;
504            return this;
505        }
506
507        /**
508         * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_MANUAL}.
509         *
510         * A manual sync is functionally equivalent to calling {@link #setIgnoreBackoff(boolean)}
511         * and {@link #setIgnoreSettings(boolean)}. Not valid for periodic sync and will throw an
512         * <code>IllegalArgumentException</code> in {@link #build()}.
513         *
514         * @param isManual User-initiated sync or not. Default false.
515         */
516        public Builder setManual(boolean isManual) {
517            mIsManual = isManual;
518            return this;
519        }
520
521        /**
522         * An expedited sync runs immediately and will preempt another non-expedited running sync.
523         *
524         * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
525         * {@link #build()}.
526         *
527         * @param expedited whether to run expedited. Default false.
528         */
529        public Builder setExpedited(boolean expedited) {
530            mExpedited = expedited;
531            return this;
532        }
533
534        /**
535         * {@hide}
536         * @param priority the priority of this request among all requests from the calling app.
537         * Range of [-2,2] similar to how this is done with notifications.
538         */
539        public Builder setPriority(int priority) {
540            if (priority < -2 || priority > 2) {
541                throw new IllegalArgumentException("Priority must be within range [-2, 2]");
542            }
543            mPriority = priority;
544            return this;
545        }
546
547        /**
548         * Performs validation over the request and throws the runtime exception
549         * <code>IllegalArgumentException</code> if this validation fails.
550         *
551         * @return a SyncRequest with the information contained within this
552         *         builder.
553         */
554        public SyncRequest build() {
555            if (mCustomExtras == null) {
556                mCustomExtras = new Bundle();
557            }
558            // Validate the extras bundle
559            ContentResolver.validateSyncExtrasBundle(mCustomExtras);
560            // Combine builder extra flags into the config bundle.
561            mSyncConfigExtras = new Bundle();
562            if (mIgnoreBackoff) {
563                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
564            }
565            if (mDisallowMetered) {
566                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, true);
567            }
568            if (mIgnoreSettings) {
569                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
570            }
571            if (mNoRetry) {
572                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
573            }
574            if (mExpedited) {
575                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
576            }
577            if (mIsManual) {
578                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
579            }
580            mSyncConfigExtras.putLong(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD, mTxBytes);
581            mSyncConfigExtras.putLong(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD, mRxBytes);
582            mSyncConfigExtras.putInt(ContentResolver.SYNC_EXTRAS_PRIORITY, mPriority);
583            if (mSyncType == SYNC_TYPE_PERIODIC) {
584                // If this is a periodic sync ensure than invalid extras were not set.
585                validatePeriodicExtras(mCustomExtras);
586                validatePeriodicExtras(mSyncConfigExtras);
587                // Verify that account and provider are not null.
588                if (mAccount == null) {
589                    throw new IllegalArgumentException("Account must not be null for periodic"
590                            + " sync.");
591                }
592                if (mAuthority == null) {
593                    throw new IllegalArgumentException("Authority must not be null for periodic"
594                            + " sync.");
595                }
596            } else if (mSyncType == SYNC_TYPE_UNKNOWN) {
597                throw new IllegalArgumentException("Must call either syncOnce() or syncPeriodic()");
598            }
599            // Ensure that a target for the sync has been set.
600            if (mSyncTarget == SYNC_TARGET_UNKNOWN) {
601                throw new IllegalArgumentException("Must specify an adapter with "
602                        + "setSyncAdapter(Account, String");
603            }
604            return new SyncRequest(this);
605        }
606
607        /**
608         * Helper function to throw an <code>IllegalArgumentException</code> if any illegal
609         * extras were set for a periodic sync.
610         *
611         * @param extras bundle to validate.
612         */
613        private void validatePeriodicExtras(Bundle extras) {
614            if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)
615                    || extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)
616                    || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)
617                    || extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
618                    || extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
619                    || extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false)
620                    || extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) {
621                throw new IllegalArgumentException("Illegal extras were set");
622            }
623        }
624    }
625}
626